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

Allow trailing comma in objects and arrays #1098

Merged
merged 3 commits into from
Nov 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions include/json/json_features.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class JSON_API Features {
/** \brief A configuration that allows all features and assumes all strings
* are UTF-8.
* - C & C++ comments are allowed
* - Trailing commas in objects and arrays are allowed.
* - Root object can be any JSON value
* - Assumes Value strings are encoded in UTF-8
*/
Expand All @@ -31,6 +32,7 @@ class JSON_API Features {
/** \brief A configuration that is strictly compatible with the JSON
* specification.
* - Comments are forbidden.
* - Trailing commas in objects and arrays are forbidden.
* - Root object must be either an array or an object value.
* - Assumes Value strings are encoded in UTF-8
*/
Expand All @@ -43,6 +45,10 @@ class JSON_API Features {
/// \c true if comments are allowed. Default: \c true.
bool allowComments_{true};

/// \c true if trailing commas in objects and arrays are allowed. Default \c
/// true.
bool allowTrailingCommas_{true};

/// \c true if root must be either an array or an object value. Default: \c
/// false.
bool strictRoot_{false};
Expand Down
2 changes: 2 additions & 0 deletions include/json/reader.h
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,8 @@ class JSON_API CharReaderBuilder : public CharReader::Factory {
* if allowComments is false.
* - `"allowComments": false or true`
* - true if comments are allowed.
* - `"allowTrailingCommas": false or true`
* - true if trailing commas in objects and arrays are allowed.
* - `"strictRoot": false or true`
* - true if root must be either an array or an object value
* - `"allowDroppedNullPlaceholders": false or true`
Expand Down
51 changes: 35 additions & 16 deletions src/lib_json/json_reader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ Features Features::all() { return {}; }
Features Features::strictMode() {
Features features;
features.allowComments_ = false;
features.allowTrailingCommas_ = false;
features.strictRoot_ = true;
features.allowDroppedNullPlaceholders_ = false;
features.allowNumericKeys_ = false;
Expand Down Expand Up @@ -454,7 +455,9 @@ bool Reader::readObject(Token& token) {
initialTokenOk = readToken(tokenName);
if (!initialTokenOk)
break;
if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object
if (tokenName.type_ == tokenObjectEnd &&
(name.empty() ||
features_.allowTrailingCommas_)) // empty object or trailing comma
return true;
name.clear();
if (tokenName.type_ == tokenString) {
Expand Down Expand Up @@ -502,15 +505,20 @@ bool Reader::readArray(Token& token) {
Value init(arrayValue);
currentValue().swapPayload(init);
currentValue().setOffsetStart(token.start_ - begin_);
skipSpaces();
if (current_ != end_ && *current_ == ']') // empty array
{
Token endArray;
readToken(endArray);
return true;
}
int index = 0;
for (;;) {
skipSpaces();
if (current_ != end_ && *current_ == ']' &&
(index == 0 ||
(features_.allowTrailingCommas_ &&
!features_.allowDroppedNullPlaceholders_))) // empty array or trailing
// comma
{
Token endArray;
readToken(endArray);
return true;
}

Value& value = currentValue()[index++];
nodes_.push(&value);
bool ok = readValue();
Expand Down Expand Up @@ -863,6 +871,7 @@ class OurFeatures {
public:
static OurFeatures all();
bool allowComments_;
bool allowTrailingCommas_;
bool strictRoot_;
bool allowDroppedNullPlaceholders_;
bool allowNumericKeys_;
Expand Down Expand Up @@ -1437,7 +1446,9 @@ bool OurReader::readObject(Token& token) {
initialTokenOk = readToken(tokenName);
if (!initialTokenOk)
break;
if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object
if (tokenName.type_ == tokenObjectEnd &&
(name.empty() ||
features_.allowTrailingCommas_)) // empty object or trailing comma
return true;
name.clear();
if (tokenName.type_ == tokenString) {
Expand Down Expand Up @@ -1491,15 +1502,19 @@ bool OurReader::readArray(Token& token) {
Value init(arrayValue);
currentValue().swapPayload(init);
currentValue().setOffsetStart(token.start_ - begin_);
skipSpaces();
if (current_ != end_ && *current_ == ']') // empty array
{
Token endArray;
readToken(endArray);
return true;
}
int index = 0;
for (;;) {
skipSpaces();
if (current_ != end_ && *current_ == ']' &&
(index == 0 ||
(features_.allowTrailingCommas_ &&
!features_.allowDroppedNullPlaceholders_))) // empty array or trailing
// comma
{
Token endArray;
readToken(endArray);
return true;
}
Value& value = currentValue()[index++];
nodes_.push(&value);
bool ok = readValue();
Expand Down Expand Up @@ -1866,6 +1881,7 @@ CharReader* CharReaderBuilder::newCharReader() const {
bool collectComments = settings_["collectComments"].asBool();
OurFeatures features = OurFeatures::all();
features.allowComments_ = settings_["allowComments"].asBool();
features.allowTrailingCommas_ = settings_["allowTrailingCommas"].asBool();
features.strictRoot_ = settings_["strictRoot"].asBool();
features.allowDroppedNullPlaceholders_ =
settings_["allowDroppedNullPlaceholders"].asBool();
Expand All @@ -1884,6 +1900,7 @@ static void getValidReaderKeys(std::set<String>* valid_keys) {
valid_keys->clear();
valid_keys->insert("collectComments");
valid_keys->insert("allowComments");
valid_keys->insert("allowTrailingCommas");
valid_keys->insert("strictRoot");
valid_keys->insert("allowDroppedNullPlaceholders");
valid_keys->insert("allowNumericKeys");
Expand Down Expand Up @@ -1917,6 +1934,7 @@ Value& CharReaderBuilder::operator[](const String& key) {
void CharReaderBuilder::strictMode(Json::Value* settings) {
//! [CharReaderBuilderStrictMode]
(*settings)["allowComments"] = false;
(*settings)["allowTrailingCommas"] = false;
(*settings)["strictRoot"] = true;
(*settings)["allowDroppedNullPlaceholders"] = false;
(*settings)["allowNumericKeys"] = false;
Expand All @@ -1932,6 +1950,7 @@ void CharReaderBuilder::setDefaults(Json::Value* settings) {
//! [CharReaderBuilderDefaults]
(*settings)["collectComments"] = true;
(*settings)["allowComments"] = true;
(*settings)["allowTrailingCommas"] = true;
(*settings)["strictRoot"] = false;
(*settings)["allowDroppedNullPlaceholders"] = false;
(*settings)["allowNumericKeys"] = false;
Expand Down
1 change: 1 addition & 0 deletions src/test_lib_json/fuzz.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
builder.settings_["rejectDupKeys_"] = hash_settings & (1 << 7);
builder.settings_["allowSpecialFloats_"] = hash_settings & (1 << 8);
builder.settings_["collectComments"] = hash_settings & (1 << 9);
builder.settings_["allowTrailingCommas_"] = hash_settings & (1 << 10);

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

Expand Down
1 change: 1 addition & 0 deletions test/data/fail_test_array_02.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[1,,]
1 change: 1 addition & 0 deletions test/data/fail_test_object_01.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "count" : 1234,, }
2 changes: 2 additions & 0 deletions test/data/test_array_08.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.=[]
.[0]=1
1 change: 1 addition & 0 deletions test/data/test_array_08.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[1,]
2 changes: 2 additions & 0 deletions test/data/test_object_05.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.={}
.count=1234
1 change: 1 addition & 0 deletions test/data/test_object_05.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "count" : 1234, }