Skip to content

Commit

Permalink
Leading plus sign (non-standard support - optional) (#774)
Browse files Browse the repository at this point in the history
  • Loading branch information
pjfanning authored Jun 23, 2022
1 parent a0620ad commit c2ca290
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 58 deletions.
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

0 comments on commit c2ca290

Please sign in to comment.