Skip to content

Commit 38852ba

Browse files
authored
Fix #984: Add JsonGenerator.copyCurrentEventExact() (#985)
1 parent 0e8db3b commit 38852ba

File tree

3 files changed

+104
-21
lines changed

3 files changed

+104
-21
lines changed

release-notes/VERSION-2.x

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,9 @@ JSON library.
1414
=== Releases ===
1515
------------------------------------------------------------------------
1616

17-
#730: JSON precision loss on `copyCurrentEvent()` for floats that require greater
18-
than `double` precision
19-
(reported by Doug R)
20-
(contributed by @pjfanning)
2117
#968: Prevent inefficient internal conversion from `BigDecimal` to `BigInteger`
2218
wrt ultra-large scale
19+
#984: Add `JsonGenerator.copyCurrentEventExact` as alternative to `copyCurrentEvent()`
2320

2421
2.15.0-rc2 (28-Mar-2023)
2522

src/main/java/com/fasterxml/jackson/core/JsonGenerator.java

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2477,6 +2477,7 @@ public void copyCurrentEvent(JsonParser p) throws IOException
24772477
_copyCurrentIntValue(p);
24782478
break;
24792479
case ID_NUMBER_FLOAT:
2480+
// Different from "copyCurrentEventExact"!
24802481
_copyCurrentFloatValue(p);
24812482
break;
24822483
case ID_TRUE:
@@ -2496,6 +2497,70 @@ public void copyCurrentEvent(JsonParser p) throws IOException
24962497
}
24972498
}
24982499

2500+
/**
2501+
* Same as {@link #copyCurrentEvent} with the exception that copying of numeric
2502+
* values tries to avoid any conversion losses; in particular for floating-point
2503+
* numbers. This usually matters when transcoding from textual format like JSON
2504+
* to a binary format.
2505+
* See {@link #_copyCurrentFloatValueExact} for details.
2506+
*
2507+
* @param p Parser that points to event (token) to copy
2508+
*
2509+
* @throws IOException if there is either an underlying I/O problem or encoding
2510+
* issue at format layer
2511+
*
2512+
* @since 2.15
2513+
*/
2514+
public void copyCurrentEventExact(JsonParser p) throws IOException
2515+
{
2516+
JsonToken t = p.currentToken();
2517+
final int token = (t == null) ? ID_NOT_AVAILABLE : t.id();
2518+
switch (token) {
2519+
case ID_NOT_AVAILABLE:
2520+
_reportError("No current event to copy");
2521+
break; // never gets here
2522+
case ID_START_OBJECT:
2523+
writeStartObject();
2524+
break;
2525+
case ID_END_OBJECT:
2526+
writeEndObject();
2527+
break;
2528+
case ID_START_ARRAY:
2529+
writeStartArray();
2530+
break;
2531+
case ID_END_ARRAY:
2532+
writeEndArray();
2533+
break;
2534+
case ID_FIELD_NAME:
2535+
writeFieldName(p.getCurrentName());
2536+
break;
2537+
case ID_STRING:
2538+
_copyCurrentStringValue(p);
2539+
break;
2540+
case ID_NUMBER_INT:
2541+
_copyCurrentIntValue(p);
2542+
break;
2543+
case ID_NUMBER_FLOAT:
2544+
// Different from "copyCurrentEvent"!
2545+
_copyCurrentFloatValueExact(p);
2546+
break;
2547+
case ID_TRUE:
2548+
writeBoolean(true);
2549+
break;
2550+
case ID_FALSE:
2551+
writeBoolean(false);
2552+
break;
2553+
case ID_NULL:
2554+
writeNull();
2555+
break;
2556+
case ID_EMBEDDED_OBJECT:
2557+
writeObject(p.getEmbeddedObject());
2558+
break;
2559+
default:
2560+
throw new IllegalStateException("Internal error: unknown current token, "+t);
2561+
}
2562+
}
2563+
24992564
/**
25002565
* Method for copying contents of the current event
25012566
* <b>and following events that it encloses</b>
@@ -2525,6 +2590,11 @@ public void copyCurrentEvent(JsonParser p) throws IOException
25252590
* <b>last event</b> that was copied. This will either be
25262591
* the event parser already pointed to (if there were no
25272592
* enclosed events), or the last enclosed event copied.
2593+
*<p>
2594+
* NOTE: copying of individual tokens/events is handled by delegating
2595+
* to {@link #copyCurrentEvent} method (make sure to read about difference
2596+
* between that method and {@link #copyCurrentEventExact} for numeric
2597+
* value accuracy).
25282598
*
25292599
* @param p Parser that points to the value to copy
25302600
*
@@ -2623,12 +2693,44 @@ protected void _copyCurrentContents(JsonParser p) throws IOException
26232693
/**
26242694
* Method for copying current {@link JsonToken#VALUE_NUMBER_FLOAT} value;
26252695
* overridable by format backend implementations.
2696+
* Implementation checks
2697+
* {@link JsonParser#getNumberType()} for declared type and uses matching
2698+
* accessors: this may cause inexact conversion for some textual formats
2699+
* (depending on settings). If this is problematic, use
2700+
* {@lnik #_copyCurrentFloatValueExact} instead (note that doing so may add
2701+
* overhead).
26262702
*
26272703
* @param p Parser that points to the value to copy
26282704
*
26292705
* @since 2.15
26302706
*/
26312707
protected void _copyCurrentFloatValue(JsonParser p) throws IOException
2708+
{
2709+
NumberType t = p.getNumberType();
2710+
if (t == NumberType.BIG_DECIMAL) {
2711+
writeNumber(p.getDecimalValue());
2712+
} else if (t == NumberType.FLOAT) {
2713+
writeNumber(p.getFloatValue());
2714+
} else {
2715+
writeNumber(p.getDoubleValue());
2716+
}
2717+
}
2718+
2719+
/**
2720+
* Method for copying current {@link JsonToken#VALUE_NUMBER_FLOAT} value;
2721+
* overridable by format backend implementations.
2722+
* Implementation ensures it uses most accurate accessors necessary to retain
2723+
* exact value in case of possible numeric conversion: in practice this means
2724+
* that {@link BigDecimal} is usually used as the representation accessed from
2725+
* {@link JsonParser}, regardless of whether {@link Double} might be accurate
2726+
* (since detecting lossy conversion is not possible to do efficiently).
2727+
* If minimal overhead is desired, use {@link #_copyCurrentFloatValue} instead.
2728+
*
2729+
* @param p Parser that points to the value to copy
2730+
*
2731+
* @since 2.15
2732+
*/
2733+
protected void _copyCurrentFloatValueExact(JsonParser p) throws IOException
26322734
{
26332735
Number n = p.getNumberValueExact();
26342736
if (n instanceof BigDecimal) {

src/test/java/com/fasterxml/jackson/core/read/ParserPrecisionLoss730Test.java

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,25 +24,9 @@ public void testCopyCurrentEventBigDecimal() throws Exception {
2424
try (JsonParser parser = JSON_F.createParser(input)) {
2525
parser.nextToken();
2626
try (JsonGenerator generator = JSON_F.createGenerator(stringWriter)) {
27-
generator.copyCurrentEvent(parser);
27+
generator.copyCurrentEventExact(parser);
2828
}
2929
}
3030
assertEquals(input, stringWriter.toString());
3131
}
32-
33-
// [jackson-core#730]
34-
/**
35-
* Same as {@link #testCopyCurrentEventBigDecimal()} using copyCurrentStructure instead.
36-
*/
37-
public void testCopyCurrentStructureBigDecimal() throws Exception {
38-
String input = "[1e999]";
39-
StringWriter stringWriter = new StringWriter();
40-
try (JsonParser parser = JSON_F.createParser(input)) {
41-
parser.nextToken();
42-
try (JsonGenerator generator = JSON_F.createGenerator(stringWriter)) {
43-
generator.copyCurrentStructure(parser);
44-
}
45-
}
46-
assertEquals("[1E+999]", stringWriter.toString());
47-
}
4832
}

0 commit comments

Comments
 (0)