Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions release-notes/CREDITS
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ seonwoojung (@seonwooj0810)
* Contributed #707: Add `JsonReadFeature.ALLOW_HEXADECIMAL_NUMBERS`
for JSON5-style hexadecimal integer literals
(3.2.0)
* Contributed fix for #1557: Parsing for non-root number values fails lazily
(3.3.0)

Max Paulus (@maxpaulus43)
* Contributed #1211: Add `JsonParser.willInternPropertyNames()` to check whether
Expand Down
2 changes: 2 additions & 0 deletions release-notes/VERSION
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ JSON library.

3.3.0 (not yet released)

#1557: Parsing for non-root number values fails lazily
(fix by @seonwooj0810)
#1637: Add `JsonPointer.startsWith(JsonPointer)` to check prefix match
(contributed by @scottslewis)

Expand Down
87 changes: 69 additions & 18 deletions src/main/java/tools/jackson/core/json/ReaderBasedJsonParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -1413,10 +1413,8 @@ protected final JsonToken _parseUnsignedNumber(int ch) throws JacksonException
// Got it all: let's add to text buffer for parsing, access
--ptr; // need to push back following separator
_inputPtr = ptr;
// As per #105, need separating space between root values; check here
if (_streamReadContext.inRoot()) {
_verifyRootSpace(ch);
}
// [core#105]/[core#1557]: verify number is properly terminated/separated
_verifyNumberSeparator(ch);
int len = ptr-startPtr;
_textBuffer.resetWithShared(_inputBuffer, startPtr, len);
return resetInt(false, intLen);
Expand Down Expand Up @@ -1480,10 +1478,8 @@ private final JsonToken _parseFloat(int ch, int startPtr, int ptr, boolean neg,
}
--ptr; // need to push back following separator
_inputPtr = ptr;
// As per #105, need separating space between root values; check here
if (_streamReadContext.inRoot()) {
_verifyRootSpace(ch);
}
// [core#105]/[core#1557]: verify number is properly terminated/separated
_verifyNumberSeparator(ch);
int len = ptr-startPtr;
_textBuffer.resetWithShared(_inputBuffer, startPtr, len);
// And there we have it!
Expand Down Expand Up @@ -1538,9 +1534,7 @@ private final JsonToken _parseSignedNumber(final boolean negative) throws Jackso
}
--ptr;
_inputPtr = ptr;
if (_streamReadContext.inRoot()) {
_verifyRootSpace(ch);
}
_verifyNumberSeparator(ch);
int len = ptr-startPtr;
_textBuffer.resetWithShared(_inputBuffer, startPtr, len);
return resetInt(negative, intLen);
Expand Down Expand Up @@ -1704,9 +1698,7 @@ private final JsonToken _parseNumber2(boolean neg, int startPtr) throws JacksonE
// Ok; unless we hit end-of-input, need to push last char read back
if (!eof) {
--_inputPtr;
if (_streamReadContext.inRoot()) {
_verifyRootSpace(c);
}
_verifyNumberSeparator(c);
}
_textBuffer.setCurrentLength(outPtr);

Expand Down Expand Up @@ -1773,9 +1765,7 @@ private final JsonToken _finishHexNumber(boolean neg,

if (!eof) {
--_inputPtr; // push back the terminating non-hex char
if (_streamReadContext.inRoot()) {
_verifyRootSpace(c);
}
_verifyNumberSeparator(c);
}
_textBuffer.setCurrentLength(outPtr);
return resetIntHex(neg, hexLen);
Expand Down Expand Up @@ -1907,6 +1897,67 @@ private final void _verifyRootSpace(int ch) throws JacksonException
_reportMissingRootWS(ch);
}

/**
* Method called to verify that a just-decoded number value is followed by a
* valid separator or terminator character. For root-level values this means
* white space (as per [core#105], see {@link #_verifyRootSpace}); for non-root
* values ([core#1557]) the number must be followed by white space, a value
* separator ({@code ','}), an enclosing-structure end ({@code ']'} or
* {@code '}'}) or a comment start marker (when comments are enabled). Without
* this, malformed content such as {@code [ 123true ]} would only fail lazily
* when accessing the following token.
*<p>
* NOTE: not called at all if the number was terminated by end-of-input; callers
* check for that first.
*<p>
* On entry the caller has pushed the trailing character back, so {@code _inputPtr}
* points <i>at</i> it; for accepted separators this method leaves {@code _inputPtr}
* untouched so the next {@code nextToken()} call can consume them normally.
*/
private final void _verifyNumberSeparator(int ch) throws JacksonException
{
if (_streamReadContext.inRoot()) {
_verifyRootSpace(ch);
return;
}
switch (ch) {
case ' ':
case '\t':
case '\n':
case '\r':
case ',':
case ']':
case '}':
return;
case '/': // possible Java/C++ style comment
if (isEnabled(JsonReadFeature.ALLOW_JAVA_COMMENTS)) {
return;
}
break;
case '#': // possible YAML/shell style comment
if (isEnabled(JsonReadFeature.ALLOW_YAML_COMMENTS)) {
return;
}
break;
}
// Align `_inputPtr` with what `_reportUnexpectedChar` ->
// `_currentLocationMinusOne()` expects (one past the offending char),
// matching `_verifyRootSpace` which advances up front.
++_inputPtr;
if (ch == '/') {
// 23-Jul-2026, tatu: [core#1557] Still fail here rather than lazily, but
// with the more useful message comment-skipping would have given.
_reportUnrecognizedComment();
}
_reportUnexpectedChar(ch,
"Expected space, comma or closing bracket/brace after numeric value");
}

// @since 3.3
private final void _reportUnrecognizedComment() throws JacksonException {
_reportUnexpectedChar('/', "maybe a (non-standard) comment? (not recognized as one since Feature 'ALLOW_COMMENTS' not enabled for parser)");
}

/*
/**********************************************************************
/* Internal methods, secondary parsing
Expand Down Expand Up @@ -2772,7 +2823,7 @@ private int _skipWSOrEnd2() throws JacksonException
private void _skipComment() throws JacksonException
{
if (!isEnabled(JsonReadFeature.ALLOW_JAVA_COMMENTS)) {
_reportUnexpectedChar('/', "maybe a (non-standard) comment? (not recognized as one since Feature 'ALLOW_COMMENTS' not enabled for parser)");
_reportUnrecognizedComment();
}
// First: check which comment (if either) it is:
if (_inputPtr >= _inputEnd && !_loadMore()) {
Expand Down
78 changes: 62 additions & 16 deletions src/main/java/tools/jackson/core/json/UTF8DataInputJsonParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -1092,11 +1092,9 @@ protected JsonToken _parseUnsignedNumber(int c) throws IOException
return _parseFloat(outBuf, outPtr, c, false, intLen);
}
_textBuffer.setCurrentLength(outPtr);
// As per [core#105], need separating space between root values; check here
// [core#105]/[core#1557]: verify number is properly terminated/separated
_nextByte = c;
if (_streamReadContext.inRoot()) {
_verifyRootSpace();
}
_verifyNumberSeparator();
// And there we have it!
return resetInt(false, intLen);
}
Expand Down Expand Up @@ -1160,11 +1158,9 @@ private final JsonToken _parseSignedNumber(boolean negative) throws IOException
return _parseFloat(outBuf, outPtr, c, negative, intLen);
}
_textBuffer.setCurrentLength(outPtr);
// As per [core#105], need separating space between root values; check here
// [core#105]/[core#1557]: verify number is properly terminated/separated
_nextByte = c;
if (_streamReadContext.inRoot()) {
_verifyRootSpace();
}
_verifyNumberSeparator();
// And there we have it!
return resetInt(negative, intLen);
}
Expand Down Expand Up @@ -1209,9 +1205,7 @@ private final JsonToken _finishHexNumber(boolean neg, char[] outBuf, int outPtr,
}
_textBuffer.setCurrentLength(outPtr);
_nextByte = c;
if (_streamReadContext.inRoot()) {
_verifyRootSpace();
}
_verifyNumberSeparator();
return resetIntHex(neg, hexLen);
}

Expand Down Expand Up @@ -1313,11 +1307,9 @@ private final JsonToken _parseFloat(char[] outBuf, int outPtr, int c,
}

// Ok; unless we hit end-of-input, need to push last char read back
// As per #105, need separating space between root values; check here
// [core#105]/[core#1557]: verify number is properly terminated/separated
_nextByte = c;
if (_streamReadContext.inRoot()) {
_verifyRootSpace();
}
_verifyNumberSeparator();
_textBuffer.setCurrentLength(outPtr);

// And there we have it!
Expand Down Expand Up @@ -1345,6 +1337,60 @@ private final void _verifyRootSpace() throws JacksonException
_reportMissingRootWS(ch);
}

/**
* Method called to verify that a just-decoded number value is followed by a
* valid separator or terminator. For root-level values this means white space
* (as per [core#105], see {@link #_verifyRootSpace}); for non-root values
* ([core#1557]) the number must be followed by white space, a value separator
* ({@code ','}), an enclosing-structure end ({@code ']'} or {@code '}'}) or a
* comment start marker (when comments are enabled). Without this, malformed
* content such as {@code [ 123true ]} would only fail lazily when accessing the
* following token.
*<p>
* The trailing character is held in {@code _nextByte}; for accepted separators
* it is left there so the next {@code nextToken()} call can consume it normally.
*/
private final void _verifyNumberSeparator() throws JacksonException
{
if (_streamReadContext.inRoot()) {
_verifyRootSpace();
return;
}
final int ch = _nextByte;
switch (ch) {
case ' ':
case '\t':
case '\n':
case '\r':
case ',':
case ']':
case '}':
return;
case '/': // possible Java/C++ style comment
if (isEnabled(JsonReadFeature.ALLOW_JAVA_COMMENTS)) {
return;
}
break;
case '#': // possible YAML/shell style comment
if (isEnabled(JsonReadFeature.ALLOW_YAML_COMMENTS)) {
return;
}
break;
}
if (ch == '/') {
// 23-Jul-2026, tatu: [core#1557] Still fail here rather than lazily, but
// with the more useful message comment-skipping would have given.
_reportUnrecognizedComment();
}
_reportUnexpectedChar(ch,
"Expected space, comma or closing bracket/brace after numeric value");
}

// @since 3.3
private final void _reportUnrecognizedComment() throws JacksonException {
_reportUnexpectedChar('/', "maybe a (non-standard) comment? (not recognized as one since Feature 'ALLOW_COMMENTS' not enabled for parser)");
}

/*
/**********************************************************************
/* Internal methods, secondary parsing
Expand Down Expand Up @@ -2727,7 +2773,7 @@ private final int _skipColon2(int i, boolean gotColon) throws IOException
private final void _skipComment() throws IOException
{
if (!isEnabled(JsonReadFeature.ALLOW_JAVA_COMMENTS)) {
_reportUnexpectedChar('/', "maybe a (non-standard) comment? (not recognized as one since Feature 'ALLOW_COMMENTS' not enabled for parser)");
_reportUnrecognizedComment();
}
int c = readUnsignedByte();
if (c == '/') {
Expand Down
Loading