From 8c74f6f619a1d0c7535ab16ad63fb9ef9875d782 Mon Sep 17 00:00:00 2001 From: Doug Roper Date: Sat, 18 Dec 2021 04:27:54 -0500 Subject: [PATCH 1/6] Add failing test for JSON roundtrip precision loss --- .../core/json/JsonParserGeneratorTest.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/test/java/com/fasterxml/jackson/core/json/JsonParserGeneratorTest.java diff --git a/src/test/java/com/fasterxml/jackson/core/json/JsonParserGeneratorTest.java b/src/test/java/com/fasterxml/jackson/core/json/JsonParserGeneratorTest.java new file mode 100644 index 0000000000..68290eff59 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/json/JsonParserGeneratorTest.java @@ -0,0 +1,27 @@ +package com.fasterxml.jackson.core.json; + +import com.fasterxml.jackson.core.BaseTest; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; + +import java.io.ByteArrayOutputStream; +import java.io.StringWriter; + +public class JsonParserGeneratorTest + extends BaseTest { + final JsonFactory JSON_F = newStreamFactory(); + + public void testRoundtripBigDecimal() throws Exception { + String input = "1e999"; + JsonParser parser = JSON_F.createParser(input); + parser.nextToken(); + StringWriter stringWriter = new StringWriter(); + JsonGenerator generator = JSON_F.createGenerator(stringWriter); + generator.copyCurrentEvent(parser); + parser.close(); + generator.close(); + String actual = stringWriter.toString(); // "Infinity" + assertEquals(input, actual); + } +} From 33a4c64c6bb9657e3f9871dac7036b8e462f2f85 Mon Sep 17 00:00:00 2001 From: Doug Roper Date: Sun, 19 Dec 2021 20:54:46 -0500 Subject: [PATCH 2/6] Add StreamReadCapabililty.EXACT_FLOATS & fix copyCurrentEvent precision loss --- .../fasterxml/jackson/core/JsonGenerator.java | 16 ++++++++++------ .../jackson/core/StreamReadCapability.java | 17 +++++++++++++++++ .../core/json/JsonParserGeneratorTest.java | 2 +- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java b/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java index 13ee1ad2f8..18e2288252 100644 --- a/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java +++ b/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java @@ -2468,13 +2468,17 @@ public void copyCurrentEvent(JsonParser p) throws IOException } case ID_NUMBER_FLOAT: { - NumberType n = p.getNumberType(); - if (n == NumberType.BIG_DECIMAL) { - writeNumber(p.getDecimalValue()); - } else if (n == NumberType.FLOAT) { - writeNumber(p.getFloatValue()); + if (p.getReadCapabilities().isEnabled(StreamReadCapability.EXACT_FLOATS)) { + NumberType n = p.getNumberType(); + if (n == NumberType.BIG_DECIMAL) { + writeNumber(p.getDecimalValue()); + } else if (n == NumberType.FLOAT) { + writeNumber(p.getFloatValue()); + } else { + writeNumber(p.getDoubleValue()); + } } else { - writeNumber(p.getDoubleValue()); + writeNumber(p.getTextCharacters(), p.getTextOffset(), p.getTextLength()); } break; } diff --git a/src/main/java/com/fasterxml/jackson/core/StreamReadCapability.java b/src/main/java/com/fasterxml/jackson/core/StreamReadCapability.java index 63d8b50282..5519e2be37 100644 --- a/src/main/java/com/fasterxml/jackson/core/StreamReadCapability.java +++ b/src/main/java/com/fasterxml/jackson/core/StreamReadCapability.java @@ -48,6 +48,23 @@ public enum StreamReadCapability * This capability is true for many textual formats like CSV, Properties and XML. */ UNTYPED_SCALARS(false), + + /** + * Capability that indicates that data format may report floats + * as a non-exact {@link com.fasterxml.jackson.core.JsonParser.NumberType}, + * due to prohibitively expensive parsing costs of determing the precision + * upfront. For example, JSON numbers may be reported as + * {@link com.fasterxml.jackson.core.JsonParser.NumberType#DOUBLE} + * even if they would not fit into a 64-bit double without precision + * loss. Methods like {@link JsonParser#getNumberValueExact()} or + * {@link JsonParser#getValueAsString()} still report values without + * precision loss. + * + * Capability is false for text formats JSON, but true for binary formats + * like Smile, MessagePack, etc., where type is precisely and inexpensively + * signaled by a tag. + */ + EXACT_FLOATS(false) ; /** diff --git a/src/test/java/com/fasterxml/jackson/core/json/JsonParserGeneratorTest.java b/src/test/java/com/fasterxml/jackson/core/json/JsonParserGeneratorTest.java index 68290eff59..127f5e467c 100644 --- a/src/test/java/com/fasterxml/jackson/core/json/JsonParserGeneratorTest.java +++ b/src/test/java/com/fasterxml/jackson/core/json/JsonParserGeneratorTest.java @@ -21,7 +21,7 @@ public void testRoundtripBigDecimal() throws Exception { generator.copyCurrentEvent(parser); parser.close(); generator.close(); - String actual = stringWriter.toString(); // "Infinity" + String actual = stringWriter.toString(); assertEquals(input, actual); } } From 1cb5dfb6199efcec3fc3c926d389ea3826ae9a79 Mon Sep 17 00:00:00 2001 From: Doug Roper Date: Sun, 19 Dec 2021 21:52:35 -0500 Subject: [PATCH 3/6] Add failing copyCurrentEvent test. --- .../core/json/JsonParserGeneratorTest.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/fasterxml/jackson/core/json/JsonParserGeneratorTest.java b/src/test/java/com/fasterxml/jackson/core/json/JsonParserGeneratorTest.java index 127f5e467c..3ddb7d76a1 100644 --- a/src/test/java/com/fasterxml/jackson/core/json/JsonParserGeneratorTest.java +++ b/src/test/java/com/fasterxml/jackson/core/json/JsonParserGeneratorTest.java @@ -12,7 +12,7 @@ public class JsonParserGeneratorTest extends BaseTest { final JsonFactory JSON_F = newStreamFactory(); - public void testRoundtripBigDecimal() throws Exception { + public void testCopyCurrentEventBigDecimal() throws Exception { String input = "1e999"; JsonParser parser = JSON_F.createParser(input); parser.nextToken(); @@ -24,4 +24,17 @@ public void testRoundtripBigDecimal() throws Exception { String actual = stringWriter.toString(); assertEquals(input, actual); } + + public void testCopyCurrentStructureBigDecimal() throws Exception { + String input = "[1e999]"; + JsonParser parser = JSON_F.createParser(input); + parser.nextToken(); + StringWriter stringWriter = new StringWriter(); + JsonGenerator generator = JSON_F.createGenerator(stringWriter); + generator.copyCurrentStructure(parser); + parser.close(); + generator.close(); + String actual = stringWriter.toString(); + assertEquals(input, actual); + } } From aaa32234705c662b70a152224915840a18892731 Mon Sep 17 00:00:00 2001 From: Doug Roper Date: Sun, 19 Dec 2021 21:54:01 -0500 Subject: [PATCH 4/6] Fix copyCurrentEvent test. --- .../fasterxml/jackson/core/JsonGenerator.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java b/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java index 18e2288252..2a5fcd0aad 100644 --- a/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java +++ b/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java @@ -2617,13 +2617,17 @@ protected void _copyCurrentContents(JsonParser p) throws IOException } case ID_NUMBER_FLOAT: { - NumberType n = p.getNumberType(); - if (n == NumberType.BIG_DECIMAL) { - writeNumber(p.getDecimalValue()); - } else if (n == NumberType.FLOAT) { - writeNumber(p.getFloatValue()); + if (p.getReadCapabilities().isEnabled(StreamReadCapability.EXACT_FLOATS)) { + NumberType n = p.getNumberType(); + if (n == NumberType.BIG_DECIMAL) { + writeNumber(p.getDecimalValue()); + } else if (n == NumberType.FLOAT) { + writeNumber(p.getFloatValue()); + } else { + writeNumber(p.getDoubleValue()); + } } else { - writeNumber(p.getDoubleValue()); + writeNumber(p.getTextCharacters(), p.getTextOffset(), p.getTextLength()); } break; } From e1d9c0722d642a7d370dedddc304f7b8df7ebaf8 Mon Sep 17 00:00:00 2001 From: Doug Roper Date: Tue, 28 Dec 2021 20:42:34 -0500 Subject: [PATCH 5/6] Descope. Remove failing tests. --- .../fasterxml/jackson/core/JsonGenerator.java | 32 ++++++--------- .../core/json/JsonParserGeneratorTest.java | 40 ------------------- 2 files changed, 12 insertions(+), 60 deletions(-) delete mode 100644 src/test/java/com/fasterxml/jackson/core/json/JsonParserGeneratorTest.java diff --git a/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java b/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java index 2a5fcd0aad..13ee1ad2f8 100644 --- a/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java +++ b/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java @@ -2468,17 +2468,13 @@ public void copyCurrentEvent(JsonParser p) throws IOException } case ID_NUMBER_FLOAT: { - if (p.getReadCapabilities().isEnabled(StreamReadCapability.EXACT_FLOATS)) { - NumberType n = p.getNumberType(); - if (n == NumberType.BIG_DECIMAL) { - writeNumber(p.getDecimalValue()); - } else if (n == NumberType.FLOAT) { - writeNumber(p.getFloatValue()); - } else { - writeNumber(p.getDoubleValue()); - } + NumberType n = p.getNumberType(); + if (n == NumberType.BIG_DECIMAL) { + writeNumber(p.getDecimalValue()); + } else if (n == NumberType.FLOAT) { + writeNumber(p.getFloatValue()); } else { - writeNumber(p.getTextCharacters(), p.getTextOffset(), p.getTextLength()); + writeNumber(p.getDoubleValue()); } break; } @@ -2617,17 +2613,13 @@ protected void _copyCurrentContents(JsonParser p) throws IOException } case ID_NUMBER_FLOAT: { - if (p.getReadCapabilities().isEnabled(StreamReadCapability.EXACT_FLOATS)) { - NumberType n = p.getNumberType(); - if (n == NumberType.BIG_DECIMAL) { - writeNumber(p.getDecimalValue()); - } else if (n == NumberType.FLOAT) { - writeNumber(p.getFloatValue()); - } else { - writeNumber(p.getDoubleValue()); - } + NumberType n = p.getNumberType(); + if (n == NumberType.BIG_DECIMAL) { + writeNumber(p.getDecimalValue()); + } else if (n == NumberType.FLOAT) { + writeNumber(p.getFloatValue()); } else { - writeNumber(p.getTextCharacters(), p.getTextOffset(), p.getTextLength()); + writeNumber(p.getDoubleValue()); } break; } diff --git a/src/test/java/com/fasterxml/jackson/core/json/JsonParserGeneratorTest.java b/src/test/java/com/fasterxml/jackson/core/json/JsonParserGeneratorTest.java deleted file mode 100644 index 3ddb7d76a1..0000000000 --- a/src/test/java/com/fasterxml/jackson/core/json/JsonParserGeneratorTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.fasterxml.jackson.core.json; - -import com.fasterxml.jackson.core.BaseTest; -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; - -import java.io.ByteArrayOutputStream; -import java.io.StringWriter; - -public class JsonParserGeneratorTest - extends BaseTest { - final JsonFactory JSON_F = newStreamFactory(); - - public void testCopyCurrentEventBigDecimal() throws Exception { - String input = "1e999"; - JsonParser parser = JSON_F.createParser(input); - parser.nextToken(); - StringWriter stringWriter = new StringWriter(); - JsonGenerator generator = JSON_F.createGenerator(stringWriter); - generator.copyCurrentEvent(parser); - parser.close(); - generator.close(); - String actual = stringWriter.toString(); - assertEquals(input, actual); - } - - public void testCopyCurrentStructureBigDecimal() throws Exception { - String input = "[1e999]"; - JsonParser parser = JSON_F.createParser(input); - parser.nextToken(); - StringWriter stringWriter = new StringWriter(); - JsonGenerator generator = JSON_F.createGenerator(stringWriter); - generator.copyCurrentStructure(parser); - parser.close(); - generator.close(); - String actual = stringWriter.toString(); - assertEquals(input, actual); - } -} From 3c77b495a0e4baadfc397d071eac925a1a62b0c2 Mon Sep 17 00:00:00 2001 From: Doug Roper Date: Fri, 31 Dec 2021 17:58:44 -0500 Subject: [PATCH 6/6] Move test to /failing --- .../failing/ParserPrecisionLoss733Test.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/test/java/com/fasterxml/jackson/failing/ParserPrecisionLoss733Test.java diff --git a/src/test/java/com/fasterxml/jackson/failing/ParserPrecisionLoss733Test.java b/src/test/java/com/fasterxml/jackson/failing/ParserPrecisionLoss733Test.java new file mode 100644 index 0000000000..fd39060fc8 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/failing/ParserPrecisionLoss733Test.java @@ -0,0 +1,47 @@ +package com.fasterxml.jackson.failing; + +import com.fasterxml.jackson.core.BaseTest; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; + +import java.io.ByteArrayOutputStream; +import java.io.StringWriter; + +public class ParserPrecisionLoss733Test extends BaseTest { + final JsonFactory JSON_F = newStreamFactory(); + + /** + * Attempt to pass a BigDecimal value through without losing precision, + * e.g. for pretty printing a file. + */ + public void testCopyCurrentEventBigDecimal() throws Exception { + String input = "1e999"; + JsonParser parser = JSON_F.createParser(input); + parser.nextToken(); + StringWriter stringWriter = new StringWriter(); + JsonGenerator generator = JSON_F.createGenerator(stringWriter); + generator.copyCurrentEvent(parser); + parser.close(); + generator.close(); + String actual = stringWriter.toString(); + assertEquals(input, actual); + } + + /** + * Same as {@link #testCopyCurrentEventBigDecimal()} using copyCurrentStructure instead. + */ + public void testCopyCurrentStructureBigDecimal() throws Exception { + String input = "[1e999]"; + JsonParser parser = JSON_F.createParser(input); + parser.nextToken(); + StringWriter stringWriter = new StringWriter(); + JsonGenerator generator = JSON_F.createGenerator(stringWriter); + generator.copyCurrentStructure(parser); + parser.close(); + generator.close(); + String actual = stringWriter.toString(); + assertEquals(input, actual); + } + +}