summaryrefslogtreecommitdiff
path: root/json4cpp/tests/src/unit-json_pointer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'json4cpp/tests/src/unit-json_pointer.cpp')
-rw-r--r--json4cpp/tests/src/unit-json_pointer.cpp805
1 files changed, 805 insertions, 0 deletions
diff --git a/json4cpp/tests/src/unit-json_pointer.cpp b/json4cpp/tests/src/unit-json_pointer.cpp
new file mode 100644
index 0000000000..e1522c9f73
--- /dev/null
+++ b/json4cpp/tests/src/unit-json_pointer.cpp
@@ -0,0 +1,805 @@
+// __ _____ _____ _____
+// __| | __| | | | 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"
+
+#define JSON_TESTS_PRIVATE
+#include <nlohmann/json.hpp>
+using nlohmann::json;
+#ifdef JSON_TEST_NO_GLOBAL_UDLS
+ using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
+#endif
+
+#include <map>
+#include <sstream>
+
+TEST_CASE("JSON pointers")
+{
+ SECTION("errors")
+ {
+ CHECK_THROWS_WITH_AS(json::json_pointer("foo"),
+ "[json.exception.parse_error.107] parse error at byte 1: JSON pointer must be empty or begin with '/' - was: 'foo'", json::parse_error&);
+
+ CHECK_THROWS_WITH_AS(json::json_pointer("/~~"),
+ "[json.exception.parse_error.108] parse error: escape character '~' must be followed with '0' or '1'", json::parse_error&);
+
+ CHECK_THROWS_WITH_AS(json::json_pointer("/~"),
+ "[json.exception.parse_error.108] parse error: escape character '~' must be followed with '0' or '1'", json::parse_error&);
+
+ json::json_pointer p;
+ CHECK_THROWS_WITH_AS(p.top(),
+ "[json.exception.out_of_range.405] JSON pointer has no parent", json::out_of_range&);
+ CHECK_THROWS_WITH_AS(p.pop_back(),
+ "[json.exception.out_of_range.405] JSON pointer has no parent", json::out_of_range&);
+
+ SECTION("array index error")
+ {
+ json v = {1, 2, 3, 4};
+ json::json_pointer const ptr("/10e");
+ CHECK_THROWS_WITH_AS(v[ptr],
+ "[json.exception.out_of_range.404] unresolved reference token '10e'", json::out_of_range&);
+ }
+ }
+
+ SECTION("examples from RFC 6901")
+ {
+ SECTION("nonconst access")
+ {
+ json j = R"(
+ {
+ "foo": ["bar", "baz"],
+ "": 0,
+ "a/b": 1,
+ "c%d": 2,
+ "e^f": 3,
+ "g|h": 4,
+ "i\\j": 5,
+ "k\"l": 6,
+ " ": 7,
+ "m~n": 8
+ }
+ )"_json;
+
+ // the whole document
+ CHECK(j[json::json_pointer()] == j);
+ CHECK(j[json::json_pointer("")] == j);
+ CHECK(j.contains(json::json_pointer()));
+ CHECK(j.contains(json::json_pointer("")));
+
+ // array access
+ CHECK(j[json::json_pointer("/foo")] == j["foo"]);
+ CHECK(j.contains(json::json_pointer("/foo")));
+ CHECK(j[json::json_pointer("/foo/0")] == j["foo"][0]);
+ CHECK(j[json::json_pointer("/foo/1")] == j["foo"][1]);
+ CHECK(j["/foo/1"_json_pointer] == j["foo"][1]);
+ CHECK(j.contains(json::json_pointer("/foo/0")));
+ CHECK(j.contains(json::json_pointer("/foo/1")));
+ CHECK(!j.contains(json::json_pointer("/foo/3")));
+ CHECK(!j.contains(json::json_pointer("/foo/+")));
+ CHECK(!j.contains(json::json_pointer("/foo/1+2")));
+ CHECK(!j.contains(json::json_pointer("/foo/-")));
+
+ // checked array access
+ CHECK(j.at(json::json_pointer("/foo/0")) == j["foo"][0]);
+ CHECK(j.at(json::json_pointer("/foo/1")) == j["foo"][1]);
+
+ // empty string access
+ CHECK(j[json::json_pointer("/")] == j[""]);
+ CHECK(j.contains(json::json_pointer("")));
+ CHECK(j.contains(json::json_pointer("/")));
+
+ // other cases
+ CHECK(j[json::json_pointer("/ ")] == j[" "]);
+ CHECK(j[json::json_pointer("/c%d")] == j["c%d"]);
+ CHECK(j[json::json_pointer("/e^f")] == j["e^f"]);
+ CHECK(j[json::json_pointer("/g|h")] == j["g|h"]);
+ CHECK(j[json::json_pointer("/i\\j")] == j["i\\j"]);
+ CHECK(j[json::json_pointer("/k\"l")] == j["k\"l"]);
+
+ // contains
+ CHECK(j.contains(json::json_pointer("/ ")));
+ CHECK(j.contains(json::json_pointer("/c%d")));
+ CHECK(j.contains(json::json_pointer("/e^f")));
+ CHECK(j.contains(json::json_pointer("/g|h")));
+ CHECK(j.contains(json::json_pointer("/i\\j")));
+ CHECK(j.contains(json::json_pointer("/k\"l")));
+
+ // checked access
+ CHECK(j.at(json::json_pointer("/ ")) == j[" "]);
+ CHECK(j.at(json::json_pointer("/c%d")) == j["c%d"]);
+ CHECK(j.at(json::json_pointer("/e^f")) == j["e^f"]);
+ CHECK(j.at(json::json_pointer("/g|h")) == j["g|h"]);
+ CHECK(j.at(json::json_pointer("/i\\j")) == j["i\\j"]);
+ CHECK(j.at(json::json_pointer("/k\"l")) == j["k\"l"]);
+
+ // escaped access
+ CHECK(j[json::json_pointer("/a~1b")] == j["a/b"]);
+ CHECK(j[json::json_pointer("/m~0n")] == j["m~n"]);
+ CHECK(j.contains(json::json_pointer("/a~1b")));
+ CHECK(j.contains(json::json_pointer("/m~0n")));
+
+ // unescaped access to nonexisting values yield object creation
+ CHECK(!j.contains(json::json_pointer("/a/b")));
+ CHECK_NOTHROW(j[json::json_pointer("/a/b")] = 42);
+ CHECK(j.contains(json::json_pointer("/a/b")));
+ CHECK(j["a"]["b"] == json(42));
+
+ CHECK(!j.contains(json::json_pointer("/a/c/1")));
+ CHECK_NOTHROW(j[json::json_pointer("/a/c/1")] = 42);
+ CHECK(j["a"]["c"] == json({nullptr, 42}));
+ CHECK(j.contains(json::json_pointer("/a/c/1")));
+
+ CHECK(!j.contains(json::json_pointer("/a/d/-")));
+ CHECK_NOTHROW(j[json::json_pointer("/a/d/-")] = 42);
+ CHECK(!j.contains(json::json_pointer("/a/d/-")));
+ CHECK(j["a"]["d"] == json::array({42}));
+ // "/a/b" works for JSON {"a": {"b": 42}}
+ CHECK(json({{"a", {{"b", 42}}}})[json::json_pointer("/a/b")] == json(42));
+
+ // unresolved access
+ json j_primitive = 1;
+ CHECK_THROWS_WITH_AS(j_primitive["/foo"_json_pointer],
+ "[json.exception.out_of_range.404] unresolved reference token 'foo'", json::out_of_range&);
+ CHECK_THROWS_WITH_AS(j_primitive.at("/foo"_json_pointer),
+ "[json.exception.out_of_range.404] unresolved reference token 'foo'", json::out_of_range&);
+ CHECK(!j_primitive.contains(json::json_pointer("/foo")));
+ }
+
+ SECTION("const access")
+ {
+ const json j = R"(
+ {
+ "foo": ["bar", "baz"],
+ "": 0,
+ "a/b": 1,
+ "c%d": 2,
+ "e^f": 3,
+ "g|h": 4,
+ "i\\j": 5,
+ "k\"l": 6,
+ " ": 7,
+ "m~n": 8
+ }
+ )"_json;
+
+ // the whole document
+ CHECK(j[json::json_pointer()] == j);
+ CHECK(j[json::json_pointer("")] == j);
+
+ // array access
+ CHECK(j[json::json_pointer("/foo")] == j["foo"]);
+ CHECK(j[json::json_pointer("/foo/0")] == j["foo"][0]);
+ CHECK(j[json::json_pointer("/foo/1")] == j["foo"][1]);
+ CHECK(j["/foo/1"_json_pointer] == j["foo"][1]);
+
+ // checked array access
+ CHECK(j.at(json::json_pointer("/foo/0")) == j["foo"][0]);
+ CHECK(j.at(json::json_pointer("/foo/1")) == j["foo"][1]);
+
+ // empty string access
+ CHECK(j[json::json_pointer("/")] == j[""]);
+
+ // other cases
+ CHECK(j[json::json_pointer("/ ")] == j[" "]);
+ CHECK(j[json::json_pointer("/c%d")] == j["c%d"]);
+ CHECK(j[json::json_pointer("/e^f")] == j["e^f"]);
+ CHECK(j[json::json_pointer("/g|h")] == j["g|h"]);
+ CHECK(j[json::json_pointer("/i\\j")] == j["i\\j"]);
+ CHECK(j[json::json_pointer("/k\"l")] == j["k\"l"]);
+
+ // checked access
+ CHECK(j.at(json::json_pointer("/ ")) == j[" "]);
+ CHECK(j.at(json::json_pointer("/c%d")) == j["c%d"]);
+ CHECK(j.at(json::json_pointer("/e^f")) == j["e^f"]);
+ CHECK(j.at(json::json_pointer("/g|h")) == j["g|h"]);
+ CHECK(j.at(json::json_pointer("/i\\j")) == j["i\\j"]);
+ CHECK(j.at(json::json_pointer("/k\"l")) == j["k\"l"]);
+
+ // escaped access
+ CHECK(j[json::json_pointer("/a~1b")] == j["a/b"]);
+ CHECK(j[json::json_pointer("/m~0n")] == j["m~n"]);
+#if JSON_DIAGNOSTIC_POSITIONS
+ // unescaped access
+ CHECK_THROWS_WITH_AS(j.at(json::json_pointer("/a/b")),
+ "[json.exception.out_of_range.403] (bytes 13-297) key 'a' not found", json::out_of_range&);
+#else
+ // unescaped access
+ CHECK_THROWS_WITH_AS(j.at(json::json_pointer("/a/b")),
+ "[json.exception.out_of_range.403] key 'a' not found", json::out_of_range&);
+#endif
+ // unresolved access
+ const json j_primitive = 1;
+ CHECK_THROWS_WITH_AS(j_primitive["/foo"_json_pointer],
+ "[json.exception.out_of_range.404] unresolved reference token 'foo'", json::out_of_range&);
+ CHECK_THROWS_WITH_AS(j_primitive.at("/foo"_json_pointer),
+ "[json.exception.out_of_range.404] unresolved reference token 'foo'", json::out_of_range&);
+ }
+
+ SECTION("user-defined string literal")
+ {
+ json j = R"(
+ {
+ "foo": ["bar", "baz"],
+ "": 0,
+ "a/b": 1,
+ "c%d": 2,
+ "e^f": 3,
+ "g|h": 4,
+ "i\\j": 5,
+ "k\"l": 6,
+ " ": 7,
+ "m~n": 8
+ }
+ )"_json;
+
+ // the whole document
+ CHECK(j[""_json_pointer] == j);
+ CHECK(j.contains(""_json_pointer));
+
+ // array access
+ CHECK(j["/foo"_json_pointer] == j["foo"]);
+ CHECK(j["/foo/0"_json_pointer] == j["foo"][0]);
+ CHECK(j["/foo/1"_json_pointer] == j["foo"][1]);
+ CHECK(j.contains("/foo"_json_pointer));
+ CHECK(j.contains("/foo/0"_json_pointer));
+ CHECK(j.contains("/foo/1"_json_pointer));
+ CHECK(!j.contains("/foo/-"_json_pointer));
+ }
+ }
+
+ SECTION("array access")
+ {
+ SECTION("nonconst access")
+ {
+ json j = {1, 2, 3};
+ const json j_const = j;
+
+ // check reading access
+ CHECK(j["/0"_json_pointer] == j[0]);
+ CHECK(j["/1"_json_pointer] == j[1]);
+ CHECK(j["/2"_json_pointer] == j[2]);
+
+ // assign to existing index
+ j["/1"_json_pointer] = 13;
+ CHECK(j[1] == json(13));
+
+ // assign to nonexisting index
+ j["/3"_json_pointer] = 33;
+ CHECK(j[3] == json(33));
+
+ // assign to nonexisting index (with gap)
+ j["/5"_json_pointer] = 55;
+ CHECK(j == json({1, 13, 3, 33, nullptr, 55}));
+
+ // error with leading 0
+ CHECK_THROWS_WITH_AS(j["/01"_json_pointer],
+ "[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(j_const["/01"_json_pointer],
+ "[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(j.at("/01"_json_pointer),
+ "[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(j_const.at("/01"_json_pointer),
+ "[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'", json::parse_error&);
+
+ CHECK(!j.contains("/01"_json_pointer));
+ CHECK(!j.contains("/01"_json_pointer));
+ CHECK(!j_const.contains("/01"_json_pointer));
+ CHECK(!j_const.contains("/01"_json_pointer));
+
+ // error with incorrect numbers
+ CHECK_THROWS_WITH_AS(j["/one"_json_pointer] = 1,
+ "[json.exception.parse_error.109] parse error: array index 'one' is not a number", json::parse_error&);
+ CHECK_THROWS_WITH_AS(j_const["/one"_json_pointer] == 1,
+ "[json.exception.parse_error.109] parse error: array index 'one' is not a number", json::parse_error&);
+
+ CHECK_THROWS_WITH_AS(j.at("/one"_json_pointer) = 1,
+ "[json.exception.parse_error.109] parse error: array index 'one' is not a number", json::parse_error&);
+ CHECK_THROWS_WITH_AS(j_const.at("/one"_json_pointer) == 1,
+ "[json.exception.parse_error.109] parse error: array index 'one' is not a number", json::parse_error&);
+
+ CHECK_THROWS_WITH_AS(j["/+1"_json_pointer] = 1,
+ "[json.exception.parse_error.109] parse error: array index '+1' is not a number", json::parse_error&);
+ CHECK_THROWS_WITH_AS(j_const["/+1"_json_pointer] == 1,
+ "[json.exception.parse_error.109] parse error: array index '+1' is not a number", json::parse_error&);
+
+ CHECK_THROWS_WITH_AS(j["/1+1"_json_pointer] = 1,
+ "[json.exception.out_of_range.404] unresolved reference token '1+1'", json::out_of_range&);
+ CHECK_THROWS_WITH_AS(j_const["/1+1"_json_pointer] == 1,
+ "[json.exception.out_of_range.404] unresolved reference token '1+1'", json::out_of_range&);
+
+ {
+ auto too_large_index = std::to_string((std::numeric_limits<unsigned long long>::max)()) + "1";
+ json::json_pointer const jp(std::string("/") + too_large_index);
+ std::string const throw_msg = std::string("[json.exception.out_of_range.404] unresolved reference token '") + too_large_index + "'";
+
+ CHECK_THROWS_WITH_AS(j[jp] = 1, throw_msg.c_str(), json::out_of_range&);
+ CHECK_THROWS_WITH_AS(j_const[jp] == 1, throw_msg.c_str(), json::out_of_range&);
+ }
+
+ // on some machines, the check below is not constant
+ DOCTEST_MSVC_SUPPRESS_WARNING_PUSH
+ DOCTEST_MSVC_SUPPRESS_WARNING(4127)
+
+ if (sizeof(typename json::size_type) < sizeof(unsigned long long))
+ {
+ auto size_type_max_uul = static_cast<unsigned long long>((std::numeric_limits<json::size_type>::max)());
+ auto too_large_index = std::to_string(size_type_max_uul);
+ json::json_pointer const jp(std::string("/") + too_large_index);
+ std::string const throw_msg = std::string("[json.exception.out_of_range.410] array index ") + too_large_index + " exceeds size_type";
+
+ CHECK_THROWS_WITH_AS(j[jp] = 1, throw_msg.c_str(), json::out_of_range&);
+ CHECK_THROWS_WITH_AS(j_const[jp] == 1, throw_msg.c_str(), json::out_of_range&);
+ }
+
+ DOCTEST_MSVC_SUPPRESS_WARNING_POP
+
+ CHECK_THROWS_WITH_AS(j.at("/one"_json_pointer) = 1,
+ "[json.exception.parse_error.109] parse error: array index 'one' is not a number", json::parse_error&);
+ CHECK_THROWS_WITH_AS(j_const.at("/one"_json_pointer) == 1,
+ "[json.exception.parse_error.109] parse error: array index 'one' is not a number", json::parse_error&);
+
+ CHECK(!j.contains("/one"_json_pointer));
+ CHECK(!j.contains("/one"_json_pointer));
+ CHECK(!j_const.contains("/one"_json_pointer));
+ CHECK(!j_const.contains("/one"_json_pointer));
+
+ CHECK_THROWS_WITH_AS(json({{"/list/0", 1}, {"/list/1", 2}, {"/list/three", 3}}).unflatten(),
+ "[json.exception.parse_error.109] parse error: array index 'three' is not a number", json::parse_error&);
+
+ // assign to "-"
+ j["/-"_json_pointer] = 99;
+ CHECK(j == json({1, 13, 3, 33, nullptr, 55, 99}));
+
+ // error when using "-" in const object
+ CHECK_THROWS_WITH_AS(j_const["/-"_json_pointer],
+ "[json.exception.out_of_range.402] array index '-' (3) is out of range", json::out_of_range&);
+ CHECK(!j_const.contains("/-"_json_pointer));
+
+ // error when using "-" with at
+ CHECK_THROWS_WITH_AS(j.at("/-"_json_pointer),
+ "[json.exception.out_of_range.402] array index '-' (7) is out of range", json::out_of_range&);
+ CHECK_THROWS_WITH_AS(j_const.at("/-"_json_pointer),
+ "[json.exception.out_of_range.402] array index '-' (3) is out of range", json::out_of_range&);
+ CHECK(!j_const.contains("/-"_json_pointer));
+ }
+
+ SECTION("const access")
+ {
+ const json j = {1, 2, 3};
+
+ // check reading access
+ CHECK(j["/0"_json_pointer] == j[0]);
+ CHECK(j["/1"_json_pointer] == j[1]);
+ CHECK(j["/2"_json_pointer] == j[2]);
+
+ // assign to nonexisting index
+ CHECK_THROWS_WITH_AS(j.at("/3"_json_pointer),
+ "[json.exception.out_of_range.401] array index 3 is out of range", json::out_of_range&);
+ CHECK(!j.contains("/3"_json_pointer));
+
+ // assign to nonexisting index (with gap)
+ CHECK_THROWS_WITH_AS(j.at("/5"_json_pointer),
+ "[json.exception.out_of_range.401] array index 5 is out of range", json::out_of_range&);
+ CHECK(!j.contains("/5"_json_pointer));
+
+ // assign to "-"
+ CHECK_THROWS_WITH_AS(j["/-"_json_pointer],
+ "[json.exception.out_of_range.402] array index '-' (3) is out of range", json::out_of_range&);
+ CHECK_THROWS_WITH_AS(j.at("/-"_json_pointer),
+ "[json.exception.out_of_range.402] array index '-' (3) is out of range", json::out_of_range&);
+ CHECK(!j.contains("/-"_json_pointer));
+ }
+ }
+
+ SECTION("flatten")
+ {
+ json j =
+ {
+ {"pi", 3.141},
+ {"happy", true},
+ {"name", "Niels"},
+ {"nothing", nullptr},
+ {
+ "answer", {
+ {"everything", 42}
+ }
+ },
+ {"list", {1, 0, 2}},
+ {
+ "object", {
+ {"currency", "USD"},
+ {"value", 42.99},
+ {"", "empty string"},
+ {"/", "slash"},
+ {"~", "tilde"},
+ {"~1", "tilde1"}
+ }
+ }
+ };
+
+ json j_flatten =
+ {
+ {"/pi", 3.141},
+ {"/happy", true},
+ {"/name", "Niels"},
+ {"/nothing", nullptr},
+ {"/answer/everything", 42},
+ {"/list/0", 1},
+ {"/list/1", 0},
+ {"/list/2", 2},
+ {"/object/currency", "USD"},
+ {"/object/value", 42.99},
+ {"/object/", "empty string"},
+ {"/object/~1", "slash"},
+ {"/object/~0", "tilde"},
+ {"/object/~01", "tilde1"}
+ };
+
+ // check if flattened result is as expected
+ CHECK(j.flatten() == j_flatten);
+
+ // check if unflattened result is as expected
+ CHECK(j_flatten.unflatten() == j);
+
+ // error for nonobjects
+ CHECK_THROWS_WITH_AS(json(1).unflatten(),
+ "[json.exception.type_error.314] only objects can be unflattened", json::type_error&);
+
+ // error for nonprimitve values
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(json({{"/1", {1, 2, 3}}}).unflatten(), "[json.exception.type_error.315] (/~11) values in object must be primitive", json::type_error&);
+#else
+ CHECK_THROWS_WITH_AS(json({{"/1", {1, 2, 3}}}).unflatten(), "[json.exception.type_error.315] values in object must be primitive", json::type_error&);
+#endif
+
+ // error for conflicting values
+ json const j_error = {{"", 42}, {"/foo", 17}};
+ CHECK_THROWS_WITH_AS(j_error.unflatten(),
+ "[json.exception.type_error.313] invalid value to unflatten", json::type_error&);
+
+ // explicit roundtrip check
+ CHECK(j.flatten().unflatten() == j);
+
+ // roundtrip for primitive values
+ json j_null;
+ CHECK(j_null.flatten().unflatten() == j_null);
+ json j_number = 42;
+ CHECK(j_number.flatten().unflatten() == j_number);
+ json j_boolean = false;
+ CHECK(j_boolean.flatten().unflatten() == j_boolean);
+ json j_string = "foo";
+ CHECK(j_string.flatten().unflatten() == j_string);
+
+ // roundtrip for empty structured values (will be unflattened to null)
+ json const j_array(json::value_t::array);
+ CHECK(j_array.flatten().unflatten() == json());
+ json const j_object(json::value_t::object);
+ CHECK(j_object.flatten().unflatten() == json());
+ }
+
+ SECTION("string representation")
+ {
+ for (const auto* ptr_str :
+ {"", "/foo", "/foo/0", "/", "/a~1b", "/c%d", "/e^f", "/g|h", "/i\\j", "/k\"l", "/ ", "/m~0n"
+ })
+ {
+ json::json_pointer const ptr(ptr_str);
+ std::stringstream ss;
+ ss << ptr;
+ CHECK(ptr.to_string() == ptr_str);
+ CHECK(std::string(ptr) == ptr_str);
+ CHECK(ss.str() == ptr_str);
+ }
+ }
+
+ SECTION("conversion")
+ {
+ SECTION("array")
+ {
+ json j;
+ // all numbers -> array
+ j["/12"_json_pointer] = 0;
+ CHECK(j.is_array());
+ }
+
+ SECTION("object")
+ {
+ json j;
+ // contains a number, but is not a number -> object
+ j["/a12"_json_pointer] = 0;
+ CHECK(j.is_object());
+ }
+ }
+
+ SECTION("empty, push, pop and parent")
+ {
+ const json j =
+ {
+ {"", "Hello"},
+ {"pi", 3.141},
+ {"happy", true},
+ {"name", "Niels"},
+ {"nothing", nullptr},
+ {
+ "answer", {
+ {"everything", 42}
+ }
+ },
+ {"list", {1, 0, 2}},
+ {
+ "object", {
+ {"currency", "USD"},
+ {"value", 42.99},
+ {"", "empty string"},
+ {"/", "slash"},
+ {"~", "tilde"},
+ {"~1", "tilde1"}
+ }
+ }
+ };
+
+ // empty json_pointer returns the root JSON-object
+ auto ptr = ""_json_pointer;
+ CHECK(ptr.empty());
+ CHECK(j[ptr] == j);
+
+ // simple field access
+ ptr.push_back("pi");
+ CHECK(!ptr.empty());
+ CHECK(j[ptr] == j["pi"]);
+
+ ptr.pop_back();
+ CHECK(ptr.empty());
+ CHECK(j[ptr] == j);
+
+ // object and children access
+ const std::string answer("answer");
+ ptr.push_back(answer);
+ ptr.push_back("everything");
+ CHECK(!ptr.empty());
+ CHECK(j[ptr] == j["answer"]["everything"]);
+
+ // check access via const pointer
+ const auto cptr = ptr;
+ CHECK(cptr.back() == "everything");
+
+ ptr.pop_back();
+ ptr.pop_back();
+ CHECK(ptr.empty());
+ CHECK(j[ptr] == j);
+
+ // push key which has to be encoded
+ ptr.push_back("object");
+ ptr.push_back("/");
+ CHECK(j[ptr] == j["object"]["/"]);
+ CHECK(ptr.to_string() == "/object/~1");
+
+ CHECK(j[ptr.parent_pointer()] == j["object"]);
+ ptr = ptr.parent_pointer().parent_pointer();
+ CHECK(ptr.empty());
+ CHECK(j[ptr] == j);
+ // parent-pointer of the empty json_pointer is empty
+ ptr = ptr.parent_pointer();
+ CHECK(ptr.empty());
+ CHECK(j[ptr] == j);
+
+ CHECK_THROWS_WITH(ptr.pop_back(),
+ "[json.exception.out_of_range.405] JSON pointer has no parent");
+ }
+
+ SECTION("operators")
+ {
+ const json j =
+ {
+ {"", "Hello"},
+ {"pi", 3.141},
+ {"happy", true},
+ {"name", "Niels"},
+ {"nothing", nullptr},
+ {
+ "answer", {
+ {"everything", 42}
+ }
+ },
+ {"list", {1, 0, 2}},
+ {
+ "object", {
+ {"currency", "USD"},
+ {"value", 42.99},
+ {"", "empty string"},
+ {"/", "slash"},
+ {"~", "tilde"},
+ {"~1", "tilde1"}
+ }
+ }
+ };
+
+ // empty json_pointer returns the root JSON-object
+ auto ptr = ""_json_pointer;
+ CHECK(j[ptr] == j);
+
+ // simple field access
+ ptr = ptr / "pi";
+ CHECK(j[ptr] == j["pi"]);
+
+ ptr.pop_back();
+ CHECK(j[ptr] == j);
+
+ // object and children access
+ const std::string answer("answer");
+ ptr /= answer;
+ ptr = ptr / "everything";
+ CHECK(j[ptr] == j["answer"]["everything"]);
+
+ ptr.pop_back();
+ ptr.pop_back();
+ CHECK(j[ptr] == j);
+
+ CHECK(ptr / ""_json_pointer == ptr);
+ CHECK(j["/answer"_json_pointer / "/everything"_json_pointer] == j["answer"]["everything"]);
+
+ // list children access
+ CHECK(j["/list"_json_pointer / 1] == j["list"][1]);
+
+ // push key which has to be encoded
+ ptr /= "object";
+ ptr = ptr / "/";
+ CHECK(j[ptr] == j["object"]["/"]);
+ CHECK(ptr.to_string() == "/object/~1");
+ }
+
+ SECTION("equality comparison")
+ {
+ const char* ptr_cpstring = "/foo/bar";
+ const char ptr_castring[] = "/foo/bar"; // NOLINT(misc-const-correctness,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-avoid-c-arrays)
+ std::string ptr_string{"/foo/bar"};
+ auto ptr1 = json::json_pointer(ptr_string);
+ auto ptr2 = json::json_pointer(ptr_string);
+
+ // build with C++20 to test rewritten candidates
+ // JSON_HAS_CPP_20
+
+ CHECK(ptr1 == ptr2);
+
+ CHECK(ptr1 == "/foo/bar");
+ CHECK(ptr1 == ptr_cpstring);
+ CHECK(ptr1 == ptr_castring);
+ CHECK(ptr1 == ptr_string);
+
+ CHECK("/foo/bar" == ptr1);
+ CHECK(ptr_cpstring == ptr1);
+ CHECK(ptr_castring == ptr1);
+ CHECK(ptr_string == ptr1);
+
+ CHECK_FALSE(ptr1 != ptr2);
+
+ CHECK_FALSE(ptr1 != "/foo/bar");
+ CHECK_FALSE(ptr1 != ptr_cpstring);
+ CHECK_FALSE(ptr1 != ptr_castring);
+ CHECK_FALSE(ptr1 != ptr_string);
+
+ CHECK_FALSE("/foo/bar" != ptr1);
+ CHECK_FALSE(ptr_cpstring != ptr1);
+ CHECK_FALSE(ptr_castring != ptr1);
+ CHECK_FALSE(ptr_string != ptr1);
+
+ SECTION("exceptions")
+ {
+ CHECK_THROWS_WITH_AS(ptr1 == "foo",
+ "[json.exception.parse_error.107] parse error at byte 1: JSON pointer must be empty or begin with '/' - was: 'foo'", json::parse_error&);
+ CHECK_THROWS_WITH_AS("foo" == ptr1,
+ "[json.exception.parse_error.107] parse error at byte 1: JSON pointer must be empty or begin with '/' - was: 'foo'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(ptr1 == "/~~",
+ "[json.exception.parse_error.108] parse error: escape character '~' must be followed with '0' or '1'", json::parse_error&);
+ CHECK_THROWS_WITH_AS("/~~" == ptr1,
+ "[json.exception.parse_error.108] parse error: escape character '~' must be followed with '0' or '1'", json::parse_error&);
+ }
+ }
+
+ SECTION("less-than comparison")
+ {
+ auto ptr1 = json::json_pointer("/foo/a");
+ auto ptr2 = json::json_pointer("/foo/b");
+
+ CHECK(ptr1 < ptr2);
+ CHECK_FALSE(ptr2 < ptr1);
+
+ // build with C++20
+ // JSON_HAS_CPP_20
+#if JSON_HAS_THREE_WAY_COMPARISON
+ CHECK((ptr1 <=> ptr2) == std::strong_ordering::less); // *NOPAD*
+ CHECK(ptr2 > ptr1);
+#endif
+ }
+
+ SECTION("usable as map key")
+ {
+ auto ptr = json::json_pointer("/foo");
+ std::map<json::json_pointer, int> m;
+
+ m[ptr] = 42;
+
+ CHECK(m.find(ptr) != m.end());
+ }
+
+ SECTION("backwards compatibility and mixing")
+ {
+ json j = R"(
+ {
+ "foo": ["bar", "baz"]
+ }
+ )"_json;
+
+ using nlohmann::ordered_json;
+ using json_ptr_str = nlohmann::json_pointer<std::string>;
+ using json_ptr_j = nlohmann::json_pointer<json>;
+ using json_ptr_oj = nlohmann::json_pointer<ordered_json>;
+
+ CHECK(std::is_same<json_ptr_str::string_t, json::json_pointer::string_t>::value);
+ CHECK(std::is_same<json_ptr_str::string_t, ordered_json::json_pointer::string_t>::value);
+ CHECK(std::is_same<json_ptr_str::string_t, json_ptr_j::string_t>::value);
+ CHECK(std::is_same<json_ptr_str::string_t, json_ptr_oj::string_t>::value);
+
+ std::string const ptr_string{"/foo/0"};
+ json_ptr_str ptr{ptr_string};
+ json_ptr_j ptr_j{ptr_string};
+ json_ptr_oj ptr_oj{ptr_string};
+
+ CHECK(j.contains(ptr));
+ CHECK(j.contains(ptr_j));
+ CHECK(j.contains(ptr_oj));
+
+ CHECK(j.at(ptr) == j.at(ptr_j));
+ CHECK(j.at(ptr) == j.at(ptr_oj));
+
+ CHECK(j[ptr] == j[ptr_j]);
+ CHECK(j[ptr] == j[ptr_oj]);
+
+ CHECK(j.value(ptr, "x") == j.value(ptr_j, "x"));
+ CHECK(j.value(ptr, "x") == j.value(ptr_oj, "x"));
+
+ CHECK(ptr == ptr_j);
+ CHECK(ptr == ptr_oj);
+ CHECK_FALSE(ptr != ptr_j);
+ CHECK_FALSE(ptr != ptr_oj);
+
+ SECTION("equality comparison")
+ {
+ // build with C++20 to test rewritten candidates
+ // JSON_HAS_CPP_20
+
+ CHECK(ptr == ptr_j);
+ CHECK(ptr == ptr_oj);
+ CHECK(ptr_j == ptr);
+ CHECK(ptr_j == ptr_oj);
+ CHECK(ptr_oj == ptr_j);
+ CHECK(ptr_oj == ptr);
+
+ CHECK_FALSE(ptr != ptr_j);
+ CHECK_FALSE(ptr != ptr_oj);
+ CHECK_FALSE(ptr_j != ptr);
+ CHECK_FALSE(ptr_j != ptr_oj);
+ CHECK_FALSE(ptr_oj != ptr_j);
+ CHECK_FALSE(ptr_oj != ptr);
+ }
+ }
+
+ // build with C++20
+ // JSON_HAS_CPP_20
+#if defined(__cpp_char8_t)
+ SECTION("Using _json_pointer with char8_t literals #4945")
+ {
+ const json j = R"({"a": {"b": {"c": 123}}})"_json;
+ const auto p1 = "/a/b/c"_json_pointer;
+ CHECK(j[p1] == 123);
+
+ const auto p2 = u8"/a/b/c"_json_pointer;
+ CHECK(j[p2] == 123);
+ }
+#endif
+}