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

Optionally allow leading decimal in float tokens #611

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -103,7 +103,18 @@ public enum JsonReadFeature
* this is a non-standard feature, and as such disabled by default.
*/
ALLOW_LEADING_ZEROS_FOR_NUMBERS(false),


/**
* Feature that determines whether parser will allow
* JSON decimal numbers to start with a deciaml point
* (like: .123). If enabled, no exception is thrown, and the number
* is parsed as though a leading 0 had been present.
*<p>
* Since JSON specification does not allow leading decimal,
* this is a non-standard feature, and as such disabled by default.
*/
ALLOW_LEADING_DECIMAL_POINT_FOR_NUMBERS(false),

/**
* Feature that allows parser to recognize set of
* "Not-a-Number" (NaN) tokens as legal floating number
Expand Down
Expand Up @@ -726,6 +726,7 @@ public final JsonToken nextToken() throws IOException
case '7':
case '8':
case '9':
case '.':
t = _parsePosNumber(i);
break;
default:
Expand Down Expand Up @@ -919,6 +920,7 @@ public String nextFieldName() throws IOException
case '7':
case '8':
case '9':
case '.':
t = _parsePosNumber(i);
break;
case 'f':
Expand Down Expand Up @@ -988,6 +990,7 @@ private final void _isNextTokenNameYes(int i) throws IOException
case '7':
case '8':
case '9':
case '.':
_nextToken = _parsePosNumber(i);
return;
}
Expand Down Expand Up @@ -1023,6 +1026,7 @@ protected boolean _isNextTokenNameMaybe(int i, String nameToMatch) throws IOExce
case '7':
case '8':
case '9':
case '.':
t = _parsePosNumber(i);
break;
case 'f':
Expand Down Expand Up @@ -1089,6 +1093,7 @@ private final JsonToken _nextTokenNotInObject(int i) throws IOException
case '7':
case '8':
case '9':
case '.':
return (_currToken = _parsePosNumber(i));
/*
* This check proceeds only if the Feature.ALLOW_MISSING_VALUES is enabled
Expand Down Expand Up @@ -1217,6 +1222,7 @@ public final Boolean nextBooleanValue() throws IOException
/**********************************************************
*/


/**
* Initial parsing method for number values. It needs to be able
* to parse enough input to be able to determine whether the
Expand All @@ -1233,6 +1239,18 @@ public final Boolean nextBooleanValue() throws IOException
* part of processing.
*/
protected final JsonToken _parsePosNumber(int ch) throws IOException
{
if (ch == '.') {
if (isEnabled(JsonReadFeature.ALLOW_LEADING_DECIMAL_POINT_FOR_NUMBERS)) {
return (_currToken = _parsePosNumber(ch, true));
} else {
return _handleOddValue(ch);
}
}
return _parsePosNumber(ch, false);
}

protected final JsonToken _parsePosNumber(int ch, boolean numberHasLeadingDecimal) throws IOException
{
/* Although we will always be complete with respect to textual
* representation (that is, all characters will be parsed),
Expand All @@ -1241,6 +1259,7 @@ protected final JsonToken _parsePosNumber(int ch) throws IOException
*/
int ptr = _inputPtr;
int startPtr = ptr-1; // to include digit already read

final int inputLen = _inputEnd;

// One special case, leading zero(es):
Expand Down Expand Up @@ -1269,7 +1288,7 @@ protected final JsonToken _parsePosNumber(int ch) throws IOException
}
++intLen;
}
if (ch == INT_PERIOD || ch == INT_e || ch == INT_E) {
if (ch == INT_PERIOD || ch == INT_e || ch == INT_E || numberHasLeadingDecimal) {
_inputPtr = ptr;
return _parseFloat(ch, startPtr, ptr, false, intLen);
}
Expand Down
Expand Up @@ -625,6 +625,7 @@ public JsonToken nextToken() throws IOException
case '7':
case '8':
case '9':
case '.':
t = _parsePosNumber(i);
break;
case 'f':
Expand Down Expand Up @@ -690,6 +691,7 @@ private final JsonToken _nextTokenNotInObject(int i) throws IOException
case '7':
case '8':
case '9':
case '.':
return (_currToken = _parsePosNumber(i));
}
return (_currToken = _handleUnexpectedValue(i));
Expand Down Expand Up @@ -795,6 +797,7 @@ public String nextFieldName() throws IOException
case '7':
case '8':
case '9':
case '.':
t = _parsePosNumber(i);
break;
case 'f':
Expand Down Expand Up @@ -949,6 +952,16 @@ public Boolean nextBooleanValue() throws IOException
*/
protected JsonToken _parsePosNumber(int c) throws IOException
{

boolean forceFloat = false;
if (c == '.') {
if (isEnabled(JsonReadFeature.ALLOW_LEADING_DECIMAL_POINT_FOR_NUMBERS)) {
forceFloat = true;
} else {
return _handleUnexpectedValue(c);
}
}

char[] outBuf = _textBuffer.emptyAndGetCurrentSegment();
int outPtr;

Expand Down Expand Up @@ -979,7 +992,7 @@ protected JsonToken _parsePosNumber(int c) throws IOException
outBuf[outPtr++] = (char) c;
c = _inputData.readUnsignedByte();
}
if (c == '.' || c == 'e' || c == 'E') {
if (c == '.' || c == 'e' || c == 'E' || forceFloat) {
return _parseFloat(outBuf, outPtr, c, false, intLen);
}
_textBuffer.setCurrentLength(outPtr);
Expand Down
Expand Up @@ -749,6 +749,7 @@ public JsonToken nextToken() throws IOException
case '7':
case '8':
case '9':
case '.':
t = _parsePosNumber(i);
break;
case 'f':
Expand Down Expand Up @@ -814,6 +815,7 @@ private final JsonToken _nextTokenNotInObject(int i) throws IOException
case '7':
case '8':
case '9':
case '.':
return (_currToken = _parsePosNumber(i));
}
return (_currToken = _handleUnexpectedValue(i));
Expand Down Expand Up @@ -930,6 +932,7 @@ public String nextFieldName() throws IOException
case '7':
case '8':
case '9':
case '.':
t = _parsePosNumber(i);
break;
case 'f':
Expand Down Expand Up @@ -1142,6 +1145,7 @@ public int nextFieldName(FieldNameMatcher matcher) throws IOException
case '7':
case '8':
case '9':
case '.':
t = _parsePosNumber(i);
break;
case 'f':
Expand Down Expand Up @@ -1260,6 +1264,7 @@ private final void _isNextTokenNameYes(int i) throws IOException
case '7':
case '8':
case '9':
case '.':
_nextToken = _parsePosNumber(i);
return;
}
Expand Down Expand Up @@ -1317,6 +1322,7 @@ private final boolean _isNextTokenNameMaybe(int i, SerializableString str) throw
case '7':
case '8':
case '9':
case '.':
t = _parsePosNumber(i);
break;
default:
Expand Down Expand Up @@ -1667,6 +1673,19 @@ public Boolean nextBooleanValue() throws IOException
/**********************************************************
*/

protected JsonToken _parsePosNumber(int c) throws IOException
{
if (c == '.') {
if (isEnabled(JsonReadFeature.ALLOW_LEADING_DECIMAL_POINT_FOR_NUMBERS)) {
return (_currToken = _parsePosNumber(c, true));
} else {
return _handleUnexpectedValue(c);
}
}

return _parsePosNumber(c, false);
}

/**
* Initial parsing method for number values. It needs to be able
* to parse enough input to be able to determine whether the
Expand All @@ -1682,17 +1701,25 @@ public Boolean nextBooleanValue() throws IOException
* deferred, since it is usually the most complicated and costliest
* part of processing.
*/
protected JsonToken _parsePosNumber(int c) throws IOException
protected JsonToken _parsePosNumber(int c, boolean leadingDecimal) throws IOException
{
char[] outBuf = _textBuffer.emptyAndGetCurrentSegment();
// One special case: if first char is 0, must not be followed by a digit
if (c == INT_0) {
c = _verifyNoLeadingZeroes();
}
// Ok: we can first just add digit we saw first:
outBuf[0] = (char) c;
int intLen = 1;
int outPtr = 1;
int intLen;
int outPtr;
if (leadingDecimal) {
_inputPtr--;
intLen = 0;
outPtr = 0;
} else {
intLen = 1;
outPtr = 1;
outBuf[0] = (char) c;
}
// And then figure out how far we can read without further checks
// for either input or output
final int end = Math.min(_inputEnd, _inputPtr + outBuf.length - 1); // 1 == outPtr
Expand Down
Expand Up @@ -765,6 +765,35 @@ public void testInvalidNumber() throws Exception {
}
}

/**
* The format ".NNN" (as opposed to "0.NNN") is not valid JSON, so this should fail
*/
public void testLeadingDotInDecimal() throws Exception {
for (int mode : ALL_MODES) {
JsonParser p = createParser(mode, " .123 ");
try {
p.nextToken();
fail("Should not pass");
} catch (JsonParseException e) {
verifyException(e, "Unexpected character ('.'");
}
p.close();
}
}

public void testLeadingDotInDecimalAllowed() throws Exception {
for (int mode : ALL_MODES) {
final JsonFactory f = JsonFactory.builder()
.enable(JsonReadFeature.ALLOW_LEADING_DECIMAL_POINT_FOR_NUMBERS)
.build();
JsonParser p = createParser(f, mode, " .123 ");
assertEquals(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken());
assertEquals(0.123, p.getValueAsDouble());
assertEquals("0.123", p.getDecimalValue().toString());
p.close();
}
}

/*
/**********************************************************
/* Helper methods
Expand Down