blob: 4d4be14aef822b91cb19c49d634198901c13fc53 [file] [log] [blame] [edit]
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++ (supporting code)
// | | |__ | | | | | | version 3.11.3
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2023 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"]);
// 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&);
// 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(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);
}
}
}