allow reading maps from number -> value
authorJohn Fremlin VII <john@fb.com>
Tue, 21 May 2013 23:04:40 +0000 (16:04 -0700)
committerOwen Yamauchi <oyamauchi@fb.com>
Mon, 3 Jun 2013 19:22:18 +0000 (12:22 -0700)
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

folly/json.cpp
folly/json.h
folly/test/JsonTest.cpp

index a49926437b1a6515f82a6c19dbf815cc230144f7..445b4b0ae5196c939f244697acb1e5b1a951fd64 100644 (file)
@@ -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;
index 11024281ecdf83ec13a45da1e2182df3546339b9..84a70661c8fd790cfb4f4aa314f5cc4421b257c0 100644 (file)
@@ -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);
 
 /*
index ff1c9008649205661c2afa0441e24a41a6fed152..b31653080ca09a3408e32a600498c8b26b9d0af6 100644 (file)
@@ -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) {