// 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/test/gmock_expected_support.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() // .SetByDottedPath("a.b", 2) .Set("a.b", 1)), R"({"a":{"b":2},"a.b":1})"); } TEST(JsonWriterTest, BinaryValues) { const auto kBinaryData = base::make_span(reinterpret_cast("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 ASSERT_OK_AND_ASSIGN(Value value, JSONReader::ReadAndReturnValueWithError( nested_json, JSON_ALLOW_TRAILING_COMMAS)); EXPECT_TRUE(WriteJson(std::move(value)).has_value()); } // 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