Merge branch '2.8'
diff --git a/.travis.yml b/.travis.yml
index 75db9cc..73b3221 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -14,8 +14,8 @@
branches:
only:
- master
+ - "2.8"
- "2.7"
- - "2.6"
env:
global:
diff --git a/README.md b/README.md
index b93f417..413657c 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,7 @@
[![Build Status](https://travis-ci.org/FasterXML/jackson-core.svg?branch=master)](https://travis-ci.org/FasterXML/jackson-core) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.fasterxml.jackson.core/jackson-core/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.fasterxml.jackson.core/jackson-core)
[![Javadoc](https://javadoc-emblem.rhcloud.com/doc/com.fasterxml.jackson.core/jackson-core/badge.svg)](http://www.javadoc.io/doc/com.fasterxml.jackson.core/jackson-core)
+[![Coverage Status](https://coveralls.io/repos/github/FasterXML/jackson-core/badge.svg?branch=master)](https://coveralls.io/github/FasterXML/jackson-core?branch=master)
# Get it!
diff --git a/pom.xml b/pom.xml
index 8752c38..53ca4bf 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,13 +3,13 @@
<parent>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-parent</artifactId>
- <version>2.8</version>
+ <version>2.9-rc1-SNAPSHOT</version>
</parent>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<name>Jackson-core</name>
- <version>2.8.5-SNAPSHOT</version>
+ <version>2.9.0-SNAPSHOT</version>
<packaging>bundle</packaging>
<description>Core Jackson abstractions, basic JSON streaming API implementation</description>
<inceptionYear>2008</inceptionYear>
@@ -23,7 +23,7 @@
</scm>
<properties>
- <!-- 29-Apr-2016, tatu: Retain Java6/JDK1.6 compatibility for streaming for Jackson 2.8 -->
+ <!-- 16-Sep-2016, tatu: Retain Java6/JDK1.6 compatibility for streaming for Jackson 2.x -->
<javac.src.version>1.6</javac.src.version>
<javac.target.version>1.6</javac.target.version>
diff --git a/src/main/java/com/fasterxml/jackson/core/JsonParser.java b/src/main/java/com/fasterxml/jackson/core/JsonParser.java
index 92f16de..36c7df5 100644
--- a/src/main/java/com/fasterxml/jackson/core/JsonParser.java
+++ b/src/main/java/com/fasterxml/jackson/core/JsonParser.java
@@ -227,7 +227,30 @@
*
* @since 2.8
*/
- ALLOW_MISSING_VALUES(false)
+ ALLOW_MISSING_VALUES(false),
+
+ /**
+ * Feature that determines whether {@link JsonParser} will allow for a single trailing
+ * comma following the final value (in an Array) or member (in an Object). These commas
+ * will simply be ignored.
+ * <p>
+ * For example, when this feature is enabled, <code>[true,true,]</code> is equivalent to
+ * <code>[true, true]</code> and <code>{"a": true,}</code> is equivalent to
+ * <code>{"a": true}</code>.
+ * <p>
+ * When combined with <code>ALLOW_MISSING_VALUES</code>, this feature takes priority, and
+ * the final trailing comma in an array declaration does not imply a missing
+ * (<code>null</code>) value. For example, when both <code>ALLOW_MISSING_VALUES</code>
+ * and <code>ALLOW_TRAILING_COMMA</code> are enabled, <code>[true,true,]</code> is
+ * equivalent to <code>[true, true]</code>, and <code>[true,true,,]</code> is equivalent to
+ * <code>[true, true, null]</code>.
+ * <p>
+ * Since the JSON specification does not permit trailing commas, this is a non-standard
+ * feature, and as such disabled by default.
+ *
+ * @since 2.9
+ */
+ ALLOW_TRAILING_COMMA(false)
;
/**
diff --git a/src/main/java/com/fasterxml/jackson/core/JsonPointer.java b/src/main/java/com/fasterxml/jackson/core/JsonPointer.java
index f927336..6681992 100644
--- a/src/main/java/com/fasterxml/jackson/core/JsonPointer.java
+++ b/src/main/java/com/fasterxml/jackson/core/JsonPointer.java
@@ -20,6 +20,13 @@
public class JsonPointer
{
/**
+ * Character used to separate segments.
+ *
+ * @since 2.9
+ */
+ public final static char SEPARATOR = '/';
+
+ /**
* Marker instance used to represent segment that matches current
* node or position (that is, returns true for
* {@link #matches()}).
diff --git a/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java b/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java
index dc80f62..b27de2e 100644
--- a/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java
+++ b/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java
@@ -652,26 +652,20 @@
_binaryValue = null;
// Closing scope?
- if (i == INT_RBRACKET) {
- _updateLocation();
- if (!_parsingContext.inArray()) {
- _reportMismatchedEndMarker(i, '}');
- }
- _parsingContext = _parsingContext.clearAndGetParent();
- return (_currToken = JsonToken.END_ARRAY);
- }
- if (i == INT_RCURLY) {
- _updateLocation();
- if (!_parsingContext.inObject()) {
- _reportMismatchedEndMarker(i, ']');
- }
- _parsingContext = _parsingContext.clearAndGetParent();
- return (_currToken = JsonToken.END_OBJECT);
+ if (i == INT_RBRACKET || i == INT_RCURLY) {
+ _closeScope(i);
+ return _currToken;
}
// Nope: do we then expect a comma?
if (_parsingContext.expectComma()) {
i = _skipComma(i);
+
+ // Was that a trailing comma?
+ if (isEnabled(Feature.ALLOW_TRAILING_COMMA) && (i == INT_RBRACKET || i == INT_RCURLY)) {
+ _closeScope(i);
+ return _currToken;
+ }
}
/* And should we now have a name? Always true for Object contexts, since
@@ -811,26 +805,20 @@
}
_binaryValue = null;
- if (i == INT_RBRACKET) {
- _updateLocation();
- if (!_parsingContext.inArray()) {
- _reportMismatchedEndMarker(i, '}');
- }
- _parsingContext = _parsingContext.clearAndGetParent();
- _currToken = JsonToken.END_ARRAY;
+ // Closing scope?
+ if (i == INT_RBRACKET || i == INT_RCURLY) {
+ _closeScope(i);
return false;
}
- if (i == INT_RCURLY) {
- _updateLocation();
- if (!_parsingContext.inObject()) {
- _reportMismatchedEndMarker(i, ']');
- }
- _parsingContext = _parsingContext.clearAndGetParent();
- _currToken = JsonToken.END_OBJECT;
- return false;
- }
+
if (_parsingContext.expectComma()) {
i = _skipComma(i);
+
+ // Was that a trailing comma?
+ if (isEnabled(Feature.ALLOW_TRAILING_COMMA) && (i == INT_RBRACKET || i == INT_RCURLY)) {
+ _closeScope(i);
+ return false;
+ }
}
if (!_parsingContext.inObject()) {
@@ -2834,4 +2822,29 @@
}
_reportError("Unrecognized token '"+sb.toString()+"': was expecting "+msg);
}
+
+ /*
+ /**********************************************************
+ /* Internal methods, other
+ /**********************************************************
+ */
+
+ private void _closeScope(int i) throws JsonParseException {
+ if (i == INT_RBRACKET) {
+ _updateLocation();
+ if (!_parsingContext.inArray()) {
+ _reportMismatchedEndMarker(i, '}');
+ }
+ _parsingContext = _parsingContext.clearAndGetParent();
+ _currToken = JsonToken.END_ARRAY;
+ }
+ if (i == INT_RCURLY) {
+ _updateLocation();
+ if (!_parsingContext.inObject()) {
+ _reportMismatchedEndMarker(i, ']');
+ }
+ _parsingContext = _parsingContext.clearAndGetParent();
+ _currToken = JsonToken.END_OBJECT;
+ }
+ }
}
diff --git a/src/main/java/com/fasterxml/jackson/core/json/UTF8DataInputJsonParser.java b/src/main/java/com/fasterxml/jackson/core/json/UTF8DataInputJsonParser.java
index 8bc3789..de5d081 100644
--- a/src/main/java/com/fasterxml/jackson/core/json/UTF8DataInputJsonParser.java
+++ b/src/main/java/com/fasterxml/jackson/core/json/UTF8DataInputJsonParser.java
@@ -575,19 +575,9 @@
_tokenInputRow = _currInputRow;
// Closing scope?
- if (i == INT_RBRACKET) {
- if (!_parsingContext.inArray()) {
- _reportMismatchedEndMarker(i, '}');
- }
- _parsingContext = _parsingContext.clearAndGetParent();
- return (_currToken = JsonToken.END_ARRAY);
- }
- if (i == INT_RCURLY) {
- if (!_parsingContext.inObject()) {
- _reportMismatchedEndMarker(i, ']');
- }
- _parsingContext = _parsingContext.clearAndGetParent();
- return (_currToken = JsonToken.END_OBJECT);
+ if (i == INT_RBRACKET || i == INT_RCURLY) {
+ _closeScope(i);
+ return _currToken;
}
// Nope: do we then expect a comma?
@@ -596,6 +586,12 @@
_reportUnexpectedChar(i, "was expecting comma to separate "+_parsingContext.typeDesc()+" entries");
}
i = _skipWS();
+
+ // Was that a trailing comma?
+ if (isEnabled(Feature.ALLOW_TRAILING_COMMA) && (i == INT_RBRACKET || i == INT_RCURLY)) {
+ _closeScope(i);
+ return _currToken;
+ }
}
/* And should we now have a name? Always true for
@@ -2788,6 +2784,23 @@
/**********************************************************
*/
+ private void _closeScope(int i) throws JsonParseException {
+ if (i == INT_RBRACKET) {
+ if (!_parsingContext.inArray()) {
+ _reportMismatchedEndMarker(i, '}');
+ }
+ _parsingContext = _parsingContext.clearAndGetParent();
+ _currToken = JsonToken.END_ARRAY;
+ }
+ if (i == INT_RCURLY) {
+ if (!_parsingContext.inObject()) {
+ _reportMismatchedEndMarker(i, ']');
+ }
+ _parsingContext = _parsingContext.clearAndGetParent();
+ _currToken = JsonToken.END_OBJECT;
+ }
+ }
+
/**
* Helper method needed to fix [Issue#148], masking of 0x00 character
*/
diff --git a/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java b/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java
index 5a0dcda..23f52bf 100644
--- a/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java
+++ b/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java
@@ -738,21 +738,9 @@
_binaryValue = null;
// Closing scope?
- if (i == INT_RBRACKET) {
- _updateLocation();
- if (!_parsingContext.inArray()) {
- _reportMismatchedEndMarker(i, '}');
- }
- _parsingContext = _parsingContext.clearAndGetParent();
- return (_currToken = JsonToken.END_ARRAY);
- }
- if (i == INT_RCURLY) {
- _updateLocation();
- if (!_parsingContext.inObject()) {
- _reportMismatchedEndMarker(i, ']');
- }
- _parsingContext = _parsingContext.clearAndGetParent();
- return (_currToken = JsonToken.END_OBJECT);
+ if (i == INT_RBRACKET || i == INT_RCURLY) {
+ _closeScope(i);
+ return _currToken;
}
// Nope: do we then expect a comma?
@@ -761,6 +749,12 @@
_reportUnexpectedChar(i, "was expecting comma to separate "+_parsingContext.typeDesc()+" entries");
}
i = _skipWS();
+
+ // Was that a trailing comma?
+ if (isEnabled(Feature.ALLOW_TRAILING_COMMA) && (i == INT_RBRACKET || i == INT_RCURLY)) {
+ _closeScope(i);
+ return _currToken;
+ }
}
/* And should we now have a name? Always true for
@@ -930,22 +924,8 @@
_binaryValue = null;
// Closing scope?
- if (i == INT_RBRACKET) {
- _updateLocation();
- if (!_parsingContext.inArray()) {
- _reportMismatchedEndMarker(i, '}');
- }
- _parsingContext = _parsingContext.clearAndGetParent();
- _currToken = JsonToken.END_ARRAY;
- return false;
- }
- if (i == INT_RCURLY) {
- _updateLocation();
- if (!_parsingContext.inObject()) {
- _reportMismatchedEndMarker(i, ']');
- }
- _parsingContext = _parsingContext.clearAndGetParent();
- _currToken = JsonToken.END_OBJECT;
+ if (i == INT_RBRACKET || i == INT_RCURLY) {
+ _closeScope(i);
return false;
}
@@ -955,6 +935,12 @@
_reportUnexpectedChar(i, "was expecting comma to separate "+_parsingContext.typeDesc()+" entries");
}
i = _skipWS();
+
+ // Was that a trailing comma?
+ if (isEnabled(Feature.ALLOW_TRAILING_COMMA) && (i == INT_RBRACKET || i == INT_RCURLY)) {
+ _closeScope(i);
+ return false;
+ }
}
if (!_parsingContext.inObject()) {
@@ -1017,22 +1003,8 @@
}
_binaryValue = null;
- if (i == INT_RBRACKET) {
- _updateLocation();
- if (!_parsingContext.inArray()) {
- _reportMismatchedEndMarker(i, '}');
- }
- _parsingContext = _parsingContext.clearAndGetParent();
- _currToken = JsonToken.END_ARRAY;
- return null;
- }
- if (i == INT_RCURLY) {
- _updateLocation();
- if (!_parsingContext.inObject()) {
- _reportMismatchedEndMarker(i, ']');
- }
- _parsingContext = _parsingContext.clearAndGetParent();
- _currToken = JsonToken.END_OBJECT;
+ if (i == INT_RBRACKET || i == INT_RCURLY) {
+ _closeScope(i);
return null;
}
@@ -1042,7 +1014,14 @@
_reportUnexpectedChar(i, "was expecting comma to separate "+_parsingContext.typeDesc()+" entries");
}
i = _skipWS();
+
+ // Was that a trailing comma?
+ if (isEnabled(Feature.ALLOW_TRAILING_COMMA) && (i == INT_RBRACKET || i == INT_RCURLY)) {
+ _closeScope(i);
+ return null;
+ }
}
+
if (!_parsingContext.inObject()) {
_updateLocation();
_nextTokenNotInObject(i);
@@ -3733,6 +3712,25 @@
/**********************************************************
*/
+ private void _closeScope(int i) throws JsonParseException {
+ if (i == INT_RBRACKET) {
+ _updateLocation();
+ if (!_parsingContext.inArray()) {
+ _reportMismatchedEndMarker(i, '}');
+ }
+ _parsingContext = _parsingContext.clearAndGetParent();
+ _currToken = JsonToken.END_ARRAY;
+ }
+ if (i == INT_RCURLY) {
+ _updateLocation();
+ if (!_parsingContext.inObject()) {
+ _reportMismatchedEndMarker(i, ']');
+ }
+ _parsingContext = _parsingContext.clearAndGetParent();
+ _currToken = JsonToken.END_OBJECT;
+ }
+ }
+
/**
* Helper method needed to fix [Issue#148], masking of 0x00 character
*/
diff --git a/src/test/java/com/fasterxml/jackson/core/read/TrailingCommasTest.java b/src/test/java/com/fasterxml/jackson/core/read/TrailingCommasTest.java
new file mode 100644
index 0000000..972b650
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/core/read/TrailingCommasTest.java
@@ -0,0 +1,316 @@
+package com.fasterxml.jackson.core.read;
+
+import com.fasterxml.jackson.core.BaseTest;
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonParser.Feature;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.core.json.UTF8DataInputJsonParser;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+
+@RunWith(Parameterized.class)
+public class TrailingCommasTest extends BaseTest {
+
+ private final JsonFactory factory;
+ private final HashSet<JsonParser.Feature> features;
+ private final int mode;
+
+ public TrailingCommasTest(int mode, List<Feature> features) {
+ this.factory = new JsonFactory();
+ this.features = new HashSet<JsonParser.Feature>(features);
+
+ for (JsonParser.Feature feature : features) {
+ factory.enable(feature);
+ }
+
+ this.mode = mode;
+ }
+
+ @Parameterized.Parameters(name = "Mode {0}, Features {1}")
+ public static Collection<Object[]> getTestCases() {
+ ArrayList<Object[]> cases = new ArrayList<Object[]>();
+
+ for (int mode : ALL_MODES) {
+ cases.add(new Object[]{mode, Collections.emptyList()});
+ cases.add(new Object[]{mode, Arrays.asList(Feature.ALLOW_MISSING_VALUES)});
+ cases.add(new Object[]{mode, Arrays.asList(Feature.ALLOW_TRAILING_COMMA)});
+ cases.add(new Object[]{mode, Arrays.asList(Feature.ALLOW_MISSING_VALUES, Feature.ALLOW_TRAILING_COMMA)});
+ }
+
+ return cases;
+ }
+
+ @Test
+ public void testArrayBasic() throws Exception {
+ String json = "[\"a\", \"b\"]";
+
+ JsonParser p = createParser(factory, mode, json);
+
+ assertEquals(JsonToken.START_ARRAY, p.nextToken());
+
+ assertToken(JsonToken.VALUE_STRING, p.nextToken());
+ assertEquals("a", p.getText());
+
+ assertToken(JsonToken.VALUE_STRING, p.nextToken());
+ assertEquals("b", p.getText());
+
+ assertEquals(JsonToken.END_ARRAY, p.nextToken());
+ assertEnd(p);
+ }
+
+ @Test
+ public void testArrayInnerComma() throws Exception {
+ String json = "[\"a\",, \"b\"]";
+
+ JsonParser p = createParser(factory, mode, json);
+
+ assertEquals(JsonToken.START_ARRAY, p.nextToken());
+
+ assertToken(JsonToken.VALUE_STRING, p.nextToken());
+ assertEquals("a", p.getText());
+
+ if (!features.contains(Feature.ALLOW_MISSING_VALUES)) {
+ assertUnexpected(p, ',');
+ return;
+ }
+
+ assertToken(JsonToken.VALUE_NULL, p.nextToken());
+
+ assertToken(JsonToken.VALUE_STRING, p.nextToken());
+ assertEquals("b", p.getText());
+
+ assertEquals(JsonToken.END_ARRAY, p.nextToken());
+ assertEnd(p);
+ }
+
+ @Test
+ public void testArrayLeadingComma() throws Exception {
+ String json = "[,\"a\", \"b\"]";
+
+ JsonParser p = createParser(factory, mode, json);
+
+ assertEquals(JsonToken.START_ARRAY, p.nextToken());
+
+ if (!features.contains(Feature.ALLOW_MISSING_VALUES)) {
+ assertUnexpected(p, ',');
+ return;
+ }
+
+ assertToken(JsonToken.VALUE_NULL, p.nextToken());
+
+ assertToken(JsonToken.VALUE_STRING, p.nextToken());
+ assertEquals("a", p.getText());
+
+ assertToken(JsonToken.VALUE_STRING, p.nextToken());
+ assertEquals("b", p.getText());
+
+ assertEquals(JsonToken.END_ARRAY, p.nextToken());
+ assertEnd(p);
+ }
+
+ @Test
+ public void testArrayTrailingComma() throws Exception {
+ String json = "[\"a\", \"b\",]";
+
+ JsonParser p = createParser(factory, mode, json);
+
+ assertEquals(JsonToken.START_ARRAY, p.nextToken());
+
+ assertToken(JsonToken.VALUE_STRING, p.nextToken());
+ assertEquals("a", p.getText());
+
+ assertToken(JsonToken.VALUE_STRING, p.nextToken());
+ assertEquals("b", p.getText());
+
+ // ALLOW_TRAILING_COMMA takes priority over ALLOW_MISSING_VALUES
+ if (features.contains(Feature.ALLOW_TRAILING_COMMA)) {
+ assertToken(JsonToken.END_ARRAY, p.nextToken());
+ assertEnd(p);
+ } else if (features.contains(Feature.ALLOW_MISSING_VALUES)) {
+ assertToken(JsonToken.VALUE_NULL, p.nextToken());
+ assertToken(JsonToken.END_ARRAY, p.nextToken());
+ assertEnd(p);
+ } else {
+ assertUnexpected(p, ']');
+ }
+ }
+
+ @Test
+ public void testArrayTrailingCommas() throws Exception {
+ String json = "[\"a\", \"b\",,]";
+
+ JsonParser p = createParser(factory, mode, json);
+
+ assertEquals(JsonToken.START_ARRAY, p.nextToken());
+
+ assertToken(JsonToken.VALUE_STRING, p.nextToken());
+ assertEquals("a", p.getText());
+
+ assertToken(JsonToken.VALUE_STRING, p.nextToken());
+ assertEquals("b", p.getText());
+
+ // ALLOW_TRAILING_COMMA takes priority over ALLOW_MISSING_VALUES
+ if (features.contains(Feature.ALLOW_MISSING_VALUES) &&
+ features.contains(Feature.ALLOW_TRAILING_COMMA)) {
+ assertToken(JsonToken.VALUE_NULL, p.nextToken());
+ assertToken(JsonToken.END_ARRAY, p.nextToken());
+ assertEnd(p);
+ } else if (features.contains(Feature.ALLOW_MISSING_VALUES)) {
+ assertToken(JsonToken.VALUE_NULL, p.nextToken());
+ assertToken(JsonToken.VALUE_NULL, p.nextToken());
+ assertToken(JsonToken.END_ARRAY, p.nextToken());
+ assertEnd(p);
+ } else {
+ assertUnexpected(p, ',');
+ }
+ }
+
+ @Test
+ public void testArrayTrailingCommasTriple() throws Exception {
+ String json = "[\"a\", \"b\",,,]";
+
+ JsonParser p = createParser(factory, mode, json);
+
+ assertEquals(JsonToken.START_ARRAY, p.nextToken());
+
+ assertToken(JsonToken.VALUE_STRING, p.nextToken());
+ assertEquals("a", p.getText());
+
+ assertToken(JsonToken.VALUE_STRING, p.nextToken());
+ assertEquals("b", p.getText());
+
+ // ALLOW_TRAILING_COMMA takes priority over ALLOW_MISSING_VALUES
+ if (features.contains(Feature.ALLOW_MISSING_VALUES) &&
+ features.contains(Feature.ALLOW_TRAILING_COMMA)) {
+ assertToken(JsonToken.VALUE_NULL, p.nextToken());
+ assertToken(JsonToken.VALUE_NULL, p.nextToken());
+ assertToken(JsonToken.END_ARRAY, p.nextToken());
+ assertEnd(p);
+ } else if (features.contains(Feature.ALLOW_MISSING_VALUES)) {
+ assertToken(JsonToken.VALUE_NULL, p.nextToken());
+ assertToken(JsonToken.VALUE_NULL, p.nextToken());
+ assertToken(JsonToken.VALUE_NULL, p.nextToken());
+ assertToken(JsonToken.END_ARRAY, p.nextToken());
+ assertEnd(p);
+ } else {
+ assertUnexpected(p, ',');
+ }
+ }
+
+ @Test
+ public void testObjectBasic() throws Exception {
+ String json = "{\"a\": true, \"b\": false}";
+
+ JsonParser p = createParser(factory, mode, json);
+
+ assertEquals(JsonToken.START_OBJECT, p.nextToken());
+
+ assertToken(JsonToken.FIELD_NAME, p.nextToken());
+ assertEquals("a", p.getText());
+ assertToken(JsonToken.VALUE_TRUE, p.nextToken());
+
+ assertToken(JsonToken.FIELD_NAME, p.nextToken());
+ assertEquals("b", p.getText());
+ assertToken(JsonToken.VALUE_FALSE, p.nextToken());
+
+ assertEquals(JsonToken.END_OBJECT, p.nextToken());
+ assertEnd(p);
+ }
+
+ @Test
+ public void testObjectInnerComma() throws Exception {
+ String json = "{\"a\": true,, \"b\": false}";
+
+ JsonParser p = createParser(factory, mode, json);
+
+ assertEquals(JsonToken.START_OBJECT, p.nextToken());
+
+ assertToken(JsonToken.FIELD_NAME, p.nextToken());
+ assertEquals("a", p.getText());
+ assertToken(JsonToken.VALUE_TRUE, p.nextToken());
+
+ assertUnexpected(p, ',');
+ }
+
+ @Test
+ public void testObjectLeadingComma() throws Exception {
+ String json = "{,\"a\": true, \"b\": false}";
+
+ JsonParser p = createParser(factory, mode, json);
+
+ assertEquals(JsonToken.START_OBJECT, p.nextToken());
+
+ assertUnexpected(p, ',');
+ }
+
+ @Test
+ public void testObjectTrailingComma() throws Exception {
+ String json = "{\"a\": true, \"b\": false,}";
+
+ JsonParser p = createParser(factory, mode, json);
+
+ assertEquals(JsonToken.START_OBJECT, p.nextToken());
+
+ assertToken(JsonToken.FIELD_NAME, p.nextToken());
+ assertEquals("a", p.getText());
+ assertToken(JsonToken.VALUE_TRUE, p.nextToken());
+
+ assertToken(JsonToken.FIELD_NAME, p.nextToken());
+ assertEquals("b", p.getText());
+ assertToken(JsonToken.VALUE_FALSE, p.nextToken());
+
+ if (features.contains(Feature.ALLOW_TRAILING_COMMA)) {
+ assertToken(JsonToken.END_OBJECT, p.nextToken());
+ assertEnd(p);
+ } else {
+ assertUnexpected(p, '}');
+ }
+ }
+
+ @Test
+ public void testObjectTrailingCommas() throws Exception {
+ String json = "{\"a\": true, \"b\": false,,}";
+
+ JsonParser p = createParser(factory, mode, json);
+
+ assertEquals(JsonToken.START_OBJECT, p.nextToken());
+
+ assertToken(JsonToken.FIELD_NAME, p.nextToken());
+ assertEquals("a", p.getText());
+ assertToken(JsonToken.VALUE_TRUE, p.nextToken());
+
+ assertToken(JsonToken.FIELD_NAME, p.nextToken());
+ assertEquals("b", p.getText());
+ assertToken(JsonToken.VALUE_FALSE, p.nextToken());
+
+ assertUnexpected(p, ',');
+ }
+
+ private void assertEnd(JsonParser p) throws IOException {
+ // Issue #325
+ if (!(p instanceof UTF8DataInputJsonParser)) {
+ JsonToken next = p.nextToken();
+ assertNull("expected end of stream but found " + next, next);
+ }
+ }
+
+ private void assertUnexpected(JsonParser p, char c) throws IOException {
+ try {
+ p.nextToken();
+ fail("No exception thrown");
+ } catch (Exception e) {
+ verifyException(e, String.format("Unexpected character ('%s' (code %d))", c, (int) c));
+ }
+ }
+}