/*
- * Copyright 2012 Facebook, Inc.
+ * Copyright 2015 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* limitations under the License.
*/
-#include "folly/json.h"
+#include <folly/json.h>
+
#include <gtest/gtest.h>
#include <gflags/gflags.h>
-#include <cmath>
#include <limits>
-#include <iostream>
#include <boost/next_prior.hpp>
-#include "folly/Benchmark.h"
using folly::dynamic;
using folly::parseJson;
EXPECT_EQ(-std::numeric_limits<double>::infinity(),
parseJson("-Infinity").asDouble());
EXPECT_TRUE(std::isnan(parseJson("NaN").asDouble()));
+
+ // case matters
EXPECT_THROW(parseJson("infinity"), std::runtime_error);
EXPECT_THROW(parseJson("inf"), 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);
auto array = parseJson(
"[12,false, false , null , [12e4,32, [], 12]]");
EXPECT_EQ(boost::prior(array.end())->size(), 4);
}
- bool caught = false;
- try {
- parseJson("\n[12,\n\nnotvalidjson");
- } catch (const std::exception& e) {
- caught = true;
- }
- EXPECT_TRUE(caught);
+ EXPECT_THROW(parseJson("\n[12,\n\nnotvalidjson"),
+ std::runtime_error);
- caught = false;
- try {
- parseJson("12e2e2");
- } catch (const std::exception& e) {
- caught = true;
- }
- EXPECT_TRUE(caught);
-
- caught = false;
- try {
- parseJson("{\"foo\":12,\"bar\":42} \"something\"");
- } catch (const std::exception& e) {
- // incomplete parse
- caught = true;
- }
- EXPECT_TRUE(caught);
+ EXPECT_THROW(parseJson("12e2e2"),
+ std::runtime_error);
+
+ EXPECT_THROW(parseJson("{\"foo\":12,\"bar\":42} \"something\""),
+ std::runtime_error);
- dynamic anotherVal = dynamic::object
+ dynamic value = dynamic::object
("foo", "bar")
("junk", 12)
("another", 32.2)
;
// Print then parse and get the same thing, hopefully.
- auto value = parseJson(toJson(anotherVal));
- EXPECT_EQ(value, anotherVal);
+ EXPECT_EQ(value, parseJson(toJson(value)));
+
// Test an object with non-string values.
- dynamic something = folly::parseJson(
+ dynamic something = parseJson(
"{\"old_value\":40,\"changed\":true,\"opened\":false}");
dynamic expected = dynamic::object
("old_value", 40)
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) {
auto badDouble = (1ll << 63ll) + 1;
dynamic badDyn = badDouble;
value = parseJson("\"Control code: \001 \002 \x1f\"");
EXPECT_EQ(toJson(value), R"("Control code: \u0001 \u0002 \u001f")");
- bool caught = false;
- try {
- dynamic d = dynamic::object;
- 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);
+ // We're not allowed to have non-string keys in json.
+ EXPECT_THROW(toJson(dynamic::object("abc", "xyz")(42.33, "asd")),
+ std::runtime_error);
+
+ // Check Infinity/Nan
+ folly::json::serialization_opts opts;
+ opts.allow_nan_inf = true;
+ EXPECT_EQ("Infinity",
+ folly::json::serialize(parseJson("Infinity"), opts).toStdString());
+ EXPECT_EQ("NaN",
+ folly::json::serialize(parseJson("NaN"), opts).toStdString());
}
TEST(Json, JsonEscape) {
folly::fbstring output = folly::parseJson(jsonInput).asString();
folly::fbstring jsonOutput = folly::toJson(output);
- LOG(INFO) << "input: " << input
- <<" => json: " << jsonInput;
- LOG(INFO) << "output: " << output
- <<" => json: " << jsonOutput;
-
EXPECT_EQ(input, output);
EXPECT_EQ(jsonInput, jsonOutput);
folly::fbstring output = folly::parseJson(jsonInput).asString();
folly::fbstring jsonOutput = folly::json::serialize(output, opts);
- LOG(INFO) << "input: " << input
- <<" => json: " << jsonInput;
- LOG(INFO) << "output: " << output
- <<" => json: " << jsonOutput;
-
EXPECT_EQ(input, output);
EXPECT_EQ(jsonInput, jsonOutput);
// test validate_utf8 with invalid utf8
EXPECT_ANY_THROW(folly::json::serialize("a\xe0\xa0\x80z\xc0\x80", opts));
EXPECT_ANY_THROW(folly::json::serialize("a\xe0\xa0\x80z\xe0\x80\x80", opts));
-}
-BENCHMARK(jsonSerialize, iters) {
- folly::json::serialization_opts opts;
- for (int i = 0; i < iters; ++i) {
- folly::json::serialize(
- "qwerty \xc2\x80 \xef\xbf\xbf poiuy"
- "qwerty \xc2\x80 \xef\xbf\xbf poiuy"
- "qwerty \xc2\x80 \xef\xbf\xbf poiuy"
- "qwerty \xc2\x80 \xef\xbf\xbf poiuy"
- "qwerty \xc2\x80 \xef\xbf\xbf poiuy"
- "qwerty \xc2\x80 \xef\xbf\xbf poiuy"
- "qwerty \xc2\x80 \xef\xbf\xbf poiuy"
- "qwerty \xc2\x80 \xef\xbf\xbf poiuy"
- "qwerty \xc2\x80 \xef\xbf\xbf poiuy"
- "qwerty \xc2\x80 \xef\xbf\xbf poiuy",
- opts);
- }
-}
+ opts.skip_invalid_utf8 = true;
+ EXPECT_EQ(folly::json::serialize("a\xe0\xa0\x80z\xc0\x80", opts),
+ "\"a\xe0\xa0\x80z\ufffd\ufffd\"");
+ EXPECT_EQ(folly::json::serialize("a\xe0\xa0\x80z\xc0\x80\x80", opts),
+ "\"a\xe0\xa0\x80z\ufffd\ufffd\ufffd\"");
+ EXPECT_EQ(folly::json::serialize("z\xc0\x80z\xe0\xa0\x80", opts),
+ "\"z\ufffd\ufffdz\xe0\xa0\x80\"");
-BENCHMARK(jsonSerializeWithNonAsciiEncoding, iters) {
- folly::json::serialization_opts opts;
opts.encode_non_ascii = true;
+ EXPECT_EQ(folly::json::serialize("a\xe0\xa0\x80z\xc0\x80", opts),
+ "\"a\\u0800z\\ufffd\\ufffd\"");
+ EXPECT_EQ(folly::json::serialize("a\xe0\xa0\x80z\xc0\x80\x80", opts),
+ "\"a\\u0800z\\ufffd\\ufffd\\ufffd\"");
+ EXPECT_EQ(folly::json::serialize("z\xc0\x80z\xe0\xa0\x80", opts),
+ "\"z\\ufffd\\ufffdz\\u0800\"");
- for (int i = 0; i < iters; ++i) {
- folly::json::serialize(
- "qwerty \xc2\x80 \xef\xbf\xbf poiuy"
- "qwerty \xc2\x80 \xef\xbf\xbf poiuy"
- "qwerty \xc2\x80 \xef\xbf\xbf poiuy"
- "qwerty \xc2\x80 \xef\xbf\xbf poiuy"
- "qwerty \xc2\x80 \xef\xbf\xbf poiuy"
- "qwerty \xc2\x80 \xef\xbf\xbf poiuy"
- "qwerty \xc2\x80 \xef\xbf\xbf poiuy"
- "qwerty \xc2\x80 \xef\xbf\xbf poiuy"
- "qwerty \xc2\x80 \xef\xbf\xbf poiuy"
- "qwerty \xc2\x80 \xef\xbf\xbf poiuy",
- opts);
- }
}
-BENCHMARK(jsonSerializeWithUtf8Validation, iters) {
+
+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.validate_utf8 = true;
+ opts.allow_non_string_keys = true;
- for (int i = 0; i < iters; ++i) {
- folly::json::serialize(
- "qwerty \xc2\x80 \xef\xbf\xbf poiuy"
- "qwerty \xc2\x80 \xef\xbf\xbf poiuy"
- "qwerty \xc2\x80 \xef\xbf\xbf poiuy"
- "qwerty \xc2\x80 \xef\xbf\xbf poiuy"
- "qwerty \xc2\x80 \xef\xbf\xbf poiuy"
- "qwerty \xc2\x80 \xef\xbf\xbf poiuy"
- "qwerty \xc2\x80 \xef\xbf\xbf poiuy"
- "qwerty \xc2\x80 \xef\xbf\xbf poiuy"
- "qwerty \xc2\x80 \xef\xbf\xbf poiuy"
- "qwerty \xc2\x80 \xef\xbf\xbf poiuy",
- opts);
- }
-}
+ auto val = parseJson("{1:[]}", opts);
+ EXPECT_EQ(1, val.items().begin()->first.asInt());
-BENCHMARK(parseSmallStringWithUtf, iters) {
- for (int i = 0; i < iters << 4; ++i) {
- parseJson("\"I \\u2665 UTF-8 thjasdhkjh blah blah blah\"");
- }
-}
-BENCHMARK(parseNormalString, iters) {
- for (int i = 0; i < iters << 4; ++i) {
- parseJson("\"akjhfk jhkjlakjhfk jhkjlakjhfk jhkjl akjhfk\"");
- }
+ // 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(parseBigString, iters) {
- for (int i = 0; i < iters; ++i) {
- parseJson("\""
- "akjhfk jhkjlakjhfk jhkjlakjhfk jhkjl akjhfk"
- "akjhfk jhkjlakjhfk jhkjlakjhfk jhkjl akjhfk"
- "akjhfk jhkjlakjhfk jhkjlakjhfk jhkjl akjhfk"
- "akjhfk jhkjlakjhfk jhkjlakjhfk jhkjl akjhfk"
- "akjhfk jhkjlakjhfk jhkjlakjhfk jhkjl akjhfk"
- "akjhfk jhkjlakjhfk jhkjlakjhfk jhkjl akjhfk"
- "akjhfk jhkjlakjhfk jhkjlakjhfk jhkjl akjhfk"
- "akjhfk jhkjlakjhfk jhkjlakjhfk jhkjl akjhfk"
- "akjhfk jhkjlakjhfk jhkjlakjhfk jhkjl akjhfk"
- "akjhfk jhkjlakjhfk jhkjlakjhfk jhkjl akjhfk"
- "akjhfk jhkjlakjhfk jhkjlakjhfk jhkjl akjhfk"
- "\"");
- }
+TEST(Json, SortKeys) {
+ folly::json::serialization_opts opts_on, opts_off;
+ opts_on.sort_keys = true;
+ opts_off.sort_keys = false;
+
+ dynamic value = dynamic::object
+ ("foo", "bar")
+ ("junk", 12)
+ ("another", 32.2)
+ ("a",
+ {
+ dynamic::object("a", "b")
+ ("c", "d"),
+ 12.5,
+ "Yo Dawg",
+ { "heh" },
+ nullptr
+ }
+ )
+ ;
+
+ std::string sorted_keys =
+ R"({"a":[{"a":"b","c":"d"},12.5,"Yo Dawg",["heh"],null],)"
+ R"("another":32.2,"foo":"bar","junk":12})";
+
+ EXPECT_EQ(value, parseJson(folly::json::serialize(value, opts_on)));
+ EXPECT_EQ(value, parseJson(folly::json::serialize(value, opts_off)));
+
+ EXPECT_EQ(sorted_keys, folly::json::serialize(value, opts_on));
}
int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);
- google::ParseCommandLineFlags(&argc, &argv, true);
- if (FLAGS_benchmark) {
- folly::runBenchmarks();
- }
+ gflags::ParseCommandLineFlags(&argc, &argv, true);
return RUN_ALL_TESTS();
}