Skip to content

Commit 781eec4

Browse files
authored
Merge pull request open-source-parsers#1098 from cdunn2001/allow-trailing-commas
Allow trailing comma in objects and arrays
2 parents d2e6a97 + 1c8f7d8 commit 781eec4

10 files changed

+52
-16
lines changed

include/json/json_features.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class JSON_API Features {
2323
/** \brief A configuration that allows all features and assumes all strings
2424
* are UTF-8.
2525
* - C & C++ comments are allowed
26+
* - Trailing commas in objects and arrays are allowed.
2627
* - Root object can be any JSON value
2728
* - Assumes Value strings are encoded in UTF-8
2829
*/
@@ -31,6 +32,7 @@ class JSON_API Features {
3132
/** \brief A configuration that is strictly compatible with the JSON
3233
* specification.
3334
* - Comments are forbidden.
35+
* - Trailing commas in objects and arrays are forbidden.
3436
* - Root object must be either an array or an object value.
3537
* - Assumes Value strings are encoded in UTF-8
3638
*/
@@ -43,6 +45,10 @@ class JSON_API Features {
4345
/// \c true if comments are allowed. Default: \c true.
4446
bool allowComments_{true};
4547

48+
/// \c true if trailing commas in objects and arrays are allowed. Default \c
49+
/// true.
50+
bool allowTrailingCommas_{true};
51+
4652
/// \c true if root must be either an array or an object value. Default: \c
4753
/// false.
4854
bool strictRoot_{false};

include/json/reader.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,8 @@ class JSON_API CharReaderBuilder : public CharReader::Factory {
299299
* if allowComments is false.
300300
* - `"allowComments": false or true`
301301
* - true if comments are allowed.
302+
* - `"allowTrailingCommas": false or true`
303+
* - true if trailing commas in objects and arrays are allowed.
302304
* - `"strictRoot": false or true`
303305
* - true if root must be either an array or an object value
304306
* - `"allowDroppedNullPlaceholders": false or true`

src/lib_json/json_reader.cpp

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ Features Features::all() { return {}; }
6767
Features Features::strictMode() {
6868
Features features;
6969
features.allowComments_ = false;
70+
features.allowTrailingCommas_ = false;
7071
features.strictRoot_ = true;
7172
features.allowDroppedNullPlaceholders_ = false;
7273
features.allowNumericKeys_ = false;
@@ -454,7 +455,9 @@ bool Reader::readObject(Token& token) {
454455
initialTokenOk = readToken(tokenName);
455456
if (!initialTokenOk)
456457
break;
457-
if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object
458+
if (tokenName.type_ == tokenObjectEnd &&
459+
(name.empty() ||
460+
features_.allowTrailingCommas_)) // empty object or trailing comma
458461
return true;
459462
name.clear();
460463
if (tokenName.type_ == tokenString) {
@@ -502,15 +505,20 @@ bool Reader::readArray(Token& token) {
502505
Value init(arrayValue);
503506
currentValue().swapPayload(init);
504507
currentValue().setOffsetStart(token.start_ - begin_);
505-
skipSpaces();
506-
if (current_ != end_ && *current_ == ']') // empty array
507-
{
508-
Token endArray;
509-
readToken(endArray);
510-
return true;
511-
}
512508
int index = 0;
513509
for (;;) {
510+
skipSpaces();
511+
if (current_ != end_ && *current_ == ']' &&
512+
(index == 0 ||
513+
(features_.allowTrailingCommas_ &&
514+
!features_.allowDroppedNullPlaceholders_))) // empty array or trailing
515+
// comma
516+
{
517+
Token endArray;
518+
readToken(endArray);
519+
return true;
520+
}
521+
514522
Value& value = currentValue()[index++];
515523
nodes_.push(&value);
516524
bool ok = readValue();
@@ -863,6 +871,7 @@ class OurFeatures {
863871
public:
864872
static OurFeatures all();
865873
bool allowComments_;
874+
bool allowTrailingCommas_;
866875
bool strictRoot_;
867876
bool allowDroppedNullPlaceholders_;
868877
bool allowNumericKeys_;
@@ -1437,7 +1446,9 @@ bool OurReader::readObject(Token& token) {
14371446
initialTokenOk = readToken(tokenName);
14381447
if (!initialTokenOk)
14391448
break;
1440-
if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object
1449+
if (tokenName.type_ == tokenObjectEnd &&
1450+
(name.empty() ||
1451+
features_.allowTrailingCommas_)) // empty object or trailing comma
14411452
return true;
14421453
name.clear();
14431454
if (tokenName.type_ == tokenString) {
@@ -1491,15 +1502,19 @@ bool OurReader::readArray(Token& token) {
14911502
Value init(arrayValue);
14921503
currentValue().swapPayload(init);
14931504
currentValue().setOffsetStart(token.start_ - begin_);
1494-
skipSpaces();
1495-
if (current_ != end_ && *current_ == ']') // empty array
1496-
{
1497-
Token endArray;
1498-
readToken(endArray);
1499-
return true;
1500-
}
15011505
int index = 0;
15021506
for (;;) {
1507+
skipSpaces();
1508+
if (current_ != end_ && *current_ == ']' &&
1509+
(index == 0 ||
1510+
(features_.allowTrailingCommas_ &&
1511+
!features_.allowDroppedNullPlaceholders_))) // empty array or trailing
1512+
// comma
1513+
{
1514+
Token endArray;
1515+
readToken(endArray);
1516+
return true;
1517+
}
15031518
Value& value = currentValue()[index++];
15041519
nodes_.push(&value);
15051520
bool ok = readValue();
@@ -1866,6 +1881,7 @@ CharReader* CharReaderBuilder::newCharReader() const {
18661881
bool collectComments = settings_["collectComments"].asBool();
18671882
OurFeatures features = OurFeatures::all();
18681883
features.allowComments_ = settings_["allowComments"].asBool();
1884+
features.allowTrailingCommas_ = settings_["allowTrailingCommas"].asBool();
18691885
features.strictRoot_ = settings_["strictRoot"].asBool();
18701886
features.allowDroppedNullPlaceholders_ =
18711887
settings_["allowDroppedNullPlaceholders"].asBool();
@@ -1884,6 +1900,7 @@ static void getValidReaderKeys(std::set<String>* valid_keys) {
18841900
valid_keys->clear();
18851901
valid_keys->insert("collectComments");
18861902
valid_keys->insert("allowComments");
1903+
valid_keys->insert("allowTrailingCommas");
18871904
valid_keys->insert("strictRoot");
18881905
valid_keys->insert("allowDroppedNullPlaceholders");
18891906
valid_keys->insert("allowNumericKeys");
@@ -1917,6 +1934,7 @@ Value& CharReaderBuilder::operator[](const String& key) {
19171934
void CharReaderBuilder::strictMode(Json::Value* settings) {
19181935
//! [CharReaderBuilderStrictMode]
19191936
(*settings)["allowComments"] = false;
1937+
(*settings)["allowTrailingCommas"] = false;
19201938
(*settings)["strictRoot"] = true;
19211939
(*settings)["allowDroppedNullPlaceholders"] = false;
19221940
(*settings)["allowNumericKeys"] = false;
@@ -1932,6 +1950,7 @@ void CharReaderBuilder::setDefaults(Json::Value* settings) {
19321950
//! [CharReaderBuilderDefaults]
19331951
(*settings)["collectComments"] = true;
19341952
(*settings)["allowComments"] = true;
1953+
(*settings)["allowTrailingCommas"] = true;
19351954
(*settings)["strictRoot"] = false;
19361955
(*settings)["allowDroppedNullPlaceholders"] = false;
19371956
(*settings)["allowNumericKeys"] = false;

src/test_lib_json/fuzz.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
4040
builder.settings_["rejectDupKeys_"] = hash_settings & (1 << 7);
4141
builder.settings_["allowSpecialFloats_"] = hash_settings & (1 << 8);
4242
builder.settings_["collectComments"] = hash_settings & (1 << 9);
43+
builder.settings_["allowTrailingCommas_"] = hash_settings & (1 << 10);
4344

4445
std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
4546

test/data/fail_test_array_02.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[1,,]

test/data/fail_test_object_01.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{ "count" : 1234,, }

test/data/test_array_08.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.=[]
2+
.[0]=1

test/data/test_array_08.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[1,]

test/data/test_object_05.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.={}
2+
.count=1234

test/data/test_object_05.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{ "count" : 1234, }

0 commit comments

Comments
 (0)