Skip to content

Commit ab9b00a

Browse files
committed
Fix #314
1 parent 59bb9f7 commit ab9b00a

File tree

7 files changed

+131
-88
lines changed

7 files changed

+131
-88
lines changed

release-notes/VERSION

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ JSON library.
2222
#312: Add `JsonProcessingException.clearLocation()` to allow clearing
2323
possibly security-sensitive information
2424
(contributed by Alex Y)
25+
#314: Add a method in `JsonParser` to allow checking for "NaN" values
2526
#323: Add `JsonParser.ALLOW_TRAILING_COMMA` to work for Arrays and Objects
2627
(contributed by Brad H)
2728
#325: `DataInput` backed parser should handle `EOFException` at end of doc

src/main/java/com/fasterxml/jackson/core/JsonGenerator.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -939,7 +939,7 @@ public void writeArray(double[] array, int offset, int length) throws IOExceptio
939939
* surrounded in double quotes, and contents will be properly
940940
* escaped as required by JSON specification.
941941
* If the reader is null, then write a null.
942-
* If len is < 0, then write all contents of the reader.
942+
* If len is &lt; 0, then write all contents of the reader.
943943
* Otherwise, write only len characters.
944944
*
945945
* @since 2.9

src/main/java/com/fasterxml/jackson/core/JsonParser.java

+63-46
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,47 @@ public void setSchema(FormatSchema schema) {
537537
@Override
538538
public abstract void close() throws IOException;
539539

540+
/**
541+
* Method that can be called to determine whether this parser
542+
* is closed or not. If it is closed, no new tokens can be
543+
* retrieved by calling {@link #nextToken} (and the underlying
544+
* stream may be closed). Closing may be due to an explicit
545+
* call to {@link #close} or because parser has encountered
546+
* end of input.
547+
*/
548+
public abstract boolean isClosed();
549+
550+
/*
551+
/**********************************************************
552+
/* Public API, simple location, context accessors
553+
/**********************************************************
554+
*/
555+
556+
/**
557+
* Method that can be used to access current parsing context reader
558+
* is in. There are 3 different types: root, array and object contexts,
559+
* with slightly different available information. Contexts are
560+
* hierarchically nested, and can be used for example for figuring
561+
* out part of the input document that correspond to specific
562+
* array or object (for highlighting purposes, or error reporting).
563+
* Contexts can also be used for simple xpath-like matching of
564+
* input, if so desired.
565+
*/
566+
public abstract JsonStreamContext getParsingContext();
567+
568+
/**
569+
* Method that return the <b>starting</b> location of the current
570+
* token; that is, position of the first character from input
571+
* that starts the current token.
572+
*/
573+
public abstract JsonLocation getTokenLocation();
574+
575+
/**
576+
* Method that returns location of the last processed character;
577+
* usually for error reporting purposes.
578+
*/
579+
public abstract JsonLocation getCurrentLocation();
580+
540581
/*
541582
/**********************************************************
542583
/* Buffer handling
@@ -857,19 +898,9 @@ public void finishToken() throws IOException {
857898
; // nothing
858899
}
859900

860-
/**
861-
* Method that can be called to determine whether this parser
862-
* is closed or not. If it is closed, no new tokens can be
863-
* retrieved by calling {@link #nextToken} (and the underlying
864-
* stream may be closed). Closing may be due to an explicit
865-
* call to {@link #close} or because parser has encountered
866-
* end of input.
867-
*/
868-
public abstract boolean isClosed();
869-
870901
/*
871902
/**********************************************************
872-
/* Public API, token accessors
903+
/* Public API, simple token id/type access
873904
/**********************************************************
874905
*/
875906

@@ -959,40 +990,6 @@ public int currentTokenId() {
959990
* @since 2.6
960991
*/
961992
public abstract boolean hasToken(JsonToken t);
962-
963-
/**
964-
* Method that can be called to get the name associated with
965-
* the current token: for {@link JsonToken#FIELD_NAME}s it will
966-
* be the same as what {@link #getText} returns;
967-
* for field values it will be preceding field name;
968-
* and for others (array values, root-level values) null.
969-
*/
970-
public abstract String getCurrentName() throws IOException;
971-
972-
/**
973-
* Method that can be used to access current parsing context reader
974-
* is in. There are 3 different types: root, array and object contexts,
975-
* with slightly different available information. Contexts are
976-
* hierarchically nested, and can be used for example for figuring
977-
* out part of the input document that correspond to specific
978-
* array or object (for highlighting purposes, or error reporting).
979-
* Contexts can also be used for simple xpath-like matching of
980-
* input, if so desired.
981-
*/
982-
public abstract JsonStreamContext getParsingContext();
983-
984-
/**
985-
* Method that return the <b>starting</b> location of the current
986-
* token; that is, position of the first character from input
987-
* that starts the current token.
988-
*/
989-
public abstract JsonLocation getTokenLocation();
990-
991-
/**
992-
* Method that returns location of the last processed character;
993-
* usually for error reporting purposes.
994-
*/
995-
public abstract JsonLocation getCurrentLocation();
996993

997994
/**
998995
* Specialized accessor that can be used to verify that the current
@@ -1022,7 +1019,18 @@ public int currentTokenId() {
10221019
* @since 2.5
10231020
*/
10241021
public boolean isExpectedStartObjectToken() { return currentToken() == JsonToken.START_OBJECT; }
1025-
1022+
1023+
/**
1024+
* Access for checking whether current token is a numeric value token, but
1025+
* one that is of "not-a-number" (NaN) variety: not supported by all formats,
1026+
* but often supported for {@link JsonToken#VALUE_NUMBER_FLOAT}.
1027+
*
1028+
* @since 2.9
1029+
*/
1030+
public boolean isNaN() {
1031+
return false;
1032+
}
1033+
10261034
/*
10271035
/**********************************************************
10281036
/* Public API, token state overrides
@@ -1071,6 +1079,15 @@ public int currentTokenId() {
10711079
/**********************************************************
10721080
*/
10731081

1082+
/**
1083+
* Method that can be called to get the name associated with
1084+
* the current token: for {@link JsonToken#FIELD_NAME}s it will
1085+
* be the same as what {@link #getText} returns;
1086+
* for field values it will be preceding field name;
1087+
* and for others (array values, root-level values) null.
1088+
*/
1089+
public abstract String getCurrentName() throws IOException;
1090+
10741091
/**
10751092
* Method for accessing textual representation of the current token;
10761093
* if no current token (before first call to {@link #nextToken}, or

src/main/java/com/fasterxml/jackson/core/base/ParserBase.java

+18-27
Original file line numberDiff line numberDiff line change
@@ -615,7 +615,17 @@ protected final JsonToken resetAsNaN(String valueStr, double value)
615615
_numTypesValid = NR_DOUBLE;
616616
return JsonToken.VALUE_NUMBER_FLOAT;
617617
}
618-
618+
619+
@Override
620+
public boolean isNaN() {
621+
if (_currToken == JsonToken.VALUE_NUMBER_FLOAT) {
622+
if ((_numTypesValid & NR_DOUBLE) != 0) {
623+
return Double.isNaN(_numberDouble);
624+
}
625+
}
626+
return false;
627+
}
628+
619629
/*
620630
/**********************************************************
621631
/* Numeric accessors of public API
@@ -1046,33 +1056,13 @@ protected void convertNumberToBigDecimal() throws IOException
10461056
/* Internal/package methods: Error reporting
10471057
/**********************************************************
10481058
*/
1049-
1050-
protected void _reportMismatchedEndMarker(int actCh, char expCh) throws JsonParseException {
1051-
String startDesc = ""+_parsingContext.getStartLocation(_getSourceReference());
1052-
_reportError("Unexpected close marker '"+((char) actCh)+"': expected '"+expCh+"' (for "+_parsingContext.typeDesc()+" starting at "+startDesc+")");
1053-
}
10541059

1055-
protected void reportUnexpectedNumberChar(int ch, String comment) throws JsonParseException {
1056-
String msg = "Unexpected character ("+_getCharDesc(ch)+") in numeric value";
1057-
if (comment != null) {
1058-
msg += ": "+comment;
1059-
}
1060-
_reportError(msg);
1061-
}
1062-
1063-
protected void reportInvalidNumber(String msg) throws JsonParseException {
1064-
_reportError("Invalid numeric value: "+msg);
1065-
}
1066-
1067-
protected void reportOverflowInt() throws IOException {
1068-
_reportError(String.format("Numeric value (%s) out of range of int (%d - %s)",
1069-
getText(), Integer.MIN_VALUE, Integer.MAX_VALUE));
1060+
protected void _reportMismatchedEndMarker(int actCh, char expCh) throws JsonParseException {
1061+
JsonReadContext ctxt = getParsingContext();
1062+
_reportError(String.format(
1063+
"Unexpected close marker '%s': expected '%c' (for %s starting at %s)",
1064+
(char) actCh, expCh, ctxt.typeDesc(), ctxt.getStartLocation(_getSourceReference())));
10701065
}
1071-
1072-
protected void reportOverflowLong() throws IOException {
1073-
_reportError(String.format("Numeric value (%s) out of range of long (%d - %s)",
1074-
getText(), Long.MIN_VALUE, Long.MAX_VALUE));
1075-
}
10761066

10771067
/*
10781068
/**********************************************************
@@ -1141,7 +1131,8 @@ protected IllegalArgumentException reportInvalidBase64Char(Base64Variant b64vari
11411131
protected IllegalArgumentException reportInvalidBase64Char(Base64Variant b64variant, int ch, int bindex, String msg) throws IllegalArgumentException {
11421132
String base;
11431133
if (ch <= INT_SPACE) {
1144-
base = "Illegal white space character (code 0x"+Integer.toHexString(ch)+") as character #"+(bindex+1)+" of 4-char base64 unit: can only used between units";
1134+
base = String.format("Illegal white space character (code 0x%s) as character #%d of 4-char base64 unit: can only used between units",
1135+
Integer.toHexString(ch), (bindex+1));
11451136
} else if (b64variant.usesPaddingChar(ch)) {
11461137
base = "Unexpected padding character ('"+b64variant.getPaddingChar()+"') as character #"+(bindex+1)+" of 4-char base64 unit: padding only legal as 3rd or 4th character";
11471138
} else if (!Character.isDefined(ch) || Character.isISOControl(ch)) {

src/main/java/com/fasterxml/jackson/core/base/ParserMinimalBase.java

+26-4
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ protected void _decodeBase64(String str, ByteArrayBuilder builder, Base64Variant
425425
/* Coercion helper methods (overridable)
426426
/**********************************************************
427427
*/
428-
428+
429429
/**
430430
* Helper method used to determine whether we are currently pointing to
431431
* a String value of "null" (NOT a null token); and, if so, that parser
@@ -434,19 +434,41 @@ protected void _decodeBase64(String str, ByteArrayBuilder builder, Base64Variant
434434
* @since 2.3
435435
*/
436436
protected boolean _hasTextualNull(String value) { return "null".equals(value); }
437-
437+
438438
/*
439439
/**********************************************************
440440
/* Error reporting
441441
/**********************************************************
442442
*/
443+
444+
protected void reportUnexpectedNumberChar(int ch, String comment) throws JsonParseException {
445+
String msg = String.format("Unexpected character (%s) in numeric value", _getCharDesc(ch));
446+
if (comment != null) {
447+
msg += ": "+comment;
448+
}
449+
_reportError(msg);
450+
}
451+
452+
protected void reportInvalidNumber(String msg) throws JsonParseException {
453+
_reportError("Invalid numeric value: "+msg);
454+
}
455+
456+
protected void reportOverflowInt() throws IOException {
457+
_reportError(String.format("Numeric value (%s) out of range of int (%d - %s)",
458+
getText(), Integer.MIN_VALUE, Integer.MAX_VALUE));
459+
}
443460

461+
protected void reportOverflowLong() throws IOException {
462+
_reportError(String.format("Numeric value (%s) out of range of long (%d - %s)",
463+
getText(), Long.MIN_VALUE, Long.MAX_VALUE));
464+
}
465+
444466
protected void _reportUnexpectedChar(int ch, String comment) throws JsonParseException
445467
{
446468
if (ch < 0) { // sanity check
447469
_reportInvalidEOF();
448470
}
449-
String msg = "Unexpected character ("+_getCharDesc(ch)+")";
471+
String msg = String.format("Unexpected character (%s)", _getCharDesc(ch));
450472
if (comment != null) {
451473
msg += ": "+comment;
452474
}
@@ -532,7 +554,7 @@ protected char _handleUnrecognizedCharacterEscape(char ch) throws JsonProcessing
532554
_reportError("Unrecognized character escape "+_getCharDesc(ch));
533555
return ch;
534556
}
535-
557+
536558
/*
537559
/**********************************************************
538560
/* Error reporting, generic

src/main/java/com/fasterxml/jackson/core/util/JsonParserDelegate.java

+1
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ public JsonParser overrideFormatFeatures(int values, int mask) {
122122
@Override public JsonStreamContext getParsingContext() { return delegate.getParsingContext(); }
123123
@Override public boolean isExpectedStartArrayToken() { return delegate.isExpectedStartArrayToken(); }
124124
@Override public boolean isExpectedStartObjectToken() { return delegate.isExpectedStartObjectToken(); }
125+
@Override public boolean isNaN() { return delegate.isNaN(); }
125126

126127
/*
127128
/**********************************************************

src/test/java/com/fasterxml/jackson/core/read/NumberParsingTest.java

+21-10
Original file line numberDiff line numberDiff line change
@@ -461,8 +461,16 @@ private void _testIssue160LongNumbers(JsonFactory f, String doc, boolean useStre
461461
*/
462462
public void testParsingOfLongerSequencesWithNonNumeric() throws Exception
463463
{
464-
JsonFactory factory = new JsonFactory();
465-
factory.enable(JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS);
464+
JsonFactory f = new JsonFactory();
465+
f.enable(JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS);
466+
_testParsingOfLongerSequencesWithNonNumeric(f, MODE_INPUT_STREAM);
467+
_testParsingOfLongerSequencesWithNonNumeric(f, MODE_INPUT_STREAM_THROTTLED);
468+
_testParsingOfLongerSequencesWithNonNumeric(f, MODE_READER);
469+
_testParsingOfLongerSequencesWithNonNumeric(f, MODE_DATA_INPUT);
470+
}
471+
472+
private void _testParsingOfLongerSequencesWithNonNumeric(JsonFactory f, int mode) throws Exception
473+
{
466474
double[] values = new double[] {
467475
0.01, -10.5, 2.1e9, 4.0e-8,
468476
Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY
@@ -479,24 +487,27 @@ public void testParsingOfLongerSequencesWithNonNumeric() throws Exception
479487
sb.append(arrayJson);
480488
String DOC = sb.toString();
481489
for (int input = 0; input < 2; ++input) {
482-
JsonParser p;
483-
if (input == 0) {
484-
p = createParserUsingStream(factory, DOC, "UTF-8");
485-
} else {
486-
p = factory.createParser(DOC);
487-
}
490+
JsonParser p = createParser(f, mode, DOC);
488491
assertToken(JsonToken.START_ARRAY, p.nextToken());
489492
for (int j = 0; j < VCOUNT; ++j) {
490493
assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken());
491-
assertEquals(values[i], p.getDoubleValue());
494+
double exp = values[i];
495+
double act = p.getDoubleValue();
496+
if (Double.compare(exp, act) != 0) {
497+
fail("Expected at #"+j+" value "+exp+", instead got "+act);
498+
}
499+
if (Double.isNaN(exp)) {
500+
assertTrue(p.isNaN());
501+
} else {
502+
assertFalse(p.isNaN());
503+
}
492504
}
493505
assertToken(JsonToken.END_ARRAY, p.nextToken());
494506
p.close();
495507
}
496508
}
497509
}
498510

499-
500511
/*
501512
/**********************************************************
502513
/* Tests for invalid access

0 commit comments

Comments
 (0)