| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/json/json_writer.h" |
| #include "base/json/json_reader.h" |
| |
| #include "base/containers/span.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| |
| #if BUILDFLAG(IS_WIN) |
| #include "base/strings/string_util.h" |
| #endif |
| |
| namespace base { |
| |
| namespace { |
| |
| std::string FixNewlines(const std::string& json) { |
| // The pretty-printer uses a different newline style on Windows than on |
| // other platforms. |
| #if BUILDFLAG(IS_WIN) |
| std::string result; |
| ReplaceChars(json, "\n", "\r\n", &result); |
| return result; |
| #else |
| return json; |
| #endif |
| } |
| |
| } // namespace |
| |
| TEST(JsonWriterTest, BasicTypes) { |
| // Test null. |
| EXPECT_EQ(WriteJson(Value()), "null"); |
| |
| // Test empty dict. |
| EXPECT_EQ(WriteJson(Value(Value::Type::DICT)), "{}"); |
| |
| // Test empty list. |
| EXPECT_EQ(WriteJson(Value(Value::Type::LIST)), "[]"); |
| |
| // Test integer values. |
| EXPECT_EQ(WriteJson(Value(42)), "42"); |
| |
| // Test boolean values. |
| EXPECT_EQ(WriteJson(Value(true)), "true"); |
| |
| // Test Real values should always have a decimal or an 'e'. |
| EXPECT_EQ(WriteJson(Value(1.0)), "1.0"); |
| |
| // Test Real values in the range (-1, 1) must have leading zeros |
| EXPECT_EQ(WriteJson(Value(0.2)), "0.2"); |
| |
| // Test Real values in the range (-1, 1) must have leading zeros |
| EXPECT_EQ(WriteJson(Value(-0.8)), "-0.8"); |
| |
| // Test String values. |
| EXPECT_EQ(WriteJson(Value("foo")), "\"foo\""); |
| } |
| |
| TEST(JsonWriterTest, NestedTypes) { |
| // Writer unittests like empty list/dict nesting, |
| // list list nesting, etc. |
| auto dict = |
| Value::Dict().Set("list", Value::List() |
| .Append(Value::Dict().Set("inner int", 10)) |
| .Append(Value::Dict()) |
| .Append(Value::List()) |
| .Append(true)); |
| |
| EXPECT_EQ(WriteJson(dict), "{\"list\":[{\"inner int\":10},{},[],true]}"); |
| |
| // Test the pretty-printer. |
| EXPECT_EQ(WriteJsonWithOptions(dict, JSONWriter::OPTIONS_PRETTY_PRINT), |
| FixNewlines(R"({ |
| "list": [ { |
| "inner int": 10 |
| }, { |
| }, [ ], true ] |
| } |
| )")); |
| } |
| |
| TEST(JsonWriterTest, KeysWithPeriods) { |
| EXPECT_EQ(WriteJson(Value::Dict() // |
| .Set("a.b", 3) |
| .Set("c", 2) |
| .Set("d.e.f", Value::Dict().Set("g.h.i.j", 1))), |
| R"({"a.b":3,"c":2,"d.e.f":{"g.h.i.j":1}})"); |
| |
| EXPECT_EQ(WriteJson(Value::Dict() // |
| .Set("a", Value::Dict().Set("b", 2)) |
| .Set("a.b", 1)), |
| R"({"a":{"b":2},"a.b":1})"); |
| } |
| |
| TEST(JsonWriterTest, BinaryValues) { |
| const auto kBinaryData = |
| base::make_span(reinterpret_cast<const uint8_t*>("asdf"), 4u); |
| |
| // Binary values should return errors unless suppressed via the |
| // `OPTIONS_OMIT_BINARY_VALUES` flag. |
| EXPECT_EQ(WriteJson(Value(kBinaryData)), absl::nullopt); |
| EXPECT_EQ(WriteJsonWithOptions(Value(kBinaryData), |
| JsonOptions::OPTIONS_OMIT_BINARY_VALUES), |
| ""); |
| |
| auto binary_list = Value::List() |
| .Append(Value(kBinaryData)) |
| .Append(5) |
| .Append(Value(kBinaryData)) |
| .Append(2) |
| .Append(Value(kBinaryData)); |
| EXPECT_EQ(WriteJson(binary_list), absl::nullopt); |
| EXPECT_EQ( |
| WriteJsonWithOptions(binary_list, JSONWriter::OPTIONS_OMIT_BINARY_VALUES), |
| "[5,2]"); |
| |
| auto binary_dict = Value::Dict() |
| .Set("a", Value(kBinaryData)) |
| .Set("b", 5) |
| .Set("c", Value(kBinaryData)) |
| .Set("d", 2) |
| .Set("e", Value(kBinaryData)); |
| EXPECT_EQ(WriteJson(binary_dict), absl::nullopt); |
| EXPECT_EQ( |
| WriteJsonWithOptions(binary_dict, JSONWriter::OPTIONS_OMIT_BINARY_VALUES), |
| R"({"b":5,"d":2})"); |
| } |
| |
| TEST(JsonWriterTest, DoublesAsInts) { |
| // Test allowing a double with no fractional part to be written as an integer. |
| Value double_value(1e10); |
| EXPECT_EQ( |
| WriteJsonWithOptions(double_value, |
| JSONWriter::OPTIONS_OMIT_DOUBLE_TYPE_PRESERVATION), |
| "10000000000"); |
| } |
| |
| TEST(JsonWriterTest, StackOverflow) { |
| Value::List deep_list; |
| const size_t max_depth = 100000; |
| |
| for (size_t i = 0; i < max_depth; ++i) { |
| Value::List new_top_list; |
| new_top_list.Append(std::move(deep_list)); |
| deep_list = std::move(new_top_list); |
| } |
| |
| Value deep_list_value(std::move(deep_list)); |
| EXPECT_EQ(WriteJson(deep_list_value), absl::nullopt); |
| EXPECT_EQ( |
| WriteJsonWithOptions(deep_list_value, JSONWriter::OPTIONS_PRETTY_PRINT), |
| absl::nullopt); |
| |
| // We cannot just let `deep_list` tear down since it |
| // would cause a stack overflow. Therefore, we tear |
| // down the deep list manually. |
| deep_list = std::move(deep_list_value).TakeList(); |
| while (!deep_list.empty()) { |
| DCHECK_EQ(deep_list.size(), 1u); |
| Value::List inner_list = std::move(deep_list[0]).TakeList(); |
| deep_list = std::move(inner_list); |
| } |
| } |
| |
| TEST(JsonWriterTest, TestMaxDepthWithValidNodes) { |
| // Create JSON to the max depth - 1. Nodes at that depth are still valid |
| // for writing which matches the JSONParser logic. |
| std::string nested_json; |
| for (int i = 0; i < 199; ++i) { |
| std::string node = "["; |
| for (int j = 0; j < 5; j++) { |
| node.append(StringPrintf("%d,", j)); |
| } |
| nested_json.insert(0, node); |
| nested_json.append("]"); |
| } |
| |
| // Ensure we can read and write the JSON |
| auto json_val = JSONReader::ReadAndReturnValueWithError( |
| nested_json, JSON_ALLOW_TRAILING_COMMAS); |
| EXPECT_TRUE(json_val.has_value()); |
| const Value& value = *json_val; |
| EXPECT_NE(WriteJson(value), absl::nullopt); |
| } |
| |
| // Test that the JSONWriter::Write method still works. |
| TEST(JsonWriterTest, JSONWriterWriteSuccess) { |
| std::string output_js; |
| |
| EXPECT_TRUE( |
| JSONWriter::Write(base::Value::Dict().Set("key", "value"), &output_js)); |
| EXPECT_EQ(output_js, R"({"key":"value"})"); |
| } |
| |
| // Test that the JSONWriter::Write method still works. |
| TEST(JsonWriterTest, JSONWriterWriteFailure) { |
| std::string output_js; |
| |
| EXPECT_FALSE(JSONWriter::Write( |
| base::Value::Dict() // |
| .Set("key", |
| base::Value::Dict().Set("nested-key", base::Value::Dict())), |
| &output_js, /*max_depth=*/1)); |
| } |
| |
| // Test that the JSONWriter::WriteWithOptions method still works. |
| TEST(JsonWriterTest, JSONWriterWriteWithOptionsSuccess) { |
| std::string output_js; |
| EXPECT_TRUE(JSONWriter::WriteWithOptions( |
| base::Value::Dict().Set("key", "value"), JSONWriter::OPTIONS_PRETTY_PRINT, |
| &output_js)); |
| EXPECT_EQ(output_js, FixNewlines(R"({ |
| "key": "value" |
| } |
| )")); |
| } |
| |
| // Test that the JSONWriter::WriteWithOptions method still works. |
| TEST(JsonWriterTest, JSONWriterWriteWithOptionsFailure) { |
| std::string output_js; |
| |
| EXPECT_FALSE(JSONWriter::WriteWithOptions( |
| base::Value::Dict().Set( |
| "key", base::Value::Dict().Set("nested-key", base::Value::Dict())), |
| JSONWriter::OPTIONS_PRETTY_PRINT, &output_js, /*max_depth=*/1)); |
| } |
| |
| } // namespace base |