Skip to content

Commit

Permalink
Fix #984: Add JsonGenerator.copyCurrentEventExact() (#985)
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder authored Apr 8, 2023
1 parent 0e8db3b commit 38852ba
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 21 deletions.
5 changes: 1 addition & 4 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,9 @@ JSON library.
=== Releases ===
------------------------------------------------------------------------

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

2.15.0-rc2 (28-Mar-2023)

Expand Down
102 changes: 102 additions & 0 deletions src/main/java/com/fasterxml/jackson/core/JsonGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -2477,6 +2477,7 @@ public void copyCurrentEvent(JsonParser p) throws IOException
_copyCurrentIntValue(p);
break;
case ID_NUMBER_FLOAT:
// Different from "copyCurrentEventExact"!
_copyCurrentFloatValue(p);
break;
case ID_TRUE:
Expand All @@ -2496,6 +2497,70 @@ public void copyCurrentEvent(JsonParser p) throws IOException
}
}

/**
* Same as {@link #copyCurrentEvent} with the exception that copying of numeric
* values tries to avoid any conversion losses; in particular for floating-point
* numbers. This usually matters when transcoding from textual format like JSON
* to a binary format.
* See {@link #_copyCurrentFloatValueExact} for details.
*
* @param p Parser that points to event (token) to copy
*
* @throws IOException if there is either an underlying I/O problem or encoding
* issue at format layer
*
* @since 2.15
*/
public void copyCurrentEventExact(JsonParser p) throws IOException
{
JsonToken t = p.currentToken();
final int token = (t == null) ? ID_NOT_AVAILABLE : t.id();
switch (token) {
case ID_NOT_AVAILABLE:
_reportError("No current event to copy");
break; // never gets here
case ID_START_OBJECT:
writeStartObject();
break;
case ID_END_OBJECT:
writeEndObject();
break;
case ID_START_ARRAY:
writeStartArray();
break;
case ID_END_ARRAY:
writeEndArray();
break;
case ID_FIELD_NAME:
writeFieldName(p.getCurrentName());
break;
case ID_STRING:
_copyCurrentStringValue(p);
break;
case ID_NUMBER_INT:
_copyCurrentIntValue(p);
break;
case ID_NUMBER_FLOAT:
// Different from "copyCurrentEvent"!
_copyCurrentFloatValueExact(p);
break;
case ID_TRUE:
writeBoolean(true);
break;
case ID_FALSE:
writeBoolean(false);
break;
case ID_NULL:
writeNull();
break;
case ID_EMBEDDED_OBJECT:
writeObject(p.getEmbeddedObject());
break;
default:
throw new IllegalStateException("Internal error: unknown current token, "+t);
}
}

/**
* Method for copying contents of the current event
* <b>and following events that it encloses</b>
Expand Down Expand Up @@ -2525,6 +2590,11 @@ public void copyCurrentEvent(JsonParser p) throws IOException
* <b>last event</b> that was copied. This will either be
* the event parser already pointed to (if there were no
* enclosed events), or the last enclosed event copied.
*<p>
* NOTE: copying of individual tokens/events is handled by delegating
* to {@link #copyCurrentEvent} method (make sure to read about difference
* between that method and {@link #copyCurrentEventExact} for numeric
* value accuracy).
*
* @param p Parser that points to the value to copy
*
Expand Down Expand Up @@ -2623,12 +2693,44 @@ protected void _copyCurrentContents(JsonParser p) throws IOException
/**
* Method for copying current {@link JsonToken#VALUE_NUMBER_FLOAT} value;
* overridable by format backend implementations.
* Implementation checks
* {@link JsonParser#getNumberType()} for declared type and uses matching
* accessors: this may cause inexact conversion for some textual formats
* (depending on settings). If this is problematic, use
* {@lnik #_copyCurrentFloatValueExact} instead (note that doing so may add
* overhead).
*
* @param p Parser that points to the value to copy
*
* @since 2.15
*/
protected void _copyCurrentFloatValue(JsonParser p) throws IOException
{
NumberType t = p.getNumberType();
if (t == NumberType.BIG_DECIMAL) {
writeNumber(p.getDecimalValue());
} else if (t == NumberType.FLOAT) {
writeNumber(p.getFloatValue());
} else {
writeNumber(p.getDoubleValue());
}
}

/**
* Method for copying current {@link JsonToken#VALUE_NUMBER_FLOAT} value;
* overridable by format backend implementations.
* Implementation ensures it uses most accurate accessors necessary to retain
* exact value in case of possible numeric conversion: in practice this means
* that {@link BigDecimal} is usually used as the representation accessed from
* {@link JsonParser}, regardless of whether {@link Double} might be accurate
* (since detecting lossy conversion is not possible to do efficiently).
* If minimal overhead is desired, use {@link #_copyCurrentFloatValue} instead.
*
* @param p Parser that points to the value to copy
*
* @since 2.15
*/
protected void _copyCurrentFloatValueExact(JsonParser p) throws IOException
{
Number n = p.getNumberValueExact();
if (n instanceof BigDecimal) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,9 @@ public void testCopyCurrentEventBigDecimal() throws Exception {
try (JsonParser parser = JSON_F.createParser(input)) {
parser.nextToken();
try (JsonGenerator generator = JSON_F.createGenerator(stringWriter)) {
generator.copyCurrentEvent(parser);
generator.copyCurrentEventExact(parser);
}
}
assertEquals(input, stringWriter.toString());
}

// [jackson-core#730]
/**
* Same as {@link #testCopyCurrentEventBigDecimal()} using copyCurrentStructure instead.
*/
public void testCopyCurrentStructureBigDecimal() throws Exception {
String input = "[1e999]";
StringWriter stringWriter = new StringWriter();
try (JsonParser parser = JSON_F.createParser(input)) {
parser.nextToken();
try (JsonGenerator generator = JSON_F.createGenerator(stringWriter)) {
generator.copyCurrentStructure(parser);
}
}
assertEquals("[1E+999]", stringWriter.toString());
}
}

0 comments on commit 38852ba

Please sign in to comment.