Commit a4c90474 authored by John Fremlin VII's avatar John Fremlin VII Committed by Owen Yamauchi

allow reading maps from number -> value

Summary:
The serialization of PHP values often uses integer -> value
maps in JSON arrays. These are emitted by the standard stream <<
operator on dynamics but cannot be read. This diff fixes that.

Test Plan: - read in serialized value with array "bucketing":{"days_stale":{0:{2:null,1:14.01,0:"more_than_two_weeks_stale"}}}

Reviewed By: delong.j@fb.com

FB internal diff: D805218
parent a896ed39
......@@ -251,9 +251,10 @@ struct ParseError : std::runtime_error {
// Wraps our input buffer with some helper functions.
struct Input {
explicit Input(StringPiece range)
: range_(range)
, lineNum_(0)
explicit Input(StringPiece range, json::serialization_opts const* opts)
: range_(range)
, opts_(*opts)
, lineNum_(0)
{
storeCurrent();
}
......@@ -351,6 +352,10 @@ struct Input {
throw ParseError(lineNum_, context(), what);
}
json::serialization_opts const& getOpts() {
return opts_;
}
private:
void storeCurrent() {
current_ = range_.empty() ? EOF : range_.front();
......@@ -358,12 +363,14 @@ private:
private:
StringPiece range_;
json::serialization_opts const& opts_;
unsigned lineNum_;
int current_;
};
dynamic parseValue(Input& in);
fbstring parseString(Input& in);
dynamic parseNumber(Input& in);
dynamic parseObject(Input& in) {
assert(*in == '{');
......@@ -378,14 +385,22 @@ dynamic parseObject(Input& in) {
}
for (;;) {
if (*in != '\"') {
if (*in == '\"') { // string
auto key = parseString(in);
in.skipWhitespace();
in.expect(':');
in.skipWhitespace();
ret.insert(std::move(key), parseValue(in));
} else if (!in.getOpts().allow_non_string_keys) {
in.error("expected string for object key name");
} else {
auto key = parseValue(in);
in.skipWhitespace();
in.expect(':');
in.skipWhitespace();
ret.insert(std::move(key), parseValue(in));
}
auto key = parseString(in);
in.skipWhitespace();
in.expect(':');
in.skipWhitespace();
ret.insert(std::move(key), parseValue(in));
in.skipWhitespace();
if (*in != ',') {
break;
......@@ -665,11 +680,18 @@ void escapeString(StringPiece input,
//////////////////////////////////////////////////////////////////////
dynamic parseJson(StringPiece range) {
json::Input in(range);
return parseJson(range, json::serialization_opts());
}
dynamic parseJson(
StringPiece range,
json::serialization_opts const& opts) {
json::Input in(range, &opts);
auto ret = parseValue(in);
in.skipWhitespace();
if (*in != '\0' && in.size()) {
if (in.size() && *in != '\0') {
in.error("parsing didn't consume all input");
}
return ret;
......
......@@ -106,6 +106,7 @@ namespace json {
* Parse a json blob out of a range and produce a dynamic representing
* it.
*/
dynamic parseJson(StringPiece, json::serialization_opts const&);
dynamic parseJson(StringPiece);
/*
......
......@@ -314,6 +314,33 @@ TEST(Json, UTF8Validation) {
EXPECT_ANY_THROW(folly::json::serialize("a\xe0\xa0\x80z\xe0\x80\x80", opts));
}
TEST(Json, ParseNonStringKeys) {
// test string keys
EXPECT_EQ("a", parseJson("{\"a\":[]}").items().begin().first.asString());
// check that we don't allow non-string keys as this violates the
// strict JSON spec (though it is emitted by the output of
// folly::dynamic with operator <<).
EXPECT_THROW(parseJson("{1:[]}"), std::runtime_error);
// check that we can parse colloquial JSON if the option is set
folly::json::serialization_opts opts;
opts.allow_non_string_keys = true;
auto val = parseJson("{1:[]}", opts);
EXPECT_EQ(1, val.items().begin().first.asInteger());
// test we can still read in strings
auto sval = parseJson("{\"a\":[]}", opts);
EXPECT_EQ("a", sval.items().begin().first.asString());
// test we can read in doubles
auto dval = parseJson("{1.5:[]}", opts);
EXPECT_EQ(1.5, dval.items().begin().first.asDouble());
}
BENCHMARK(jsonSerialize, iters) {
folly::json::serialization_opts opts;
for (int i = 0; i < iters; ++i) {
......
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