mirror of
https://github.com/nlohmann/json.git
synced 2026-10-01 06:25:17 +00:00
* Add tests for uncovered code paths Cover code the test suite did not reach, found from the Coveralls report of develop and a local coverage run of HEAD: - dump() of every kind of value below the bound of the recursive descent (pretty-printed objects, binary values, discarded values, scalars), and flushes of the escape and write buffers mid-string and mid-binary - the iterative comparison: objects with different keys, containers that are a prefix of each other, and elements that cannot be ordered, each both at the top level and below the nesting bound - SAX handlers that stop at any event, including the end of a nested container, in the BSON, CBOR, MessagePack, UBJSON and BJData readers - from_bson/cbor/msgpack/ubjson/bjdata returning a discarded value through the iterator and pointer overloads - JSON Patch, diff, merge_patch and update(..., true) on ordered_json - smaller gaps: get_allocator(), to_ubjson/to_bjdata into a string, value() with an unresolvable JSON pointer, integer/float comparison below the integer range and with negative fractions, conversion to a custom binary type, std::formatter::parse on a spec without '}', unescape() of a lone '~', and the callback parser's start_array() Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Cover more paths that were thought unreachable - parse_float_fast() declining malformed or inexact input, called directly since the lexer only passes well-formed numbers to it - a UTF-16 high surrogate followed by a unit above the low surrogates - self-assignment of a const_iterator - a truncated CBOR string read through non-contiguous iterators - serializing a long double under the de_DE locale, which undoes the locale's decimal point and thousands separator - values read from a binary format carrying no diagnostic positions, with and without a parser callback Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix the CI failures of the new coverage tests - declare the self-assignment reference const (misc-const-correctness) - expect the (/path) prefix that JSON_DIAGNOSTICS adds to the messages of the failing ordered_json patch operations Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Expect the byte range JSON_DIAGNOSTIC_POSITIONS adds to the patch errors Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Build the expected dump of the nested-object test with += clang-tidy (performance-inefficient-string-concatenation) reported the chain of operator+ calls that assembled the expected indented output. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Compare the BJData and UBJSON test outputs byte by byte Building a std::string from the byte vector converts each byte implicitly, which -fsanitize=integer reports for bytes of 0x80 and above (ci_test_clang_sanitizer). Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
377 lines
11 KiB
C++
377 lines
11 KiB
C++
// __ _____ _____ _____
|
|
// __| | __| | | | JSON for Modern C++ (supporting code)
|
|
// | | |__ | | | | | | version 3.12.0
|
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
//
|
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#include "doctest_compatibility.h"
|
|
|
|
#include <nlohmann/json.hpp>
|
|
using nlohmann::json;
|
|
#ifdef JSON_TEST_NO_GLOBAL_UDLS
|
|
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
|
#endif
|
|
|
|
#include <string>
|
|
|
|
namespace
|
|
{
|
|
// RFC 7396's MergePatch, written recursively as in the RFC; only usable on
|
|
// values nested a few hundred levels deep
|
|
void reference_merge_patch(json& target, const json& patch)
|
|
{
|
|
if (!patch.is_object())
|
|
{
|
|
target = patch;
|
|
return;
|
|
}
|
|
if (!target.is_object())
|
|
{
|
|
target = json::object();
|
|
}
|
|
for (auto it = patch.begin(); it != patch.end(); ++it)
|
|
{
|
|
if (it.value().is_null())
|
|
{
|
|
target.erase(it.key());
|
|
}
|
|
else
|
|
{
|
|
reference_merge_patch(target[it.key()], it.value());
|
|
}
|
|
}
|
|
}
|
|
|
|
// objects nested `depth` levels deep under the key "a", with members that
|
|
// differ by `variant` on the way down
|
|
std::string nested_objects(const std::size_t depth, const int variant)
|
|
{
|
|
std::string text;
|
|
for (std::size_t i = 0; i < depth; ++i)
|
|
{
|
|
text += "{";
|
|
if ((i + static_cast<std::size_t>(variant)) % 3 == 0)
|
|
{
|
|
text += "\"s" + std::to_string(variant) + "\":" + std::to_string(i) + ",";
|
|
}
|
|
if (variant == 2 && i % 5 == 0)
|
|
{
|
|
text += "\"s0\":null,";
|
|
}
|
|
text += "\"a\":";
|
|
}
|
|
text += variant == 1 ? R"({"x":1,"y":null})" : "{\"y\":2}";
|
|
text.append(depth, '}');
|
|
return text;
|
|
}
|
|
} // namespace
|
|
|
|
TEST_CASE("JSON Merge Patch")
|
|
{
|
|
SECTION("examples from RFC 7396")
|
|
{
|
|
SECTION("Section 1")
|
|
{
|
|
json document = R"({
|
|
"a": "b",
|
|
"c": {
|
|
"d": "e",
|
|
"f": "g"
|
|
}
|
|
})"_json;
|
|
|
|
json const patch = R"({
|
|
"a": "z",
|
|
"c": {
|
|
"f": null
|
|
}
|
|
})"_json;
|
|
|
|
json expected = R"({
|
|
"a": "z",
|
|
"c": {
|
|
"d": "e"
|
|
}
|
|
})"_json;
|
|
|
|
document.merge_patch(patch);
|
|
CHECK(document == expected);
|
|
}
|
|
|
|
SECTION("Section 3")
|
|
{
|
|
json document = R"({
|
|
"title": "Goodbye!",
|
|
"author": {
|
|
"givenName": "John",
|
|
"familyName": "Doe"
|
|
},
|
|
"tags": [
|
|
"example",
|
|
"sample"
|
|
],
|
|
"content": "This will be unchanged"
|
|
})"_json;
|
|
|
|
json const patch = R"({
|
|
"title": "Hello!",
|
|
"phoneNumber": "+01-123-456-7890",
|
|
"author": {
|
|
"familyName": null
|
|
},
|
|
"tags": [
|
|
"example"
|
|
]
|
|
})"_json;
|
|
|
|
json expected = R"({
|
|
"title": "Hello!",
|
|
"author": {
|
|
"givenName": "John"
|
|
},
|
|
"tags": [
|
|
"example"
|
|
],
|
|
"content": "This will be unchanged",
|
|
"phoneNumber": "+01-123-456-7890"
|
|
})"_json;
|
|
|
|
document.merge_patch(patch);
|
|
CHECK(document == expected);
|
|
}
|
|
|
|
SECTION("Appendix A")
|
|
{
|
|
SECTION("Example 1")
|
|
{
|
|
json original = R"({"a":"b"})"_json;
|
|
json const patch = R"({"a":"c"})"_json;
|
|
json result = R"({"a":"c"})"_json;
|
|
|
|
original.merge_patch(patch);
|
|
CHECK(original == result);
|
|
}
|
|
|
|
SECTION("Example 2")
|
|
{
|
|
json original = R"({"a":"b"})"_json;
|
|
json const patch = R"({"b":"c"})"_json;
|
|
json result = R"({"a":"b", "b":"c"})"_json;
|
|
|
|
original.merge_patch(patch);
|
|
CHECK(original == result);
|
|
}
|
|
|
|
SECTION("Example 3")
|
|
{
|
|
json original = R"({"a":"b"})"_json;
|
|
json const patch = R"({"a":null})"_json;
|
|
json result = R"({})"_json;
|
|
|
|
original.merge_patch(patch);
|
|
CHECK(original == result);
|
|
}
|
|
|
|
SECTION("Example 4")
|
|
{
|
|
json original = R"({"a":"b","b":"c"})"_json;
|
|
json const patch = R"({"a":null})"_json;
|
|
json result = R"({"b":"c"})"_json;
|
|
|
|
original.merge_patch(patch);
|
|
CHECK(original == result);
|
|
}
|
|
|
|
SECTION("Example 5")
|
|
{
|
|
json original = R"({"a":["b"]})"_json;
|
|
json const patch = R"({"a":"c"})"_json;
|
|
json result = R"({"a":"c"})"_json;
|
|
|
|
original.merge_patch(patch);
|
|
CHECK(original == result);
|
|
}
|
|
|
|
SECTION("Example 6")
|
|
{
|
|
json original = R"({"a":"c"})"_json;
|
|
json const patch = R"({"a":["b"]})"_json;
|
|
json result = R"({"a":["b"]})"_json;
|
|
|
|
original.merge_patch(patch);
|
|
CHECK(original == result);
|
|
}
|
|
|
|
SECTION("Example 7")
|
|
{
|
|
json original = R"({"a":{"b": "c"}})"_json;
|
|
json const patch = R"({"a":{"b":"d","c":null}})"_json;
|
|
json result = R"({"a": {"b": "d"}})"_json;
|
|
|
|
original.merge_patch(patch);
|
|
CHECK(original == result);
|
|
}
|
|
|
|
SECTION("Example 8")
|
|
{
|
|
json original = R"({"a":[{"b":"c"}]})"_json;
|
|
json const patch = R"({"a":[1]})"_json;
|
|
json result = R"({"a":[1]})"_json;
|
|
|
|
original.merge_patch(patch);
|
|
CHECK(original == result);
|
|
}
|
|
|
|
SECTION("Example 9")
|
|
{
|
|
json original = R"(["a","b"])"_json;
|
|
json const patch = R"(["c","d"])"_json;
|
|
json result = R"(["c","d"])"_json;
|
|
|
|
original.merge_patch(patch);
|
|
CHECK(original == result);
|
|
}
|
|
|
|
SECTION("Example 10")
|
|
{
|
|
json original = R"({"a":"b"})"_json;
|
|
json const patch = R"(["c"])"_json;
|
|
json result = R"(["c"])"_json;
|
|
|
|
original.merge_patch(patch);
|
|
CHECK(original == result);
|
|
}
|
|
|
|
SECTION("Example 11")
|
|
{
|
|
json original = R"({"a":"foo"})"_json;
|
|
json const patch = R"(null)"_json;
|
|
json result = R"(null)"_json;
|
|
|
|
original.merge_patch(patch);
|
|
CHECK(original == result);
|
|
}
|
|
|
|
SECTION("Example 12")
|
|
{
|
|
json original = R"({"a":"foo"})"_json;
|
|
json const patch = R"("bar")"_json;
|
|
json result = R"("bar")"_json;
|
|
|
|
original.merge_patch(patch);
|
|
CHECK(original == result);
|
|
}
|
|
|
|
SECTION("Example 13")
|
|
{
|
|
json original = R"({"e":null})"_json;
|
|
json const patch = R"({"a":1})"_json;
|
|
json result = R"({"e":null,"a":1})"_json;
|
|
|
|
original.merge_patch(patch);
|
|
CHECK(original == result);
|
|
}
|
|
|
|
SECTION("Example 14")
|
|
{
|
|
json original = R"([1,2])"_json;
|
|
json const patch = R"({"a":"b","c":null})"_json;
|
|
json result = R"({"a":"b"})"_json;
|
|
|
|
original.merge_patch(patch);
|
|
CHECK(original == result);
|
|
}
|
|
|
|
SECTION("Example 15")
|
|
{
|
|
json original = R"({})"_json;
|
|
json const patch = R"({"a":{"bb":{"ccc":null}}})"_json;
|
|
json result = R"({"a":{"bb":{}}})"_json;
|
|
|
|
original.merge_patch(patch);
|
|
CHECK(original == result);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("JSON Merge Patch on deeply nested values")
|
|
{
|
|
SECTION("patching past the descent bound gives the same result")
|
|
{
|
|
// every depth on either side of where the iterative version takes
|
|
// over (detail::recursion_depth_limit(), 128)
|
|
for (std::size_t depth = 0; depth <= 300; ++depth)
|
|
{
|
|
CAPTURE(depth);
|
|
for (int variant = 0; variant < 3; ++variant)
|
|
{
|
|
CAPTURE(variant);
|
|
const json patch = json::parse(nested_objects(depth, variant));
|
|
|
|
json result = json::parse(nested_objects(depth, (variant + 1) % 3));
|
|
json expected = result;
|
|
result.merge_patch(patch);
|
|
reference_merge_patch(expected, patch);
|
|
CHECK(result == expected);
|
|
|
|
// a target that is not an object, and an empty one
|
|
json from_null;
|
|
from_null.merge_patch(patch);
|
|
json expected_from_null;
|
|
reference_merge_patch(expected_from_null, patch);
|
|
CHECK(from_null == expected_from_null);
|
|
}
|
|
}
|
|
}
|
|
|
|
SECTION("patches nested too deeply for the call stack (#5393)")
|
|
{
|
|
// applying a patch used to recurse once per nesting level. The result
|
|
// is only walked, never copied or compared, since those recurse too.
|
|
const std::size_t depth = 100000;
|
|
json target = json::parse(nested_objects(depth, 0));
|
|
target.merge_patch(json::parse(nested_objects(depth, 1)));
|
|
|
|
const json* p = ⌖
|
|
for (std::size_t i = 0; i < depth; ++i)
|
|
{
|
|
p = &p->at("a");
|
|
}
|
|
// {"y":2} patched with {"x":1,"y":null}
|
|
CHECK(p->size() == 1);
|
|
CHECK(p->at("x") == 1);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("JSON Merge Patch and update on ordered_json")
|
|
{
|
|
using nlohmann::ordered_json;
|
|
|
|
SECTION("merge_patch")
|
|
{
|
|
ordered_json target = ordered_json::parse(R"({"a": {"b": 1, "c": 2}, "d": 3, "e": [1]})");
|
|
target.merge_patch(ordered_json::parse(R"({"a": {"b": null, "f": 4}, "d": {"x": {"y": null}}, "e": null, "g": {"h": 5}})"));
|
|
CHECK(target == ordered_json::parse(R"({"a": {"c": 2, "f": 4}, "d": {"x": {}}, "g": {"h": 5}})"));
|
|
|
|
// a patch that is not an object replaces the target
|
|
target.merge_patch(ordered_json({1, 2}));
|
|
CHECK(target == ordered_json({1, 2}));
|
|
// an object patch turns a target that is not an object into one
|
|
target.merge_patch(ordered_json::parse(R"({"k": {"l": null}})"));
|
|
CHECK(target == ordered_json::parse(R"({"k": {}})"));
|
|
}
|
|
|
|
SECTION("update with merge_objects")
|
|
{
|
|
ordered_json target = ordered_json::parse(R"({"a": {"b": 1, "c": {"d": 2}}, "e": 3})");
|
|
target.update(ordered_json::parse(R"({"a": {"c": {"x": 1}, "f": 4}, "e": {"y": 5}, "g": 6})"), true);
|
|
CHECK(target == ordered_json::parse(R"({"a": {"b": 1, "c": {"d": 2, "x": 1}, "f": 4}, "e": {"y": 5}, "g": 6})"));
|
|
|
|
target.update(ordered_json::parse(R"({"a": 1})"), false);
|
|
CHECK(target == ordered_json::parse(R"({"a": 1, "e": {"y": 5}, "g": 6})"));
|
|
}
|
|
}
|