Commit ea72b037 authored by Tom Jackson's avatar Tom Jackson Committed by Sara Golemon

Allowing trailing comma in folly::parseJson

Summary:
Introduced a new serialization option, `allow_trailing_comma`, which allows trailing commas to be included in strings when they are parsed. This isn't strictly allowed by RFC 4627, but we're allowing more than that anyway.

I've run into this dozens of times, especially while using SMC.

Test Plan: Unit tests

Reviewed By: delong.j@fb.com

FB internal diff: D872581
parent f13297ee
...@@ -385,6 +385,9 @@ dynamic parseObject(Input& in) { ...@@ -385,6 +385,9 @@ dynamic parseObject(Input& in) {
} }
for (;;) { for (;;) {
if (in.getOpts().allow_trailing_comma && *in == '}') {
break;
}
if (*in == '\"') { // string if (*in == '\"') { // string
auto key = parseString(in); auto key = parseString(in);
in.skipWhitespace(); in.skipWhitespace();
...@@ -426,6 +429,9 @@ dynamic parseArray(Input& in) { ...@@ -426,6 +429,9 @@ dynamic parseArray(Input& in) {
} }
for (;;) { for (;;) {
if (in.getOpts().allow_trailing_comma && *in == ']') {
break;
}
ret.push_back(parseValue(in)); ret.push_back(parseValue(in));
in.skipWhitespace(); in.skipWhitespace();
if (*in != ',') { if (*in != ',') {
......
...@@ -58,6 +58,7 @@ namespace json { ...@@ -58,6 +58,7 @@ namespace json {
, pretty_formatting(false) , pretty_formatting(false)
, encode_non_ascii(false) , encode_non_ascii(false)
, validate_utf8(false) , validate_utf8(false)
, allow_trailing_comma(false)
{} {}
// If true, keys in an object can be non-strings. (In strict // If true, keys in an object can be non-strings. (In strict
...@@ -81,6 +82,9 @@ namespace json { ...@@ -81,6 +82,9 @@ namespace json {
// Check that strings are valid utf8 // Check that strings are valid utf8
bool validate_utf8; bool validate_utf8;
// Allow trailing comma in lists of values / items
bool allow_trailing_comma;
}; };
/* /*
......
...@@ -75,6 +75,8 @@ TEST(Json, Parse) { ...@@ -75,6 +75,8 @@ TEST(Json, Parse) {
EXPECT_EQ(-std::numeric_limits<double>::infinity(), EXPECT_EQ(-std::numeric_limits<double>::infinity(),
parseJson("-Infinity").asDouble()); parseJson("-Infinity").asDouble());
EXPECT_TRUE(std::isnan(parseJson("NaN").asDouble())); EXPECT_TRUE(std::isnan(parseJson("NaN").asDouble()));
// case matters
EXPECT_THROW(parseJson("infinity"), std::runtime_error); EXPECT_THROW(parseJson("infinity"), std::runtime_error);
EXPECT_THROW(parseJson("inf"), std::runtime_error); EXPECT_THROW(parseJson("inf"), std::runtime_error);
EXPECT_THROW(parseJson("nan"), std::runtime_error); EXPECT_THROW(parseJson("nan"), std::runtime_error);
...@@ -86,32 +88,16 @@ TEST(Json, Parse) { ...@@ -86,32 +88,16 @@ TEST(Json, Parse) {
EXPECT_EQ(boost::prior(array.end())->size(), 4); EXPECT_EQ(boost::prior(array.end())->size(), 4);
} }
bool caught = false; EXPECT_THROW(parseJson("\n[12,\n\nnotvalidjson"),
try { std::runtime_error);
parseJson("\n[12,\n\nnotvalidjson");
} catch (const std::exception& e) {
caught = true;
}
EXPECT_TRUE(caught);
caught = false; EXPECT_THROW(parseJson("12e2e2"),
try { std::runtime_error);
parseJson("12e2e2");
} catch (const std::exception& e) { EXPECT_THROW(parseJson("{\"foo\":12,\"bar\":42} \"something\""),
caught = true; std::runtime_error);
}
EXPECT_TRUE(caught);
caught = false;
try {
parseJson("{\"foo\":12,\"bar\":42} \"something\"");
} catch (const std::exception& e) {
// incomplete parse
caught = true;
}
EXPECT_TRUE(caught);
dynamic anotherVal = dynamic::object dynamic value = dynamic::object
("foo", "bar") ("foo", "bar")
("junk", 12) ("junk", 12)
("another", 32.2) ("another", 32.2)
...@@ -128,11 +114,11 @@ TEST(Json, Parse) { ...@@ -128,11 +114,11 @@ TEST(Json, Parse) {
; ;
// Print then parse and get the same thing, hopefully. // Print then parse and get the same thing, hopefully.
auto value = parseJson(toJson(anotherVal)); EXPECT_EQ(value, parseJson(toJson(value)));
EXPECT_EQ(value, anotherVal);
// Test an object with non-string values. // Test an object with non-string values.
dynamic something = folly::parseJson( dynamic something = parseJson(
"{\"old_value\":40,\"changed\":true,\"opened\":false}"); "{\"old_value\":40,\"changed\":true,\"opened\":false}");
dynamic expected = dynamic::object dynamic expected = dynamic::object
("old_value", 40) ("old_value", 40)
...@@ -141,6 +127,28 @@ TEST(Json, Parse) { ...@@ -141,6 +127,28 @@ TEST(Json, Parse) {
EXPECT_EQ(something, expected); EXPECT_EQ(something, expected);
} }
TEST(Json, ParseTrailingComma) {
folly::json::serialization_opts on, off;
on.allow_trailing_comma = true;
off.allow_trailing_comma = false;
dynamic arr { 1, 2 };
EXPECT_EQ(arr, parseJson("[1, 2]", on));
EXPECT_EQ(arr, parseJson("[1, 2,]", on));
EXPECT_EQ(arr, parseJson("[1, 2, ]", on));
EXPECT_EQ(arr, parseJson("[1, 2 , ]", on));
EXPECT_EQ(arr, parseJson("[1, 2 ,]", on));
EXPECT_THROW(parseJson("[1, 2,]", off), std::runtime_error);
dynamic obj = dynamic::object("a", 1);
EXPECT_EQ(obj, parseJson("{\"a\": 1}", on));
EXPECT_EQ(obj, parseJson("{\"a\": 1,}", on));
EXPECT_EQ(obj, parseJson("{\"a\": 1, }", on));
EXPECT_EQ(obj, parseJson("{\"a\": 1 , }", on));
EXPECT_EQ(obj, parseJson("{\"a\": 1 ,}", on));
EXPECT_THROW(parseJson("{\"a\":1,}", off), std::runtime_error);
}
TEST(Json, JavascriptSafe) { TEST(Json, JavascriptSafe) {
auto badDouble = (1ll << 63ll) + 1; auto badDouble = (1ll << 63ll) + 1;
dynamic badDyn = badDouble; dynamic badDyn = badDouble;
...@@ -160,17 +168,9 @@ TEST(Json, Produce) { ...@@ -160,17 +168,9 @@ TEST(Json, Produce) {
value = parseJson("\"Control code: \001 \002 \x1f\""); value = parseJson("\"Control code: \001 \002 \x1f\"");
EXPECT_EQ(toJson(value), R"("Control code: \u0001 \u0002 \u001f")"); EXPECT_EQ(toJson(value), R"("Control code: \u0001 \u0002 \u001f")");
bool caught = false; // We're not allowed to have non-string keys in json.
try { EXPECT_THROW(toJson(dynamic::object("abc", "xyz")(42.33, "asd")),
dynamic d = dynamic::object; std::runtime_error);
d["abc"] = "xyz";
d[42.33] = "asd";
auto str = toJson(d);
} catch (std::exception const& e) {
// We're not allowed to have non-string keys in json.
caught = true;
}
EXPECT_TRUE(caught);
} }
TEST(Json, JsonEscape) { TEST(Json, JsonEscape) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment