| // __ _____ _____ _____ | |
| // __| | __| | | | 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; | |
| using nlohmann::ordered_json; | |
| #ifdef JSON_TEST_NO_GLOBAL_UDLS | |
| using namespace nlohmann::literals; // NOLINT(google-build-using-namespace) | |
| #endif | |
| #include <string> | |
| #include <vector> | |
| namespace | |
| { | |
| // merge_diff() recursing once per nesting level, without the bound on its | |
| // descent; only usable on values nested a few hundred levels deep | |
| template<typename BasicJsonType> | |
| BasicJsonType reference_merge_diff(const BasicJsonType& source, const BasicJsonType& target) | |
| { | |
| if (!source.is_object() || !target.is_object()) | |
| { | |
| return target; | |
| } | |
| BasicJsonType result = BasicJsonType::object(); | |
| for (auto it = source.begin(); it != source.end(); ++it) | |
| { | |
| const auto itf = target.find(it.key()); | |
| if (itf == target.end()) | |
| { | |
| result[it.key()] = nullptr; | |
| } | |
| else if (it.value().is_object() && itf.value().is_object()) | |
| { | |
| auto diff = reference_merge_diff(it.value(), itf.value()); | |
| if (!diff.empty()) | |
| { | |
| result[it.key()] = std::move(diff); | |
| } | |
| } | |
| else if (it.value() != itf.value()) | |
| { | |
| result[it.key()] = itf.value(); | |
| } | |
| } | |
| for (auto it = target.begin(); it != target.end(); ++it) | |
| { | |
| if (source.find(it.key()) == source.end()) | |
| { | |
| result[it.key()] = it.value(); | |
| } | |
| } | |
| return result; | |
| } | |
| // objects nested `depth` levels deep under the key "a", with members before | |
| // and after it that differ by `variant` on the way down, so that diffing two | |
| // variants removes, adds and replaces members on many levels, and diffs nested | |
| // objects that are equal, that differ, or that are not objects in one of the | |
| // variants; only variant 2 has members that are null | |
| template<typename BasicJsonType> | |
| BasicJsonType nested_objects(const std::size_t depth, const int variant) | |
| { | |
| BasicJsonType value = BasicJsonType::object({{"v", variant}}); | |
| for (std::size_t i = 0; i < depth; ++i) | |
| { | |
| BasicJsonType object = BasicJsonType::object(); | |
| if ((i + static_cast<std::size_t>(variant)) % 3 == 0) | |
| { | |
| object["s" + std::to_string(variant)] = i; | |
| } | |
| object["c"] = i % 4 == 0 ? variant : 0; | |
| if (i % 5 == 0) | |
| { | |
| if (variant == 0) | |
| { | |
| object["o"] = BasicJsonType::object({{"p", i}}); | |
| } | |
| else if (variant == 1) | |
| { | |
| object["o"] = BasicJsonType::array({i}); | |
| } | |
| else | |
| { | |
| object["o"] = nullptr; | |
| } | |
| } | |
| if (variant == 2 && i % 4 == 2) | |
| { | |
| object["n"] = nullptr; | |
| } | |
| object["a"] = std::move(value); | |
| if (i % 6 == 0) | |
| { | |
| object["d"] = BasicJsonType::object({{"e", variant == 1 ? 1 : 0}}); | |
| } | |
| if (variant == 1 && i % 7 == 0) | |
| { | |
| object["z"] = "z"; | |
| } | |
| value = std::move(object); | |
| } | |
| return value; | |
| } | |
| } // namespace | |
| TEST_CASE("JSON Merge Patch diff") | |
| { | |
| SECTION("examples from RFC 7396") | |
| { | |
| SECTION("Section 1") | |
| { | |
| json document = R"({ | |
| "a": "b", | |
| "c": { | |
| "d": "e", | |
| "f": "g" | |
| } | |
| })"_json; | |
| json expected = R"({ | |
| "a": "z", | |
| "c": { | |
| "d": "e" | |
| } | |
| })"_json; | |
| json const expected_patch = R"({ | |
| "a": "z", | |
| "c": { | |
| "f": null | |
| } | |
| })"_json; | |
| auto patch = json::merge_diff(document, expected); | |
| CHECK(patch == expected_patch); | |
| 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 expected = R"({ | |
| "title": "Hello!", | |
| "author": { | |
| "givenName": "John" | |
| }, | |
| "tags": [ | |
| "example" | |
| ], | |
| "content": "This will be unchanged", | |
| "phoneNumber": "+01-123-456-7890" | |
| })"_json; | |
| json const expected_patch = R"({ | |
| "title": "Hello!", | |
| "phoneNumber": "+01-123-456-7890", | |
| "author": { | |
| "familyName": null | |
| }, | |
| "tags": [ | |
| "example" | |
| ] | |
| })"_json; | |
| auto patch = json::merge_diff(document, expected); | |
| CHECK(patch == expected_patch); | |
| document.merge_patch(patch); | |
| CHECK(document == expected); | |
| } | |
| SECTION("Appendix A") | |
| { | |
| SECTION("Example 1") | |
| { | |
| json original = R"({"a":"b"})"_json; | |
| json result = R"({"a":"c"})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"a":"c"})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("Example 2") | |
| { | |
| json original = R"({"a":"b"})"_json; | |
| json result = R"({"a":"b", "b":"c"})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"b":"c"})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("Example 3") | |
| { | |
| json original = R"({"a":"b"})"_json; | |
| json result = R"({})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"a":null})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("Example 4") | |
| { | |
| json original = R"({"a":"b","b":"c"})"_json; | |
| json result = R"({"b":"c"})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"a":null})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("Example 5") | |
| { | |
| json original = R"({"a":["b"]})"_json; | |
| json result = R"({"a":"c"})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"a":"c"})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("Example 6") | |
| { | |
| json original = R"({"a":"c"})"_json; | |
| json result = R"({"a":["b"]})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"a":["b"]})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("Example 7") | |
| { | |
| json original = R"({"a":{"b": "c"}})"_json; | |
| json result = R"({"a": {"b": "d"}})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| // differs from the RFC 7396, "c": null is not in the patch | |
| // because neither the source nor the target have the "c" key | |
| CHECK(patch == R"({"a":{"b":"d"}})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("Example 8") | |
| { | |
| json original = R"({"a":[{"b":"c"}]})"_json; | |
| json result = R"({"a":[1]})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"a":[1]})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("Example 9") | |
| { | |
| json original = R"(["a","b"])"_json; | |
| json result = R"(["c","d"])"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"(["c","d"])"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("Example 10") | |
| { | |
| json original = R"({"a":"b"})"_json; | |
| json result = R"(["c"])"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"(["c"])"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("Example 11") | |
| { | |
| json original = R"({"a":"foo"})"_json; | |
| json result = R"(null)"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"(null)"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("Example 12") | |
| { | |
| json original = R"({"a":"foo"})"_json; | |
| json result = R"("bar")"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"("bar")"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("Example 13") | |
| { | |
| json original = R"({"e":null})"_json; | |
| json result = R"({"e":null,"a":1})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"a":1})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("Example 14") | |
| { | |
| json original = R"([1,2])"_json; | |
| json result = R"({"a":"b"})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| // differs from the RFC 7396, "c": null is not in the patch | |
| // because neither the source nor the target have the "c" key | |
| CHECK(patch == R"({"a":"b"})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("Example 15") | |
| { | |
| json original = R"({})"_json; | |
| json result = R"({"a":{"bb":{}}})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| // differs from the RFC 7396, "ccc": null is not in the patch | |
| // because neither the source nor the target have the "ccc" key | |
| CHECK(patch == R"({"a":{"bb":{}}})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| } | |
| } | |
| SECTION("null values") | |
| { | |
| SECTION("object with null value to object") | |
| { | |
| json original = R"({"a":null})"_json; | |
| json result = R"({"a":{"b":"c"}})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"a":{"b":"c"}})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == R"({"a":{"b":"c"}})"_json); | |
| } | |
| SECTION("object to object with null value") | |
| { | |
| json original = R"({"a":{"b":"c"}})"_json; | |
| json result = R"({"a":null})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"a":null})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == R"({})"_json); | |
| } | |
| SECTION("primitive to object with null value") | |
| { | |
| json original = R"({"a":1})"_json; | |
| json result = R"({"a":null})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"a":null})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == R"({})"_json); | |
| } | |
| SECTION("nested primitive to object with null value") | |
| { | |
| json original = R"({"a":{"b":1}})"_json; | |
| json result = R"({"a":{"b":null}})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"a":{"b":null}})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == R"({"a":{}})"_json); | |
| } | |
| SECTION("array value to object with null value") | |
| { | |
| json original = R"({"a":[1,2]})"_json; | |
| json result = R"({"a":null})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"a":null})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == R"({})"_json); | |
| } | |
| SECTION("nested object to object with null value") | |
| { | |
| json original = R"({"a":{"b":{"c":"d"}}})"_json; | |
| json result = R"({"a":{"b":null}})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"a":{"b":null}})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == R"({"a":{}})"_json); | |
| } | |
| SECTION("nested primitive to object with null value with sibling") | |
| { | |
| json original = R"({"x":"y","a":{"b":"c"}})"_json; | |
| json result = R"({"x":"y","a":{"b":null, "c":"d"}})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"a":{"b":null,"c":"d"}})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == R"({"x":"y","a":{"c":"d"}})"_json); | |
| } | |
| SECTION("empty object to object with null value") | |
| { | |
| json original = R"({})"_json; | |
| json result = R"({"a":null})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"a":null})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == R"({})"_json); | |
| } | |
| SECTION("null to object with null value") | |
| { | |
| json original = R"(null)"_json; | |
| json result = R"({"a":null})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"a":null})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == R"({})"_json); | |
| } | |
| SECTION("array to object with null value") | |
| { | |
| json original = R"([])"_json; | |
| json result = R"({"a":null})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"a":null})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == R"({})"_json); | |
| } | |
| SECTION("empty object to nested null value") | |
| { | |
| json original = R"({})"_json; | |
| json result = R"({"a":{"b":null}})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"a":{"b":null}})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == R"({"a":{}})"_json); | |
| } | |
| SECTION("object to nested null value") | |
| { | |
| json original = R"({"a": 1})"_json; | |
| json result = R"({"a":{"b":null}})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"a":{"b":null}})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == R"({"a":{}})"_json); | |
| } | |
| SECTION("primitive to nested null value") | |
| { | |
| json original = R"(1)"_json; | |
| json result = R"({"a":{"b":null}})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"a":{"b":null}})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == R"({"a":{}})"_json); | |
| } | |
| SECTION("object with null value unchanged") | |
| { | |
| json original = R"({"a":null})"_json; | |
| json result = R"({"a":null})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("object with null value to empty object") | |
| { | |
| json original = R"({"a":null})"_json; | |
| json result = R"({})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"a":null})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("object with null value to primitive") | |
| { | |
| json original = R"({"a":null})"_json; | |
| json result = R"({"a":1})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"a":1})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("array to array with null value") | |
| { | |
| json original = R"({"a":[1]})"_json; | |
| json result = R"({"a":[null]})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"a":[null]})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("empty object to array with null value") | |
| { | |
| json original = R"({})"_json; | |
| json result = R"({"a":[null]})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"a":[null]})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("doubly nested primitive to null value") | |
| { | |
| json original = R"({"a":{"b":{"c":1}}})"_json; | |
| json result = R"({"a":{"b":{"c":null}}})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"a":{"b":{"c":null}}})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == R"({"a":{"b":{}}})"_json); | |
| } | |
| } | |
| SECTION("no change") | |
| { | |
| SECTION("object") | |
| { | |
| json original = R"({"a":"b","b":"c"})"_json; | |
| json result = R"({"a":"b","b":"c"})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch.empty()); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("empty object") | |
| { | |
| json original = R"({})"_json; | |
| json result = R"({})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch.empty()); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("array") | |
| { | |
| json original = R"([1,2,3])"_json; | |
| json result = R"([1,2,3])"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(!patch.empty()); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("null") | |
| { | |
| json original = R"(null)"_json; | |
| json result = R"(null)"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch.empty()); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("string") | |
| { | |
| json original = R"("ab")"_json; | |
| json result = R"("ab")"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(!patch.empty()); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("number") | |
| { | |
| json original = R"(42)"_json; | |
| json result = R"(42)"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(!patch.empty()); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| } | |
| SECTION("primitives") | |
| { | |
| SECTION("string") | |
| { | |
| json original = R"("a")"_json; | |
| json result = R"("b")"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"("b")"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("number") | |
| { | |
| json original = R"(1)"_json; | |
| json result = R"(2)"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"(2)"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("boolean") | |
| { | |
| json original = R"(false)"_json; | |
| json result = R"(true)"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"(true)"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("object to primitive") | |
| { | |
| json original = R"({"a":{"b":"c"}})"_json; | |
| json result = R"({"a":1})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"a":1})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("primitive to object") | |
| { | |
| json original = R"({"a":1})"_json; | |
| json result = R"({"a":{"b":"c"}})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"a":{"b":"c"}})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("object to empty string") | |
| { | |
| json original = R"({"a":{"b":"c"}})"_json; | |
| json result = R"({"a":""})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"a":""})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("object to zero") | |
| { | |
| json original = R"({"a":{"b":"c"}})"_json; | |
| json result = R"({"a":0})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"a":0})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("object to empty object") | |
| { | |
| json original = R"({"a":{"b":"c"}})"_json; | |
| json result = R"({"a":{}})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"a":{"b":null}})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| } | |
| SECTION("arrays") | |
| { | |
| SECTION("array to array") | |
| { | |
| json original = R"([1,2,3])"_json; | |
| json result = R"([1,2,4])"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"([1,2,4])"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("array to empty array") | |
| { | |
| json original = R"([1,2,3])"_json; | |
| json result = R"([])"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"([])"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("empty array to array") | |
| { | |
| json original = R"([])"_json; | |
| json result = R"([1,2,3])"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"([1,2,3])"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("object to empty array") | |
| { | |
| json original = R"({"a":{"b":"c"}})"_json; | |
| json result = R"({"a":[]})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"a":[]})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("same keys") | |
| { | |
| json original = R"(["a","b"])"_json; | |
| json result = R"({"a":1,"b":2})"_json; | |
| auto patch = json::merge_diff(original, result); | |
| CHECK(patch == R"({"a":1,"b":2})"_json); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| } | |
| SECTION("ordered_json") | |
| { | |
| SECTION("changed value keeps member order") | |
| { | |
| auto original = ordered_json::parse(R"({"c":1,"b":2,"a":3})"); | |
| auto result = ordered_json::parse(R"({"c":1,"b":5,"a":3})"); | |
| auto patch = ordered_json::merge_diff(original, result); | |
| CHECK(patch.dump() == R"({"b":5})"); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| CHECK(original.dump() == R"({"c":1,"b":5,"a":3})"); | |
| } | |
| SECTION("added members follow target order") | |
| { | |
| auto original = ordered_json::parse(R"({"b":1})"); | |
| auto result = ordered_json::parse(R"({"b":1,"z":2,"a":3})"); | |
| auto patch = ordered_json::merge_diff(original, result); | |
| CHECK(patch.dump() == R"({"z":2,"a":3})"); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| CHECK(original.dump() == R"({"b":1,"z":2,"a":3})"); | |
| } | |
| SECTION("removed members follow source order") | |
| { | |
| auto original = ordered_json::parse(R"({"c":1,"b":2,"a":3})"); | |
| auto result = ordered_json::parse(R"({"b":2})"); | |
| auto patch = ordered_json::merge_diff(original, result); | |
| CHECK(patch.dump() == R"({"c":null,"a":null})"); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| } | |
| SECTION("nested objects keep member order") | |
| { | |
| auto original = ordered_json::parse(R"({"z":{"y":1,"x":2},"a":0})"); | |
| auto result = ordered_json::parse(R"({"z":{"y":1,"x":3,"w":4},"a":0})"); | |
| auto patch = ordered_json::merge_diff(original, result); | |
| CHECK(patch.dump() == R"({"z":{"x":3,"w":4}})"); | |
| original.merge_patch(patch); | |
| CHECK(original == result); | |
| CHECK(original.dump() == R"({"z":{"y":1,"x":3,"w":4},"a":0})"); | |
| } | |
| SECTION("reordered members cannot be expressed") | |
| { | |
| auto original = ordered_json::parse(R"({"a":1,"b":2})"); | |
| auto result = ordered_json::parse(R"({"b":2,"a":1})"); | |
| auto patch = ordered_json::merge_diff(original, result); | |
| CHECK(patch.empty()); | |
| original.merge_patch(patch); | |
| CHECK(original != result); | |
| CHECK(original.dump() == R"({"a":1,"b":2})"); | |
| } | |
| SECTION("member added before existing ones is appended") | |
| { | |
| auto original = ordered_json::parse(R"({"b":1})"); | |
| auto result = ordered_json::parse(R"({"a":0,"b":1})"); | |
| auto patch = ordered_json::merge_diff(original, result); | |
| CHECK(patch.dump() == R"({"a":0})"); | |
| original.merge_patch(patch); | |
| CHECK(original != result); | |
| CHECK(original.dump() == R"({"b":1,"a":0})"); | |
| } | |
| } | |
| } | |
| TEST_CASE("JSON Merge Patch diff of deeply nested values") | |
| { | |
| SECTION("the patch is the same past the descent bound") | |
| { | |
| // depths on either side of the nesting depth up to which merge_diff() | |
| // recurses (detail::recursion_depth_limit(), 128); not every depth up | |
| // to 300, to keep the test fast under Valgrind | |
| std::vector<std::size_t> depths; | |
| for (std::size_t depth = 0; depth <= 16; ++depth) | |
| { | |
| depths.push_back(depth); | |
| } | |
| for (std::size_t depth = 120; depth <= 136; ++depth) | |
| { | |
| depths.push_back(depth); | |
| } | |
| depths.push_back(300); | |
| for (const auto depth : depths) | |
| { | |
| CAPTURE(depth) | |
| for (int from = 0; from < 3; ++from) | |
| { | |
| for (int to = 0; to < 3; ++to) | |
| { | |
| CAPTURE(from) | |
| CAPTURE(to) | |
| const auto source = nested_objects<json>(depth, from); | |
| const auto target = nested_objects<json>(depth, to); | |
| const auto patch = json::merge_diff(source, target); | |
| CHECK(patch == reference_merge_diff(source, target)); | |
| CHECK(patch.empty() == (from == to)); | |
| // a merge patch cannot set a member to null, so it only | |
| // reproduces a target without null members | |
| if (to != 2) | |
| { | |
| json patched = source; | |
| patched.merge_patch(patch); | |
| CHECK(patched == target); | |
| } | |
| // the members of the patch also come in the same order | |
| const auto ordered_source = nested_objects<ordered_json>(depth, from); | |
| const auto ordered_target = nested_objects<ordered_json>(depth, to); | |
| CHECK(ordered_json::merge_diff(ordered_source, ordered_target).dump() == reference_merge_diff(ordered_source, ordered_target).dump()); | |
| } | |
| } | |
| } | |
| } | |
| SECTION("a difference only in the innermost value is a patch nested as deeply") | |
| { | |
| for (std::size_t depth = 0; depth <= 300; ++depth) | |
| { | |
| CAPTURE(depth) | |
| json source = {{"v", 1}}; | |
| json target = {{"v", 2}}; | |
| json expected = {{"v", 2}}; | |
| for (std::size_t i = 0; i < depth; ++i) | |
| { | |
| source = json{{"a", std::move(source)}, {"b", i}}; | |
| target = json{{"a", std::move(target)}, {"b", i}}; | |
| expected = json{{"a", std::move(expected)}}; | |
| } | |
| CHECK(json::merge_diff(source, target) == expected); | |
| } | |
| } | |
| SECTION("values nested too deeply for the call stack") | |
| { | |
| // recursing once per nesting level would exhaust the call stack here. | |
| // The patch is walked rather than compared as a whole, to keep the | |
| // test fast. | |
| const std::size_t depth = 100000; | |
| std::string opening; | |
| for (std::size_t i = 0; i < depth; ++i) | |
| { | |
| opening += R"({"a":)"; | |
| } | |
| const std::string closing(depth, '}'); | |
| const auto source = json::parse(opening + R"({"v":1,"w":0})" + closing); | |
| const auto patch = json::merge_diff(source, json::parse(opening + R"({"v":2,"w":0})" + closing)); | |
| std::size_t members = 0; | |
| const json* p = &patch; | |
| for (std::size_t i = 0; i < depth; ++i) | |
| { | |
| members += p->size(); | |
| p = &p->at("a"); | |
| } | |
| CHECK(members == depth); | |
| CHECK(*p == json({{"v", 2}})); | |
| CHECK(json::merge_diff(source, json::parse(opening + R"({"v":1,"w":0})" + closing)).empty()); | |
| } | |
| } |