Skip to content

Commit ad6d3cc

Browse files
authored
Merge pull request #327 from bdhess/allow-trailing-comma
Add JsonParser feature to ignore a trailing comma (fixes #118, #323)
2 parents 5e59e9c + d7a6f6c commit ad6d3cc

File tree

5 files changed

+455
-92
lines changed

5 files changed

+455
-92
lines changed

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

+24-1
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,30 @@ public enum Feature {
227227
*
228228
* @since 2.8
229229
*/
230-
ALLOW_MISSING_VALUES(false)
230+
ALLOW_MISSING_VALUES(false),
231+
232+
/**
233+
* Feature that determines whether {@link JsonParser} will allow for a single trailing
234+
* comma following the final value (in an Array) or member (in an Object). These commas
235+
* will simply be ignored.
236+
* <p>
237+
* For example, when this feature is enabled, <code>[true,true,]</code> is equivalent to
238+
* <code>[true, true]</code> and <code>{"a": true,}</code> is equivalent to
239+
* <code>{"a": true}</code>.
240+
* <p>
241+
* When combined with <code>ALLOW_MISSING_VALUES</code>, this feature takes priority, and
242+
* the final trailing comma in an array declaration does not imply a missing
243+
* (<code>null</code>) value. For example, when both <code>ALLOW_MISSING_VALUES</code>
244+
* and <code>ALLOW_TRAILING_COMMA</code> are enabled, <code>[true,true,]</code> is
245+
* equivalent to <code>[true, true]</code>, and <code>[true,true,,]</code> is equivalent to
246+
* <code>[true, true, null]</code>.
247+
* <p>
248+
* Since the JSON specification does not permit trailing commas, this is a non-standard
249+
* feature, and as such disabled by default.
250+
*
251+
* @since 2.9
252+
*/
253+
ALLOW_TRAILING_COMMA(false)
231254
;
232255

233256
/**

src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java

+44-31
Original file line numberDiff line numberDiff line change
@@ -652,26 +652,20 @@ public final JsonToken nextToken() throws IOException
652652
_binaryValue = null;
653653

654654
// Closing scope?
655-
if (i == INT_RBRACKET) {
656-
_updateLocation();
657-
if (!_parsingContext.inArray()) {
658-
_reportMismatchedEndMarker(i, '}');
659-
}
660-
_parsingContext = _parsingContext.clearAndGetParent();
661-
return (_currToken = JsonToken.END_ARRAY);
662-
}
663-
if (i == INT_RCURLY) {
664-
_updateLocation();
665-
if (!_parsingContext.inObject()) {
666-
_reportMismatchedEndMarker(i, ']');
667-
}
668-
_parsingContext = _parsingContext.clearAndGetParent();
669-
return (_currToken = JsonToken.END_OBJECT);
655+
if (i == INT_RBRACKET || i == INT_RCURLY) {
656+
_closeScope(i);
657+
return _currToken;
670658
}
671659

672660
// Nope: do we then expect a comma?
673661
if (_parsingContext.expectComma()) {
674662
i = _skipComma(i);
663+
664+
// Was that a trailing comma?
665+
if (isEnabled(Feature.ALLOW_TRAILING_COMMA) && (i == INT_RBRACKET || i == INT_RCURLY)) {
666+
_closeScope(i);
667+
return _currToken;
668+
}
675669
}
676670

677671
/* And should we now have a name? Always true for Object contexts, since
@@ -811,26 +805,20 @@ public boolean nextFieldName(SerializableString sstr) throws IOException
811805
}
812806
_binaryValue = null;
813807

814-
if (i == INT_RBRACKET) {
815-
_updateLocation();
816-
if (!_parsingContext.inArray()) {
817-
_reportMismatchedEndMarker(i, '}');
818-
}
819-
_parsingContext = _parsingContext.clearAndGetParent();
820-
_currToken = JsonToken.END_ARRAY;
821-
return false;
822-
}
823-
if (i == INT_RCURLY) {
824-
_updateLocation();
825-
if (!_parsingContext.inObject()) {
826-
_reportMismatchedEndMarker(i, ']');
827-
}
828-
_parsingContext = _parsingContext.clearAndGetParent();
829-
_currToken = JsonToken.END_OBJECT;
808+
// Closing scope?
809+
if (i == INT_RBRACKET || i == INT_RCURLY) {
810+
_closeScope(i);
830811
return false;
831812
}
813+
832814
if (_parsingContext.expectComma()) {
833815
i = _skipComma(i);
816+
817+
// Was that a trailing comma?
818+
if (isEnabled(Feature.ALLOW_TRAILING_COMMA) && (i == INT_RBRACKET || i == INT_RCURLY)) {
819+
_closeScope(i);
820+
return false;
821+
}
834822
}
835823

836824
if (!_parsingContext.inObject()) {
@@ -2834,4 +2822,29 @@ protected void _reportInvalidToken(String matchedPart, String msg) throws IOExce
28342822
}
28352823
_reportError("Unrecognized token '"+sb.toString()+"': was expecting "+msg);
28362824
}
2825+
2826+
/*
2827+
/**********************************************************
2828+
/* Internal methods, other
2829+
/**********************************************************
2830+
*/
2831+
2832+
private void _closeScope(int i) throws JsonParseException {
2833+
if (i == INT_RBRACKET) {
2834+
_updateLocation();
2835+
if (!_parsingContext.inArray()) {
2836+
_reportMismatchedEndMarker(i, '}');
2837+
}
2838+
_parsingContext = _parsingContext.clearAndGetParent();
2839+
_currToken = JsonToken.END_ARRAY;
2840+
}
2841+
if (i == INT_RCURLY) {
2842+
_updateLocation();
2843+
if (!_parsingContext.inObject()) {
2844+
_reportMismatchedEndMarker(i, ']');
2845+
}
2846+
_parsingContext = _parsingContext.clearAndGetParent();
2847+
_currToken = JsonToken.END_OBJECT;
2848+
}
2849+
}
28372850
}

src/main/java/com/fasterxml/jackson/core/json/UTF8DataInputJsonParser.java

+26-13
Original file line numberDiff line numberDiff line change
@@ -575,19 +575,9 @@ public JsonToken nextToken() throws IOException
575575
_tokenInputRow = _currInputRow;
576576

577577
// Closing scope?
578-
if (i == INT_RBRACKET) {
579-
if (!_parsingContext.inArray()) {
580-
_reportMismatchedEndMarker(i, '}');
581-
}
582-
_parsingContext = _parsingContext.clearAndGetParent();
583-
return (_currToken = JsonToken.END_ARRAY);
584-
}
585-
if (i == INT_RCURLY) {
586-
if (!_parsingContext.inObject()) {
587-
_reportMismatchedEndMarker(i, ']');
588-
}
589-
_parsingContext = _parsingContext.clearAndGetParent();
590-
return (_currToken = JsonToken.END_OBJECT);
578+
if (i == INT_RBRACKET || i == INT_RCURLY) {
579+
_closeScope(i);
580+
return _currToken;
591581
}
592582

593583
// Nope: do we then expect a comma?
@@ -596,6 +586,12 @@ public JsonToken nextToken() throws IOException
596586
_reportUnexpectedChar(i, "was expecting comma to separate "+_parsingContext.typeDesc()+" entries");
597587
}
598588
i = _skipWS();
589+
590+
// Was that a trailing comma?
591+
if (isEnabled(Feature.ALLOW_TRAILING_COMMA) && (i == INT_RBRACKET || i == INT_RCURLY)) {
592+
_closeScope(i);
593+
return _currToken;
594+
}
599595
}
600596

601597
/* And should we now have a name? Always true for
@@ -2788,6 +2784,23 @@ public JsonLocation getCurrentLocation() {
27882784
/**********************************************************
27892785
*/
27902786

2787+
private void _closeScope(int i) throws JsonParseException {
2788+
if (i == INT_RBRACKET) {
2789+
if (!_parsingContext.inArray()) {
2790+
_reportMismatchedEndMarker(i, '}');
2791+
}
2792+
_parsingContext = _parsingContext.clearAndGetParent();
2793+
_currToken = JsonToken.END_ARRAY;
2794+
}
2795+
if (i == INT_RCURLY) {
2796+
if (!_parsingContext.inObject()) {
2797+
_reportMismatchedEndMarker(i, ']');
2798+
}
2799+
_parsingContext = _parsingContext.clearAndGetParent();
2800+
_currToken = JsonToken.END_OBJECT;
2801+
}
2802+
}
2803+
27912804
/**
27922805
* Helper method needed to fix [Issue#148], masking of 0x00 character
27932806
*/

src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java

+45-47
Original file line numberDiff line numberDiff line change
@@ -738,21 +738,9 @@ public JsonToken nextToken() throws IOException
738738
_binaryValue = null;
739739

740740
// Closing scope?
741-
if (i == INT_RBRACKET) {
742-
_updateLocation();
743-
if (!_parsingContext.inArray()) {
744-
_reportMismatchedEndMarker(i, '}');
745-
}
746-
_parsingContext = _parsingContext.clearAndGetParent();
747-
return (_currToken = JsonToken.END_ARRAY);
748-
}
749-
if (i == INT_RCURLY) {
750-
_updateLocation();
751-
if (!_parsingContext.inObject()) {
752-
_reportMismatchedEndMarker(i, ']');
753-
}
754-
_parsingContext = _parsingContext.clearAndGetParent();
755-
return (_currToken = JsonToken.END_OBJECT);
741+
if (i == INT_RBRACKET || i == INT_RCURLY) {
742+
_closeScope(i);
743+
return _currToken;
756744
}
757745

758746
// Nope: do we then expect a comma?
@@ -761,6 +749,12 @@ public JsonToken nextToken() throws IOException
761749
_reportUnexpectedChar(i, "was expecting comma to separate "+_parsingContext.typeDesc()+" entries");
762750
}
763751
i = _skipWS();
752+
753+
// Was that a trailing comma?
754+
if (isEnabled(Feature.ALLOW_TRAILING_COMMA) && (i == INT_RBRACKET || i == INT_RCURLY)) {
755+
_closeScope(i);
756+
return _currToken;
757+
}
764758
}
765759

766760
/* And should we now have a name? Always true for
@@ -930,22 +924,8 @@ public boolean nextFieldName(SerializableString str) throws IOException
930924
_binaryValue = null;
931925

932926
// Closing scope?
933-
if (i == INT_RBRACKET) {
934-
_updateLocation();
935-
if (!_parsingContext.inArray()) {
936-
_reportMismatchedEndMarker(i, '}');
937-
}
938-
_parsingContext = _parsingContext.clearAndGetParent();
939-
_currToken = JsonToken.END_ARRAY;
940-
return false;
941-
}
942-
if (i == INT_RCURLY) {
943-
_updateLocation();
944-
if (!_parsingContext.inObject()) {
945-
_reportMismatchedEndMarker(i, ']');
946-
}
947-
_parsingContext = _parsingContext.clearAndGetParent();
948-
_currToken = JsonToken.END_OBJECT;
927+
if (i == INT_RBRACKET || i == INT_RCURLY) {
928+
_closeScope(i);
949929
return false;
950930
}
951931

@@ -955,6 +935,12 @@ public boolean nextFieldName(SerializableString str) throws IOException
955935
_reportUnexpectedChar(i, "was expecting comma to separate "+_parsingContext.typeDesc()+" entries");
956936
}
957937
i = _skipWS();
938+
939+
// Was that a trailing comma?
940+
if (isEnabled(Feature.ALLOW_TRAILING_COMMA) && (i == INT_RBRACKET || i == INT_RCURLY)) {
941+
_closeScope(i);
942+
return false;
943+
}
958944
}
959945

960946
if (!_parsingContext.inObject()) {
@@ -1017,22 +1003,8 @@ public String nextFieldName() throws IOException
10171003
}
10181004
_binaryValue = null;
10191005

1020-
if (i == INT_RBRACKET) {
1021-
_updateLocation();
1022-
if (!_parsingContext.inArray()) {
1023-
_reportMismatchedEndMarker(i, '}');
1024-
}
1025-
_parsingContext = _parsingContext.clearAndGetParent();
1026-
_currToken = JsonToken.END_ARRAY;
1027-
return null;
1028-
}
1029-
if (i == INT_RCURLY) {
1030-
_updateLocation();
1031-
if (!_parsingContext.inObject()) {
1032-
_reportMismatchedEndMarker(i, ']');
1033-
}
1034-
_parsingContext = _parsingContext.clearAndGetParent();
1035-
_currToken = JsonToken.END_OBJECT;
1006+
if (i == INT_RBRACKET || i == INT_RCURLY) {
1007+
_closeScope(i);
10361008
return null;
10371009
}
10381010

@@ -1042,7 +1014,14 @@ public String nextFieldName() throws IOException
10421014
_reportUnexpectedChar(i, "was expecting comma to separate "+_parsingContext.typeDesc()+" entries");
10431015
}
10441016
i = _skipWS();
1017+
1018+
// Was that a trailing comma?
1019+
if (isEnabled(Feature.ALLOW_TRAILING_COMMA) && (i == INT_RBRACKET || i == INT_RCURLY)) {
1020+
_closeScope(i);
1021+
return null;
1022+
}
10451023
}
1024+
10461025
if (!_parsingContext.inObject()) {
10471026
_updateLocation();
10481027
_nextTokenNotInObject(i);
@@ -3733,6 +3712,25 @@ private final void _updateNameLocation()
37333712
/**********************************************************
37343713
*/
37353714

3715+
private void _closeScope(int i) throws JsonParseException {
3716+
if (i == INT_RBRACKET) {
3717+
_updateLocation();
3718+
if (!_parsingContext.inArray()) {
3719+
_reportMismatchedEndMarker(i, '}');
3720+
}
3721+
_parsingContext = _parsingContext.clearAndGetParent();
3722+
_currToken = JsonToken.END_ARRAY;
3723+
}
3724+
if (i == INT_RCURLY) {
3725+
_updateLocation();
3726+
if (!_parsingContext.inObject()) {
3727+
_reportMismatchedEndMarker(i, ']');
3728+
}
3729+
_parsingContext = _parsingContext.clearAndGetParent();
3730+
_currToken = JsonToken.END_OBJECT;
3731+
}
3732+
}
3733+
37363734
/**
37373735
* Helper method needed to fix [Issue#148], masking of 0x00 character
37383736
*/

0 commit comments

Comments
 (0)