Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a feature to allow leading plus sign (JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS) #774

Merged
merged 16 commits into from
Jun 23, 2022
Merged
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
5 changes: 5 additions & 0 deletions src/main/java/com/fasterxml/jackson/core/JsonParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,11 @@ public enum Feature {
@Deprecated
ALLOW_NUMERIC_LEADING_ZEROS(false),

/**
* @deprecated Use {@link com.fasterxml.jackson.core.json.JsonReadFeature#ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS} instead
*/
ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS(false),

/**
* @deprecated Use {@link com.fasterxml.jackson.core.json.JsonReadFeature#ALLOW_LEADING_DECIMAL_POINT_FOR_NUMBERS} instead
*/
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/com/fasterxml/jackson/core/json/JsonReadFeature.java
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,21 @@ public enum JsonReadFeature
@SuppressWarnings("deprecation")
ALLOW_LEADING_ZEROS_FOR_NUMBERS(false, JsonParser.Feature.ALLOW_NUMERIC_LEADING_ZEROS),

/**
* Feature that determines whether parser will allow
* JSON decimal numbers to start with a plus sign
* (like: +123). If enabled, no exception is thrown, and the number
* is parsed as though a leading sign had not been present.
*<p>
* Since JSON specification does not allow leading plus signs,
* this is a non-standard feature, and as such disabled by default.
*
* @since 2.14
*/
@SuppressWarnings("deprecation")
ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS(false, JsonParser.Feature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS),


/**
* Feature that determines whether parser will allow
* JSON decimal numbers to start with a decimal point
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -777,12 +777,15 @@ public final JsonToken nextToken() throws IOException
break;

case '-':
/* Should we have separate handling for plus? Although
* it is not allowed per se, it may be erroneously used,
* and could be indicate by a more specific error message.
*/
t = _parseNegNumber();
break;
case '+':
if (!isEnabled(JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS.mappedFeature())) {
t = _handleOddValue(i);
} else {
t = _parsePosNumber();
}
break;
case '.': // [core#61]]
t = _parseFloatThatStartsWithPeriod();
break;
Expand Down Expand Up @@ -1453,32 +1456,45 @@ private final JsonToken _parseFloat(int ch, int startPtr, int ptr, boolean neg,
return resetFloat(neg, intLen, fractLen, expLen);
}

protected final JsonToken _parsePosNumber() throws IOException
{
return _parsePossibleNumber(false);
}

protected final JsonToken _parseNegNumber() throws IOException
{
return _parsePossibleNumber(true);
}

private JsonToken _parsePossibleNumber(final boolean negative) throws IOException
{
int ptr = _inputPtr;
int startPtr = ptr-1; // to include sign/digit already read
int startPtr = negative ? ptr-1 : ptr; // to include sign/digit already read
final int inputLen = _inputEnd;

if (ptr >= inputLen) {
return _parseNumber2(true, startPtr);
return _parseNumber2(negative, startPtr);
}
int ch = _inputBuffer[ptr++];
// First check: must have a digit to follow minus sign
if (ch > INT_9 || ch < INT_0) {
_inputPtr = ptr;
return _handleInvalidNumberStart(ch, true);
if (ch == INT_PERIOD) {
return _parseFloatThatStartsWithPeriod();
}
return _handleInvalidNumberStart(ch, negative);
}
// One special case, leading zero(es):
if (ch == INT_0) {
return _parseNumber2(true, startPtr);
return _parseNumber2(negative, startPtr);
}
int intLen = 1; // already got one

// First let's get the obligatory integer part:
int_loop:
while (true) {
if (ptr >= inputLen) {
return _parseNumber2(true, startPtr);
return _parseNumber2(negative, startPtr);
}
ch = (int) _inputBuffer[ptr++];
if (ch < INT_0 || ch > INT_9) {
Expand All @@ -1489,7 +1505,7 @@ protected final JsonToken _parseNegNumber() throws IOException

if (ch == INT_PERIOD || ch == INT_e || ch == INT_E) {
_inputPtr = ptr;
return _parseFloat(ch, startPtr, ptr, true, intLen);
return _parseFloat(ch, startPtr, ptr, negative, intLen);
}
--ptr;
_inputPtr = ptr;
Expand All @@ -1498,7 +1514,7 @@ protected final JsonToken _parseNegNumber() throws IOException
}
int len = ptr-startPtr;
_textBuffer.resetWithShared(_inputBuffer, startPtr, len);
return resetInt(true, intLen);
return resetInt(negative, intLen);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -660,10 +660,13 @@ public JsonToken nextToken() throws IOException
case '-':
t = _parseNegNumber();
break;

// Should we have separate handling for plus? Although
// it is not allowed per se, it may be erroneously used,
// and could be indicate by a more specific error message.
case '+':
if (!isEnabled(JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS.mappedFeature())) {
t = _handleUnexpectedValue(i);
} else {
t = _parsePosNumber();
}
break;
case '.': // as per [core#611]
t = _parseFloatThatStartsWithPeriod();
break;
Expand Down Expand Up @@ -729,9 +732,11 @@ private final JsonToken _nextTokenNotInObject(int i) throws IOException
return (_currToken = JsonToken.VALUE_NULL);
case '-':
return (_currToken = _parseNegNumber());
// Should we have separate handling for plus? Although it is not allowed
// per se, it may be erroneously used, and could be indicated by a more
// specific error message.
case '+':
if (!isEnabled(JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS.mappedFeature())) {
return (_currToken = _handleUnexpectedValue(i));
}
return (_currToken = _parsePosNumber());
case '.': // as per [core#611]
return (_currToken = _parseFloatThatStartsWithPeriod());
case '0':
Expand Down Expand Up @@ -1067,27 +1072,41 @@ protected JsonToken _parsePosNumber(int c) throws IOException
// And there we have it!
return resetInt(false, intLen);
}


protected JsonToken _parsePosNumber() throws IOException
{
return _parsePossibleNumber(false);
}

protected JsonToken _parseNegNumber() throws IOException
{
return _parsePossibleNumber(true);
}

private JsonToken _parsePossibleNumber(boolean negative) throws IOException
{
char[] outBuf = _textBuffer.emptyAndGetCurrentSegment();
int outPtr = 0;

// Need to prepend sign?
outBuf[outPtr++] = '-';
if (negative) {
// Need to prepend sign?
outBuf[outPtr++] = '-';
}
int c = _inputData.readUnsignedByte();
outBuf[outPtr++] = (char) c;
// Note: must be followed by a digit
if (c <= INT_0) {
// One special case: if first char is 0 need to check no leading zeroes
if (c == INT_0) {
c = _handleLeadingZeroes();
} else if (c == INT_PERIOD) {
return _parseFloatThatStartsWithPeriod();
} else {
return _handleInvalidNumberStart(c, true);
return _handleInvalidNumberStart(c, negative);
}
} else {
if (c > INT_9) {
return _handleInvalidNumberStart(c, true);
return _handleInvalidNumberStart(c, negative);
}
c = _inputData.readUnsignedByte();
}
Expand All @@ -1101,7 +1120,7 @@ protected JsonToken _parseNegNumber() throws IOException
c = _inputData.readUnsignedByte();
}
if (c == '.' || c == 'e' || c == 'E') {
return _parseFloat(outBuf, outPtr, c, true, intLen);
return _parseFloat(outBuf, outPtr, c, negative, intLen);
}
_textBuffer.setCurrentLength(outPtr);
// As per [core#105], need separating space between root values; check here
Expand All @@ -1110,7 +1129,7 @@ protected JsonToken _parseNegNumber() throws IOException
_verifyRootSpace();
}
// And there we have it!
return resetInt(true, intLen);
return resetInt(negative, intLen);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -814,9 +814,13 @@ public JsonToken nextToken() throws IOException
case '-':
t = _parseNegNumber();
break;

// Should we have separate handling for plus? Although it is not allowed per se,
// it may be erroneously used, and could be indicate by a more specific error message.
case '+':
if (!isEnabled(JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS.mappedFeature())) {
t = _handleUnexpectedValue(i);
} else {
t = _parsePosNumber();
}
break;
case '.': // [core#611]:
t = _parseFloatThatStartsWithPeriod();
break;
Expand Down Expand Up @@ -882,9 +886,11 @@ private final JsonToken _nextTokenNotInObject(int i) throws IOException
return (_currToken = JsonToken.VALUE_NULL);
case '-':
return (_currToken = _parseNegNumber());

// Should we have separate handling for plus? Although it is not allowed per se,
// it may be erroneously used, and could be indicate by a more specific error message.
case '+':
if (!isEnabled(JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS.mappedFeature())) {
return (_currToken = _handleUnexpectedValue(i));
}
return (_currToken = _parsePosNumber());
case '.': // [core#611]:
return (_currToken = _parseFloatThatStartsWithPeriod());
case '0':
Expand Down Expand Up @@ -1475,14 +1481,26 @@ protected JsonToken _parsePosNumber(int c) throws IOException
// And there we have it!
return resetInt(false, intLen);
}


protected JsonToken _parsePosNumber() throws IOException
{
return _parsePossibleNumber(false);
}

protected JsonToken _parseNegNumber() throws IOException
{
return _parsePossibleNumber(true);
}

private JsonToken _parsePossibleNumber(boolean negative) throws IOException
{
char[] outBuf = _textBuffer.emptyAndGetCurrentSegment();
int outPtr = 0;

// Need to prepend sign?
outBuf[outPtr++] = '-';
if (negative) {
// Need to prepend sign?
outBuf[outPtr++] = '-';
}
// Must have something after sign too
if (_inputPtr >= _inputEnd) {
_loadMoreGuaranteed();
Expand All @@ -1492,13 +1510,16 @@ protected JsonToken _parseNegNumber() throws IOException
if (c <= INT_0) {
// One special case: if first char is 0, must not be followed by a digit
if (c != INT_0) {
return _handleInvalidNumberStart(c, true);
if (c == INT_PERIOD) {
return _parseFloatThatStartsWithPeriod();
}
return _handleInvalidNumberStart(c, negative);
}
c = _verifyNoLeadingZeroes();
} else if (c > INT_9) {
return _handleInvalidNumberStart(c, true);
return _handleInvalidNumberStart(c, negative);
}

// Ok: we can first just add digit we saw first:
outBuf[outPtr++] = (char) c;
int intLen = 1;
Expand All @@ -1510,7 +1531,7 @@ protected JsonToken _parseNegNumber() throws IOException
while (true) {
if (_inputPtr >= end) {
// Long enough to be split across boundary, so:
return _parseNumber2(outBuf, outPtr, true, intLen);
return _parseNumber2(outBuf, outPtr, negative, intLen);
}
c = (int) _inputBuffer[_inputPtr++] & 0xFF;
if (c < INT_0 || c > INT_9) {
Expand All @@ -1520,9 +1541,9 @@ protected JsonToken _parseNegNumber() throws IOException
outBuf[outPtr++] = (char) c;
}
if (c == INT_PERIOD || c == INT_e || c == INT_E) {
return _parseFloat(outBuf, outPtr, c, true, intLen);
return _parseFloat(outBuf, outPtr, c, negative, intLen);
}

--_inputPtr; // to push back trailing char (comma etc)
_textBuffer.setCurrentLength(outPtr);
// As per #105, need separating space between root values; check here
Expand All @@ -1531,7 +1552,7 @@ protected JsonToken _parseNegNumber() throws IOException
}

// And there we have it!
return resetInt(true, intLen);
return resetInt(negative, intLen);
}

// Method called to handle parsing when input is split across buffer boundary
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public class FastParserNonStandardNumberParsingTest
{
private final JsonFactory fastFactory =
JsonFactory.builder()
.enable(JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS)
.enable(JsonReadFeature.ALLOW_LEADING_DECIMAL_POINT_FOR_NUMBERS)
.enable(JsonReadFeature.ALLOW_TRAILING_DECIMAL_POINT_FOR_NUMBERS)
.enable(StreamReadFeature.USE_FAST_DOUBLE_PARSER)
Expand Down
Loading