diff --git a/src/main/java/com/fasterxml/jackson/core/JsonParser.java b/src/main/java/com/fasterxml/jackson/core/JsonParser.java index 6fa8c43f5c..cdfc11f7fe 100644 --- a/src/main/java/com/fasterxml/jackson/core/JsonParser.java +++ b/src/main/java/com/fasterxml/jackson/core/JsonParser.java @@ -188,6 +188,12 @@ public enum Feature { @Deprecated ALLOW_LEADING_DECIMAL_POINT_FOR_NUMBERS(false), + /** + * @deprecated Use {@link com.fasterxml.jackson.core.json.JsonReadFeature#ALLOW_TRAILING_DECIMAL_POINT_FOR_NUMBERS} instead + */ + @Deprecated + ALLOW_TRAILING_DECIMAL_POINT_FOR_NUMBERS(false), + /** * Feature that allows parser to recognize set of * "Not-a-Number" (NaN) tokens as legal floating number diff --git a/src/main/java/com/fasterxml/jackson/core/json/JsonReadFeature.java b/src/main/java/com/fasterxml/jackson/core/json/JsonReadFeature.java index 304b320a12..6b0037dd50 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/JsonReadFeature.java +++ b/src/main/java/com/fasterxml/jackson/core/json/JsonReadFeature.java @@ -113,7 +113,7 @@ public enum JsonReadFeature * (like: .123). If enabled, no exception is thrown, and the number * is parsed as though a leading 0 had been present. *

- * Since JSON specification does not allow leading decimal, + * Since JSON specification does not allow leading decimal points, * this is a non-standard feature, and as such disabled by default. * * @since 2.11 @@ -121,6 +121,20 @@ public enum JsonReadFeature @SuppressWarnings("deprecation") ALLOW_LEADING_DECIMAL_POINT_FOR_NUMBERS(false, JsonParser.Feature.ALLOW_LEADING_DECIMAL_POINT_FOR_NUMBERS), + /** + * Feature that determines whether parser will allow + * JSON decimal numbers to end with a decimal point + * (like: 123.). If enabled, no exception is thrown, and the number + * is parsed as though the trailing decimal point had not been present. + *

+ * Since JSON specification does not allow trailing decimal points, + * this is a non-standard feature, and as such disabled by default. + * + * @since 2.11 + */ + @SuppressWarnings("deprecation") + ALLOW_TRAILING_DECIMAL_POINT_FOR_NUMBERS(false, JsonParser.Feature.ALLOW_TRAILING_DECIMAL_POINT_FOR_NUMBERS), + /** * Feature that allows parser to recognize set of * "Not-a-Number" (NaN) tokens as legal floating number diff --git a/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java b/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java index da788986c6..a7f51fa396 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java +++ b/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java @@ -1408,7 +1408,9 @@ private final JsonToken _parseFloat(int ch, int startPtr, int ptr, boolean neg, } // must be followed by sequence of ints, one minimum if (fractLen == 0) { - reportUnexpectedNumberChar(ch, "Decimal point not followed by a digit"); + if (!isEnabled(JsonReadFeature.ALLOW_TRAILING_DECIMAL_POINT_FOR_NUMBERS.mappedFeature())) { + reportUnexpectedNumberChar(ch, "Decimal point not followed by a digit"); + } } } int expLen = 0; @@ -1585,7 +1587,9 @@ private final JsonToken _parseNumber2(boolean neg, int startPtr) throws IOExcept } // must be followed by sequence of ints, one minimum if (fractLen == 0) { - reportUnexpectedNumberChar(c, "Decimal point not followed by a digit"); + if (!isEnabled(JsonReadFeature.ALLOW_TRAILING_DECIMAL_POINT_FOR_NUMBERS.mappedFeature())) { + reportUnexpectedNumberChar(c, "Decimal point not followed by a digit"); + } } } diff --git a/src/main/java/com/fasterxml/jackson/core/json/UTF8DataInputJsonParser.java b/src/main/java/com/fasterxml/jackson/core/json/UTF8DataInputJsonParser.java index 3d2fd99777..44b9053563 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/UTF8DataInputJsonParser.java +++ b/src/main/java/com/fasterxml/jackson/core/json/UTF8DataInputJsonParser.java @@ -1165,7 +1165,9 @@ private final JsonToken _parseFloat(char[] outBuf, int outPtr, int c, } // must be followed by sequence of ints, one minimum if (fractLen == 0) { - reportUnexpectedNumberChar(c, "Decimal point not followed by a digit"); + if (!isEnabled(JsonReadFeature.ALLOW_TRAILING_DECIMAL_POINT_FOR_NUMBERS.mappedFeature())) { + reportUnexpectedNumberChar(c, "Decimal point not followed by a digit"); + } } } diff --git a/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java b/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java index 5ad5502d63..32b50e3a13 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java +++ b/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java @@ -1638,7 +1638,9 @@ private final JsonToken _parseFloat(char[] outBuf, int outPtr, int c, } // must be followed by sequence of ints, one minimum if (fractLen == 0) { - reportUnexpectedNumberChar(c, "Decimal point not followed by a digit"); + if (!isEnabled(JsonReadFeature.ALLOW_TRAILING_DECIMAL_POINT_FOR_NUMBERS.mappedFeature())) { + reportUnexpectedNumberChar(c, "Decimal point not followed by a digit"); + } } } diff --git a/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingJsonParser.java b/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingJsonParser.java index 9c7e2adbd5..2e7bfc278c 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingJsonParser.java +++ b/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingJsonParser.java @@ -1655,7 +1655,9 @@ protected JsonToken _startFloat(char[] outBuf, int outPtr, int ch) throws IOExce ch &= 0xFF; // but here we'll want to mask it to unsigned 8-bit // must be followed by sequence of ints, one minimum if (fractLen == 0) { - reportUnexpectedNumberChar(ch, "Decimal point not followed by a digit"); + if (!isEnabled(JsonReadFeature.ALLOW_TRAILING_DECIMAL_POINT_FOR_NUMBERS.mappedFeature())) { + reportUnexpectedNumberChar(ch, "Decimal point not followed by a digit"); + } } break; } @@ -1745,7 +1747,9 @@ protected JsonToken _finishFloatFraction() throws IOException // Ok, fraction done; what have we got next? // must be followed by sequence of ints, one minimum if (fractLen == 0) { - reportUnexpectedNumberChar(ch, "Decimal point not followed by a digit"); + if (!isEnabled(JsonReadFeature.ALLOW_TRAILING_DECIMAL_POINT_FOR_NUMBERS.mappedFeature())) { + reportUnexpectedNumberChar(ch, "Decimal point not followed by a digit"); + } } _fractLength = fractLen; _textBuffer.setCurrentLength(outPtr); diff --git a/src/test/java/com/fasterxml/jackson/core/read/FastParserNonStandardNumberParsingTest.java b/src/test/java/com/fasterxml/jackson/core/read/FastParserNonStandardNumberParsingTest.java index 5bb40b89d7..21c8dbfa0b 100644 --- a/src/test/java/com/fasterxml/jackson/core/read/FastParserNonStandardNumberParsingTest.java +++ b/src/test/java/com/fasterxml/jackson/core/read/FastParserNonStandardNumberParsingTest.java @@ -10,6 +10,7 @@ public class FastParserNonStandardNumberParsingTest private final JsonFactory fastFactory = JsonFactory.builder() .enable(JsonReadFeature.ALLOW_LEADING_DECIMAL_POINT_FOR_NUMBERS) + .enable(JsonReadFeature.ALLOW_TRAILING_DECIMAL_POINT_FOR_NUMBERS) .enable(StreamReadFeature.USE_FAST_DOUBLE_PARSER) .build(); diff --git a/src/test/java/com/fasterxml/jackson/core/read/NonStandardNumberParsingTest.java b/src/test/java/com/fasterxml/jackson/core/read/NonStandardNumberParsingTest.java index 7cdd194909..996412a66f 100644 --- a/src/test/java/com/fasterxml/jackson/core/read/NonStandardNumberParsingTest.java +++ b/src/test/java/com/fasterxml/jackson/core/read/NonStandardNumberParsingTest.java @@ -8,6 +8,7 @@ public class NonStandardNumberParsingTest { private final JsonFactory JSON_F = JsonFactory.builder() .enable(JsonReadFeature.ALLOW_LEADING_DECIMAL_POINT_FOR_NUMBERS) + .enable(JsonReadFeature.ALLOW_TRAILING_DECIMAL_POINT_FOR_NUMBERS) .build(); protected JsonFactory jsonFactory() { @@ -30,6 +31,22 @@ public void testLeadingDotInDecimal() throws Exception { } } + /** + * The format "NNN." (as opposed to "NNN") is not valid JSON, so this should fail + */ + public void testTrailingDotInDecimal() throws Exception { + for (int mode : ALL_MODES) { + JsonParser p = createParser(mode, " 123. "); + try { + p.nextToken(); + fail("Should not pass"); + } catch (JsonParseException e) { + verifyException(e, "Decimal point not followed by a digit"); + } + p.close(); + } + } + public void testLeadingDotInDecimalAllowedAsync() throws Exception { _testLeadingDotInDecimalAllowed(jsonFactory(), MODE_DATA_INPUT); } @@ -43,6 +60,19 @@ public void testLeadingDotInDecimalAllowedReader() throws Exception { _testLeadingDotInDecimalAllowed(jsonFactory(), MODE_READER); } + public void testTrailingDotInDecimalAllowedAsync() throws Exception { + _testTrailingDotInDecimalAllowed(jsonFactory(), MODE_DATA_INPUT); + } + + public void testTrailingDotInDecimalAllowedBytes() throws Exception { + _testTrailingDotInDecimalAllowed(jsonFactory(), MODE_INPUT_STREAM); + _testTrailingDotInDecimalAllowed(jsonFactory(), MODE_INPUT_STREAM_THROTTLED); + } + + public void testTrailingDotInDecimalAllowedReader() throws Exception { + _testTrailingDotInDecimalAllowed(jsonFactory(), MODE_READER); + } + private void _testLeadingDotInDecimalAllowed(JsonFactory f, int mode) throws Exception { JsonParser p = createParser(f, mode, " .125 "); @@ -52,4 +82,14 @@ private void _testLeadingDotInDecimalAllowed(JsonFactory f, int mode) throws Exc assertEquals(".125", p.getText()); p.close(); } + + private void _testTrailingDotInDecimalAllowed(JsonFactory f, int mode) throws Exception + { + JsonParser p = createParser(f, mode, " 125. "); + assertEquals(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken()); + assertEquals(125.0, p.getValueAsDouble()); + assertEquals("125", p.getDecimalValue().toString()); + assertEquals("125.", p.getText()); + p.close(); + } }