summaryrefslogtreecommitdiff
path: root/json4cpp/tests/src
diff options
context:
space:
mode:
Diffstat (limited to 'json4cpp/tests/src')
-rw-r--r--json4cpp/tests/src/fuzzer-driver_afl.cpp39
-rw-r--r--json4cpp/tests/src/fuzzer-parse_bjdata.cpp85
-rw-r--r--json4cpp/tests/src/fuzzer-parse_bson.cpp74
-rw-r--r--json4cpp/tests/src/fuzzer-parse_cbor.cpp69
-rw-r--r--json4cpp/tests/src/fuzzer-parse_json.cpp70
-rw-r--r--json4cpp/tests/src/fuzzer-parse_msgpack.cpp69
-rw-r--r--json4cpp/tests/src/fuzzer-parse_ubjson.cpp85
-rw-r--r--json4cpp/tests/src/make_test_data_available.hpp31
-rw-r--r--json4cpp/tests/src/test_utils.hpp33
-rw-r--r--json4cpp/tests/src/unit-32bit.cpp134
-rw-r--r--json4cpp/tests/src/unit-algorithms.cpp365
-rw-r--r--json4cpp/tests/src/unit-allocator.cpp263
-rw-r--r--json4cpp/tests/src/unit-alt-string.cpp370
-rw-r--r--json4cpp/tests/src/unit-assert_macro.cpp48
-rw-r--r--json4cpp/tests/src/unit-binary_formats.cpp211
-rw-r--r--json4cpp/tests/src/unit-bjdata.cpp3838
-rw-r--r--json4cpp/tests/src/unit-bson.cpp1296
-rw-r--r--json4cpp/tests/src/unit-byte_container_with_subtype.cpp76
-rw-r--r--json4cpp/tests/src/unit-capacity.cpp542
-rw-r--r--json4cpp/tests/src/unit-cbor.cpp2780
-rw-r--r--json4cpp/tests/src/unit-class_const_iterator.cpp393
-rw-r--r--json4cpp/tests/src/unit-class_iterator.cpp468
-rw-r--r--json4cpp/tests/src/unit-class_lexer.cpp226
-rw-r--r--json4cpp/tests/src/unit-class_parser.cpp1754
-rw-r--r--json4cpp/tests/src/unit-class_parser_diagnostic_positions.cpp1957
-rw-r--r--json4cpp/tests/src/unit-comparison.cpp596
-rw-r--r--json4cpp/tests/src/unit-concepts.cpp149
-rw-r--r--json4cpp/tests/src/unit-constructor1.cpp1654
-rw-r--r--json4cpp/tests/src/unit-constructor2.cpp186
-rw-r--r--json4cpp/tests/src/unit-convenience.cpp205
-rw-r--r--json4cpp/tests/src/unit-conversions.cpp1753
-rw-r--r--json4cpp/tests/src/unit-custom-base-class.cpp335
-rw-r--r--json4cpp/tests/src/unit-deserialization.cpp1236
-rw-r--r--json4cpp/tests/src/unit-diagnostic-positions-only.cpp44
-rw-r--r--json4cpp/tests/src/unit-diagnostic-positions.cpp40
-rw-r--r--json4cpp/tests/src/unit-diagnostics.cpp265
-rw-r--r--json4cpp/tests/src/unit-disabled_exceptions.cpp51
-rw-r--r--json4cpp/tests/src/unit-element_access1.cpp880
-rw-r--r--json4cpp/tests/src/unit-element_access2.cpp1844
-rw-r--r--json4cpp/tests/src/unit-hash.cpp113
-rw-r--r--json4cpp/tests/src/unit-inspection.cpp459
-rw-r--r--json4cpp/tests/src/unit-items.cpp1433
-rw-r--r--json4cpp/tests/src/unit-iterators1.cpp1630
-rw-r--r--json4cpp/tests/src/unit-iterators2.cpp972
-rw-r--r--json4cpp/tests/src/unit-iterators3.cpp35
-rw-r--r--json4cpp/tests/src/unit-json_patch.cpp1336
-rw-r--r--json4cpp/tests/src/unit-json_pointer.cpp805
-rw-r--r--json4cpp/tests/src/unit-large_json.cpp29
-rw-r--r--json4cpp/tests/src/unit-locale-cpp.cpp166
-rw-r--r--json4cpp/tests/src/unit-merge_patch.cpp244
-rw-r--r--json4cpp/tests/src/unit-meta.cpp36
-rw-r--r--json4cpp/tests/src/unit-modifiers.cpp952
-rw-r--r--json4cpp/tests/src/unit-msgpack.cpp1961
-rw-r--r--json4cpp/tests/src/unit-no-mem-leak-on-adl-serialize.cpp86
-rw-r--r--json4cpp/tests/src/unit-noexcept.cpp83
-rw-r--r--json4cpp/tests/src/unit-ordered_json.cpp71
-rw-r--r--json4cpp/tests/src/unit-ordered_map.cpp310
-rw-r--r--json4cpp/tests/src/unit-pointer_access.cpp479
-rw-r--r--json4cpp/tests/src/unit-readme.cpp304
-rw-r--r--json4cpp/tests/src/unit-reference_access.cpp247
-rw-r--r--json4cpp/tests/src/unit-regression1.cpp1530
-rw-r--r--json4cpp/tests/src/unit-regression2.cpp1206
-rw-r--r--json4cpp/tests/src/unit-serialization.cpp297
-rw-r--r--json4cpp/tests/src/unit-testsuites.cpp1392
-rw-r--r--json4cpp/tests/src/unit-to_chars.cpp516
-rw-r--r--json4cpp/tests/src/unit-type_traits.cpp56
-rw-r--r--json4cpp/tests/src/unit-ubjson.cpp2547
-rw-r--r--json4cpp/tests/src/unit-udl.cpp57
-rw-r--r--json4cpp/tests/src/unit-udt.cpp908
-rw-r--r--json4cpp/tests/src/unit-udt_macro.cpp709
-rw-r--r--json4cpp/tests/src/unit-unicode1.cpp620
-rw-r--r--json4cpp/tests/src/unit-unicode2.cpp610
-rw-r--r--json4cpp/tests/src/unit-unicode3.cpp324
-rw-r--r--json4cpp/tests/src/unit-unicode4.cpp324
-rw-r--r--json4cpp/tests/src/unit-unicode5.cpp324
-rw-r--r--json4cpp/tests/src/unit-user_defined_input.cpp130
-rw-r--r--json4cpp/tests/src/unit-windows_h.cpp23
-rw-r--r--json4cpp/tests/src/unit-wstring.cpp99
-rw-r--r--json4cpp/tests/src/unit.cpp10
79 files changed, 47947 insertions, 0 deletions
diff --git a/json4cpp/tests/src/fuzzer-driver_afl.cpp b/json4cpp/tests/src/fuzzer-driver_afl.cpp
new file mode 100644
index 0000000000..1cde49bd8a
--- /dev/null
+++ b/json4cpp/tests/src/fuzzer-driver_afl.cpp
@@ -0,0 +1,39 @@
+// __ _____ _____ _____
+// __| | __| | | | 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
+
+/*
+This file implements a driver for American Fuzzy Lop (afl-fuzz). It relies on
+an implementation of the `LLVMFuzzerTestOneInput` function which processes a
+passed byte array.
+*/
+
+#include <vector> // for vector
+#include <cstdint> // for uint8_t
+#include <iostream> // for cin
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
+
+int main()
+{
+#ifdef __AFL_HAVE_MANUAL_CONTROL
+ while (__AFL_LOOP(1000))
+ {
+#endif
+ // copy stdin to byte vector
+ std::vector<uint8_t> vec;
+ char c = 0;
+ while (std::cin.get(c))
+ {
+ vec.push_back(static_cast<uint8_t>(c));
+ }
+
+ LLVMFuzzerTestOneInput(vec.data(), vec.size());
+#ifdef __AFL_HAVE_MANUAL_CONTROL
+ }
+#endif
+}
diff --git a/json4cpp/tests/src/fuzzer-parse_bjdata.cpp b/json4cpp/tests/src/fuzzer-parse_bjdata.cpp
new file mode 100644
index 0000000000..1d1d56a5c4
--- /dev/null
+++ b/json4cpp/tests/src/fuzzer-parse_bjdata.cpp
@@ -0,0 +1,85 @@
+// __ _____ _____ _____
+// __| | __| | | | 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
+
+/*
+This file implements a parser test suitable for fuzz testing. Given a byte
+array data, it performs the following steps:
+
+- j1 = from_bjdata(data)
+- vec = to_bjdata(j1)
+- j2 = from_bjdata(vec)
+- assert(j1 == j2)
+- vec2 = to_bjdata(j1, use_size = true, use_type = false)
+- j3 = from_bjdata(vec2)
+- assert(j1 == j3)
+- vec3 = to_bjdata(j1, use_size = true, use_type = true)
+- j4 = from_bjdata(vec3)
+- assert(j1 == j4)
+
+The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
+drivers.
+*/
+
+#include <iostream>
+#include <sstream>
+#include <nlohmann/json.hpp>
+
+using json = nlohmann::json;
+
+// see http://llvm.org/docs/LibFuzzer.html
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ try
+ {
+ // step 1: parse input
+ std::vector<uint8_t> const vec1(data, data + size);
+ json const j1 = json::from_bjdata(vec1);
+
+ try
+ {
+ // step 2.1: round trip without adding size annotations to container types
+ std::vector<uint8_t> const vec2 = json::to_bjdata(j1, false, false);
+
+ // step 2.2: round trip with adding size annotations but without adding type annotations to container types
+ std::vector<uint8_t> const vec3 = json::to_bjdata(j1, true, false);
+
+ // step 2.3: round trip with adding size as well as type annotations to container types
+ std::vector<uint8_t> const vec4 = json::to_bjdata(j1, true, true);
+
+ // parse serialization
+ json const j2 = json::from_bjdata(vec2);
+ json const j3 = json::from_bjdata(vec3);
+ json const j4 = json::from_bjdata(vec4);
+
+ // serializations must match
+ assert(json::to_bjdata(j2, false, false) == vec2);
+ assert(json::to_bjdata(j3, true, false) == vec3);
+ assert(json::to_bjdata(j4, true, true) == vec4);
+ }
+ catch (const json::parse_error&)
+ {
+ // parsing a BJData serialization must not fail
+ assert(false);
+ }
+ }
+ catch (const json::parse_error&)
+ {
+ // parse errors are ok, because input may be random bytes
+ }
+ catch (const json::type_error&)
+ {
+ // type errors can occur during parsing, too
+ }
+ catch (const json::out_of_range&)
+ {
+ // out of range errors may happen if provided sizes are excessive
+ }
+
+ // return 0 - non-zero return values are reserved for future use
+ return 0;
+}
diff --git a/json4cpp/tests/src/fuzzer-parse_bson.cpp b/json4cpp/tests/src/fuzzer-parse_bson.cpp
new file mode 100644
index 0000000000..c5f74c7cc8
--- /dev/null
+++ b/json4cpp/tests/src/fuzzer-parse_bson.cpp
@@ -0,0 +1,74 @@
+// __ _____ _____ _____
+// __| | __| | | | 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
+
+/*
+This file implements a parser test suitable for fuzz testing. Given a byte
+array data, it performs the following steps:
+
+- j1 = from_bson(data)
+- vec = to_bson(j1)
+- j2 = from_bson(vec)
+- assert(j1 == j2)
+
+The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
+drivers.
+*/
+
+#include <iostream>
+#include <sstream>
+#include <nlohmann/json.hpp>
+
+using json = nlohmann::json;
+
+// see http://llvm.org/docs/LibFuzzer.html
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ try
+ {
+ // step 1: parse input
+ std::vector<uint8_t> const vec1(data, data + size);
+ json const j1 = json::from_bson(vec1);
+
+ if (j1.is_discarded())
+ {
+ return 0;
+ }
+
+ try
+ {
+ // step 2: round trip
+ std::vector<uint8_t> const vec2 = json::to_bson(j1);
+
+ // parse serialization
+ json const j2 = json::from_bson(vec2);
+
+ // serializations must match
+ assert(json::to_bson(j2) == vec2);
+ }
+ catch (const json::parse_error&)
+ {
+ // parsing a BSON serialization must not fail
+ assert(false);
+ }
+ }
+ catch (const json::parse_error&)
+ {
+ // parse errors are ok, because input may be random bytes
+ }
+ catch (const json::type_error&)
+ {
+ // type errors can occur during parsing, too
+ }
+ catch (const json::out_of_range&)
+ {
+ // out of range errors can occur during parsing, too
+ }
+
+ // return 0 - non-zero return values are reserved for future use
+ return 0;
+}
diff --git a/json4cpp/tests/src/fuzzer-parse_cbor.cpp b/json4cpp/tests/src/fuzzer-parse_cbor.cpp
new file mode 100644
index 0000000000..b38e3c1e54
--- /dev/null
+++ b/json4cpp/tests/src/fuzzer-parse_cbor.cpp
@@ -0,0 +1,69 @@
+// __ _____ _____ _____
+// __| | __| | | | 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
+
+/*
+This file implements a parser test suitable for fuzz testing. Given a byte
+array data, it performs the following steps:
+
+- j1 = from_cbor(data)
+- vec = to_cbor(j1)
+- j2 = from_cbor(vec)
+- assert(j1 == j2)
+
+The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
+drivers.
+*/
+
+#include <iostream>
+#include <sstream>
+#include <nlohmann/json.hpp>
+
+using json = nlohmann::json;
+
+// see http://llvm.org/docs/LibFuzzer.html
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ try
+ {
+ // step 1: parse input
+ std::vector<uint8_t> const vec1(data, data + size);
+ json const j1 = json::from_cbor(vec1);
+
+ try
+ {
+ // step 2: round trip
+ std::vector<uint8_t> const vec2 = json::to_cbor(j1);
+
+ // parse serialization
+ json const j2 = json::from_cbor(vec2);
+
+ // serializations must match
+ assert(json::to_cbor(j2) == vec2);
+ }
+ catch (const json::parse_error&)
+ {
+ // parsing a CBOR serialization must not fail
+ assert(false);
+ }
+ }
+ catch (const json::parse_error&)
+ {
+ // parse errors are ok, because input may be random bytes
+ }
+ catch (const json::type_error&)
+ {
+ // type errors can occur during parsing, too
+ }
+ catch (const json::out_of_range&)
+ {
+ // out of range errors can occur during parsing, too
+ }
+
+ // return 0 - non-zero return values are reserved for future use
+ return 0;
+}
diff --git a/json4cpp/tests/src/fuzzer-parse_json.cpp b/json4cpp/tests/src/fuzzer-parse_json.cpp
new file mode 100644
index 0000000000..59a278c7bd
--- /dev/null
+++ b/json4cpp/tests/src/fuzzer-parse_json.cpp
@@ -0,0 +1,70 @@
+// __ _____ _____ _____
+// __| | __| | | | 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
+
+/*
+This file implements a parser test suitable for fuzz testing. Given a byte
+array data, it performs the following steps:
+
+- j1 = parse(data)
+- s1 = serialize(j1)
+- j2 = parse(s1)
+- s2 = serialize(j2)
+- assert(s1 == s2)
+
+The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
+drivers.
+*/
+
+#include <iostream>
+#include <sstream>
+#include <nlohmann/json.hpp>
+
+using json = nlohmann::json;
+
+// see http://llvm.org/docs/LibFuzzer.html
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ try
+ {
+ // step 1: parse input
+ json const j1 = json::parse(data, data + size);
+
+ try
+ {
+ // step 2: round trip
+
+ // first serialization
+ std::string const s1 = j1.dump();
+
+ // parse serialization
+ json const j2 = json::parse(s1);
+
+ // second serialization
+ std::string const s2 = j2.dump();
+
+ // serializations must match
+ assert(s1 == s2);
+ }
+ catch (const json::parse_error&)
+ {
+ // parsing a JSON serialization must not fail
+ assert(false);
+ }
+ }
+ catch (const json::parse_error&)
+ {
+ // parse errors are ok, because input may be random bytes
+ }
+ catch (const json::out_of_range&)
+ {
+ // out of range errors may happen if provided sizes are excessive
+ }
+
+ // return 0 - non-zero return values are reserved for future use
+ return 0;
+}
diff --git a/json4cpp/tests/src/fuzzer-parse_msgpack.cpp b/json4cpp/tests/src/fuzzer-parse_msgpack.cpp
new file mode 100644
index 0000000000..0b4ab0af71
--- /dev/null
+++ b/json4cpp/tests/src/fuzzer-parse_msgpack.cpp
@@ -0,0 +1,69 @@
+// __ _____ _____ _____
+// __| | __| | | | 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
+
+/*
+This file implements a parser test suitable for fuzz testing. Given a byte
+array data, it performs the following steps:
+
+- j1 = from_msgpack(data)
+- vec = to_msgpack(j1)
+- j2 = from_msgpack(vec)
+- assert(j1 == j2)
+
+The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
+drivers.
+*/
+
+#include <iostream>
+#include <sstream>
+#include <nlohmann/json.hpp>
+
+using json = nlohmann::json;
+
+// see http://llvm.org/docs/LibFuzzer.html
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ try
+ {
+ // step 1: parse input
+ std::vector<uint8_t> const vec1(data, data + size);
+ json const j1 = json::from_msgpack(vec1);
+
+ try
+ {
+ // step 2: round trip
+ std::vector<uint8_t> const vec2 = json::to_msgpack(j1);
+
+ // parse serialization
+ json const j2 = json::from_msgpack(vec2);
+
+ // serializations must match
+ assert(json::to_msgpack(j2) == vec2);
+ }
+ catch (const json::parse_error&)
+ {
+ // parsing a MessagePack serialization must not fail
+ assert(false);
+ }
+ }
+ catch (const json::parse_error&)
+ {
+ // parse errors are ok, because input may be random bytes
+ }
+ catch (const json::type_error&)
+ {
+ // type errors can occur during parsing, too
+ }
+ catch (const json::out_of_range&)
+ {
+ // out of range errors may happen if provided sizes are excessive
+ }
+
+ // return 0 - non-zero return values are reserved for future use
+ return 0;
+}
diff --git a/json4cpp/tests/src/fuzzer-parse_ubjson.cpp b/json4cpp/tests/src/fuzzer-parse_ubjson.cpp
new file mode 100644
index 0000000000..463656c71f
--- /dev/null
+++ b/json4cpp/tests/src/fuzzer-parse_ubjson.cpp
@@ -0,0 +1,85 @@
+// __ _____ _____ _____
+// __| | __| | | | 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
+
+/*
+This file implements a parser test suitable for fuzz testing. Given a byte
+array data, it performs the following steps:
+
+- j1 = from_ubjson(data)
+- vec = to_ubjson(j1)
+- j2 = from_ubjson(vec)
+- assert(j1 == j2)
+- vec2 = to_ubjson(j1, use_size = true, use_type = false)
+- j3 = from_ubjson(vec2)
+- assert(j1 == j3)
+- vec3 = to_ubjson(j1, use_size = true, use_type = true)
+- j4 = from_ubjson(vec3)
+- assert(j1 == j4)
+
+The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
+drivers.
+*/
+
+#include <iostream>
+#include <sstream>
+#include <nlohmann/json.hpp>
+
+using json = nlohmann::json;
+
+// see http://llvm.org/docs/LibFuzzer.html
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ try
+ {
+ // step 1: parse input
+ std::vector<uint8_t> const vec1(data, data + size);
+ json const j1 = json::from_ubjson(vec1);
+
+ try
+ {
+ // step 2.1: round trip without adding size annotations to container types
+ std::vector<uint8_t> const vec2 = json::to_ubjson(j1, false, false);
+
+ // step 2.2: round trip with adding size annotations but without adding type annotations to container types
+ std::vector<uint8_t> const vec3 = json::to_ubjson(j1, true, false);
+
+ // step 2.3: round trip with adding size as well as type annotations to container types
+ std::vector<uint8_t> const vec4 = json::to_ubjson(j1, true, true);
+
+ // parse serialization
+ json const j2 = json::from_ubjson(vec2);
+ json const j3 = json::from_ubjson(vec3);
+ json const j4 = json::from_ubjson(vec4);
+
+ // serializations must match
+ assert(json::to_ubjson(j2, false, false) == vec2);
+ assert(json::to_ubjson(j3, true, false) == vec3);
+ assert(json::to_ubjson(j4, true, true) == vec4);
+ }
+ catch (const json::parse_error&)
+ {
+ // parsing a UBJSON serialization must not fail
+ assert(false);
+ }
+ }
+ catch (const json::parse_error&)
+ {
+ // parse errors are ok, because input may be random bytes
+ }
+ catch (const json::type_error&)
+ {
+ // type errors can occur during parsing, too
+ }
+ catch (const json::out_of_range&)
+ {
+ // out of range errors may happen if provided sizes are excessive
+ }
+
+ // return 0 - non-zero return values are reserved for future use
+ return 0;
+}
diff --git a/json4cpp/tests/src/make_test_data_available.hpp b/json4cpp/tests/src/make_test_data_available.hpp
new file mode 100644
index 0000000000..7df1a719af
--- /dev/null
+++ b/json4cpp/tests/src/make_test_data_available.hpp
@@ -0,0 +1,31 @@
+// __ _____ _____ _____
+// __| | __| | | | 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
+
+#pragma once
+
+#include <cstdio> // fopen, fclose, FILE
+#include <memory> // unique_ptr
+#include <test_data.hpp>
+#include <doctest.h>
+
+namespace utils
+{
+
+inline bool check_testsuite_downloaded()
+{
+ using FilePtr = std::unique_ptr<FILE, int(*)(FILE*)>;
+ const FilePtr file(std::fopen(TEST_DATA_DIRECTORY "/README.md", "r"), std::fclose);
+ return file != nullptr;
+}
+
+TEST_CASE("check test suite is downloaded")
+{
+ REQUIRE_MESSAGE(utils::check_testsuite_downloaded(), "Test data not found in '" TEST_DATA_DIRECTORY "'. Please execute target 'download_test_data' before running this test suite. See <https://github.com/nlohmann/json#execute-unit-tests> for more information.");
+}
+
+} // namespace utils
diff --git a/json4cpp/tests/src/test_utils.hpp b/json4cpp/tests/src/test_utils.hpp
new file mode 100644
index 0000000000..37afa21e8b
--- /dev/null
+++ b/json4cpp/tests/src/test_utils.hpp
@@ -0,0 +1,33 @@
+// __ _____ _____ _____
+// __| | __| | | | 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
+
+#pragma once
+
+#include <cstdint> // uint8_t
+#include <fstream> // ifstream, istreambuf_iterator, ios
+#include <vector> // vector
+
+namespace utils
+{
+
+inline std::vector<std::uint8_t> read_binary_file(const std::string& filename)
+{
+ std::ifstream file(filename, std::ios::binary);
+ file.unsetf(std::ios::skipws);
+
+ file.seekg(0, std::ios::end);
+ const auto size = file.tellg();
+ file.seekg(0, std::ios::beg);
+
+ std::vector<std::uint8_t> byte_vector;
+ byte_vector.reserve(static_cast<std::size_t>(size));
+ byte_vector.insert(byte_vector.begin(), std::istream_iterator<std::uint8_t>(file), std::istream_iterator<std::uint8_t>());
+ return byte_vector;
+}
+
+} // namespace utils
diff --git a/json4cpp/tests/src/unit-32bit.cpp b/json4cpp/tests/src/unit-32bit.cpp
new file mode 100644
index 0000000000..e160304e88
--- /dev/null
+++ b/json4cpp/tests/src/unit-32bit.cpp
@@ -0,0 +1,134 @@
+// __ _____ _____ _____
+// __| | __| | | | 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;
+
+#include <climits> // SIZE_MAX
+#include <limits> // numeric_limits
+
+template <typename OfType, typename T, bool MinInRange, bool MaxInRange>
+struct trait_test_arg
+{
+ using of_type = OfType;
+ using type = T;
+ static constexpr bool min_in_range = MinInRange;
+ static constexpr bool max_in_range = MaxInRange;
+};
+
+TEST_CASE_TEMPLATE_DEFINE("value_in_range_of trait", T, value_in_range_of_test) // NOLINT(readability-math-missing-parentheses)
+{
+ using nlohmann::detail::value_in_range_of;
+
+ using of_type = typename T::of_type;
+ using type = typename T::type;
+ constexpr bool min_in_range = T::min_in_range;
+ constexpr bool max_in_range = T::max_in_range;
+
+ type const val_min = std::numeric_limits<type>::min();
+ type const val_min2 = val_min + 1;
+ type const val_max = std::numeric_limits<type>::max();
+ type const val_max2 = val_max - 1;
+
+ REQUIRE(CHAR_BIT == 8);
+
+ std::string of_type_str;
+ if (std::is_unsigned<of_type>::value)
+ {
+ of_type_str += "u";
+ }
+ of_type_str += "int";
+ of_type_str += std::to_string(sizeof(of_type) * 8);
+
+ INFO("of_type := ", of_type_str);
+
+ std::string type_str;
+ if (std::is_unsigned<type>::value)
+ {
+ type_str += "u";
+ }
+ type_str += "int";
+ type_str += std::to_string(sizeof(type) * 8);
+
+ INFO("type := ", type_str);
+
+ CAPTURE(val_min);
+ CAPTURE(min_in_range);
+ CAPTURE(val_max);
+ CAPTURE(max_in_range);
+
+ if (min_in_range)
+ {
+ CHECK(value_in_range_of<of_type>(val_min));
+ CHECK(value_in_range_of<of_type>(val_min2));
+ }
+ else
+ {
+ CHECK_FALSE(value_in_range_of<of_type>(val_min));
+ CHECK_FALSE(value_in_range_of<of_type>(val_min2));
+ }
+
+ if (max_in_range)
+ {
+ CHECK(value_in_range_of<of_type>(val_max));
+ CHECK(value_in_range_of<of_type>(val_max2));
+ }
+ else
+ {
+ CHECK_FALSE(value_in_range_of<of_type>(val_max));
+ CHECK_FALSE(value_in_range_of<of_type>(val_max2));
+ }
+}
+
+TEST_CASE("32bit")
+{
+ REQUIRE(SIZE_MAX == 0xffffffff);
+}
+
+TEST_CASE_TEMPLATE_INVOKE(value_in_range_of_test, \
+ trait_test_arg<std::size_t, std::int32_t, false, true>, \
+ trait_test_arg<std::size_t, std::uint32_t, true, true>, \
+ trait_test_arg<std::size_t, std::int64_t, false, false>, \
+ trait_test_arg<std::size_t, std::uint64_t, true, false>);
+
+TEST_CASE("BJData")
+{
+ SECTION("parse errors")
+ {
+ SECTION("array")
+ {
+ SECTION("optimized array: negative size")
+ {
+ std::vector<uint8_t> const vM = {'[', '$', 'M', '#', '[', 'I', 0x00, 0x20, 'M', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xFF, ']'};
+ std::vector<uint8_t> const vMX = {'[', '$', 'U', '#', '[', 'M', 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 'U', 0x01, ']'};
+
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vM), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&);
+ CHECK(json::from_bjdata(vM, true, false).is_discarded());
+
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vMX), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&);
+ CHECK(json::from_bjdata(vMX, true, false).is_discarded());
+ }
+
+ SECTION("optimized array: integer value overflow")
+ {
+ std::vector<uint8_t> const vL = {'[', '#', 'L', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F};
+ std::vector<uint8_t> const vM = {'[', '$', 'M', '#', '[', 'I', 0x00, 0x20, 'M', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xFF, ']'};
+
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vL), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&);
+ CHECK(json::from_bjdata(vL, true, false).is_discarded());
+
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vM), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&);
+ CHECK(json::from_bjdata(vM, true, false).is_discarded());
+ }
+ }
+ }
+}
diff --git a/json4cpp/tests/src/unit-algorithms.cpp b/json4cpp/tests/src/unit-algorithms.cpp
new file mode 100644
index 0000000000..0aac52f0bc
--- /dev/null
+++ b/json4cpp/tests/src/unit-algorithms.cpp
@@ -0,0 +1,365 @@
+// __ _____ _____ _____
+// __| | __| | | | 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 <algorithm>
+#include <nlohmann/json.hpp>
+using nlohmann::json;
+
+TEST_CASE("algorithms")
+{
+ json j_array = {13, 29, 3, {{"one", 1}, {"two", 2}}, true, false, {1, 2, 3}, "foo", "baz"};
+ json j_object = {{"one", 1}, {"two", 2}};
+
+ SECTION("non-modifying sequence operations")
+ {
+ SECTION("std::all_of")
+ {
+ CHECK(std::all_of(j_array.begin(), j_array.end(), [](const json & value)
+ {
+ return !value.empty();
+ }));
+ CHECK(std::all_of(j_object.begin(), j_object.end(), [](const json & value)
+ {
+ return value.type() == json::value_t::number_integer;
+ }));
+ }
+
+ SECTION("std::any_of")
+ {
+ CHECK(std::any_of(j_array.begin(), j_array.end(), [](const json & value)
+ {
+ return value.is_string() && value.get<std::string>() == "foo";
+ }));
+ CHECK(std::any_of(j_object.begin(), j_object.end(), [](const json & value)
+ {
+ return value.get<int>() > 1;
+ }));
+ }
+
+ SECTION("std::none_of")
+ {
+ CHECK(std::none_of(j_array.begin(), j_array.end(), [](const json & value)
+ {
+ return value.empty();
+ }));
+ CHECK(std::none_of(j_object.begin(), j_object.end(), [](const json & value)
+ {
+ return value.get<int>() <= 0;
+ }));
+ }
+
+ SECTION("std::for_each")
+ {
+ SECTION("reading")
+ {
+ int sum = 0;
+
+ std::for_each(j_array.cbegin(), j_array.cend(), [&sum](const json & value)
+ {
+ if (value.is_number())
+ {
+ sum += static_cast<int>(value);
+ }
+ });
+
+ CHECK(sum == 45);
+ }
+
+ SECTION("writing")
+ {
+ auto add17 = [](json & value)
+ {
+ if (value.is_array())
+ {
+ value.push_back(17);
+ }
+ };
+
+ std::for_each(j_array.begin(), j_array.end(), add17);
+
+ CHECK(j_array[6] == json({1, 2, 3, 17}));
+ }
+ }
+
+ SECTION("std::count")
+ {
+ CHECK(std::count(j_array.begin(), j_array.end(), json(true)) == 1);
+ }
+
+ SECTION("std::count_if")
+ {
+ CHECK(std::count_if(j_array.begin(), j_array.end(), [](const json & value)
+ {
+ return (value.is_number());
+ }) == 3);
+ CHECK(std::count_if(j_array.begin(), j_array.end(), [](const json&)
+ {
+ return true;
+ }) == 9);
+ }
+
+ SECTION("std::mismatch")
+ {
+ json j_array2 = {13, 29, 3, {{"one", 1}, {"two", 2}, {"three", 3}}, true, false, {1, 2, 3}, "foo", "baz"};
+ auto res = std::mismatch(j_array.begin(), j_array.end(), j_array2.begin());
+ CHECK(*res.first == json({{"one", 1}, {"two", 2}}));
+ CHECK(*res.second == json({{"one", 1}, {"two", 2}, {"three", 3}}));
+ }
+
+ SECTION("std::equal")
+ {
+ SECTION("using operator==")
+ {
+ CHECK(std::equal(j_array.begin(), j_array.end(), j_array.begin()));
+ CHECK(std::equal(j_object.begin(), j_object.end(), j_object.begin()));
+ CHECK(!std::equal(j_array.begin(), j_array.end(), j_object.begin()));
+ }
+
+ SECTION("using user-defined comparison")
+ {
+ // compare objects only by size of its elements
+ json j_array2 = {13, 29, 3, {"Hello", "World"}, true, false, {{"one", 1}, {"two", 2}, {"three", 3}}, "foo", "baz"};
+ CHECK(!std::equal(j_array.begin(), j_array.end(), j_array2.begin()));
+ CHECK(std::equal(j_array.begin(), j_array.end(), j_array2.begin(),
+ [](const json & a, const json & b)
+ {
+ return (a.size() == b.size());
+ }));
+ }
+ }
+
+ SECTION("std::find")
+ {
+ auto it = std::find(j_array.begin(), j_array.end(), json(false));
+ CHECK(std::distance(j_array.begin(), it) == 5);
+ }
+
+ SECTION("std::find_if")
+ {
+ auto it = std::find_if(j_array.begin(), j_array.end(),
+ [](const json & value)
+ {
+ return value.is_boolean();
+ });
+ CHECK(std::distance(j_array.begin(), it) == 4);
+ }
+
+ SECTION("std::find_if_not")
+ {
+ auto it = std::find_if_not(j_array.begin(), j_array.end(),
+ [](const json & value)
+ {
+ return value.is_number();
+ });
+ CHECK(std::distance(j_array.begin(), it) == 3);
+ }
+
+ SECTION("std::adjacent_find")
+ {
+ CHECK(std::adjacent_find(j_array.begin(), j_array.end()) == j_array.end());
+ CHECK(std::adjacent_find(j_array.begin(), j_array.end(),
+ [](const json & v1, const json & v2)
+ {
+ return v1.type() == v2.type();
+ }) == j_array.begin());
+ }
+ }
+
+ SECTION("modifying sequence operations")
+ {
+ SECTION("std::reverse")
+ {
+ std::reverse(j_array.begin(), j_array.end());
+ CHECK(j_array == json({"baz", "foo", {1, 2, 3}, false, true, {{"one", 1}, {"two", 2}}, 3, 29, 13}));
+ }
+
+ SECTION("std::rotate")
+ {
+ std::rotate(j_array.begin(), j_array.begin() + 1, j_array.end());
+ CHECK(j_array == json({29, 3, {{"one", 1}, {"two", 2}}, true, false, {1, 2, 3}, "foo", "baz", 13}));
+ }
+
+ SECTION("std::partition")
+ {
+ auto it = std::partition(j_array.begin(), j_array.end(), [](const json & v)
+ {
+ return v.is_string();
+ });
+ CHECK(std::distance(j_array.begin(), it) == 2);
+ CHECK(!it[2].is_string());
+ }
+ }
+
+ SECTION("sorting operations")
+ {
+ SECTION("std::sort")
+ {
+ SECTION("with standard comparison")
+ {
+ json j = {13, 29, 3, {{"one", 1}, {"two", 2}}, true, false, {1, 2, 3}, "foo", "baz", nullptr};
+ std::sort(j.begin(), j.end());
+ CHECK(j == json({nullptr, false, true, 3, 13, 29, {{"one", 1}, {"two", 2}}, {1, 2, 3}, "baz", "foo"}));
+ }
+
+ SECTION("with user-defined comparison")
+ {
+ json j = {3, {{"one", 1}, {"two", 2}}, {1, 2, 3}, nullptr};
+ std::sort(j.begin(), j.end(), [](const json & a, const json & b)
+ {
+ return a.size() < b.size();
+ });
+ CHECK(j == json({nullptr, 3, {{"one", 1}, {"two", 2}}, {1, 2, 3}}));
+ }
+
+ SECTION("sorting an object")
+ {
+ json j({{"one", 1}, {"two", 2}});
+ CHECK_THROWS_WITH_AS(std::sort(j.begin(), j.end()), "[json.exception.invalid_iterator.209] cannot use offsets with object iterators", json::invalid_iterator&);
+ }
+ }
+
+ SECTION("std::partial_sort")
+ {
+ json j = {13, 29, 3, {{"one", 1}, {"two", 2}}, true, false, {1, 2, 3}, "foo", "baz", nullptr};
+ std::partial_sort(j.begin(), j.begin() + 4, j.end());
+ CHECK(j == json({nullptr, false, true, 3, {{"one", 1}, {"two", 2}}, 29, {1, 2, 3}, "foo", "baz", 13}));
+ }
+ }
+
+ SECTION("set operations")
+ {
+ SECTION("std::merge")
+ {
+ {
+ json j1 = {2, 4, 6, 8};
+ json j2 = {1, 2, 3, 5, 7};
+ json j3;
+
+ std::merge(j1.begin(), j1.end(), j2.begin(), j2.end(), std::back_inserter(j3));
+ CHECK(j3 == json({1, 2, 2, 3, 4, 5, 6, 7, 8}));
+ }
+ }
+
+ SECTION("std::set_difference")
+ {
+ json j1 = {1, 2, 3, 4, 5, 6, 7, 8};
+ json j2 = {1, 2, 3, 5, 7};
+ json j3;
+
+ std::set_difference(j1.begin(), j1.end(), j2.begin(), j2.end(), std::back_inserter(j3));
+ CHECK(j3 == json({4, 6, 8}));
+ }
+
+ SECTION("std::set_intersection")
+ {
+ json j1 = {1, 2, 3, 4, 5, 6, 7, 8};
+ json j2 = {1, 2, 3, 5, 7};
+ json j3;
+
+ std::set_intersection(j1.begin(), j1.end(), j2.begin(), j2.end(), std::back_inserter(j3));
+ CHECK(j3 == json({1, 2, 3, 5, 7}));
+ }
+
+ SECTION("std::set_union")
+ {
+ json j1 = {2, 4, 6, 8};
+ json j2 = {1, 2, 3, 5, 7};
+ json j3;
+
+ std::set_union(j1.begin(), j1.end(), j2.begin(), j2.end(), std::back_inserter(j3));
+ CHECK(j3 == json({1, 2, 3, 4, 5, 6, 7, 8}));
+ }
+
+ SECTION("std::set_symmetric_difference")
+ {
+ json j1 = {2, 4, 6, 8};
+ json j2 = {1, 2, 3, 5, 7};
+ json j3;
+
+ std::set_symmetric_difference(j1.begin(), j1.end(), j2.begin(), j2.end(), std::back_inserter(j3));
+ CHECK(j3 == json({1, 3, 4, 5, 6, 7, 8}));
+ }
+ }
+
+ SECTION("heap operations")
+ {
+ std::make_heap(j_array.begin(), j_array.end());
+ CHECK(std::is_heap(j_array.begin(), j_array.end()));
+ std::sort_heap(j_array.begin(), j_array.end());
+ CHECK(j_array == json({false, true, 3, 13, 29, {{"one", 1}, {"two", 2}}, {1, 2, 3}, "baz", "foo"}));
+ }
+
+ SECTION("iota")
+ {
+ SECTION("int")
+ {
+ json json_arr = {0, 5, 2, 4, 10, 20, 30, 40, 50, 1};
+ std::iota(json_arr.begin(), json_arr.end(), 0);
+ CHECK(json_arr == json({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}));
+ }
+ SECTION("double")
+ {
+ json json_arr = {0.5, 1.5, 1.3, 4.1, 10.2, 20.5, 30.6, 40.1, 50.22, 1.5};
+ std::iota(json_arr.begin(), json_arr.end(), 0.5);
+ CHECK(json_arr == json({0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5}));
+ }
+
+ SECTION("char")
+ {
+ json json_arr = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', '0', '1'};
+ std::iota(json_arr.begin(), json_arr.end(), '0');
+ CHECK(json_arr == json({'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}));
+ }
+ }
+
+ SECTION("copy")
+ {
+ SECTION("copy without if")
+ {
+ json dest_arr;
+ const json source_arr = {1, 2, 3, 4};
+
+ std::copy(source_arr.begin(), source_arr.end(), std::back_inserter(dest_arr));
+
+ CHECK(dest_arr == source_arr);
+ }
+ SECTION("copy if")
+ {
+ json dest_arr;
+ const json source_arr = {0, 3, 6, 9, 12, 15, 20};
+
+ std::copy_if(source_arr.begin(), source_arr.end(), std::back_inserter(dest_arr), [](const json & _value)
+ {
+ return _value.get<int>() % 3 == 0;
+ });
+ CHECK(dest_arr == json({0, 3, 6, 9, 12, 15}));
+ }
+ SECTION("copy n")
+ {
+ const json source_arr = {0, 1, 2, 3, 4, 5, 6, 7};
+ json dest_arr;
+ const unsigned char numToCopy = 2;
+
+ std::copy_n(source_arr.begin(), numToCopy, std::back_inserter(dest_arr));
+ CHECK(dest_arr == json{0, 1});
+
+ }
+ SECTION("copy n chars")
+ {
+ const json source_arr = {'1', '2', '3', '4', '5', '6', '7'};
+ json dest_arr;
+ const unsigned char numToCopy = 4;
+
+ std::copy_n(source_arr.begin(), numToCopy, std::back_inserter(dest_arr));
+ CHECK(dest_arr == json{'1', '2', '3', '4'});
+ }
+ }
+
+}
diff --git a/json4cpp/tests/src/unit-allocator.cpp b/json4cpp/tests/src/unit-allocator.cpp
new file mode 100644
index 0000000000..6f2c70148c
--- /dev/null
+++ b/json4cpp/tests/src/unit-allocator.cpp
@@ -0,0 +1,263 @@
+// __ _____ _____ _____
+// __| | __| | | | 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;
+
+namespace
+{
+// special test case to check if memory is leaked if constructor throws
+template<class T>
+struct bad_allocator : std::allocator<T>
+{
+ using std::allocator<T>::allocator;
+
+ bad_allocator() = default;
+ template<class U> bad_allocator(const bad_allocator<U>& /*unused*/) { }
+
+ template<class... Args>
+ void construct(T* /*unused*/, Args&& ... /*unused*/) // NOLINT(cppcoreguidelines-missing-std-forward)
+ {
+ throw std::bad_alloc();
+ }
+
+ template <class U>
+ struct rebind
+ {
+ using other = bad_allocator<U>;
+ };
+};
+} // namespace
+
+TEST_CASE("bad_alloc")
+{
+ SECTION("bad_alloc")
+ {
+ // create JSON type using the throwing allocator
+ using bad_json = nlohmann::basic_json<std::map,
+ std::vector,
+ std::string,
+ bool,
+ std::int64_t,
+ std::uint64_t,
+ double,
+ bad_allocator>;
+
+ // creating an object should throw
+ CHECK_THROWS_AS(bad_json(bad_json::value_t::object), std::bad_alloc&);
+ }
+}
+
+namespace
+{
+bool next_construct_fails = false;
+bool next_destroy_fails = false;
+bool next_deallocate_fails = false;
+
+template<class T>
+struct my_allocator : std::allocator<T>
+{
+ using std::allocator<T>::allocator;
+
+ template<class... Args>
+ void construct(T* p, Args&& ... args)
+ {
+ if (next_construct_fails)
+ {
+ next_construct_fails = false;
+ throw std::bad_alloc();
+ }
+
+ ::new (reinterpret_cast<void*>(p)) T(std::forward<Args>(args)...);
+ }
+
+ void deallocate(T* p, std::size_t n)
+ {
+ if (next_deallocate_fails)
+ {
+ next_deallocate_fails = false;
+ throw std::bad_alloc();
+ }
+
+ std::allocator<T>::deallocate(p, n);
+ }
+
+ void destroy(T* p)
+ {
+ if (next_destroy_fails)
+ {
+ next_destroy_fails = false;
+ throw std::bad_alloc();
+ }
+
+ static_cast<void>(p); // fix MSVC's C4100 warning
+ p->~T();
+ }
+
+ template <class U>
+ struct rebind
+ {
+ using other = my_allocator<U>;
+ };
+};
+
+// allows deletion of raw pointer, usually hold by json_value
+template<class T>
+void my_allocator_clean_up(T* p)
+{
+ assert(p != nullptr);
+ my_allocator<T> alloc;
+ alloc.destroy(p);
+ alloc.deallocate(p, 1);
+}
+} // namespace
+
+TEST_CASE("controlled bad_alloc")
+{
+ // create JSON type using the throwing allocator
+ using my_json = nlohmann::basic_json<std::map,
+ std::vector,
+ std::string,
+ bool,
+ std::int64_t,
+ std::uint64_t,
+ double,
+ my_allocator>;
+
+ SECTION("class json_value")
+ {
+ SECTION("json_value(value_t)")
+ {
+ SECTION("object")
+ {
+ next_construct_fails = false;
+ auto t = my_json::value_t::object;
+ CHECK_NOTHROW(my_allocator_clean_up(my_json::json_value(t).object));
+ next_construct_fails = true;
+ CHECK_THROWS_AS(my_json::json_value(t), std::bad_alloc&);
+ next_construct_fails = false;
+ }
+ SECTION("array")
+ {
+ next_construct_fails = false;
+ auto t = my_json::value_t::array;
+ CHECK_NOTHROW(my_allocator_clean_up(my_json::json_value(t).array));
+ next_construct_fails = true;
+ CHECK_THROWS_AS(my_json::json_value(t), std::bad_alloc&);
+ next_construct_fails = false;
+ }
+ SECTION("string")
+ {
+ next_construct_fails = false;
+ auto t = my_json::value_t::string;
+ CHECK_NOTHROW(my_allocator_clean_up(my_json::json_value(t).string));
+ next_construct_fails = true;
+ CHECK_THROWS_AS(my_json::json_value(t), std::bad_alloc&);
+ next_construct_fails = false;
+ }
+ }
+
+ SECTION("json_value(const string_t&)")
+ {
+ next_construct_fails = false;
+ const my_json::string_t v("foo");
+ CHECK_NOTHROW(my_allocator_clean_up(my_json::json_value(v).string));
+ next_construct_fails = true;
+ CHECK_THROWS_AS(my_json::json_value(v), std::bad_alloc&);
+ next_construct_fails = false;
+ }
+ }
+
+ SECTION("class basic_json")
+ {
+ SECTION("basic_json(const CompatibleObjectType&)")
+ {
+ next_construct_fails = false;
+ const std::map<std::string, std::string> v {{"foo", "bar"}};
+ CHECK_NOTHROW(my_json(v));
+ next_construct_fails = true;
+ CHECK_THROWS_AS(my_json(v), std::bad_alloc&);
+ next_construct_fails = false;
+ }
+
+ SECTION("basic_json(const CompatibleArrayType&)")
+ {
+ next_construct_fails = false;
+ const std::vector<std::string> v {"foo", "bar", "baz"};
+ CHECK_NOTHROW(my_json(v));
+ next_construct_fails = true;
+ CHECK_THROWS_AS(my_json(v), std::bad_alloc&);
+ next_construct_fails = false;
+ }
+
+ SECTION("basic_json(const typename string_t::value_type*)")
+ {
+ next_construct_fails = false;
+ CHECK_NOTHROW(my_json("foo"));
+ next_construct_fails = true;
+ CHECK_THROWS_AS(my_json("foo"), std::bad_alloc&);
+ next_construct_fails = false;
+ }
+
+ SECTION("basic_json(const typename string_t::value_type*)")
+ {
+ next_construct_fails = false;
+ const std::string s("foo");
+ CHECK_NOTHROW(my_json(s));
+ next_construct_fails = true;
+ CHECK_THROWS_AS(my_json(s), std::bad_alloc&);
+ next_construct_fails = false;
+ }
+ }
+}
+
+namespace
+{
+template<class T>
+struct allocator_no_forward : std::allocator<T>
+{
+ allocator_no_forward() = default;
+ template <class U>
+ allocator_no_forward(allocator_no_forward<U> /*unused*/) {}
+
+ template <class U>
+ struct rebind
+ {
+ using other = allocator_no_forward<U>;
+ };
+
+ template <class... Args>
+ void construct(T* p, const Args& ... args) noexcept(noexcept(::new (static_cast<void*>(p)) T(args...)))
+ {
+ // force copy even if move is available
+ ::new (static_cast<void*>(p)) T(args...);
+ }
+};
+} // namespace
+
+TEST_CASE("bad my_allocator::construct")
+{
+ SECTION("my_allocator::construct doesn't forward")
+ {
+ using bad_alloc_json = nlohmann::basic_json<std::map,
+ std::vector,
+ std::string,
+ bool,
+ std::int64_t,
+ std::uint64_t,
+ double,
+ allocator_no_forward>;
+
+ bad_alloc_json j;
+ j["test"] = bad_alloc_json::array_t();
+ j["test"].push_back("should not leak");
+ }
+}
diff --git a/json4cpp/tests/src/unit-alt-string.cpp b/json4cpp/tests/src/unit-alt-string.cpp
new file mode 100644
index 0000000000..9c04afa4a5
--- /dev/null
+++ b/json4cpp/tests/src/unit-alt-string.cpp
@@ -0,0 +1,370 @@
+// __ _____ _____ _____
+// __| | __| | | | 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-FileCopyrightText: 2018 Vitaliy Manushkin <agri@akamo.info>
+// SPDX-License-Identifier: MIT
+
+#include "doctest_compatibility.h"
+
+#include <nlohmann/json.hpp>
+
+#include <string>
+#include <utility>
+
+/* forward declarations */
+class alt_string;
+bool operator<(const char* op1, const alt_string& op2) noexcept; // NOLINT(misc-use-internal-linkage)
+void int_to_string(alt_string& target, std::size_t value); // NOLINT(misc-use-internal-linkage)
+
+/*
+ * This is virtually a string class.
+ * It covers std::string under the hood.
+ */
+class alt_string
+{
+ public:
+ using value_type = std::string::value_type;
+
+ static constexpr auto npos = (std::numeric_limits<std::size_t>::max)();
+
+ alt_string(const char* str): str_impl(str) {}
+ alt_string(const char* str, std::size_t count): str_impl(str, count) {}
+ alt_string(size_t count, char chr): str_impl(count, chr) {}
+ alt_string() = default;
+
+ alt_string& append(char ch)
+ {
+ str_impl.push_back(ch);
+ return *this;
+ }
+
+ alt_string& append(const alt_string& str)
+ {
+ str_impl.append(str.str_impl);
+ return *this;
+ }
+
+ alt_string& append(const char* s, std::size_t length)
+ {
+ str_impl.append(s, length);
+ return *this;
+ }
+
+ void push_back(char c)
+ {
+ str_impl.push_back(c);
+ }
+
+ template <typename op_type>
+ bool operator==(const op_type& op) const
+ {
+ return str_impl == op;
+ }
+
+ bool operator==(const alt_string& op) const
+ {
+ return str_impl == op.str_impl;
+ }
+
+ template <typename op_type>
+ bool operator!=(const op_type& op) const
+ {
+ return str_impl != op;
+ }
+
+ bool operator!=(const alt_string& op) const
+ {
+ return str_impl != op.str_impl;
+ }
+
+ std::size_t size() const noexcept
+ {
+ return str_impl.size();
+ }
+
+ void resize (std::size_t n)
+ {
+ str_impl.resize(n);
+ }
+
+ void resize (std::size_t n, char c)
+ {
+ str_impl.resize(n, c);
+ }
+
+ template <typename op_type>
+ bool operator<(const op_type& op) const noexcept
+ {
+ return str_impl < op;
+ }
+
+ bool operator<(const alt_string& op) const noexcept
+ {
+ return str_impl < op.str_impl;
+ }
+
+ const char* c_str() const
+ {
+ return str_impl.c_str();
+ }
+
+ char& operator[](std::size_t index)
+ {
+ return str_impl[index];
+ }
+
+ const char& operator[](std::size_t index) const
+ {
+ return str_impl[index];
+ }
+
+ char& back()
+ {
+ return str_impl.back();
+ }
+
+ const char& back() const
+ {
+ return str_impl.back();
+ }
+
+ void clear()
+ {
+ str_impl.clear();
+ }
+
+ const value_type* data() const
+ {
+ return str_impl.data();
+ }
+
+ bool empty() const
+ {
+ return str_impl.empty();
+ }
+
+ std::size_t find(const alt_string& str, std::size_t pos = 0) const
+ {
+ return str_impl.find(str.str_impl, pos);
+ }
+
+ std::size_t find_first_of(char c, std::size_t pos = 0) const
+ {
+ return str_impl.find_first_of(c, pos);
+ }
+
+ alt_string substr(std::size_t pos = 0, std::size_t count = npos) const
+ {
+ const std::string s = str_impl.substr(pos, count);
+ return {s.data(), s.size()};
+ }
+
+ alt_string& replace(std::size_t pos, std::size_t count, const alt_string& str)
+ {
+ str_impl.replace(pos, count, str.str_impl);
+ return *this;
+ }
+
+ void reserve( std::size_t new_cap = 0 )
+ {
+ str_impl.reserve(new_cap);
+ }
+
+ private:
+ std::string str_impl {}; // NOLINT(readability-redundant-member-init)
+
+ friend bool operator<(const char* /*op1*/, const alt_string& /*op2*/) noexcept;
+};
+
+void int_to_string(alt_string& target, std::size_t value)
+{
+ target = std::to_string(value).c_str();
+}
+
+using alt_json = nlohmann::basic_json <
+ std::map,
+ std::vector,
+ alt_string,
+ bool,
+ std::int64_t,
+ std::uint64_t,
+ double,
+ std::allocator,
+ nlohmann::adl_serializer >;
+
+bool operator<(const char* op1, const alt_string& op2) noexcept
+{
+ return op1 < op2.str_impl;
+}
+
+TEST_CASE("alternative string type")
+{
+ SECTION("dump")
+ {
+ {
+ alt_json doc;
+ doc["pi"] = 3.141;
+ const alt_string dump = doc.dump();
+ CHECK(dump == R"({"pi":3.141})");
+ }
+
+ {
+ alt_json doc;
+ doc["happy"] = true;
+ const alt_string dump = doc.dump();
+ CHECK(dump == R"({"happy":true})");
+ }
+
+ {
+ alt_json doc;
+ doc["name"] = "I'm Batman";
+ const alt_string dump = doc.dump();
+ CHECK(dump == R"({"name":"I'm Batman"})");
+ }
+
+ {
+ alt_json doc;
+ doc["nothing"] = nullptr;
+ const alt_string dump = doc.dump();
+ CHECK(dump == R"({"nothing":null})");
+ }
+
+ {
+ alt_json doc;
+ doc["answer"]["everything"] = 42;
+ const alt_string dump = doc.dump();
+ CHECK(dump == R"({"answer":{"everything":42}})");
+ }
+
+ {
+ alt_json doc;
+ doc["list"] = { 1, 0, 2 };
+ const alt_string dump = doc.dump();
+ CHECK(dump == R"({"list":[1,0,2]})");
+ }
+
+ {
+ alt_json doc;
+ doc["object"] = { {"currency", "USD"}, {"value", 42.99} };
+ const alt_string dump = doc.dump();
+ CHECK(dump == R"({"object":{"currency":"USD","value":42.99}})");
+ }
+ }
+
+ SECTION("parse")
+ {
+ auto doc = alt_json::parse(R"({"foo": "bar"})");
+ const alt_string dump = doc.dump();
+ CHECK(dump == R"({"foo":"bar"})");
+ }
+
+ SECTION("items")
+ {
+ auto doc = alt_json::parse(R"({"foo": "bar"})");
+
+ for (const auto& item : doc.items())
+ {
+ CHECK(item.key() == "foo");
+ CHECK(item.value() == "bar");
+ }
+
+ auto doc_array = alt_json::parse(R"(["foo", "bar"])");
+
+ for (const auto& item : doc_array.items())
+ {
+ if (item.key() == "0" )
+ {
+ CHECK( item.value() == "foo" );
+ }
+ else if (item.key() == "1" )
+ {
+ CHECK(item.value() == "bar");
+ }
+ else
+ {
+ CHECK(false);
+ }
+ }
+ }
+
+ SECTION("equality")
+ {
+ alt_json doc;
+ doc["Who are you?"] = "I'm Batman";
+
+ CHECK("I'm Batman" == doc["Who are you?"]);
+ CHECK(doc["Who are you?"] == "I'm Batman");
+ CHECK_FALSE("I'm Batman" != doc["Who are you?"]);
+ CHECK_FALSE(doc["Who are you?"] != "I'm Batman");
+
+ CHECK("I'm Bruce Wayne" != doc["Who are you?"]);
+ CHECK(doc["Who are you?"] != "I'm Bruce Wayne");
+ CHECK_FALSE("I'm Bruce Wayne" == doc["Who are you?"]);
+ CHECK_FALSE(doc["Who are you?"] == "I'm Bruce Wayne");
+
+ {
+ const alt_json& const_doc = doc;
+
+ CHECK("I'm Batman" == const_doc["Who are you?"]);
+ CHECK(const_doc["Who are you?"] == "I'm Batman");
+ CHECK_FALSE("I'm Batman" != const_doc["Who are you?"]);
+ CHECK_FALSE(const_doc["Who are you?"] != "I'm Batman");
+
+ CHECK("I'm Bruce Wayne" != const_doc["Who are you?"]);
+ CHECK(const_doc["Who are you?"] != "I'm Bruce Wayne");
+ CHECK_FALSE("I'm Bruce Wayne" == const_doc["Who are you?"]);
+ CHECK_FALSE(const_doc["Who are you?"] == "I'm Bruce Wayne");
+ }
+ }
+
+ SECTION("JSON pointer")
+ {
+ // conversion from json to alt_json fails to compile (see #3425);
+ // attempted fix(*) produces: [[['b','a','r'],['b','a','z']]] (with each char being an integer)
+ // (*) disable implicit conversion for json_refs of any basic_json type
+ // alt_json j = R"(
+ // {
+ // "foo": ["bar", "baz"]
+ // }
+ // )"_json;
+ auto j = alt_json::parse(R"({"foo": ["bar", "baz"]})");
+
+ CHECK(j.at(alt_json::json_pointer("/foo/0")) == j["foo"][0]);
+ CHECK(j.at(alt_json::json_pointer("/foo/1")) == j["foo"][1]);
+ }
+
+ SECTION("patch")
+ {
+ alt_json const patch1 = alt_json::parse(R"([{ "op": "add", "path": "/a/b", "value": [ "foo", "bar" ] }])");
+ alt_json const doc1 = alt_json::parse(R"({ "a": { "foo": 1 } })");
+
+ CHECK_NOTHROW(doc1.patch(patch1));
+ alt_json doc1_ans = alt_json::parse(R"(
+ {
+ "a": {
+ "foo": 1,
+ "b": [ "foo", "bar" ]
+ }
+ }
+ )");
+ CHECK(doc1.patch(patch1) == doc1_ans);
+ }
+
+ SECTION("diff")
+ {
+ alt_json const j1 = {"foo", "bar", "baz"};
+ alt_json const j2 = {"foo", "bam"};
+ CHECK(alt_json::diff(j1, j2).dump() == "[{\"op\":\"replace\",\"path\":\"/1\",\"value\":\"bam\"},{\"op\":\"remove\",\"path\":\"/2\"}]");
+ }
+
+ SECTION("flatten")
+ {
+ // a JSON value
+ const alt_json j = alt_json::parse(R"({"foo": ["bar", "baz"]})");
+ const auto j2 = j.flatten();
+ CHECK(j2.dump() == R"({"/foo/0":"bar","/foo/1":"baz"})");
+ }
+}
diff --git a/json4cpp/tests/src/unit-assert_macro.cpp b/json4cpp/tests/src/unit-assert_macro.cpp
new file mode 100644
index 0000000000..adedfc9344
--- /dev/null
+++ b/json4cpp/tests/src/unit-assert_macro.cpp
@@ -0,0 +1,48 @@
+// __ _____ _____ _____
+// __| | __| | | | 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"
+
+// avoid warning when assert does not abort
+DOCTEST_GCC_SUPPRESS_WARNING_PUSH
+DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow")
+DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wstrict-overflow")
+
+/// global variable to record side effect of assert calls
+static int assert_counter;
+
+/// set failure variable to true instead of calling assert(x)
+#define JSON_ASSERT(x) {if (!(x)) ++assert_counter; }
+
+#include <nlohmann/json.hpp>
+using nlohmann::json;
+
+// the test assumes exceptions to work
+#if !defined(JSON_NOEXCEPTION)
+TEST_CASE("JSON_ASSERT(x)")
+{
+ SECTION("basic_json(first, second)")
+ {
+ assert_counter = 0;
+ CHECK(assert_counter == 0);
+
+ const json::iterator it{};
+ json j;
+
+ // in case assertions do not abort execution, an exception is thrown
+ CHECK_THROWS_WITH_AS(json(it, j.end()), "[json.exception.invalid_iterator.201] iterators are not compatible", json::invalid_iterator);
+
+ // check that assertion actually happened
+ CHECK(assert_counter == 1);
+ }
+}
+#endif
+
+DOCTEST_GCC_SUPPRESS_WARNING_POP
+DOCTEST_CLANG_SUPPRESS_WARNING_POP
diff --git a/json4cpp/tests/src/unit-binary_formats.cpp b/json4cpp/tests/src/unit-binary_formats.cpp
new file mode 100644
index 0000000000..0c084b720a
--- /dev/null
+++ b/json4cpp/tests/src/unit-binary_formats.cpp
@@ -0,0 +1,211 @@
+// __ _____ _____ _____
+// __| | __| | | | 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;
+
+#include <fstream>
+#include "make_test_data_available.hpp"
+
+TEST_CASE("Binary Formats" * doctest::skip())
+{
+ SECTION("canada.json")
+ {
+ const auto* filename = TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json";
+ const json j = json::parse(std::ifstream(filename));
+
+ const auto json_size = j.dump().size();
+ const auto bjdata_1_size = json::to_bjdata(j).size();
+ const auto bjdata_2_size = json::to_bjdata(j, true).size();
+ const auto bjdata_3_size = json::to_bjdata(j, true, true).size();
+ const auto bson_size = json::to_bson(j).size();
+ const auto cbor_size = json::to_cbor(j).size();
+ const auto msgpack_size = json::to_msgpack(j).size();
+ const auto ubjson_1_size = json::to_ubjson(j).size();
+ const auto ubjson_2_size = json::to_ubjson(j, true).size();
+ const auto ubjson_3_size = json::to_ubjson(j, true, true).size();
+
+ CHECK(json_size == 2090303);
+ CHECK(bjdata_1_size == 1112030);
+ CHECK(bjdata_2_size == 1224148);
+ CHECK(bjdata_3_size == 1224148);
+ CHECK(bson_size == 1794522);
+ CHECK(cbor_size == 1055552);
+ CHECK(msgpack_size == 1056145);
+ CHECK(ubjson_1_size == 1112030);
+ CHECK(ubjson_2_size == 1224148);
+ CHECK(ubjson_3_size == 1169069);
+
+ CHECK((100.0 * double(json_size) / double(json_size)) == Approx(100.0));
+ CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(53.199));
+ CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(58.563));
+ CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(58.563));
+ CHECK((100.0 * double(bson_size) / double(json_size)) == Approx(85.849));
+ CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(50.497));
+ CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(50.526));
+ CHECK((100.0 * double(ubjson_1_size) / double(json_size)) == Approx(53.199));
+ CHECK((100.0 * double(ubjson_2_size) / double(json_size)) == Approx(58.563));
+ CHECK((100.0 * double(ubjson_3_size) / double(json_size)) == Approx(55.928));
+ }
+
+ SECTION("twitter.json")
+ {
+ const auto* filename = TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json";
+ const json j = json::parse(std::ifstream(filename));
+
+ const auto json_size = j.dump().size();
+ const auto bjdata_1_size = json::to_bjdata(j).size();
+ const auto bjdata_2_size = json::to_bjdata(j, true).size();
+ const auto bjdata_3_size = json::to_bjdata(j, true, true).size();
+ const auto bson_size = json::to_bson(j).size();
+ const auto cbor_size = json::to_cbor(j).size();
+ const auto msgpack_size = json::to_msgpack(j).size();
+ const auto ubjson_1_size = json::to_ubjson(j).size();
+ const auto ubjson_2_size = json::to_ubjson(j, true).size();
+ const auto ubjson_3_size = json::to_ubjson(j, true, true).size();
+
+ CHECK(json_size == 466906);
+ CHECK(bjdata_1_size == 425342);
+ CHECK(bjdata_2_size == 429970);
+ CHECK(bjdata_3_size == 429970);
+ CHECK(bson_size == 444568);
+ CHECK(cbor_size == 402814);
+ CHECK(msgpack_size == 401510);
+ CHECK(ubjson_1_size == 426160);
+ CHECK(ubjson_2_size == 430788);
+ CHECK(ubjson_3_size == 430798);
+
+ CHECK((100.0 * double(json_size) / double(json_size)) == Approx(100.0));
+ CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(91.097));
+ CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(92.089));
+ CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(92.089));
+ CHECK((100.0 * double(bson_size) / double(json_size)) == Approx(95.215));
+ CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(86.273));
+ CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(85.993));
+ CHECK((100.0 * double(ubjson_1_size) / double(json_size)) == Approx(91.273));
+ CHECK((100.0 * double(ubjson_2_size) / double(json_size)) == Approx(92.264));
+ CHECK((100.0 * double(ubjson_3_size) / double(json_size)) == Approx(92.266));
+ }
+
+ SECTION("citm_catalog.json")
+ {
+ const auto* filename = TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json";
+ const json j = json::parse(std::ifstream(filename));
+
+ const auto json_size = j.dump().size();
+ const auto bjdata_1_size = json::to_bjdata(j).size();
+ const auto bjdata_2_size = json::to_bjdata(j, true).size();
+ const auto bjdata_3_size = json::to_bjdata(j, true, true).size();
+ const auto bson_size = json::to_bson(j).size();
+ const auto cbor_size = json::to_cbor(j).size();
+ const auto msgpack_size = json::to_msgpack(j).size();
+ const auto ubjson_1_size = json::to_ubjson(j).size();
+ const auto ubjson_2_size = json::to_ubjson(j, true).size();
+ const auto ubjson_3_size = json::to_ubjson(j, true, true).size();
+
+ CHECK(json_size == 500299);
+ CHECK(bjdata_1_size == 390781);
+ CHECK(bjdata_2_size == 433557);
+ CHECK(bjdata_3_size == 432964);
+ CHECK(bson_size == 479430);
+ CHECK(cbor_size == 342373);
+ CHECK(msgpack_size == 342473);
+ CHECK(ubjson_1_size == 391463);
+ CHECK(ubjson_2_size == 434239);
+ CHECK(ubjson_3_size == 425073);
+
+ CHECK((100.0 * double(json_size) / double(json_size)) == Approx(100.0));
+ CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(78.109));
+ CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(86.659));
+ CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(86.541));
+ CHECK((100.0 * double(bson_size) / double(json_size)) == Approx(95.828));
+ CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(68.433));
+ CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(68.453));
+ CHECK((100.0 * double(ubjson_1_size) / double(json_size)) == Approx(78.245));
+ CHECK((100.0 * double(ubjson_2_size) / double(json_size)) == Approx(86.795));
+ CHECK((100.0 * double(ubjson_3_size) / double(json_size)) == Approx(84.963));
+ }
+
+ SECTION("jeopardy.json")
+ {
+ const auto* filename = TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json";
+ json j = json::parse(std::ifstream(filename));
+
+ const auto json_size = j.dump().size();
+ const auto bjdata_1_size = json::to_bjdata(j).size();
+ const auto bjdata_2_size = json::to_bjdata(j, true).size();
+ const auto bjdata_3_size = json::to_bjdata(j, true, true).size();
+ const auto bson_size = json::to_bson({{"", j}}).size(); // wrap array in object for BSON
+ const auto cbor_size = json::to_cbor(j).size();
+ const auto msgpack_size = json::to_msgpack(j).size();
+ const auto ubjson_1_size = json::to_ubjson(j).size();
+ const auto ubjson_2_size = json::to_ubjson(j, true).size();
+ const auto ubjson_3_size = json::to_ubjson(j, true, true).size();
+
+ CHECK(json_size == 52508728);
+ CHECK(bjdata_1_size == 50710965);
+ CHECK(bjdata_2_size == 51144830);
+ CHECK(bjdata_3_size == 51144830);
+ CHECK(bson_size == 56008520);
+ CHECK(cbor_size == 46187320);
+ CHECK(msgpack_size == 46158575);
+ CHECK(ubjson_1_size == 50710965);
+ CHECK(ubjson_2_size == 51144830);
+ CHECK(ubjson_3_size == 49861422);
+
+ CHECK((100.0 * double(json_size) / double(json_size)) == Approx(100.0));
+ CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(96.576));
+ CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(97.402));
+ CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(97.402));
+ CHECK((100.0 * double(bson_size) / double(json_size)) == Approx(106.665));
+ CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(87.961));
+ CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(87.906));
+ CHECK((100.0 * double(ubjson_1_size) / double(json_size)) == Approx(96.576));
+ CHECK((100.0 * double(ubjson_2_size) / double(json_size)) == Approx(97.402));
+ CHECK((100.0 * double(ubjson_3_size) / double(json_size)) == Approx(94.958));
+ }
+
+ SECTION("sample.json")
+ {
+ const auto* filename = TEST_DATA_DIRECTORY "/json_testsuite/sample.json";
+ const json j = json::parse(std::ifstream(filename));
+
+ const auto json_size = j.dump().size();
+ const auto bjdata_1_size = json::to_bjdata(j).size();
+ const auto bjdata_2_size = json::to_bjdata(j, true).size();
+ const auto bjdata_3_size = json::to_bjdata(j, true, true).size();
+ // BSON cannot process the file as it contains code point U+0000
+ const auto cbor_size = json::to_cbor(j).size();
+ const auto msgpack_size = json::to_msgpack(j).size();
+ const auto ubjson_1_size = json::to_ubjson(j).size();
+ const auto ubjson_2_size = json::to_ubjson(j, true).size();
+ const auto ubjson_3_size = json::to_ubjson(j, true, true).size();
+
+ CHECK(json_size == 168677);
+ CHECK(bjdata_1_size == 148695);
+ CHECK(bjdata_2_size == 150569);
+ CHECK(bjdata_3_size == 150569);
+ CHECK(cbor_size == 147095);
+ CHECK(msgpack_size == 147017);
+ CHECK(ubjson_1_size == 148695);
+ CHECK(ubjson_2_size == 150569);
+ CHECK(ubjson_3_size == 150883);
+
+ CHECK((100.0 * double(json_size) / double(json_size)) == Approx(100.0));
+ CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(88.153));
+ CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(89.264));
+ CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(89.264));
+ CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(87.205));
+ CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(87.158));
+ CHECK((100.0 * double(ubjson_1_size) / double(json_size)) == Approx(88.153));
+ CHECK((100.0 * double(ubjson_2_size) / double(json_size)) == Approx(89.264));
+ CHECK((100.0 * double(ubjson_3_size) / double(json_size)) == Approx(89.450));
+ }
+}
diff --git a/json4cpp/tests/src/unit-bjdata.cpp b/json4cpp/tests/src/unit-bjdata.cpp
new file mode 100644
index 0000000000..c8459315d1
--- /dev/null
+++ b/json4cpp/tests/src/unit-bjdata.cpp
@@ -0,0 +1,3838 @@
+// __ _____ _____ _____
+// __| | __| | | | 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;
+
+#include <algorithm>
+#include <climits>
+#include <limits>
+#include <iostream>
+#include <fstream>
+#include <set>
+#include "make_test_data_available.hpp"
+#include "test_utils.hpp"
+
+namespace
+{
+class SaxCountdown
+{
+ public:
+ explicit SaxCountdown(const int count) : events_left(count)
+ {}
+
+ bool null()
+ {
+ return events_left-- > 0;
+ }
+
+ bool boolean(bool /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool number_integer(json::number_integer_t /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool number_unsigned(json::number_unsigned_t /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool number_float(json::number_float_t /*unused*/, const std::string& /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool string(std::string& /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool binary(std::vector<std::uint8_t>& /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool start_object(std::size_t /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool key(std::string& /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool end_object()
+ {
+ return events_left-- > 0;
+ }
+
+ bool start_array(std::size_t /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool end_array()
+ {
+ return events_left-- > 0;
+ }
+
+ bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const json::exception& /*unused*/) // NOLINT(readability-convert-member-functions-to-static)
+ {
+ return false;
+ }
+
+ private:
+ int events_left = 0;
+};
+} // namespace
+
+// at some point in the future, a unit test dedicated to type traits might be a good idea
+template <typename OfType, typename T, bool MinInRange, bool MaxInRange>
+struct trait_test_arg
+{
+ using of_type = OfType;
+ using type = T;
+ static constexpr bool min_in_range = MinInRange;
+ static constexpr bool max_in_range = MaxInRange;
+};
+
+TEST_CASE_TEMPLATE_DEFINE("value_in_range_of trait", T, value_in_range_of_test) // NOLINT(readability-math-missing-parentheses)
+{
+ using nlohmann::detail::value_in_range_of;
+
+ using of_type = typename T::of_type;
+ using type = typename T::type;
+ constexpr bool min_in_range = T::min_in_range;
+ constexpr bool max_in_range = T::max_in_range;
+
+ type const val_min = std::numeric_limits<type>::min();
+ type const val_min2 = val_min + 1;
+ type const val_max = std::numeric_limits<type>::max();
+ type const val_max2 = val_max - 1;
+
+ REQUIRE(CHAR_BIT == 8);
+
+ std::string of_type_str;
+ if (std::is_unsigned<of_type>::value)
+ {
+ of_type_str += "u";
+ }
+ of_type_str += "int";
+ of_type_str += std::to_string(sizeof(of_type) * 8);
+
+ INFO("of_type := ", of_type_str);
+
+ std::string type_str;
+ if (std::is_unsigned<type>::value)
+ {
+ type_str += "u";
+ }
+ type_str += "int";
+ type_str += std::to_string(sizeof(type) * 8);
+
+ INFO("type := ", type_str);
+
+ CAPTURE(val_min);
+ CAPTURE(min_in_range);
+ CAPTURE(val_max);
+ CAPTURE(max_in_range);
+
+ if (min_in_range)
+ {
+ CHECK(value_in_range_of<of_type>(val_min));
+ CHECK(value_in_range_of<of_type>(val_min2));
+ }
+ else
+ {
+ CHECK_FALSE(value_in_range_of<of_type>(val_min));
+ CHECK_FALSE(value_in_range_of<of_type>(val_min2));
+ }
+
+ if (max_in_range)
+ {
+ CHECK(value_in_range_of<of_type>(val_max));
+ CHECK(value_in_range_of<of_type>(val_max2));
+ }
+ else
+ {
+ CHECK_FALSE(value_in_range_of<of_type>(val_max));
+ CHECK_FALSE(value_in_range_of<of_type>(val_max2));
+ }
+}
+
+// NOLINTNEXTLINE(bugprone-throwing-static-initialization)
+TEST_CASE_TEMPLATE_INVOKE(value_in_range_of_test, \
+ trait_test_arg<std::int32_t, std::int32_t, true, true>, \
+ trait_test_arg<std::int32_t, std::uint32_t, true, false>, \
+ trait_test_arg<std::uint32_t, std::int32_t, false, true>, \
+ trait_test_arg<std::uint32_t, std::uint32_t, true, true>, \
+ trait_test_arg<std::int32_t, std::int64_t, false, false>, \
+ trait_test_arg<std::int32_t, std::uint64_t, true, false>, \
+ trait_test_arg<std::uint32_t, std::int64_t, false, false>, \
+ trait_test_arg<std::uint32_t, std::uint64_t, true, false>, \
+ trait_test_arg<std::int64_t, std::int32_t, true, true>, \
+ trait_test_arg<std::int64_t, std::uint32_t, true, true>, \
+ trait_test_arg<std::uint64_t, std::int32_t, false, true>, \
+ trait_test_arg<std::uint64_t, std::uint32_t, true, true>, \
+ trait_test_arg<std::int64_t, std::int64_t, true, true>, \
+ trait_test_arg<std::int64_t, std::uint64_t, true, false>, \
+ trait_test_arg<std::uint64_t, std::int64_t, false, true>, \
+ trait_test_arg<std::uint64_t, std::uint64_t, true, true>);
+
+#if SIZE_MAX == 0xffffffff
+TEST_CASE_TEMPLATE_INVOKE(value_in_range_of_test, \
+ trait_test_arg<std::size_t, std::int32_t, false, true>, \
+ trait_test_arg<std::size_t, std::uint32_t, true, true>, \
+ trait_test_arg<std::size_t, std::int64_t, false, false>, \
+ trait_test_arg<std::size_t, std::uint64_t, true, false>);
+#else
+// NOLINTNEXTLINE(bugprone-throwing-static-initialization)
+TEST_CASE_TEMPLATE_INVOKE(value_in_range_of_test, \
+ trait_test_arg<std::size_t, std::int32_t, false, true>, \
+ trait_test_arg<std::size_t, std::uint32_t, true, true>, \
+ trait_test_arg<std::size_t, std::int64_t, false, true>, \
+ trait_test_arg<std::size_t, std::uint64_t, true, true>);
+#endif
+
+TEST_CASE("BJData")
+{
+ SECTION("binary_reader BJData LUT arrays are sorted")
+ {
+ std::vector<std::uint8_t> const data;
+ auto ia = nlohmann::detail::input_adapter(data);
+ // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
+ nlohmann::detail::binary_reader<json, decltype(ia)> const br{std::move(ia), json::input_format_t::bjdata};
+
+ CHECK(std::is_sorted(br.bjd_optimized_type_markers.begin(), br.bjd_optimized_type_markers.end()));
+ CHECK(std::is_sorted(br.bjd_types_map.begin(), br.bjd_types_map.end()));
+ }
+
+ SECTION("individual values")
+ {
+ SECTION("discarded")
+ {
+ // discarded values are not serialized
+ json const j = json::value_t::discarded;
+ const auto result = json::to_bjdata(j);
+ CHECK(result.empty());
+ }
+
+ SECTION("null")
+ {
+ json const j = nullptr;
+ std::vector<uint8_t> const expected = {'Z'};
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+
+ SECTION("boolean")
+ {
+ SECTION("true")
+ {
+ json const j = true;
+ std::vector<uint8_t> const expected = {'T'};
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+
+ SECTION("false")
+ {
+ json const j = false;
+ std::vector<uint8_t> const expected = {'F'};
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+
+ SECTION("byte")
+ {
+ SECTION("0..255 (uint8)")
+ {
+ for (size_t i = 0; i <= 255; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number (no byte type in JSON)
+ json j = -1;
+ j.get_ref<json::number_integer_t&>() = static_cast<json::number_integer_t>(i);
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create byte vector
+ std::vector<uint8_t> const value
+ {
+ static_cast<uint8_t>('B'),
+ static_cast<uint8_t>(i),
+ };
+
+ // compare value
+ CHECK(json::from_bjdata(value) == j);
+ }
+ }
+ }
+
+ SECTION("number")
+ {
+ SECTION("signed")
+ {
+ SECTION("-9223372036854775808..-2147483649 (int64)")
+ {
+ std::vector<int64_t> const numbers
+ {
+ (std::numeric_limits<int64_t>::min)(),
+ -1000000000000000000LL,
+ -100000000000000000LL,
+ -10000000000000000LL,
+ -1000000000000000LL,
+ -100000000000000LL,
+ -10000000000000LL,
+ -1000000000000LL,
+ -100000000000LL,
+ -10000000000LL,
+ -2147483649LL,
+ };
+ for (const auto i : numbers)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json const j = i;
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ static_cast<uint8_t>('L'),
+ static_cast<uint8_t>(i & 0xff),
+ static_cast<uint8_t>((i >> 8) & 0xff),
+ static_cast<uint8_t>((i >> 16) & 0xff),
+ static_cast<uint8_t>((i >> 24) & 0xff),
+ static_cast<uint8_t>((i >> 32) & 0xff),
+ static_cast<uint8_t>((i >> 40) & 0xff),
+ static_cast<uint8_t>((i >> 48) & 0xff),
+ static_cast<uint8_t>((i >> 56) & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 9);
+
+ // check individual bytes
+ CHECK(result[0] == 'L');
+ int64_t const restored = (static_cast<int64_t>(result[8]) << 070) +
+ (static_cast<int64_t>(result[7]) << 060) +
+ (static_cast<int64_t>(result[6]) << 050) +
+ (static_cast<int64_t>(result[5]) << 040) +
+ (static_cast<int64_t>(result[4]) << 030) +
+ (static_cast<int64_t>(result[3]) << 020) +
+ (static_cast<int64_t>(result[2]) << 010) +
+ static_cast<int64_t>(result[1]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+
+ SECTION("-2147483648..-32769 (int32)")
+ {
+ std::vector<int32_t> const numbers
+ {
+ -32769,
+ -100000,
+ -1000000,
+ -10000000,
+ -100000000,
+ -1000000000,
+ -2147483647 - 1, // https://stackoverflow.com/a/29356002/266378
+ };
+ for (const auto i : numbers)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json const j = i;
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ static_cast<uint8_t>('l'),
+ static_cast<uint8_t>(i & 0xff),
+ static_cast<uint8_t>((i >> 8) & 0xff),
+ static_cast<uint8_t>((i >> 16) & 0xff),
+ static_cast<uint8_t>((i >> 24) & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 5);
+
+ // check individual bytes
+ CHECK(result[0] == 'l');
+ int32_t const restored = (static_cast<int32_t>(result[4]) << 030) +
+ (static_cast<int32_t>(result[3]) << 020) +
+ (static_cast<int32_t>(result[2]) << 010) +
+ static_cast<int32_t>(result[1]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+
+ SECTION("-32768..-129 (int16)")
+ {
+ for (int32_t i = -32768; i <= -129; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json const j = i;
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ static_cast<uint8_t>('I'),
+ static_cast<uint8_t>(i & 0xff),
+ static_cast<uint8_t>((i >> 8) & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 3);
+
+ // check individual bytes
+ CHECK(result[0] == 'I');
+ auto const restored = static_cast<int16_t>(((result[2] << 8) + result[1]));
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+
+ SECTION("-9263 (int16)")
+ {
+ json const j = -9263;
+ std::vector<uint8_t> const expected = {'I', 0xd1, 0xdb};
+
+ // compare result + size
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 3);
+
+ // check individual bytes
+ CHECK(result[0] == 'I');
+ auto const restored = static_cast<int16_t>(((result[2] << 8) + result[1]));
+ CHECK(restored == -9263);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+
+ SECTION("-128..-1 (int8)")
+ {
+ for (auto i = -128; i <= -1; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json const j = i;
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ 'i',
+ static_cast<uint8_t>(i),
+ };
+
+ // compare result + size
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 2);
+
+ // check individual bytes
+ CHECK(result[0] == 'i');
+ CHECK(static_cast<int8_t>(result[1]) == i);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+
+ SECTION("0..127 (int8)")
+ {
+ for (size_t i = 0; i <= 127; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json j = -1;
+ j.get_ref<json::number_integer_t&>() = static_cast<json::number_integer_t>(i);
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ static_cast<uint8_t>('i'),
+ static_cast<uint8_t>(i),
+ };
+
+ // compare result + size
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 2);
+
+ // check individual bytes
+ CHECK(result[0] == 'i');
+ CHECK(result[1] == i);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+
+ SECTION("128..255 (uint8)")
+ {
+ for (size_t i = 128; i <= 255; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json j = -1;
+ j.get_ref<json::number_integer_t&>() = static_cast<json::number_integer_t>(i);
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ static_cast<uint8_t>('U'),
+ static_cast<uint8_t>(i),
+ };
+
+ // compare result + size
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 2);
+
+ // check individual bytes
+ CHECK(result[0] == 'U');
+ CHECK(result[1] == i);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+
+ SECTION("256..32767 (int16)")
+ {
+ for (size_t i = 256; i <= 32767; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json j = -1;
+ j.get_ref<json::number_integer_t&>() = static_cast<json::number_integer_t>(i);
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ static_cast<uint8_t>('I'),
+ static_cast<uint8_t>(i & 0xff),
+ static_cast<uint8_t>((i >> 8) & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 3);
+
+ // check individual bytes
+ CHECK(result[0] == 'I');
+ auto const restored = static_cast<uint16_t>((static_cast<uint8_t>(result[2]) * 256) + static_cast<uint8_t>(result[1]));
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+
+ SECTION("32768..65535 (uint16)")
+ {
+ for (const uint32_t i :
+ {
+ 32768u, 55555u, 65535u
+ })
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json j = -1;
+ j.get_ref<json::number_integer_t&>() = static_cast<json::number_integer_t>(i);
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ static_cast<uint8_t>('u'),
+ static_cast<uint8_t>(i & 0xff),
+ static_cast<uint8_t>((i >> 8) & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 3);
+
+ // check individual bytes
+ CHECK(result[0] == 'u');
+ auto const restored = static_cast<uint16_t>((static_cast<uint8_t>(result[2]) * 256) + static_cast<uint8_t>(result[1]));
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+
+ SECTION("65536..2147483647 (int32)")
+ {
+ for (const uint32_t i :
+ {
+ 65536u, 77777u, 2147483647u
+ })
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json j = -1;
+ j.get_ref<json::number_integer_t&>() = static_cast<json::number_integer_t>(i);
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ 'l',
+ static_cast<uint8_t>(i & 0xff),
+ static_cast<uint8_t>((i >> 8) & 0xff),
+ static_cast<uint8_t>((i >> 16) & 0xff),
+ static_cast<uint8_t>((i >> 24) & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 5);
+
+ // check individual bytes
+ CHECK(result[0] == 'l');
+ uint32_t const restored = (static_cast<uint32_t>(result[4]) << 030) +
+ (static_cast<uint32_t>(result[3]) << 020) +
+ (static_cast<uint32_t>(result[2]) << 010) +
+ static_cast<uint32_t>(result[1]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+
+ SECTION("2147483648..4294967295 (uint32)")
+ {
+ for (const uint32_t i :
+ {
+ 2147483648u, 3333333333u, 4294967295u
+ })
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json j = -1;
+ j.get_ref<json::number_integer_t&>() = static_cast<json::number_integer_t>(i);
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ 'm',
+ static_cast<uint8_t>(i & 0xff),
+ static_cast<uint8_t>((i >> 8) & 0xff),
+ static_cast<uint8_t>((i >> 16) & 0xff),
+ static_cast<uint8_t>((i >> 24) & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 5);
+
+ // check individual bytes
+ CHECK(result[0] == 'm');
+ uint32_t const restored = (static_cast<uint32_t>(result[4]) << 030) +
+ (static_cast<uint32_t>(result[3]) << 020) +
+ (static_cast<uint32_t>(result[2]) << 010) +
+ static_cast<uint32_t>(result[1]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+
+ SECTION("4294967296..9223372036854775807 (int64)")
+ {
+ std::vector<uint64_t> const v = {4294967296LU, 9223372036854775807LU};
+ for (const uint64_t i : v)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json j = -1;
+ j.get_ref<json::number_integer_t&>() = static_cast<json::number_integer_t>(i);
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ 'L',
+ static_cast<uint8_t>(i & 0xff),
+ static_cast<uint8_t>((i >> 010) & 0xff),
+ static_cast<uint8_t>((i >> 020) & 0xff),
+ static_cast<uint8_t>((i >> 030) & 0xff),
+ static_cast<uint8_t>((i >> 040) & 0xff),
+ static_cast<uint8_t>((i >> 050) & 0xff),
+ static_cast<uint8_t>((i >> 060) & 0xff),
+ static_cast<uint8_t>((i >> 070) & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 9);
+
+ // check individual bytes
+ CHECK(result[0] == 'L');
+ uint64_t const restored = (static_cast<uint64_t>(result[8]) << 070) +
+ (static_cast<uint64_t>(result[7]) << 060) +
+ (static_cast<uint64_t>(result[6]) << 050) +
+ (static_cast<uint64_t>(result[5]) << 040) +
+ (static_cast<uint64_t>(result[4]) << 030) +
+ (static_cast<uint64_t>(result[3]) << 020) +
+ (static_cast<uint64_t>(result[2]) << 010) +
+ static_cast<uint64_t>(result[1]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+
+ SECTION("9223372036854775808..18446744073709551615 (uint64)")
+ {
+ std::vector<uint64_t> const v = {9223372036854775808ull, 18446744073709551615ull};
+ for (const uint64_t i : v)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json const j = i;
+
+ // check type
+ CHECK(j.is_number_unsigned());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ 'M',
+ static_cast<uint8_t>(i & 0xff),
+ static_cast<uint8_t>((i >> 010) & 0xff),
+ static_cast<uint8_t>((i >> 020) & 0xff),
+ static_cast<uint8_t>((i >> 030) & 0xff),
+ static_cast<uint8_t>((i >> 040) & 0xff),
+ static_cast<uint8_t>((i >> 050) & 0xff),
+ static_cast<uint8_t>((i >> 060) & 0xff),
+ static_cast<uint8_t>((i >> 070) & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 9);
+
+ // check individual bytes
+ CHECK(result[0] == 'M');
+ uint64_t const restored = (static_cast<uint64_t>(result[8]) << 070) +
+ (static_cast<uint64_t>(result[7]) << 060) +
+ (static_cast<uint64_t>(result[6]) << 050) +
+ (static_cast<uint64_t>(result[5]) << 040) +
+ (static_cast<uint64_t>(result[4]) << 030) +
+ (static_cast<uint64_t>(result[3]) << 020) +
+ (static_cast<uint64_t>(result[2]) << 010) +
+ static_cast<uint64_t>(result[1]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+ }
+
+ SECTION("unsigned")
+ {
+ SECTION("0..127 (int8)")
+ {
+ for (size_t i = 0; i <= 127; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with unsigned integer number
+ json const j = i;
+
+ // check type
+ CHECK(j.is_number_unsigned());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected{'i', static_cast<uint8_t>(i)};
+
+ // compare result + size
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 2);
+
+ // check individual bytes
+ CHECK(result[0] == 'i');
+ auto const restored = static_cast<uint8_t>(result[1]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+
+ SECTION("128..255 (uint8)")
+ {
+ for (size_t i = 128; i <= 255; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with unsigned integer number
+ json const j = i;
+
+ // check type
+ CHECK(j.is_number_unsigned());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected{'U', static_cast<uint8_t>(i)};
+
+ // compare result + size
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 2);
+
+ // check individual bytes
+ CHECK(result[0] == 'U');
+ auto const restored = static_cast<uint8_t>(result[1]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+
+ SECTION("256..32767 (int16)")
+ {
+ for (size_t i = 256; i <= 32767; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with unsigned integer number
+ json const j = i;
+
+ // check type
+ CHECK(j.is_number_unsigned());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ 'I',
+ static_cast<uint8_t>(i & 0xff),
+ static_cast<uint8_t>((i >> 8) & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 3);
+
+ // check individual bytes
+ CHECK(result[0] == 'I');
+ auto const restored = static_cast<uint16_t>((static_cast<uint8_t>(result[2]) * 256) + static_cast<uint8_t>(result[1]));
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+
+ SECTION("32768..65535 (uint16)")
+ {
+ for (const uint32_t i :
+ {
+ 32768u, 55555u, 65535u
+ })
+ {
+ CAPTURE(i)
+
+ // create JSON value with unsigned integer number
+ json const j = i;
+
+ // check type
+ CHECK(j.is_number_unsigned());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ 'u',
+ static_cast<uint8_t>(i & 0xff),
+ static_cast<uint8_t>((i >> 8) & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 3);
+
+ // check individual bytes
+ CHECK(result[0] == 'u');
+ auto const restored = static_cast<uint16_t>((static_cast<uint8_t>(result[2]) * 256) + static_cast<uint8_t>(result[1]));
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+ SECTION("65536..2147483647 (int32)")
+ {
+ for (const uint32_t i :
+ {
+ 65536u, 77777u, 2147483647u
+ })
+ {
+ CAPTURE(i)
+
+ // create JSON value with unsigned integer number
+ json const j = i;
+
+ // check type
+ CHECK(j.is_number_unsigned());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ 'l',
+ static_cast<uint8_t>(i & 0xff),
+ static_cast<uint8_t>((i >> 8) & 0xff),
+ static_cast<uint8_t>((i >> 16) & 0xff),
+ static_cast<uint8_t>((i >> 24) & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 5);
+
+ // check individual bytes
+ CHECK(result[0] == 'l');
+ uint32_t const restored = (static_cast<uint32_t>(result[4]) << 030) +
+ (static_cast<uint32_t>(result[3]) << 020) +
+ (static_cast<uint32_t>(result[2]) << 010) +
+ static_cast<uint32_t>(result[1]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+
+ SECTION("2147483648..4294967295 (uint32)")
+ {
+ for (const uint32_t i :
+ {
+ 2147483648u, 3333333333u, 4294967295u
+ })
+ {
+ CAPTURE(i)
+
+ // create JSON value with unsigned integer number
+ json const j = i;
+
+ // check type
+ CHECK(j.is_number_unsigned());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ 'm',
+ static_cast<uint8_t>(i & 0xff),
+ static_cast<uint8_t>((i >> 8) & 0xff),
+ static_cast<uint8_t>((i >> 16) & 0xff),
+ static_cast<uint8_t>((i >> 24) & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 5);
+
+ // check individual bytes
+ CHECK(result[0] == 'm');
+ uint32_t const restored = (static_cast<uint32_t>(result[4]) << 030) +
+ (static_cast<uint32_t>(result[3]) << 020) +
+ (static_cast<uint32_t>(result[2]) << 010) +
+ static_cast<uint32_t>(result[1]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+
+ SECTION("4294967296..9223372036854775807 (int64)")
+ {
+ std::vector<uint64_t> const v = {4294967296ul, 9223372036854775807ul};
+ for (const uint64_t i : v)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json const j = i;
+
+ // check type
+ CHECK(j.is_number_unsigned());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ 'L',
+ static_cast<uint8_t>(i & 0xff),
+ static_cast<uint8_t>((i >> 010) & 0xff),
+ static_cast<uint8_t>((i >> 020) & 0xff),
+ static_cast<uint8_t>((i >> 030) & 0xff),
+ static_cast<uint8_t>((i >> 040) & 0xff),
+ static_cast<uint8_t>((i >> 050) & 0xff),
+ static_cast<uint8_t>((i >> 060) & 0xff),
+ static_cast<uint8_t>((i >> 070) & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 9);
+
+ // check individual bytes
+ CHECK(result[0] == 'L');
+ uint64_t const restored = (static_cast<uint64_t>(result[8]) << 070) +
+ (static_cast<uint64_t>(result[7]) << 060) +
+ (static_cast<uint64_t>(result[6]) << 050) +
+ (static_cast<uint64_t>(result[5]) << 040) +
+ (static_cast<uint64_t>(result[4]) << 030) +
+ (static_cast<uint64_t>(result[3]) << 020) +
+ (static_cast<uint64_t>(result[2]) << 010) +
+ static_cast<uint64_t>(result[1]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+
+ SECTION("9223372036854775808..18446744073709551615 (uint64)")
+ {
+ std::vector<uint64_t> const v = {9223372036854775808ull, 18446744073709551615ull};
+ for (const uint64_t i : v)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json const j = i;
+
+ // check type
+ CHECK(j.is_number_unsigned());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ 'M',
+ static_cast<uint8_t>(i & 0xff),
+ static_cast<uint8_t>((i >> 010) & 0xff),
+ static_cast<uint8_t>((i >> 020) & 0xff),
+ static_cast<uint8_t>((i >> 030) & 0xff),
+ static_cast<uint8_t>((i >> 040) & 0xff),
+ static_cast<uint8_t>((i >> 050) & 0xff),
+ static_cast<uint8_t>((i >> 060) & 0xff),
+ static_cast<uint8_t>((i >> 070) & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 9);
+
+ // check individual bytes
+ CHECK(result[0] == 'M');
+ uint64_t const restored = (static_cast<uint64_t>(result[8]) << 070) +
+ (static_cast<uint64_t>(result[7]) << 060) +
+ (static_cast<uint64_t>(result[6]) << 050) +
+ (static_cast<uint64_t>(result[5]) << 040) +
+ (static_cast<uint64_t>(result[4]) << 030) +
+ (static_cast<uint64_t>(result[3]) << 020) +
+ (static_cast<uint64_t>(result[2]) << 010) +
+ static_cast<uint64_t>(result[1]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+ }
+ SECTION("float64")
+ {
+ SECTION("3.1415925")
+ {
+ double v = 3.1415925;
+ json const j = v;
+ std::vector<uint8_t> const expected =
+ {
+ 'D', 0xfc, 0xde, 0xa6, 0x3f, 0xfb, 0x21, 0x09, 0x40
+ };
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result) == v);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+
+ SECTION("half-precision float")
+ {
+ SECTION("simple half floats")
+ {
+ CHECK(json::parse("0.0") == json::from_bjdata(std::vector<uint8_t>({'h', 0x00, 0x00})));
+ CHECK(json::parse("-0.0") == json::from_bjdata(std::vector<uint8_t>({'h', 0x00, 0x80})));
+ CHECK(json::parse("1.0") == json::from_bjdata(std::vector<uint8_t>({'h', 0x00, 0x3c})));
+ CHECK(json::parse("1.5") == json::from_bjdata(std::vector<uint8_t>({'h', 0x00, 0x3e})));
+ CHECK(json::parse("65504.0") == json::from_bjdata(std::vector<uint8_t>({'h', 0xff, 0x7b})));
+ }
+
+ SECTION("errors")
+ {
+ SECTION("no byte follows")
+ {
+ json _;
+ std::vector<uint8_t> const vec0 = {'h'};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec0), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BJData number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(vec0, true, false).is_discarded());
+ }
+
+ SECTION("only one byte follows")
+ {
+ json _;
+ std::vector<uint8_t> const vec1 = {'h', 0x00};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec1), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BJData number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(vec1, true, false).is_discarded());
+ }
+ }
+ }
+
+ SECTION("half-precision float (edge cases)")
+ {
+ SECTION("exp = 0b00000")
+ {
+ SECTION("0 (0 00000 0000000000)")
+ {
+ json const j = json::from_bjdata(std::vector<uint8_t>({'h', 0x00, 0x00}));
+ const json::number_float_t d{j};
+ CHECK(d == 0.0);
+ }
+
+ SECTION("-0 (1 00000 0000000000)")
+ {
+ json const j = json::from_bjdata(std::vector<uint8_t>({'h', 0x00, 0x80}));
+ const json::number_float_t d{j};
+ CHECK(d == -0.0);
+ }
+
+ SECTION("2**-24 (0 00000 0000000001)")
+ {
+ json const j = json::from_bjdata(std::vector<uint8_t>({'h', 0x01, 0x00}));
+ const json::number_float_t d{j};
+ CHECK(d == std::pow(2.0, -24.0));
+ }
+ }
+
+ SECTION("exp = 0b11111")
+ {
+ SECTION("infinity (0 11111 0000000000)")
+ {
+ json const j = json::from_bjdata(std::vector<uint8_t>({'h', 0x00, 0x7c}));
+ const json::number_float_t d{j};
+ CHECK(d == std::numeric_limits<json::number_float_t>::infinity());
+ CHECK(j.dump() == "null");
+ }
+
+ SECTION("-infinity (1 11111 0000000000)")
+ {
+ json const j = json::from_bjdata(std::vector<uint8_t>({'h', 0x00, 0xfc}));
+ const json::number_float_t d{j};
+ CHECK(d == -std::numeric_limits<json::number_float_t>::infinity());
+ CHECK(j.dump() == "null");
+ }
+ }
+
+ SECTION("other values from https://en.wikipedia.org/wiki/Half-precision_floating-point_format")
+ {
+ SECTION("1 (0 01111 0000000000)")
+ {
+ json const j = json::from_bjdata(std::vector<uint8_t>({'h', 0x00, 0x3c}));
+ const json::number_float_t d{j};
+ CHECK(d == 1);
+ }
+
+ SECTION("-2 (1 10000 0000000000)")
+ {
+ json const j = json::from_bjdata(std::vector<uint8_t>({'h', 0x00, 0xc0}));
+ const json::number_float_t d{j};
+ CHECK(d == -2);
+ }
+
+ SECTION("65504 (0 11110 1111111111)")
+ {
+ json const j = json::from_bjdata(std::vector<uint8_t>({'h', 0xff, 0x7b}));
+ const json::number_float_t d{j};
+ CHECK(d == 65504);
+ }
+ }
+
+ SECTION("infinity")
+ {
+ json const j = json::from_bjdata(std::vector<uint8_t>({'h', 0x00, 0x7c}));
+ json::number_float_t const d{j};
+ CHECK_FALSE(std::isfinite(d));
+ CHECK(j.dump() == "null");
+ }
+
+ SECTION("NaN")
+ {
+ json const j = json::from_bjdata(std::vector<uint8_t>({'h', 0x00, 0x7e }));
+ json::number_float_t const d{j};
+ CHECK(std::isnan(d));
+ CHECK(j.dump() == "null");
+ }
+ }
+
+ SECTION("high-precision number")
+ {
+ SECTION("unsigned integer number")
+ {
+ std::vector<uint8_t> const vec = {'H', 'i', 0x14, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'};
+ const auto j = json::from_bjdata(vec);
+ CHECK(j.is_number_unsigned());
+ CHECK(j.dump() == "12345678901234567890");
+ }
+
+ SECTION("signed integer number")
+ {
+ std::vector<uint8_t> const vec = {'H', 'i', 0x13, '-', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8'};
+ const auto j = json::from_bjdata(vec);
+ CHECK(j.is_number_integer());
+ CHECK(j.dump() == "-123456789012345678");
+ }
+
+ SECTION("floating-point number")
+ {
+ std::vector<uint8_t> const vec = {'H', 'i', 0x16, '3', '.', '1', '4', '1', '5', '9', '2', '6', '5', '3', '5', '8', '9', '7', '9', '3', '2', '3', '8', '4', '6'};
+ const auto j = json::from_bjdata(vec);
+ CHECK(j.is_number_float());
+ CHECK(j.dump() == "3.141592653589793");
+ }
+
+ SECTION("errors")
+ {
+ // error while parsing length
+ std::vector<uint8_t> const vec0 = {'H', 'i'};
+ CHECK(json::from_bjdata(vec0, true, false).is_discarded());
+ // error while parsing string
+ std::vector<uint8_t> const vec1 = {'H', 'i', '1'};
+ CHECK(json::from_bjdata(vec1, true, false).is_discarded());
+
+ json _;
+ std::vector<uint8_t> const vec2 = {'H', 'i', 2, '1', 'A', '3'};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec2), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing BJData high-precision number: invalid number text: 1A", json::parse_error);
+ std::vector<uint8_t> const vec3 = {'H', 'i', 2, '1', '.'};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec3), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing BJData high-precision number: invalid number text: 1.", json::parse_error);
+ std::vector<uint8_t> const vec4 = {'H', 2, '1', '0'};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec4), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing BJData size: expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x02", json::parse_error);
+ }
+ }
+ }
+
+ SECTION("string")
+ {
+ SECTION("N = 0..127")
+ {
+ for (size_t N = 0; N <= 127; ++N)
+ {
+ CAPTURE(N)
+
+ // create JSON value with string containing of N * 'x'
+ const auto s = std::string(N, 'x');
+ json const j = s;
+
+ // create expected byte vector
+ std::vector<uint8_t> expected;
+ expected.push_back('S');
+ expected.push_back('i');
+ expected.push_back(static_cast<uint8_t>(N));
+ for (size_t i = 0; i < N; ++i)
+ {
+ expected.push_back('x');
+ }
+
+ // compare result + size
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 3);
+ // check that no null byte is appended
+ if (N > 0)
+ {
+ CHECK(result.back() != '\x00');
+ }
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+
+ SECTION("N = 128..255")
+ {
+ for (size_t N = 128; N <= 255; ++N)
+ {
+ CAPTURE(N)
+
+ // create JSON value with string containing of N * 'x'
+ const auto s = std::string(N, 'x');
+ json const j = s;
+
+ // create expected byte vector
+ std::vector<uint8_t> expected;
+ expected.push_back('S');
+ expected.push_back('U');
+ expected.push_back(static_cast<uint8_t>(N));
+ for (size_t i = 0; i < N; ++i)
+ {
+ expected.push_back('x');
+ }
+
+ // compare result + size
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 3);
+ // check that no null byte is appended
+ CHECK(result.back() != '\x00');
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+
+ SECTION("N = 256..32767")
+ {
+ for (const size_t N :
+ {
+ 256u, 999u, 1025u, 3333u, 2048u, 32767u
+ })
+ {
+ CAPTURE(N)
+
+ // create JSON value with string containing of N * 'x'
+ const auto s = std::string(N, 'x');
+ json const j = s;
+
+ // create expected byte vector (hack: create string first)
+ std::vector<uint8_t> expected(N, 'x');
+ // reverse order of commands, because we insert at begin()
+ expected.insert(expected.begin(), static_cast<uint8_t>((N >> 8) & 0xff));
+ expected.insert(expected.begin(), static_cast<uint8_t>(N & 0xff));
+ expected.insert(expected.begin(), 'I');
+ expected.insert(expected.begin(), 'S');
+
+ // compare result + size
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 4);
+ // check that no null byte is appended
+ CHECK(result.back() != '\x00');
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+
+ SECTION("N = 32768..65535")
+ {
+ for (const size_t N :
+ {
+ 32768u, 55555u, 65535u
+ })
+ {
+ CAPTURE(N)
+
+ // create JSON value with string containing of N * 'x'
+ const auto s = std::string(N, 'x');
+ json const j = s;
+
+ // create expected byte vector (hack: create string first)
+ std::vector<uint8_t> expected(N, 'x');
+ // reverse order of commands, because we insert at begin()
+ expected.insert(expected.begin(), static_cast<uint8_t>((N >> 8) & 0xff));
+ expected.insert(expected.begin(), static_cast<uint8_t>(N & 0xff));
+ expected.insert(expected.begin(), 'u');
+ expected.insert(expected.begin(), 'S');
+
+ // compare result + size
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 4);
+ // check that no null byte is appended
+ CHECK(result.back() != '\x00');
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+
+ SECTION("N = 65536..2147483647")
+ {
+ for (const size_t N :
+ {
+ 65536u, 77777u, 1048576u
+ })
+ {
+ CAPTURE(N)
+
+ // create JSON value with string containing of N * 'x'
+ const auto s = std::string(N, 'x');
+ json const j = s;
+
+ // create expected byte vector (hack: create string first)
+ std::vector<uint8_t> expected(N, 'x');
+ // reverse order of commands, because we insert at begin()
+ expected.insert(expected.begin(), static_cast<uint8_t>((N >> 24) & 0xff));
+ expected.insert(expected.begin(), static_cast<uint8_t>((N >> 16) & 0xff));
+ expected.insert(expected.begin(), static_cast<uint8_t>((N >> 8) & 0xff));
+ expected.insert(expected.begin(), static_cast<uint8_t>(N & 0xff));
+ expected.insert(expected.begin(), 'l');
+ expected.insert(expected.begin(), 'S');
+
+ // compare result + size
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 6);
+ // check that no null byte is appended
+ CHECK(result.back() != '\x00');
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+ }
+
+ SECTION("binary")
+ {
+ for (json::bjdata_version_t bjdata_version :
+ {
+ json::bjdata_version_t::draft2, json::bjdata_version_t::draft3
+ })
+ {
+ CAPTURE(bjdata_version)
+ const bool draft3 = (bjdata_version == json::bjdata_version_t::draft3);
+
+ SECTION("N = 0..127")
+ {
+ for (std::size_t N = 0; N <= 127; ++N)
+ {
+ CAPTURE(N)
+
+ // create JSON value with byte array containing of N * 'x'
+ const auto s = std::vector<std::uint8_t>(N, 'x');
+ json const j = json::binary(s);
+
+ // create expected byte vector
+ std::vector<std::uint8_t> expected;
+ expected.push_back(static_cast<std::uint8_t>('['));
+ if (draft3 || N != 0)
+ {
+ expected.push_back(static_cast<std::uint8_t>('$'));
+ expected.push_back(static_cast<std::uint8_t>(draft3 ? 'B' : 'U'));
+ }
+ expected.push_back(static_cast<std::uint8_t>('#'));
+ expected.push_back(static_cast<std::uint8_t>('i'));
+ expected.push_back(static_cast<std::uint8_t>(N));
+ for (size_t i = 0; i < N; ++i)
+ {
+ expected.push_back(0x78);
+ }
+
+ // compare result + size
+ const auto result = json::to_bjdata(j, true, true, bjdata_version);
+ CHECK(result == expected);
+ if (!draft3 && N == 0)
+ {
+ CHECK(result.size() == N + 4);
+ }
+ else
+ {
+ CHECK(result.size() == N + 6);
+ }
+
+ // check that no null byte is appended
+ if (N > 0)
+ {
+ CHECK(result.back() != '\x00');
+ }
+
+ if (draft3)
+ {
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ else
+ {
+ // roundtrip only works to an array of numbers
+ json j_out = s;
+ CHECK(json::from_bjdata(result) == j_out);
+ CHECK(json::from_bjdata(result, true, false) == j_out);
+ }
+ }
+ }
+
+ SECTION("N = 128..255")
+ {
+ for (std::size_t N = 128; N <= 255; ++N)
+ {
+ CAPTURE(N)
+
+ // create JSON value with byte array containing of N * 'x'
+ const auto s = std::vector<std::uint8_t>(N, 'x');
+ json const j = json::binary(s);
+
+ // create expected byte vector
+ std::vector<uint8_t> expected;
+ expected.push_back(static_cast<std::uint8_t>('['));
+ expected.push_back(static_cast<std::uint8_t>('$'));
+ expected.push_back(static_cast<std::uint8_t>(draft3 ? 'B' : 'U'));
+ expected.push_back(static_cast<std::uint8_t>('#'));
+ expected.push_back(static_cast<std::uint8_t>('U'));
+ expected.push_back(static_cast<std::uint8_t>(N));
+ for (size_t i = 0; i < N; ++i)
+ {
+ expected.push_back(0x78);
+ }
+
+ // compare result + size
+ const auto result = json::to_bjdata(j, true, true, bjdata_version);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 6);
+ // check that no null byte is appended
+ CHECK(result.back() != '\x00');
+
+ if (draft3)
+ {
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ else
+ {
+ // roundtrip only works to an array of numbers
+ json j_out = s;
+ CHECK(json::from_bjdata(result) == j_out);
+ CHECK(json::from_bjdata(result, true, false) == j_out);
+ }
+ }
+ }
+
+ SECTION("N = 256..32767")
+ {
+ for (const std::size_t N :
+ {
+ 256u, 999u, 1025u, 3333u, 2048u, 32767u
+ })
+ {
+ CAPTURE(N)
+
+ // create JSON value with byte array containing of N * 'x'
+ const auto s = std::vector<std::uint8_t>(N, 'x');
+ json const j = json::binary(s);
+
+ // create expected byte vector
+ std::vector<std::uint8_t> expected(N + 7, 'x');
+ expected[0] = '[';
+ expected[1] = '$';
+ expected[2] = draft3 ? 'B' : 'U';
+ expected[3] = '#';
+ expected[4] = 'I';
+ expected[5] = static_cast<std::uint8_t>(N & 0xFF);
+ expected[6] = static_cast<std::uint8_t>((N >> 8) & 0xFF);
+
+ // compare result + size
+ const auto result = json::to_bjdata(j, true, true, bjdata_version);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 7);
+ // check that no null byte is appended
+ CHECK(result.back() != '\x00');
+
+ if (draft3)
+ {
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ else
+ {
+ // roundtrip only works to an array of numbers
+ json j_out = s;
+ CHECK(json::from_bjdata(result) == j_out);
+ CHECK(json::from_bjdata(result, true, false) == j_out);
+ }
+ }
+ }
+
+ SECTION("N = 32768..65535")
+ {
+ for (const std::size_t N :
+ {
+ 32768u, 55555u, 65535u
+ })
+ {
+ CAPTURE(N)
+
+ // create JSON value with byte array containing of N * 'x'
+ const auto s = std::vector<std::uint8_t>(N, 'x');
+ json const j = json::binary(s);
+
+ // create expected byte vector
+ std::vector<std::uint8_t> expected(N + 7, 'x');
+ expected[0] = '[';
+ expected[1] = '$';
+ expected[2] = draft3 ? 'B' : 'U';
+ expected[3] = '#';
+ expected[4] = 'u';
+ expected[5] = static_cast<std::uint8_t>(N & 0xFF);
+ expected[6] = static_cast<std::uint8_t>((N >> 8) & 0xFF);
+
+ // compare result + size
+ const auto result = json::to_bjdata(j, true, true, bjdata_version);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 7);
+ // check that no null byte is appended
+ CHECK(result.back() != '\x00');
+
+ if (draft3)
+ {
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ else
+ {
+ // roundtrip only works to an array of numbers
+ json j_out = s;
+ CHECK(json::from_bjdata(result) == j_out);
+ CHECK(json::from_bjdata(result, true, false) == j_out);
+ }
+ }
+ }
+
+ SECTION("N = 65536..2147483647")
+ {
+ for (const std::size_t N :
+ {
+ 65536u, 77777u, 1048576u
+ })
+ {
+ CAPTURE(N)
+
+ // create JSON value with byte array containing of N * 'x'
+ const auto s = std::vector<std::uint8_t>(N, 'x');
+ json const j = json::binary(s);
+
+ // create expected byte vector
+ std::vector<std::uint8_t> expected(N + 9, 'x');
+ expected[0] = '[';
+ expected[1] = '$';
+ expected[2] = draft3 ? 'B' : 'U';
+ expected[3] = '#';
+ expected[4] = 'l';
+ expected[5] = static_cast<std::uint8_t>(N & 0xFF);
+ expected[6] = static_cast<std::uint8_t>((N >> 8) & 0xFF);
+ expected[7] = static_cast<std::uint8_t>((N >> 16) & 0xFF);
+ expected[8] = static_cast<std::uint8_t>((N >> 24) & 0xFF);
+
+ // compare result + size
+ const auto result = json::to_bjdata(j, true, true, bjdata_version);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 9);
+ // check that no null byte is appended
+ CHECK(result.back() != '\x00');
+
+ if (draft3)
+ {
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ else
+ {
+ // roundtrip only works to an array of numbers
+ json j_out = s;
+ CHECK(json::from_bjdata(result) == j_out);
+ CHECK(json::from_bjdata(result, true, false) == j_out);
+ }
+ }
+ }
+
+ SECTION("Other Serializations")
+ {
+ const std::size_t N = 10;
+ const auto s = std::vector<std::uint8_t>(N, 'x');
+ json const j = json::binary(s);
+
+ SECTION("No Count No Type")
+ {
+ std::vector<uint8_t> expected;
+ expected.push_back(static_cast<std::uint8_t>('['));
+ for (std::size_t i = 0; i < N; ++i)
+ {
+ expected.push_back(static_cast<std::uint8_t>(draft3 ? 'B' : 'U'));
+ expected.push_back(static_cast<std::uint8_t>(0x78));
+ }
+ expected.push_back(static_cast<std::uint8_t>(']'));
+
+ // compare result + size
+ const auto result = json::to_bjdata(j, false, false, bjdata_version);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 12);
+ // check that no null byte is appended
+ CHECK(result.back() != '\x00');
+
+ // roundtrip only works to an array of numbers
+ json j_out = s;
+ CHECK(json::from_bjdata(result) == j_out);
+ CHECK(json::from_bjdata(result, true, false) == j_out);
+ }
+
+ SECTION("Yes Count No Type")
+ {
+ std::vector<std::uint8_t> expected;
+ expected.push_back(static_cast<std::uint8_t>('['));
+ expected.push_back(static_cast<std::uint8_t>('#'));
+ expected.push_back(static_cast<std::uint8_t>('i'));
+ expected.push_back(static_cast<std::uint8_t>(N));
+
+ for (size_t i = 0; i < N; ++i)
+ {
+ expected.push_back(static_cast<std::uint8_t>(draft3 ? 'B' : 'U'));
+ expected.push_back(static_cast<std::uint8_t>(0x78));
+ }
+
+ // compare result + size
+ const auto result = json::to_bjdata(j, true, false, bjdata_version);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 14);
+ // check that no null byte is appended
+ CHECK(result.back() != '\x00');
+
+ // roundtrip only works to an array of numbers
+ json j_out = s;
+ CHECK(json::from_bjdata(result) == j_out);
+ CHECK(json::from_bjdata(result, true, false) == j_out);
+ }
+ }
+ }
+ }
+ SECTION("array")
+ {
+ SECTION("empty")
+ {
+ SECTION("size=false type=false")
+ {
+ json const j = json::array();
+ std::vector<uint8_t> const expected = {'[', ']'};
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+
+ SECTION("size=true type=false")
+ {
+ json const j = json::array();
+ std::vector<uint8_t> const expected = {'[', '#', 'i', 0};
+ const auto result = json::to_bjdata(j, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+
+ SECTION("size=true type=true")
+ {
+ json const j = json::array();
+ std::vector<uint8_t> const expected = {'[', '#', 'i', 0};
+ const auto result = json::to_bjdata(j, true, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+
+ SECTION("[null]")
+ {
+ SECTION("size=false type=false")
+ {
+ json const j = {nullptr};
+ std::vector<uint8_t> const expected = {'[', 'Z', ']'};
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+
+ SECTION("size=true type=false")
+ {
+ json const j = {nullptr};
+ std::vector<uint8_t> const expected = {'[', '#', 'i', 1, 'Z'};
+ const auto result = json::to_bjdata(j, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+
+ SECTION("size=true type=true")
+ {
+ json const j = {nullptr};
+ std::vector<uint8_t> const expected = {'[', '#', 'i', 1, 'Z'};
+ const auto result = json::to_bjdata(j, true, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+
+ SECTION("[1,2,3,4,5]")
+ {
+ SECTION("size=false type=false")
+ {
+ json const j = json::parse("[1,2,3,4,5]");
+ std::vector<uint8_t> const expected = {'[', 'i', 1, 'i', 2, 'i', 3, 'i', 4, 'i', 5, ']'};
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+
+ SECTION("size=true type=false")
+ {
+ json const j = json::parse("[1,2,3,4,5]");
+ std::vector<uint8_t> const expected = {'[', '#', 'i', 5, 'i', 1, 'i', 2, 'i', 3, 'i', 4, 'i', 5};
+ const auto result = json::to_bjdata(j, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+
+ SECTION("size=true type=true")
+ {
+ json const j = json::parse("[1,2,3,4,5]");
+ std::vector<uint8_t> const expected = {'[', '$', 'i', '#', 'i', 5, 1, 2, 3, 4, 5};
+ const auto result = json::to_bjdata(j, true, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+
+ SECTION("[[[[]]]]")
+ {
+ SECTION("size=false type=false")
+ {
+ json const j = json::parse("[[[[]]]]");
+ std::vector<uint8_t> const expected = {'[', '[', '[', '[', ']', ']', ']', ']'};
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+
+ SECTION("size=true type=false")
+ {
+ json const j = json::parse("[[[[]]]]");
+ std::vector<uint8_t> const expected = {'[', '#', 'i', 1, '[', '#', 'i', 1, '[', '#', 'i', 1, '[', '#', 'i', 0};
+ const auto result = json::to_bjdata(j, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+
+ SECTION("size=true type=true")
+ {
+ json const j = json::parse("[[[[]]]]");
+ std::vector<uint8_t> const expected = {'[', '#', 'i', 1, '[', '#', 'i', 1, '[', '#', 'i', 1, '[', '#', 'i', 0};
+ const auto result = json::to_bjdata(j, true, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+
+ SECTION("array with int16_t elements")
+ {
+ SECTION("size=false type=false")
+ {
+ json j(257, nullptr);
+ std::vector<uint8_t> expected(j.size() + 2, 'Z'); // all null
+ expected[0] = '['; // opening array
+ expected[258] = ']'; // closing array
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+
+ SECTION("size=true type=false")
+ {
+ json j(257, nullptr);
+ std::vector<uint8_t> expected(j.size() + 5, 'Z'); // all null
+ expected[0] = '['; // opening array
+ expected[1] = '#'; // array size
+ expected[2] = 'I'; // int16
+ expected[3] = 0x01; // 0x0101, first byte
+ expected[4] = 0x01; // 0x0101, second byte
+ const auto result = json::to_bjdata(j, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+
+ SECTION("array with uint16_t elements")
+ {
+ SECTION("size=false type=false")
+ {
+ json j(32768, nullptr);
+ std::vector<uint8_t> expected(j.size() + 2, 'Z'); // all null
+ expected[0] = '['; // opening array
+ expected[32769] = ']'; // closing array
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+
+ SECTION("size=true type=false")
+ {
+ json j(32768, nullptr);
+ std::vector<uint8_t> expected(j.size() + 5, 'Z'); // all null
+ expected[0] = '['; // opening array
+ expected[1] = '#'; // array size
+ expected[2] = 'u'; // int16
+ expected[3] = 0x00; // 0x0101, first byte
+ expected[4] = 0x80; // 0x0101, second byte
+ const auto result = json::to_bjdata(j, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+
+ SECTION("array with int32_t elements")
+ {
+ SECTION("size=false type=false")
+ {
+ json j(65793, nullptr);
+ std::vector<uint8_t> expected(j.size() + 2, 'Z'); // all null
+ expected[0] = '['; // opening array
+ expected[65794] = ']'; // closing array
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+
+ SECTION("size=true type=false")
+ {
+ json j(65793, nullptr);
+ std::vector<uint8_t> expected(j.size() + 7, 'Z'); // all null
+ expected[0] = '['; // opening array
+ expected[1] = '#'; // array size
+ expected[2] = 'l'; // int32
+ expected[3] = 0x01; // 0x00010101, fourth byte
+ expected[4] = 0x01; // 0x00010101, third byte
+ expected[5] = 0x01; // 0x00010101, second byte
+ expected[6] = 0x00; // 0x00010101, first byte
+ const auto result = json::to_bjdata(j, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+ }
+
+ SECTION("object")
+ {
+ SECTION("empty")
+ {
+ SECTION("size=false type=false")
+ {
+ json const j = json::object();
+ std::vector<uint8_t> const expected = {'{', '}'};
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+
+ SECTION("size=true type=false")
+ {
+ json const j = json::object();
+ std::vector<uint8_t> const expected = {'{', '#', 'i', 0};
+ const auto result = json::to_bjdata(j, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+
+ SECTION("size=true type=true")
+ {
+ json const j = json::object();
+ std::vector<uint8_t> const expected = {'{', '#', 'i', 0};
+ const auto result = json::to_bjdata(j, true, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+
+ SECTION("{\"\":null}")
+ {
+ SECTION("size=false type=false")
+ {
+ json const j = {{"", nullptr}};
+ std::vector<uint8_t> const expected = {'{', 'i', 0, 'Z', '}'};
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+
+ SECTION("size=true type=false")
+ {
+ json const j = {{"", nullptr}};
+ std::vector<uint8_t> const expected = {'{', '#', 'i', 1, 'i', 0, 'Z'};
+ const auto result = json::to_bjdata(j, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+
+ SECTION("{\"a\": {\"b\": {\"c\": {}}}}")
+ {
+ SECTION("size=false type=false")
+ {
+ json const j = json::parse(R"({"a": {"b": {"c": {}}}})");
+ std::vector<uint8_t> const expected =
+ {
+ '{', 'i', 1, 'a', '{', 'i', 1, 'b', '{', 'i', 1, 'c', '{', '}', '}', '}', '}'
+ };
+ const auto result = json::to_bjdata(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+
+ SECTION("size=true type=false")
+ {
+ json const j = json::parse(R"({"a": {"b": {"c": {}}}})");
+ std::vector<uint8_t> const expected =
+ {
+ '{', '#', 'i', 1, 'i', 1, 'a', '{', '#', 'i', 1, 'i', 1, 'b', '{', '#', 'i', 1, 'i', 1, 'c', '{', '#', 'i', 0
+ };
+ const auto result = json::to_bjdata(j, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+
+ SECTION("size=true type=true ignore object type marker")
+ {
+ json const j = json::parse(R"({"a": {"b": {"c": {}}}})");
+ std::vector<uint8_t> const expected =
+ {
+ '{', '#', 'i', 1, 'i', 1, 'a', '{', '#', 'i', 1, 'i', 1, 'b', '{', '#', 'i', 1, 'i', 1, 'c', '{', '#', 'i', 0
+ };
+ const auto result = json::to_bjdata(j, true, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bjdata(result) == j);
+ CHECK(json::from_bjdata(result, true, false) == j);
+ }
+ }
+ }
+ }
+
+ SECTION("errors")
+ {
+ SECTION("strict mode")
+ {
+ std::vector<uint8_t> const vec = {'Z', 'Z'};
+ SECTION("non-strict mode")
+ {
+ const auto result = json::from_bjdata(vec, false);
+ CHECK(result == json());
+ }
+
+ SECTION("strict mode")
+ {
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec),
+ "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BJData value: expected end of input; last byte: 0x5A", json::parse_error&);
+ }
+ }
+ }
+
+ SECTION("SAX aborts")
+ {
+ SECTION("start_array()")
+ {
+ std::vector<uint8_t> const v = {'[', 'T', 'F', ']'};
+ SaxCountdown scp(0);
+ CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
+ }
+
+ SECTION("start_object()")
+ {
+ std::vector<uint8_t> const v = {'{', 'i', 3, 'f', 'o', 'o', 'F', '}'};
+ SaxCountdown scp(0);
+ CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
+ }
+
+ SECTION("key() in object")
+ {
+ std::vector<uint8_t> const v = {'{', 'i', 3, 'f', 'o', 'o', 'F', '}'};
+ SaxCountdown scp(1);
+ CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
+ }
+
+ SECTION("start_array(len)")
+ {
+ std::vector<uint8_t> const v = {'[', '#', 'i', '2', 'T', 'F'};
+ SaxCountdown scp(0);
+ CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
+ }
+
+ SECTION("start_object(len)")
+ {
+ std::vector<uint8_t> const v = {'{', '#', 'i', '1', 3, 'f', 'o', 'o', 'F'};
+ SaxCountdown scp(0);
+ CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
+ }
+
+ SECTION("key() in object with length")
+ {
+ std::vector<uint8_t> const v = {'{', 'i', 3, 'f', 'o', 'o', 'F', '}'};
+ SaxCountdown scp(1);
+ CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
+ }
+
+ SECTION("start_array() in ndarray _ArraySize_")
+ {
+ std::vector<uint8_t> const v = {'[', '$', 'i', '#', '[', '$', 'i', '#', 'i', 2, 2, 1, 1, 2};
+ SaxCountdown scp(2);
+ CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
+ }
+
+ SECTION("number_integer() in ndarray _ArraySize_")
+ {
+ std::vector<uint8_t> const v = {'[', '$', 'U', '#', '[', '$', 'i', '#', 'i', 2, 2, 1, 1, 2};
+ SaxCountdown scp(3);
+ CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
+ }
+
+ SECTION("key() in ndarray _ArrayType_")
+ {
+ std::vector<uint8_t> const v = {'[', '$', 'U', '#', '[', '$', 'U', '#', 'i', 2, 2, 2, 1, 2, 3, 4};
+ SaxCountdown scp(6);
+ CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
+ }
+
+ SECTION("string() in ndarray _ArrayType_")
+ {
+ std::vector<uint8_t> const v = {'[', '$', 'U', '#', '[', '$', 'U', '#', 'i', 2, 2, 2, 1, 2, 3, 4};
+ SaxCountdown scp(7);
+ CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
+ }
+
+ SECTION("key() in ndarray _ArrayData_")
+ {
+ std::vector<uint8_t> const v = {'[', '$', 'U', '#', '[', '$', 'U', '#', 'i', 2, 2, 2, 1, 2, 3, 4};
+ SaxCountdown scp(8);
+ CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
+ }
+
+ SECTION("string() in ndarray _ArrayData_")
+ {
+ std::vector<uint8_t> const v = {'[', '$', 'U', '#', '[', '$', 'U', '#', 'i', 2, 2, 2, 1, 2, 3, 4};
+ SaxCountdown scp(9);
+ CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
+ }
+
+ SECTION("string() in ndarray _ArrayType_")
+ {
+ std::vector<uint8_t> const v = {'[', '$', 'U', '#', '[', '$', 'i', '#', 'i', 2, 3, 2, 6, 5, 4, 3, 2, 1};
+ SaxCountdown scp(11);
+ CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
+ }
+
+ SECTION("start_array() in ndarray _ArrayData_")
+ {
+ std::vector<uint8_t> const v = {'[', '$', 'U', '#', '[', 'i', 2, 'i', 3, ']', 6, 5, 4, 3, 2, 1};
+ SaxCountdown scp(13);
+ CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
+ }
+ }
+
+ SECTION("parsing values")
+ {
+ SECTION("strings")
+ {
+ // create a single-character string for all number types
+ std::vector<uint8_t> s_i = {'S', 'i', 1, 'a'};
+ std::vector<uint8_t> const s_U = {'S', 'U', 1, 'a'};
+ std::vector<uint8_t> const s_I = {'S', 'I', 1, 0, 'a'};
+ std::vector<uint8_t> const s_u = {'S', 'u', 1, 0, 'a'};
+ std::vector<uint8_t> const s_l = {'S', 'l', 1, 0, 0, 0, 'a'};
+ std::vector<uint8_t> const s_m = {'S', 'm', 1, 0, 0, 0, 'a'};
+ std::vector<uint8_t> const s_L = {'S', 'L', 1, 0, 0, 0, 0, 0, 0, 0, 'a'};
+ std::vector<uint8_t> const s_M = {'S', 'M', 1, 0, 0, 0, 0, 0, 0, 0, 'a'};
+
+ // check if string is parsed correctly to "a"
+ CHECK(json::from_bjdata(s_i) == "a");
+ CHECK(json::from_bjdata(s_U) == "a");
+ CHECK(json::from_bjdata(s_I) == "a");
+ CHECK(json::from_bjdata(s_u) == "a");
+ CHECK(json::from_bjdata(s_l) == "a");
+ CHECK(json::from_bjdata(s_m) == "a");
+ CHECK(json::from_bjdata(s_L) == "a");
+ CHECK(json::from_bjdata(s_M) == "a");
+
+ // roundtrip: output should be optimized
+ CHECK(json::to_bjdata(json::from_bjdata(s_i)) == s_i);
+ CHECK(json::to_bjdata(json::from_bjdata(s_U)) == s_i);
+ CHECK(json::to_bjdata(json::from_bjdata(s_I)) == s_i);
+ CHECK(json::to_bjdata(json::from_bjdata(s_u)) == s_i);
+ CHECK(json::to_bjdata(json::from_bjdata(s_l)) == s_i);
+ CHECK(json::to_bjdata(json::from_bjdata(s_m)) == s_i);
+ CHECK(json::to_bjdata(json::from_bjdata(s_L)) == s_i);
+ CHECK(json::to_bjdata(json::from_bjdata(s_M)) == s_i);
+ }
+
+ SECTION("number")
+ {
+ SECTION("float")
+ {
+ // float32
+ std::vector<uint8_t> const v_d = {'d', 0xd0, 0x0f, 0x49, 0x40};
+ CHECK(json::from_bjdata(v_d) == 3.14159f);
+
+ // float64
+ std::vector<uint8_t> const v_D = {'D', 0x6e, 0x86, 0x1b, 0xf0, 0xf9, 0x21, 0x09, 0x40};
+ CHECK(json::from_bjdata(v_D) == 3.14159);
+
+ // float32 is serialized as float64 as the library does not support float32
+ CHECK(json::to_bjdata(json::from_bjdata(v_d)) == json::to_bjdata(3.14159f));
+ }
+ }
+
+ SECTION("array")
+ {
+ SECTION("optimized version (length only)")
+ {
+ // create vector with two elements of the same type
+ std::vector<uint8_t> const v_TU = {'[', '#', 'U', 2, 'T', 'T'};
+ std::vector<uint8_t> const v_T = {'[', '#', 'i', 2, 'T', 'T'};
+ std::vector<uint8_t> const v_F = {'[', '#', 'i', 2, 'F', 'F'};
+ std::vector<uint8_t> const v_Z = {'[', '#', 'i', 2, 'Z', 'Z'};
+ std::vector<uint8_t> const v_i = {'[', '#', 'i', 2, 'i', 0x7F, 'i', 0x7F};
+ std::vector<uint8_t> const v_U = {'[', '#', 'i', 2, 'U', 0xFF, 'U', 0xFF};
+ std::vector<uint8_t> const v_I = {'[', '#', 'i', 2, 'I', 0xFF, 0x7F, 'I', 0xFF, 0x7F};
+ std::vector<uint8_t> const v_u = {'[', '#', 'i', 2, 'u', 0x0F, 0xA7, 'u', 0x0F, 0xA7};
+ std::vector<uint8_t> const v_l = {'[', '#', 'i', 2, 'l', 0xFF, 0xFF, 0xFF, 0x7F, 'l', 0xFF, 0xFF, 0xFF, 0x7F};
+ std::vector<uint8_t> const v_m = {'[', '#', 'i', 2, 'm', 0xFF, 0xC9, 0x9A, 0xBB, 'm', 0xFF, 0xC9, 0x9A, 0xBB};
+ std::vector<uint8_t> const v_L = {'[', '#', 'i', 2, 'L', 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 'L', 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F};
+ std::vector<uint8_t> const v_M = {'[', '#', 'i', 2, 'M', 0xFF, 0xFF, 0x63, 0xA7, 0xB3, 0xB6, 0xE0, 0x8D, 'M', 0xFF, 0xFF, 0x63, 0xA7, 0xB3, 0xB6, 0xE0, 0x8D};
+ std::vector<uint8_t> const v_D = {'[', '#', 'i', 2, 'D', 0x4a, 0xd8, 0x12, 0x4d, 0xfb, 0x21, 0x09, 0x40, 'D', 0x4a, 0xd8, 0x12, 0x4d, 0xfb, 0x21, 0x09, 0x40};
+ std::vector<uint8_t> const v_S = {'[', '#', 'i', 2, 'S', 'i', 1, 'a', 'S', 'i', 1, 'a'};
+ std::vector<uint8_t> const v_C = {'[', '#', 'i', 2, 'C', 'a', 'C', 'a'};
+ std::vector<uint8_t> const v_B = {'[', '#', 'i', 2, 'B', 0xFF, 'B', 0xFF};
+
+ // check if vector is parsed correctly
+ CHECK(json::from_bjdata(v_TU) == json({true, true}));
+ CHECK(json::from_bjdata(v_T) == json({true, true}));
+ CHECK(json::from_bjdata(v_F) == json({false, false}));
+ CHECK(json::from_bjdata(v_Z) == json({nullptr, nullptr}));
+ CHECK(json::from_bjdata(v_i) == json({127, 127}));
+ CHECK(json::from_bjdata(v_U) == json({255, 255}));
+ CHECK(json::from_bjdata(v_I) == json({32767, 32767}));
+ CHECK(json::from_bjdata(v_u) == json({42767, 42767}));
+ CHECK(json::from_bjdata(v_l) == json({2147483647, 2147483647}));
+ CHECK(json::from_bjdata(v_m) == json({3147483647, 3147483647}));
+ CHECK(json::from_bjdata(v_L) == json({9223372036854775807, 9223372036854775807}));
+ CHECK(json::from_bjdata(v_M) == json({10223372036854775807ull, 10223372036854775807ull}));
+ CHECK(json::from_bjdata(v_D) == json({3.1415926, 3.1415926}));
+ CHECK(json::from_bjdata(v_S) == json({"a", "a"}));
+ CHECK(json::from_bjdata(v_C) == json({"a", "a"}));
+ CHECK(json::from_bjdata(v_B) == json({255, 255}));
+
+ // roundtrip: output should be optimized
+ CHECK(json::to_bjdata(json::from_bjdata(v_T), true) == v_T);
+ CHECK(json::to_bjdata(json::from_bjdata(v_F), true) == v_F);
+ CHECK(json::to_bjdata(json::from_bjdata(v_Z), true) == v_Z);
+ CHECK(json::to_bjdata(json::from_bjdata(v_i), true) == v_i);
+ CHECK(json::to_bjdata(json::from_bjdata(v_U), true) == v_U);
+ CHECK(json::to_bjdata(json::from_bjdata(v_I), true) == v_I);
+ CHECK(json::to_bjdata(json::from_bjdata(v_u), true) == v_u);
+ CHECK(json::to_bjdata(json::from_bjdata(v_l), true) == v_l);
+ CHECK(json::to_bjdata(json::from_bjdata(v_m), true) == v_m);
+ CHECK(json::to_bjdata(json::from_bjdata(v_L), true) == v_L);
+ CHECK(json::to_bjdata(json::from_bjdata(v_M), true) == v_M);
+ CHECK(json::to_bjdata(json::from_bjdata(v_D), true) == v_D);
+ CHECK(json::to_bjdata(json::from_bjdata(v_S), true) == v_S);
+ CHECK(json::to_bjdata(json::from_bjdata(v_C), true) == v_S); // char is serialized to string
+ CHECK(json::to_bjdata(json::from_bjdata(v_B), true) == v_U); // byte is serialized to uint8
+ }
+
+ SECTION("optimized version (type and length)")
+ {
+ // create vector with two elements of the same type
+ std::vector<uint8_t> const v_i = {'[', '$', 'i', '#', 'i', 2, 0x7F, 0x7F};
+ std::vector<uint8_t> const v_U = {'[', '$', 'U', '#', 'i', 2, 0xFF, 0xFF};
+ std::vector<uint8_t> const v_I = {'[', '$', 'I', '#', 'i', 2, 0xFF, 0x7F, 0xFF, 0x7F};
+ std::vector<uint8_t> const v_u = {'[', '$', 'u', '#', 'i', 2, 0x0F, 0xA7, 0x0F, 0xA7};
+ std::vector<uint8_t> const v_l = {'[', '$', 'l', '#', 'i', 2, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0x7F};
+ std::vector<uint8_t> const v_m = {'[', '$', 'm', '#', 'i', 2, 0xFF, 0xC9, 0x9A, 0xBB, 0xFF, 0xC9, 0x9A, 0xBB};
+ std::vector<uint8_t> const v_L = {'[', '$', 'L', '#', 'i', 2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F};
+ std::vector<uint8_t> const v_M = {'[', '$', 'M', '#', 'i', 2, 0xFF, 0xFF, 0x63, 0xA7, 0xB3, 0xB6, 0xE0, 0x8D, 0xFF, 0xFF, 0x63, 0xA7, 0xB3, 0xB6, 0xE0, 0x8D};
+ std::vector<uint8_t> const v_D = {'[', '$', 'D', '#', 'i', 2, 0x4a, 0xd8, 0x12, 0x4d, 0xfb, 0x21, 0x09, 0x40, 0x4a, 0xd8, 0x12, 0x4d, 0xfb, 0x21, 0x09, 0x40};
+ std::vector<uint8_t> const v_S = {'[', '#', 'i', 2, 'S', 'i', 1, 'a', 'S', 'i', 1, 'a'};
+ std::vector<uint8_t> const v_C = {'[', '$', 'C', '#', 'i', 2, 'a', 'a'};
+ std::vector<uint8_t> const v_B = {'[', '$', 'B', '#', 'i', 2, 0xFF, 0xFF};
+
+ // check if vector is parsed correctly
+ CHECK(json::from_bjdata(v_i) == json({127, 127}));
+ CHECK(json::from_bjdata(v_U) == json({255, 255}));
+ CHECK(json::from_bjdata(v_I) == json({32767, 32767}));
+ CHECK(json::from_bjdata(v_u) == json({42767, 42767}));
+ CHECK(json::from_bjdata(v_l) == json({2147483647, 2147483647}));
+ CHECK(json::from_bjdata(v_m) == json({3147483647, 3147483647}));
+ CHECK(json::from_bjdata(v_L) == json({9223372036854775807, 9223372036854775807}));
+ CHECK(json::from_bjdata(v_M) == json({10223372036854775807ull, 10223372036854775807ull}));
+ CHECK(json::from_bjdata(v_D) == json({3.1415926, 3.1415926}));
+ CHECK(json::from_bjdata(v_S) == json({"a", "a"}));
+ CHECK(json::from_bjdata(v_C) == json({"a", "a"}));
+ CHECK(json::from_bjdata(v_B) == json::binary(std::vector<uint8_t>({static_cast<uint8_t>(255), static_cast<uint8_t>(255)})));
+
+ // roundtrip: output should be optimized
+ std::vector<uint8_t> const v_empty = {'[', '#', 'i', 0};
+ CHECK(json::to_bjdata(json::from_bjdata(v_i), true, true) == v_i);
+ CHECK(json::to_bjdata(json::from_bjdata(v_U), true, true) == v_U);
+ CHECK(json::to_bjdata(json::from_bjdata(v_I), true, true) == v_I);
+ CHECK(json::to_bjdata(json::from_bjdata(v_u), true, true) == v_u);
+ CHECK(json::to_bjdata(json::from_bjdata(v_l), true, true) == v_l);
+ CHECK(json::to_bjdata(json::from_bjdata(v_m), true, true) == v_m);
+ CHECK(json::to_bjdata(json::from_bjdata(v_L), true, true) == v_L);
+ CHECK(json::to_bjdata(json::from_bjdata(v_M), true, true) == v_M);
+ CHECK(json::to_bjdata(json::from_bjdata(v_D), true, true) == v_D);
+ CHECK(json::to_bjdata(json::from_bjdata(v_S), true, true) == v_S);
+ CHECK(json::to_bjdata(json::from_bjdata(v_C), true, true) == v_S); // char is serialized to string
+ CHECK(json::to_bjdata(json::from_bjdata(v_B), true, true, json::bjdata_version_t::draft2) == v_U);
+ CHECK(json::to_bjdata(json::from_bjdata(v_B), true, true, json::bjdata_version_t::draft3) == v_B);
+ }
+
+ SECTION("optimized ndarray (type and vector-size as optimized 1D array)")
+ {
+ // create vector with two elements of the same type
+ std::vector<uint8_t> const v_0 = {'[', '$', 'i', '#', '[', '$', 'i', '#', 'i', 1, 0};
+ std::vector<uint8_t> const v_1 = {'[', '$', 'i', '#', '[', '$', 'i', '#', 'i', 1, 2, 0x7F, 0x7F};
+ std::vector<uint8_t> const v_i = {'[', '$', 'i', '#', '[', '$', 'i', '#', 'i', 2, 1, 2, 0x7F, 0x7F};
+ std::vector<uint8_t> const v_U = {'[', '$', 'U', '#', '[', '$', 'i', '#', 'i', 2, 1, 2, 0xFF, 0xFF};
+ std::vector<uint8_t> const v_I = {'[', '$', 'I', '#', '[', '$', 'i', '#', 'i', 2, 1, 2, 0xFF, 0x7F, 0xFF, 0x7F};
+ std::vector<uint8_t> const v_u = {'[', '$', 'u', '#', '[', '$', 'i', '#', 'i', 2, 1, 2, 0x0F, 0xA7, 0x0F, 0xA7};
+ std::vector<uint8_t> const v_l = {'[', '$', 'l', '#', '[', '$', 'i', '#', 'i', 2, 1, 2, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0x7F};
+ std::vector<uint8_t> const v_m = {'[', '$', 'm', '#', '[', '$', 'i', '#', 'i', 2, 1, 2, 0xFF, 0xC9, 0x9A, 0xBB, 0xFF, 0xC9, 0x9A, 0xBB};
+ std::vector<uint8_t> const v_L = {'[', '$', 'L', '#', '[', '$', 'i', '#', 'i', 2, 1, 2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F};
+ std::vector<uint8_t> const v_M = {'[', '$', 'M', '#', '[', '$', 'i', '#', 'i', 2, 1, 2, 0xFF, 0xFF, 0x63, 0xA7, 0xB3, 0xB6, 0xE0, 0x8D, 0xFF, 0xFF, 0x63, 0xA7, 0xB3, 0xB6, 0xE0, 0x8D};
+ std::vector<uint8_t> const v_D = {'[', '$', 'D', '#', '[', '$', 'i', '#', 'i', 2, 1, 2, 0x4a, 0xd8, 0x12, 0x4d, 0xfb, 0x21, 0x09, 0x40, 0x4a, 0xd8, 0x12, 0x4d, 0xfb, 0x21, 0x09, 0x40};
+ std::vector<uint8_t> const v_S = {'[', '#', '[', '$', 'i', '#', 'i', 2, 1, 2, 'S', 'i', 1, 'a', 'S', 'i', 1, 'a'};
+ std::vector<uint8_t> const v_C = {'[', '$', 'C', '#', '[', '$', 'i', '#', 'i', 2, 1, 2, 'a', 'a'};
+ std::vector<uint8_t> const v_B = {'[', '$', 'B', '#', '[', '$', 'i', '#', 'i', 2, 1, 2, 0xFF, 0xFF};
+
+ // check if vector is parsed correctly
+ CHECK(json::from_bjdata(v_0) == json::array());
+ CHECK(json::from_bjdata(v_1) == json({127, 127}));
+ CHECK(json::from_bjdata(v_i) == json({127, 127}));
+ CHECK(json::from_bjdata(v_U) == json({255, 255}));
+ CHECK(json::from_bjdata(v_I) == json({32767, 32767}));
+ CHECK(json::from_bjdata(v_u) == json({42767, 42767}));
+ CHECK(json::from_bjdata(v_l) == json({2147483647, 2147483647}));
+ CHECK(json::from_bjdata(v_m) == json({3147483647, 3147483647}));
+ CHECK(json::from_bjdata(v_L) == json({9223372036854775807, 9223372036854775807}));
+ CHECK(json::from_bjdata(v_M) == json({10223372036854775807ull, 10223372036854775807ull}));
+ CHECK(json::from_bjdata(v_D) == json({3.1415926, 3.1415926}));
+ CHECK(json::from_bjdata(v_S) == json({"a", "a"}));
+ CHECK(json::from_bjdata(v_C) == json({"a", "a"}));
+ CHECK(json::from_bjdata(v_B) == json::binary(std::vector<uint8_t>({static_cast<uint8_t>(255), static_cast<uint8_t>(255)})));
+ }
+
+ SECTION("optimized ndarray (type and vector-size ndarray with JData annotations)")
+ {
+ // create vector with 0, 1, 2 elements of the same type
+ std::vector<uint8_t> const v_e = {'[', '$', 'U', '#', '[', '$', 'i', '#', 'i', 2, 2, 1, 0xFE, 0xFF};
+ std::vector<uint8_t> const v_U = {'[', '$', 'U', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
+ std::vector<uint8_t> const v_i = {'[', '$', 'i', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
+ std::vector<uint8_t> const v_u = {'[', '$', 'u', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00};
+ std::vector<uint8_t> const v_I = {'[', '$', 'I', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00};
+ std::vector<uint8_t> const v_m = {'[', '$', 'm', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00};
+ std::vector<uint8_t> const v_l = {'[', '$', 'l', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00};
+ std::vector<uint8_t> const v_M = {'[', '$', 'M', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ std::vector<uint8_t> const v_L = {'[', '$', 'L', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ std::vector<uint8_t> const v_d = {'[', '$', 'd', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0xA0, 0x40, 0x00, 0x00, 0xC0, 0x40};
+ std::vector<uint8_t> const v_D = {'[', '$', 'D', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x40};
+ std::vector<uint8_t> const v_C = {'[', '$', 'C', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 'a', 'b', 'c', 'd', 'e', 'f'};
+ std::vector<uint8_t> const v_B = {'[', '$', 'B', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
+
+ // check if vector is parsed correctly
+ CHECK(json::from_bjdata(v_e) == json({{"_ArrayData_", {254, 255}}, {"_ArraySize_", {2, 1}}, {"_ArrayType_", "uint8"}}));
+ CHECK(json::from_bjdata(v_U) == json({{"_ArrayData_", {1, 2, 3, 4, 5, 6}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "uint8"}}));
+ CHECK(json::from_bjdata(v_i) == json({{"_ArrayData_", {1, 2, 3, 4, 5, 6}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "int8"}}));
+ CHECK(json::from_bjdata(v_i) == json({{"_ArrayData_", {1, 2, 3, 4, 5, 6}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "int8"}}));
+ CHECK(json::from_bjdata(v_u) == json({{"_ArrayData_", {1, 2, 3, 4, 5, 6}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "uint16"}}));
+ CHECK(json::from_bjdata(v_I) == json({{"_ArrayData_", {1, 2, 3, 4, 5, 6}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "int16"}}));
+ CHECK(json::from_bjdata(v_m) == json({{"_ArrayData_", {1, 2, 3, 4, 5, 6}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "uint32"}}));
+ CHECK(json::from_bjdata(v_l) == json({{"_ArrayData_", {1, 2, 3, 4, 5, 6}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "int32"}}));
+ CHECK(json::from_bjdata(v_M) == json({{"_ArrayData_", {1, 2, 3, 4, 5, 6}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "uint64"}}));
+ CHECK(json::from_bjdata(v_L) == json({{"_ArrayData_", {1, 2, 3, 4, 5, 6}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "int64"}}));
+ CHECK(json::from_bjdata(v_d) == json({{"_ArrayData_", {1.f, 2.f, 3.f, 4.f, 5.f, 6.f}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "single"}}));
+ CHECK(json::from_bjdata(v_D) == json({{"_ArrayData_", {1., 2., 3., 4., 5., 6.}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "double"}}));
+ CHECK(json::from_bjdata(v_C) == json({{"_ArrayData_", {'a', 'b', 'c', 'd', 'e', 'f'}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "char"}}));
+ CHECK(json::from_bjdata(v_B) == json({{"_ArrayData_", {1, 2, 3, 4, 5, 6}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "byte"}}));
+
+ // roundtrip: output should be optimized
+ CHECK(json::to_bjdata(json::from_bjdata(v_e), true, true) == v_e);
+ CHECK(json::to_bjdata(json::from_bjdata(v_U), true, true) == v_U);
+ CHECK(json::to_bjdata(json::from_bjdata(v_i), true, true) == v_i);
+ CHECK(json::to_bjdata(json::from_bjdata(v_u), true, true) == v_u);
+ CHECK(json::to_bjdata(json::from_bjdata(v_I), true, true) == v_I);
+ CHECK(json::to_bjdata(json::from_bjdata(v_m), true, true) == v_m);
+ CHECK(json::to_bjdata(json::from_bjdata(v_l), true, true) == v_l);
+ CHECK(json::to_bjdata(json::from_bjdata(v_M), true, true) == v_M);
+ CHECK(json::to_bjdata(json::from_bjdata(v_L), true, true) == v_L);
+ CHECK(json::to_bjdata(json::from_bjdata(v_d), true, true) == v_d);
+ CHECK(json::to_bjdata(json::from_bjdata(v_D), true, true) == v_D);
+ CHECK(json::to_bjdata(json::from_bjdata(v_C), true, true) == v_C);
+ CHECK(json::to_bjdata(json::from_bjdata(v_B), true, true) == v_B);
+ }
+
+ SECTION("optimized ndarray (type and vector-size as 1D array)")
+ {
+ // create vector with two elements of the same type
+ std::vector<uint8_t> const v_0 = {'[', '$', 'i', '#', '[', ']'};
+ std::vector<uint8_t> const v_E = {'[', '$', 'i', '#', '[', 'i', 2, 'i', 0, ']'};
+ std::vector<uint8_t> const v_i = {'[', '$', 'i', '#', '[', 'i', 1, 'i', 2, ']', 0x7F, 0x7F};
+ std::vector<uint8_t> const v_U = {'[', '$', 'U', '#', '[', 'i', 1, 'i', 2, ']', 0xFF, 0xFF};
+ std::vector<uint8_t> const v_I = {'[', '$', 'I', '#', '[', 'i', 1, 'i', 2, ']', 0xFF, 0x7F, 0xFF, 0x7F};
+ std::vector<uint8_t> const v_u = {'[', '$', 'u', '#', '[', 'i', 1, 'i', 2, ']', 0x0F, 0xA7, 0x0F, 0xA7};
+ std::vector<uint8_t> const v_l = {'[', '$', 'l', '#', '[', 'i', 1, 'i', 2, ']', 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0x7F};
+ std::vector<uint8_t> const v_m = {'[', '$', 'm', '#', '[', 'i', 1, 'i', 2, ']', 0xFF, 0xC9, 0x9A, 0xBB, 0xFF, 0xC9, 0x9A, 0xBB};
+ std::vector<uint8_t> const v_L = {'[', '$', 'L', '#', '[', 'i', 1, 'i', 2, ']', 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F};
+ std::vector<uint8_t> const v_M = {'[', '$', 'M', '#', '[', 'i', 1, 'i', 2, ']', 0xFF, 0xFF, 0x63, 0xA7, 0xB3, 0xB6, 0xE0, 0x8D, 0xFF, 0xFF, 0x63, 0xA7, 0xB3, 0xB6, 0xE0, 0x8D};
+ std::vector<uint8_t> const v_D = {'[', '$', 'D', '#', '[', 'i', 1, 'i', 2, ']', 0x4a, 0xd8, 0x12, 0x4d, 0xfb, 0x21, 0x09, 0x40, 0x4a, 0xd8, 0x12, 0x4d, 0xfb, 0x21, 0x09, 0x40};
+ std::vector<uint8_t> const v_S = {'[', '#', '[', 'i', 1, 'i', 2, ']', 'S', 'i', 1, 'a', 'S', 'i', 1, 'a'};
+ std::vector<uint8_t> const v_C = {'[', '$', 'C', '#', '[', 'i', 1, 'i', 2, ']', 'a', 'a'};
+ std::vector<uint8_t> const v_B = {'[', '$', 'B', '#', '[', 'i', 1, 'i', 2, ']', 0xFF, 0xFF};
+ std::vector<uint8_t> const v_R = {'[', '#', '[', 'i', 2, ']', 'i', 6, 'U', 7};
+
+ // check if vector is parsed correctly
+ CHECK(json::from_bjdata(v_0) == json::array());
+ CHECK(json::from_bjdata(v_E) == json::array());
+ CHECK(json::from_bjdata(v_i) == json({127, 127}));
+ CHECK(json::from_bjdata(v_U) == json({255, 255}));
+ CHECK(json::from_bjdata(v_I) == json({32767, 32767}));
+ CHECK(json::from_bjdata(v_u) == json({42767, 42767}));
+ CHECK(json::from_bjdata(v_l) == json({2147483647, 2147483647}));
+ CHECK(json::from_bjdata(v_m) == json({3147483647, 3147483647}));
+ CHECK(json::from_bjdata(v_L) == json({9223372036854775807, 9223372036854775807}));
+ CHECK(json::from_bjdata(v_M) == json({10223372036854775807ull, 10223372036854775807ull}));
+ CHECK(json::from_bjdata(v_D) == json({3.1415926, 3.1415926}));
+ CHECK(json::from_bjdata(v_S) == json({"a", "a"}));
+ CHECK(json::from_bjdata(v_C) == json({"a", "a"}));
+ CHECK(json::from_bjdata(v_B) == json::binary(std::vector<uint8_t>({static_cast<uint8_t>(255), static_cast<uint8_t>(255)})));
+ CHECK(json::from_bjdata(v_R) == json({6, 7}));
+ }
+
+ SECTION("optimized ndarray (type and vector-size as size-optimized array)")
+ {
+ // create vector with two elements of the same type
+ std::vector<uint8_t> const v_i = {'[', '$', 'i', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2, 0x7F, 0x7F};
+ std::vector<uint8_t> const v_U = {'[', '$', 'U', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2, 0xFF, 0xFF};
+ std::vector<uint8_t> const v_I = {'[', '$', 'I', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2, 0xFF, 0x7F, 0xFF, 0x7F};
+ std::vector<uint8_t> const v_u = {'[', '$', 'u', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2, 0x0F, 0xA7, 0x0F, 0xA7};
+ std::vector<uint8_t> const v_l = {'[', '$', 'l', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0x7F};
+ std::vector<uint8_t> const v_m = {'[', '$', 'm', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2, 0xFF, 0xC9, 0x9A, 0xBB, 0xFF, 0xC9, 0x9A, 0xBB};
+ std::vector<uint8_t> const v_L = {'[', '$', 'L', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F};
+ std::vector<uint8_t> const v_M = {'[', '$', 'M', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2, 0xFF, 0xFF, 0x63, 0xA7, 0xB3, 0xB6, 0xE0, 0x8D, 0xFF, 0xFF, 0x63, 0xA7, 0xB3, 0xB6, 0xE0, 0x8D};
+ std::vector<uint8_t> const v_D = {'[', '$', 'D', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2, 0x4a, 0xd8, 0x12, 0x4d, 0xfb, 0x21, 0x09, 0x40, 0x4a, 0xd8, 0x12, 0x4d, 0xfb, 0x21, 0x09, 0x40};
+ std::vector<uint8_t> const v_S = {'[', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2, 'S', 'i', 1, 'a', 'S', 'i', 1, 'a'};
+ std::vector<uint8_t> const v_C = {'[', '$', 'C', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2, 'a', 'a'};
+ std::vector<uint8_t> const v_B = {'[', '$', 'B', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2, 0xFF, 0xFF};
+
+ // check if vector is parsed correctly
+ CHECK(json::from_bjdata(v_i) == json({127, 127}));
+ CHECK(json::from_bjdata(v_U) == json({255, 255}));
+ CHECK(json::from_bjdata(v_I) == json({32767, 32767}));
+ CHECK(json::from_bjdata(v_u) == json({42767, 42767}));
+ CHECK(json::from_bjdata(v_l) == json({2147483647, 2147483647}));
+ CHECK(json::from_bjdata(v_m) == json({3147483647, 3147483647}));
+ CHECK(json::from_bjdata(v_L) == json({9223372036854775807, 9223372036854775807}));
+ CHECK(json::from_bjdata(v_M) == json({10223372036854775807ull, 10223372036854775807ull}));
+ CHECK(json::from_bjdata(v_D) == json({3.1415926, 3.1415926}));
+ CHECK(json::from_bjdata(v_S) == json({"a", "a"}));
+ CHECK(json::from_bjdata(v_C) == json({"a", "a"}));
+ CHECK(json::from_bjdata(v_B) == json::binary(std::vector<uint8_t>({static_cast<uint8_t>(255), static_cast<uint8_t>(255)})));
+ }
+
+ SECTION("invalid ndarray annotations remains as object")
+ {
+ // check if invalid ND array annotations stay as object
+ json j_type = json({{"_ArrayData_", {1, 2, 3, 4, 5, 6}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "invalidtype"}});
+ json j_size = json({{"_ArrayData_", {1, 2, 3, 4, 5}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "uint8"}});
+
+ // roundtrip: output should stay as object
+ CHECK(json::from_bjdata(json::to_bjdata(j_type), true, true) == j_type);
+ CHECK(json::from_bjdata(json::to_bjdata(j_size), true, true) == j_size);
+ }
+ }
+ }
+
+ SECTION("parse errors")
+ {
+ SECTION("empty byte vector")
+ {
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(std::vector<uint8_t>()),
+ "[json.exception.parse_error.110] parse error at byte 1: syntax error while parsing BJData value: unexpected end of input", json::parse_error&);
+ }
+
+ SECTION("char")
+ {
+ SECTION("eof after C byte")
+ {
+ std::vector<uint8_t> const v = {'C'};
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BJData char: unexpected end of input", json::parse_error&);
+ }
+
+ SECTION("byte out of range")
+ {
+ std::vector<uint8_t> const v = {'C', 130};
+ json _;
+ CHECK_THROWS_WITH(_ = json::from_bjdata(v), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing BJData char: byte after 'C' must be in range 0x00..0x7F; last byte: 0x82");
+ }
+ }
+
+ SECTION("byte")
+ {
+ SECTION("parse bjdata markers in ubjson")
+ {
+ std::vector<uint8_t> const v = {'B', 1};
+
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v), "[json.exception.parse_error.112] parse error at byte 1: syntax error while parsing UBJSON value: invalid byte: 0x42", json::parse_error&);
+ }
+ }
+
+ SECTION("strings")
+ {
+ SECTION("eof after S byte")
+ {
+ std::vector<uint8_t> const v = {'S'};
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BJData value: unexpected end of input", json::parse_error&);
+ }
+
+ SECTION("invalid byte")
+ {
+ std::vector<uint8_t> const v = {'S', '1', 'a'};
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing BJData string: expected length type specification (U, i, u, I, m, l, M, L); last byte: 0x31", json::parse_error&);
+ }
+
+ SECTION("parse bjdata markers in ubjson")
+ {
+ // create a single-character string for all number types
+ std::vector<uint8_t> const s_u = {'S', 'u', 1, 0, 'a'};
+ std::vector<uint8_t> const s_m = {'S', 'm', 1, 0, 0, 0, 'a'};
+ std::vector<uint8_t> const s_M = {'S', 'M', 1, 0, 0, 0, 0, 0, 0, 0, 'a'};
+
+ json _;
+ // check if string is parsed correctly to "a"
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(s_u), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON string: expected length type specification (U, i, I, l, L); last byte: 0x75", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(s_m), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON string: expected length type specification (U, i, I, l, L); last byte: 0x6D", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(s_M), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON string: expected length type specification (U, i, I, l, L); last byte: 0x4D", json::parse_error&);
+ }
+ }
+
+ SECTION("array")
+ {
+ SECTION("optimized array: no size following type")
+ {
+ std::vector<uint8_t> const v = {'[', '$', 'i', 2};
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.112] parse error at byte 4: syntax error while parsing BJData size: expected '#' after type information; last byte: 0x02", json::parse_error&);
+ }
+
+ SECTION("optimized array: negative size")
+ {
+ std::vector<uint8_t> const v1 = {'[', '#', 'i', 0xF1};
+ std::vector<uint8_t> const v2 = {'[', '$', 'I', '#', 'i', 0xF2};
+ std::vector<uint8_t> const v3 = {'[', '$', 'I', '#', '[', 'i', 0xF4, 'i', 0x02, ']'};
+ std::vector<uint8_t> const v4 = {'[', '$', 0xF6, '#', 'i', 0xF7};
+ std::vector<uint8_t> const v5 = {'[', '$', 'I', '#', '[', 'i', 0xF5, 'i', 0xF1, ']'};
+ std::vector<uint8_t> const v6 = {'[', '#', '[', 'i', 0xF3, 'i', 0x02, ']'};
+
+ std::vector<uint8_t> const vI = {'[', '#', 'I', 0x00, 0xF1};
+ std::vector<uint8_t> const vl = {'[', '#', 'l', 0x00, 0x00, 0x00, 0xF2};
+ std::vector<uint8_t> const vL = {'[', '#', 'L', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF3};
+ std::vector<uint8_t> const vM = {'[', '$', 'M', '#', '[', 'I', 0x00, 0x20, 'M', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xFF, ']'};
+ std::vector<uint8_t> const vMX = {'[', '$', 'U', '#', '[', 'M', 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 'U', 0x01, ']'};
+
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v1), "[json.exception.parse_error.113] parse error at byte 4: syntax error while parsing BJData size: count in an optimized container must be positive", json::parse_error&);
+ CHECK(json::from_bjdata(v1, true, false).is_discarded());
+
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v2), "[json.exception.parse_error.113] parse error at byte 6: syntax error while parsing BJData size: count in an optimized container must be positive", json::parse_error&);
+ CHECK(json::from_bjdata(v2, true, false).is_discarded());
+
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v3), "[json.exception.parse_error.113] parse error at byte 7: syntax error while parsing BJData size: count in an optimized container must be positive", json::parse_error&);
+ CHECK(json::from_bjdata(v3, true, false).is_discarded());
+
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v4), "[json.exception.parse_error.113] parse error at byte 6: syntax error while parsing BJData size: count in an optimized container must be positive", json::parse_error&);
+ CHECK(json::from_bjdata(v4, true, false).is_discarded());
+
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v5), "[json.exception.parse_error.113] parse error at byte 7: syntax error while parsing BJData size: count in an optimized container must be positive", json::parse_error&);
+ CHECK(json::from_bjdata(v5, true, false).is_discarded());
+
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v6), "[json.exception.parse_error.113] parse error at byte 5: syntax error while parsing BJData size: count in an optimized container must be positive", json::parse_error&);
+ CHECK(json::from_bjdata(v6, true, false).is_discarded());
+
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vI), "[json.exception.parse_error.113] parse error at byte 5: syntax error while parsing BJData size: count in an optimized container must be positive", json::parse_error&);
+ CHECK(json::from_bjdata(vI, true, false).is_discarded());
+
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vl), "[json.exception.parse_error.113] parse error at byte 7: syntax error while parsing BJData size: count in an optimized container must be positive", json::parse_error&);
+ CHECK(json::from_bjdata(vl, true, false).is_discarded());
+
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vL), "[json.exception.parse_error.113] parse error at byte 11: syntax error while parsing BJData size: count in an optimized container must be positive", json::parse_error&);
+ CHECK(json::from_bjdata(vL, true, false).is_discarded());
+
+#if SIZE_MAX != 0xffffffff
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vM), "[json.exception.out_of_range.408] syntax error while parsing BJData size: excessive ndarray size caused overflow", json::out_of_range&);
+#else
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vM), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&);
+#endif
+ CHECK(json::from_bjdata(vM, true, false).is_discarded());
+
+#if SIZE_MAX != 0xffffffff
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vMX), "[json.exception.out_of_range.408] syntax error while parsing BJData size: excessive ndarray size caused overflow", json::out_of_range&);
+#else
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vMX), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&);
+#endif
+ CHECK(json::from_bjdata(vMX, true, false).is_discarded());
+ }
+
+ SECTION("optimized array: integer value overflow")
+ {
+#if SIZE_MAX == 0xffffffff
+ std::vector<uint8_t> const vL = {'[', '#', 'L', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F};
+ std::vector<uint8_t> const vM = {'[', '$', 'M', '#', '[', 'I', 0x00, 0x20, 'M', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xFF, ']'};
+
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vL), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&);
+ CHECK(json::from_bjdata(vL, true, false).is_discarded());
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vM), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&);
+ CHECK(json::from_bjdata(vM, true, false).is_discarded());
+#endif
+ }
+
+ SECTION("overflow detection in dimension multiplication")
+ {
+ // Simple SAX handler just to monitor if overflow is detected
+ struct SimpleOverflowSaxHandler : public nlohmann::json_sax<json>
+ {
+ bool overflow_detected = false;
+
+ // Implement all required virtual methods with minimal implementation
+ bool null() override
+ {
+ return true;
+ }
+ bool boolean(bool /*val*/) override
+ {
+ return true;
+ }
+ bool number_integer(json::number_integer_t /*val*/) override
+ {
+ return true;
+ }
+ bool number_unsigned(json::number_unsigned_t /*val*/) override
+ {
+ return true;
+ }
+ bool number_float(json::number_float_t /*val*/, const std::string& /*s*/) override
+ {
+ return true;
+ }
+ bool string(std::string& /*val*/) override
+ {
+ return true;
+ }
+ bool binary(json::binary_t& /*val*/) override
+ {
+ return true;
+ }
+ bool start_object(std::size_t /*elements*/) override
+ {
+ return true;
+ }
+ bool key(std::string& /*val*/) override
+ {
+ return true;
+ }
+ bool end_object() override
+ {
+ return true;
+ }
+ bool start_array(std::size_t /*elements*/) override
+ {
+ return true;
+ }
+ bool end_array() override
+ {
+ return true;
+ }
+
+ // This is the only method we care about - detecting error 408
+ bool parse_error(std::size_t /*position*/, const std::string& /*last_token*/, const json::exception& ex) override
+ {
+ if (ex.id == 408)
+ {
+ overflow_detected = true;
+ }
+ return false;
+ }
+ };
+
+ // Create BJData payload with overflow-causing dimensions (2^32+1) × (2^32)
+ const std::vector<uint8_t> bjdata_payload =
+ {
+ 0x5B, // '[' start array
+ 0x24, 0x55, // '$', 'U' (type uint8)
+ 0x23, 0x5B, // '#', '[' (dimensions array)
+ 0x4D, // 'M' (uint64)
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // 2^32 + 1 (4294967297) as little-endian
+ 0x4D, // 'M' (uint64)
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // 2^32 (4294967296) as little-endian
+ 0x5D // ']' end dimensions
+ // No data - we don't need it for this test, we just want to hit the overflow check
+ };
+
+ // Test with overflow dimensions using SAX parser
+ {
+ SimpleOverflowSaxHandler handler;
+ const auto result = json::sax_parse(bjdata_payload, &handler,
+ nlohmann::detail::input_format_t::bjdata, false);
+
+ // Should detect overflow
+ CHECK(handler.overflow_detected == true);
+ CHECK(result == false);
+ }
+
+ // Test with DOM parser (should throw)
+ {
+ json _;
+ CHECK_THROWS_AS(_ = json::from_bjdata(bjdata_payload), json::out_of_range);
+ }
+
+ // Test with normal dimensions
+ const std::vector<uint8_t> normal_payload =
+ {
+ 0x5B, // '[' start array
+ 0x24, 0x55, // '$', 'U' (type uint8)
+ 0x23, 0x5B, // '#', '[' (dimensions array)
+ 0x55, 0x02, // 'U', 2 (uint8)
+ 0x55, 0x03, // 'U', 3 (uint8)
+ 0x5D, // ']' end dimensions
+ // 6 data bytes for a 2×3 array (enough to avoid EOF but not entire array)
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06
+ };
+
+ // For normal dimensions, overflow should not be detected
+ {
+ SimpleOverflowSaxHandler handler;
+ const auto result = json::sax_parse(normal_payload, &handler,
+ nlohmann::detail::input_format_t::bjdata, false);
+
+ CHECK(handler.overflow_detected == false);
+ CHECK(result == true);
+ }
+ }
+
+ SECTION("do not accept NTFZ markers in ndarray optimized type (with count)")
+ {
+ json _;
+ std::vector<uint8_t> const v_N = {'[', '$', 'N', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2};
+ std::vector<uint8_t> const v_T = {'[', '$', 'T', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2};
+ std::vector<uint8_t> const v_F = {'[', '$', 'F', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2};
+ std::vector<uint8_t> const v_Z = {'[', '$', 'Z', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2};
+
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v_N), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x4E is not a permitted optimized array type", json::parse_error&);
+ CHECK(json::from_bjdata(v_N, true, false).is_discarded());
+
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v_T), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x54 is not a permitted optimized array type", json::parse_error&);
+ CHECK(json::from_bjdata(v_T, true, false).is_discarded());
+
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v_F), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x46 is not a permitted optimized array type", json::parse_error&);
+ CHECK(json::from_bjdata(v_F, true, false).is_discarded());
+
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v_Z), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x5A is not a permitted optimized array type", json::parse_error&);
+ CHECK(json::from_bjdata(v_Z, true, false).is_discarded());
+ }
+
+ SECTION("do not accept NTFZ markers in ndarray optimized type (without count)")
+ {
+ json _;
+ std::vector<uint8_t> const v_N = {'[', '$', 'N', '#', '[', 'i', 1, 'i', 2, ']'};
+ std::vector<uint8_t> const v_T = {'[', '$', 'T', '#', '[', 'i', 1, 'i', 2, ']'};
+ std::vector<uint8_t> const v_F = {'[', '$', 'F', '#', '[', 'i', 1, 'i', 2, ']'};
+ std::vector<uint8_t> const v_Z = {'[', '$', 'Z', '#', '[', 'i', 1, 'i', 2, ']'};
+
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v_N), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x4E is not a permitted optimized array type", json::parse_error&);
+ CHECK(json::from_bjdata(v_N, true, false).is_discarded());
+
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v_T), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x54 is not a permitted optimized array type", json::parse_error&);
+ CHECK(json::from_bjdata(v_T, true, false).is_discarded());
+
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v_F), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x46 is not a permitted optimized array type", json::parse_error&);
+ CHECK(json::from_bjdata(v_F, true, false).is_discarded());
+
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v_Z), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x5A is not a permitted optimized array type", json::parse_error&);
+ CHECK(json::from_bjdata(v_Z, true, false).is_discarded());
+ }
+ }
+
+ SECTION("strings")
+ {
+ std::vector<uint8_t> const vS = {'S'};
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vS), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BJData value: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(vS, true, false).is_discarded());
+
+ std::vector<uint8_t> const v = {'S', 'i', '2', 'a'};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing BJData string: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(v, true, false).is_discarded());
+
+ std::vector<uint8_t> const vC = {'C'};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vC), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BJData char: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(vC, true, false).is_discarded());
+ }
+
+ SECTION("sizes")
+ {
+ std::vector<uint8_t> const vU = {'[', '#', 'U'};
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vU), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(vU, true, false).is_discarded());
+
+ std::vector<uint8_t> const vi = {'[', '#', 'i'};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vi), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(vi, true, false).is_discarded());
+
+ std::vector<uint8_t> const vI = {'[', '#', 'I'};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vI), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(vI, true, false).is_discarded());
+
+ std::vector<uint8_t> const vu = {'[', '#', 'u'};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vu), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(vu, true, false).is_discarded());
+
+ std::vector<uint8_t> const vl = {'[', '#', 'l'};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vl), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(vl, true, false).is_discarded());
+
+ std::vector<uint8_t> const vm = {'[', '#', 'm'};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vm), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(vm, true, false).is_discarded());
+
+ std::vector<uint8_t> const vL = {'[', '#', 'L'};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vL), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(vL, true, false).is_discarded());
+
+ std::vector<uint8_t> const vM = {'[', '#', 'M'};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vM), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(vM, true, false).is_discarded());
+
+ std::vector<uint8_t> const v0 = {'[', '#', 'T', ']'};
+ CHECK_THROWS_WITH(_ = json::from_bjdata(v0), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing BJData size: expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x54");
+ CHECK(json::from_bjdata(v0, true, false).is_discarded());
+
+ std::vector<uint8_t> const vB = {'[', '#', 'B', ']'};
+ CHECK_THROWS_WITH(_ = json::from_bjdata(vB), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing BJData size: expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x42");
+ CHECK(json::from_bjdata(v0, true, false).is_discarded());
+ }
+
+ SECTION("parse bjdata markers as array size in ubjson")
+ {
+ json _;
+ std::vector<uint8_t> const vu = {'[', '#', 'u'};
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vu), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing UBJSON size: expected length type specification (U, i, I, l, L) after '#'; last byte: 0x75", json::parse_error&);
+ CHECK(json::from_ubjson(vu, true, false).is_discarded());
+
+ std::vector<uint8_t> const vm = {'[', '#', 'm'};
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vm), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing UBJSON size: expected length type specification (U, i, I, l, L) after '#'; last byte: 0x6D", json::parse_error&);
+ CHECK(json::from_ubjson(vm, true, false).is_discarded());
+
+ std::vector<uint8_t> const vM = {'[', '#', 'M'};
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vM), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing UBJSON size: expected length type specification (U, i, I, l, L) after '#'; last byte: 0x4D", json::parse_error&);
+ CHECK(json::from_ubjson(vM, true, false).is_discarded());
+
+ std::vector<uint8_t> const v0 = {'[', '#', '['};
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v0), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing UBJSON size: expected length type specification (U, i, I, l, L) after '#'; last byte: 0x5B", json::parse_error&);
+ CHECK(json::from_ubjson(v0, true, false).is_discarded());
+ }
+
+ SECTION("types")
+ {
+ std::vector<uint8_t> const v0 = {'[', '$'};
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v0), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BJData type: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(v0, true, false).is_discarded());
+
+ std::vector<uint8_t> const vi = {'[', '$', '#'};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vi), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData value: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(vi, true, false).is_discarded());
+
+ std::vector<uint8_t> const vU = {'[', '$', 'U'};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vU), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData value: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(vU, true, false).is_discarded());
+
+ std::vector<uint8_t> const v1 = {'[', '$', '['};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v1), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x5B is not a permitted optimized array type", json::parse_error&);
+ CHECK(json::from_bjdata(v1, true, false).is_discarded());
+ }
+
+ SECTION("arrays")
+ {
+ std::vector<uint8_t> const vST = {'[', '$', 'i', '#', 'i', 2, 1};
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vST), "[json.exception.parse_error.110] parse error at byte 8: syntax error while parsing BJData number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(vST, true, false).is_discarded());
+
+ std::vector<uint8_t> const vS = {'[', '#', 'i', 2, 'i', 1};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vS), "[json.exception.parse_error.110] parse error at byte 7: syntax error while parsing BJData value: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(vS, true, false).is_discarded());
+
+ std::vector<uint8_t> const v = {'[', 'i', 2, 'i', 1};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.110] parse error at byte 6: syntax error while parsing BJData value: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(v, true, false).is_discarded());
+ }
+
+ SECTION("ndarrays")
+ {
+ std::vector<uint8_t> const vST = {'[', '$', 'i', '#', '[', '$', 'i', '#'};
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vST), "[json.exception.parse_error.113] parse error at byte 9: syntax error while parsing BJData size: expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0xFF", json::parse_error&);
+ CHECK(json::from_bjdata(vST, true, false).is_discarded());
+
+ std::vector<uint8_t> const v = {'[', '$', 'i', '#', '[', '$', 'i', '#', 'i', 2, 1, 2};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.110] parse error at byte 13: syntax error while parsing BJData number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(v, true, false).is_discarded());
+
+ std::vector<uint8_t> const vS0 = {'[', '$', 'i', '#', '[', '$', 'i', '#', 'i', 2, 1};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vS0), "[json.exception.parse_error.110] parse error at byte 12: syntax error while parsing BJData number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(vS0, true, false).is_discarded());
+
+ std::vector<uint8_t> const vS = {'[', '$', 'i', '#', '[', '#', 'i', 2, 1, 2, 1};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vS), "[json.exception.parse_error.113] parse error at byte 9: syntax error while parsing BJData size: expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x01", json::parse_error&);
+ CHECK(json::from_bjdata(vS, true, false).is_discarded());
+
+ std::vector<uint8_t> const vT = {'[', '$', 'i', '#', '[', 'i', 2, 'i'};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vT), "[json.exception.parse_error.110] parse error at byte 9: syntax error while parsing BJData number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(vT, true, false).is_discarded());
+
+ std::vector<uint8_t> const vT0 = {'[', '$', 'i', '#', '[', 'i'};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vT0), "[json.exception.parse_error.110] parse error at byte 7: syntax error while parsing BJData number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(vT0, true, false).is_discarded());
+
+ std::vector<uint8_t> const vu = {'[', '$', 'i', '#', '[', '$', 'i', '#', 'u', 1, 0};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vu), "[json.exception.parse_error.110] parse error at byte 12: syntax error while parsing BJData number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(vu, true, false).is_discarded());
+
+ std::vector<uint8_t> const vm = {'[', '$', 'i', '#', '[', '$', 'i', '#', 'm', 1, 0, 0, 0};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vm), "[json.exception.parse_error.110] parse error at byte 14: syntax error while parsing BJData number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(vm, true, false).is_discarded());
+
+ std::vector<uint8_t> const vM = {'[', '$', 'i', '#', '[', '$', 'i', '#', 'M', 1, 0, 0, 0, 0, 0, 0, 0};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vM), "[json.exception.parse_error.110] parse error at byte 18: syntax error while parsing BJData number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(vM, true, false).is_discarded());
+
+ std::vector<uint8_t> const vU = {'[', '$', 'U', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 1, 2, 3, 4, 5};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vU), "[json.exception.parse_error.110] parse error at byte 18: syntax error while parsing BJData number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(vU, true, false).is_discarded());
+
+ std::vector<uint8_t> const vB = {'[', '$', 'B', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 1, 2, 3, 4, 5};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vU), "[json.exception.parse_error.110] parse error at byte 18: syntax error while parsing BJData number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(vU, true, false).is_discarded());
+
+ std::vector<uint8_t> const vT1 = {'[', '$', 'T', '#', '[', '$', 'i', '#', 'i', 2, 2, 3};
+ CHECK(json::from_bjdata(vT1, true, false).is_discarded());
+
+ std::vector<uint8_t> const vh = {'[', '$', 'h', '#', '[', '$', 'i', '#', 'i', 2, 2, 3};
+ CHECK(json::from_bjdata(vh, true, false).is_discarded());
+
+ std::vector<uint8_t> const vR = {'[', '$', 'i', '#', '[', 'i', 1, '[', ']', ']', 1};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vR), "[json.exception.parse_error.113] parse error at byte 8: syntax error while parsing BJData size: ndarray dimensional vector is not allowed", json::parse_error&);
+ CHECK(json::from_bjdata(vR, true, false).is_discarded());
+
+ std::vector<uint8_t> const vRo = {'[', '$', 'i', '#', '[', 'i', 0, '{', '}', ']', 1};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vRo), "[json.exception.parse_error.113] parse error at byte 8: syntax error while parsing BJData size: expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x7B", json::parse_error&);
+ CHECK(json::from_bjdata(vRo, true, false).is_discarded());
+
+ std::vector<uint8_t> const vR1 = {'[', '$', 'i', '#', '[', '[', 'i', 1, ']', ']', 1};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vR1), "[json.exception.parse_error.113] parse error at byte 6: syntax error while parsing BJData size: ndarray dimensional vector is not allowed", json::parse_error&);
+ CHECK(json::from_bjdata(vR1, true, false).is_discarded());
+
+ std::vector<uint8_t> const vR2 = {'[', '$', 'i', '#', '[', '#', '[', 'i', 1, ']', ']', 1};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vR2), "[json.exception.parse_error.113] parse error at byte 11: syntax error while parsing BJData size: expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x5D", json::parse_error&);
+ CHECK(json::from_bjdata(vR2, true, false).is_discarded());
+
+ std::vector<uint8_t> const vR3 = {'[', '#', '[', 'i', '2', 'i', 2, ']'};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vR3), "[json.exception.parse_error.112] parse error at byte 8: syntax error while parsing BJData size: ndarray requires both type and size", json::parse_error&);
+ CHECK(json::from_bjdata(vR3, true, false).is_discarded());
+
+ std::vector<uint8_t> const vR4 = {'[', '$', 'i', '#', '[', '$', 'i', '#', '[', 'i', 1, ']', 1};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vR4), "[json.exception.parse_error.110] parse error at byte 14: syntax error while parsing BJData number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(vR4, true, false).is_discarded());
+
+ std::vector<uint8_t> const vR5 = {'[', '$', 'i', '#', '[', '[', '[', ']', ']', ']'};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vR5), "[json.exception.parse_error.113] parse error at byte 6: syntax error while parsing BJData size: ndarray dimensional vector is not allowed", json::parse_error&);
+ CHECK(json::from_bjdata(vR5, true, false).is_discarded());
+
+ std::vector<uint8_t> const vR6 = {'[', '$', 'i', '#', '[', '$', 'i', '#', '[', 'i', '2', 'i', 2, ']'};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vR6), "[json.exception.parse_error.112] parse error at byte 14: syntax error while parsing BJData size: ndarray can not be recursive", json::parse_error&);
+ CHECK(json::from_bjdata(vR6, true, false).is_discarded());
+
+ std::vector<uint8_t> const vH = {'[', 'H', '[', '#', '[', '$', 'i', '#', '[', 'i', '2', 'i', 2, ']'};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vH), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing BJData size: ndarray dimensional vector is not allowed", json::parse_error&);
+ CHECK(json::from_bjdata(vH, true, false).is_discarded());
+ }
+
+ SECTION("objects")
+ {
+ std::vector<uint8_t> const vST = {'{', '$', 'i', '#', 'i', 2, 'i', 1, 'a', 1};
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vST), "[json.exception.parse_error.110] parse error at byte 11: syntax error while parsing BJData value: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(vST, true, false).is_discarded());
+
+ std::vector<uint8_t> const vT = {'{', '$', 'i', 'i', 1, 'a', 1};
+ CHECK_THROWS_WITH(_ = json::from_bjdata(vT), "[json.exception.parse_error.112] parse error at byte 4: syntax error while parsing BJData size: expected '#' after type information; last byte: 0x69");
+ CHECK(json::from_bjdata(vT, true, false).is_discarded());
+
+ std::vector<uint8_t> const vS = {'{', '#', 'i', 2, 'i', 1, 'a', 'i', 1};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vS), "[json.exception.parse_error.110] parse error at byte 10: syntax error while parsing BJData value: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(vS, true, false).is_discarded());
+
+ std::vector<uint8_t> const v = {'{', 'i', 1, 'a', 'i', 1};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.110] parse error at byte 7: syntax error while parsing BJData value: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(v, true, false).is_discarded());
+
+ std::vector<uint8_t> const v2 = {'{', 'i', 1, 'a', 'i', 1, 'i'};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v2), "[json.exception.parse_error.110] parse error at byte 8: syntax error while parsing BJData number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(v2, true, false).is_discarded());
+
+ std::vector<uint8_t> const v3 = {'{', 'i', 1, 'a'};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v3), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing BJData value: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(v3, true, false).is_discarded());
+
+ std::vector<uint8_t> const vST1 = {'{', '$', 'd', '#', 'i', 2, 'i', 1, 'a'};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vST1), "[json.exception.parse_error.110] parse error at byte 10: syntax error while parsing BJData number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(vST1, true, false).is_discarded());
+
+ std::vector<uint8_t> const vST2 = {'{', '#', 'i', 2, 'i', 1, 'a'};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vST2), "[json.exception.parse_error.110] parse error at byte 8: syntax error while parsing BJData value: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bjdata(vST2, true, false).is_discarded());
+
+ std::vector<uint8_t> const vO = {'{', '#', '[', 'i', 2, 'i', 1, ']', 'i', 1, 'a', 'i', 1, 'i', 1, 'b', 'i', 2};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vO), "[json.exception.parse_error.112] parse error at byte 8: syntax error while parsing BJData size: ndarray requires both type and size", json::parse_error&);
+ CHECK(json::from_bjdata(vO, true, false).is_discarded());
+
+ std::vector<uint8_t> const vO2 = {'{', '$', 'i', '#', '[', 'i', 2, 'i', 1, ']', 'i', 1, 'a', 1, 'i', 1, 'b', 2};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vO2), "[json.exception.parse_error.112] parse error at byte 10: syntax error while parsing BJData object: BJData object does not support ND-array size in optimized format", json::parse_error&);
+ CHECK(json::from_bjdata(vO2, true, false).is_discarded());
+ }
+ }
+
+ SECTION("writing optimized values")
+ {
+ SECTION("integer")
+ {
+ SECTION("array of i")
+ {
+ json const j = {1, -1};
+ std::vector<uint8_t> const expected = {'[', '$', 'i', '#', 'i', 2, 1, 0xff};
+ CHECK(json::to_bjdata(j, true, true) == expected);
+ }
+
+ SECTION("array of U")
+ {
+ json const j = {200, 201};
+ std::vector<uint8_t> const expected = {'[', '$', 'U', '#', 'i', 2, 0xC8, 0xC9};
+ CHECK(json::to_bjdata(j, true, true) == expected);
+ }
+
+ SECTION("array of I")
+ {
+ json const j = {30000, -30000};
+ std::vector<uint8_t> const expected = {'[', '$', 'I', '#', 'i', 2, 0x30, 0x75, 0xd0, 0x8a};
+ CHECK(json::to_bjdata(j, true, true) == expected);
+ }
+
+ SECTION("array of u")
+ {
+ json const j = {50000, 50001};
+ std::vector<uint8_t> const expected = {'[', '$', 'u', '#', 'i', 2, 0x50, 0xC3, 0x51, 0xC3};
+ CHECK(json::to_bjdata(j, true, true) == expected);
+ }
+
+ SECTION("array of l")
+ {
+ json const j = {70000, -70000};
+ std::vector<uint8_t> const expected = {'[', '$', 'l', '#', 'i', 2, 0x70, 0x11, 0x01, 0x00, 0x90, 0xEE, 0xFE, 0xFF};
+ CHECK(json::to_bjdata(j, true, true) == expected);
+ }
+
+ SECTION("array of m")
+ {
+ json const j = {3147483647, 3147483648};
+ std::vector<uint8_t> const expected = {'[', '$', 'm', '#', 'i', 2, 0xFF, 0xC9, 0x9A, 0xBB, 0x00, 0xCA, 0x9A, 0xBB};
+ CHECK(json::to_bjdata(j, true, true) == expected);
+ }
+
+ SECTION("array of L")
+ {
+ json const j = {5000000000, -5000000000};
+ std::vector<uint8_t> const expected = {'[', '$', 'L', '#', 'i', 2, 0x00, 0xF2, 0x05, 0x2A, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0E, 0xFA, 0xD5, 0xFE, 0xFF, 0xFF, 0xFF};
+ CHECK(json::to_bjdata(j, true, true) == expected);
+ }
+ }
+
+ SECTION("unsigned integer")
+ {
+ SECTION("array of i")
+ {
+ json const j = {1u, 2u};
+ std::vector<uint8_t> const expected = {'[', '$', 'i', '#', 'i', 2, 1, 2};
+ std::vector<uint8_t> const expected_size = {'[', '#', 'i', 2, 'i', 1, 'i', 2};
+ CHECK(json::to_bjdata(j, true, true) == expected);
+ CHECK(json::to_bjdata(j, true) == expected_size);
+ }
+
+ SECTION("array of U")
+ {
+ json const j = {200u, 201u};
+ std::vector<uint8_t> const expected = {'[', '$', 'U', '#', 'i', 2, 0xC8, 0xC9};
+ std::vector<uint8_t> const expected_size = {'[', '#', 'i', 2, 'U', 0xC8, 'U', 0xC9};
+ CHECK(json::to_bjdata(j, true, true) == expected);
+ CHECK(json::to_bjdata(j, true) == expected_size);
+ }
+
+ SECTION("array of I")
+ {
+ json const j = {30000u, 30001u};
+ std::vector<uint8_t> const expected = {'[', '$', 'I', '#', 'i', 2, 0x30, 0x75, 0x31, 0x75};
+ std::vector<uint8_t> const expected_size = {'[', '#', 'i', 2, 'I', 0x30, 0x75, 'I', 0x31, 0x75};
+ CHECK(json::to_bjdata(j, true, true) == expected);
+ CHECK(json::to_bjdata(j, true) == expected_size);
+ }
+
+ SECTION("array of u")
+ {
+ json const j = {50000u, 50001u};
+ std::vector<uint8_t> const expected = {'[', '$', 'u', '#', 'i', 2, 0x50, 0xC3, 0x51, 0xC3};
+ std::vector<uint8_t> const expected_size = {'[', '#', 'i', 2, 'u', 0x50, 0xC3, 'u', 0x51, 0xC3};
+ CHECK(json::to_bjdata(j, true, true) == expected);
+ CHECK(json::to_bjdata(j, true) == expected_size);
+ }
+
+ SECTION("array of l")
+ {
+ json const j = {70000u, 70001u};
+ std::vector<uint8_t> const expected = {'[', '$', 'l', '#', 'i', 2, 0x70, 0x11, 0x01, 0x00, 0x71, 0x11, 0x01, 0x00};
+ std::vector<uint8_t> const expected_size = {'[', '#', 'i', 2, 'l', 0x70, 0x11, 0x01, 0x00, 'l', 0x71, 0x11, 0x01, 0x00};
+ CHECK(json::to_bjdata(j, true, true) == expected);
+ CHECK(json::to_bjdata(j, true) == expected_size);
+ }
+
+ SECTION("array of m")
+ {
+ json const j = {3147483647u, 3147483648u};
+ std::vector<uint8_t> const expected = {'[', '$', 'm', '#', 'i', 2, 0xFF, 0xC9, 0x9A, 0xBB, 0x00, 0xCA, 0x9A, 0xBB};
+ std::vector<uint8_t> const expected_size = {'[', '#', 'i', 2, 'm', 0xFF, 0xC9, 0x9A, 0xBB, 'm', 0x00, 0xCA, 0x9A, 0xBB};
+ CHECK(json::to_bjdata(j, true, true) == expected);
+ CHECK(json::to_bjdata(j, true) == expected_size);
+ }
+
+ SECTION("array of L")
+ {
+ json const j = {5000000000u, 5000000001u};
+ std::vector<uint8_t> const expected = {'[', '$', 'L', '#', 'i', 2, 0x00, 0xF2, 0x05, 0x2A, 0x01, 0x00, 0x00, 0x00, 0x01, 0xF2, 0x05, 0x2A, 0x01, 0x00, 0x00, 0x00};
+ std::vector<uint8_t> const expected_size = {'[', '#', 'i', 2, 'L', 0x00, 0xF2, 0x05, 0x2A, 0x01, 0x00, 0x00, 0x00, 'L', 0x01, 0xF2, 0x05, 0x2A, 0x01, 0x00, 0x00, 0x00};
+ CHECK(json::to_bjdata(j, true, true) == expected);
+ CHECK(json::to_bjdata(j, true) == expected_size);
+ }
+
+ SECTION("array of M")
+ {
+ json const j = {10223372036854775807ull, 10223372036854775808ull};
+ std::vector<uint8_t> const expected = {'[', '$', 'M', '#', 'i', 2, 0xFF, 0xFF, 0x63, 0xA7, 0xB3, 0xB6, 0xE0, 0x8D, 0x00, 0x00, 0x64, 0xA7, 0xB3, 0xB6, 0xE0, 0x8D};
+ std::vector<uint8_t> const expected_size = {'[', '#', 'i', 2, 'M', 0xFF, 0xFF, 0x63, 0xA7, 0xB3, 0xB6, 0xE0, 0x8D, 'M', 0x00, 0x00, 0x64, 0xA7, 0xB3, 0xB6, 0xE0, 0x8D};
+ CHECK(json::to_bjdata(j, true, true) == expected);
+ CHECK(json::to_bjdata(j, true) == expected_size);
+ }
+ }
+ }
+}
+
+TEST_CASE("Universal Binary JSON Specification Examples 1")
+{
+ SECTION("Null Value")
+ {
+ json const j = {{"passcode", nullptr}};
+ std::vector<uint8_t> v = {'{', 'i', 8, 'p', 'a', 's', 's', 'c', 'o', 'd', 'e', 'Z', '}'};
+ CHECK(json::to_bjdata(j) == v);
+ CHECK(json::from_bjdata(v) == j);
+ }
+
+ SECTION("No-Op Value")
+ {
+ json const j = {"foo", "bar", "baz"};
+ std::vector<uint8_t> v = {'[', 'S', 'i', 3, 'f', 'o', 'o',
+ 'S', 'i', 3, 'b', 'a', 'r',
+ 'S', 'i', 3, 'b', 'a', 'z', ']'
+ };
+ std::vector<uint8_t> const v2 = {'[', 'S', 'i', 3, 'f', 'o', 'o', 'N',
+ 'S', 'i', 3, 'b', 'a', 'r', 'N', 'N', 'N',
+ 'S', 'i', 3, 'b', 'a', 'z', 'N', 'N', ']'
+ };
+ CHECK(json::to_bjdata(j) == v);
+ CHECK(json::from_bjdata(v) == j);
+ CHECK(json::from_bjdata(v2) == j);
+ }
+
+ SECTION("Boolean Types")
+ {
+ json const j = {{"authorized", true}, {"verified", false}};
+ std::vector<uint8_t> v = {'{', 'i', 10, 'a', 'u', 't', 'h', 'o', 'r', 'i', 'z', 'e', 'd', 'T',
+ 'i', 8, 'v', 'e', 'r', 'i', 'f', 'i', 'e', 'd', 'F', '}'
+ };
+ CHECK(json::to_bjdata(j) == v);
+ CHECK(json::from_bjdata(v) == j);
+ }
+
+ SECTION("Numeric Types")
+ {
+ json j =
+ {
+ {"int8", 16},
+ {"uint8", 255},
+ {"int16", 32767},
+ {"uint16", 42767},
+ {"int32", 2147483647},
+ {"uint32", 3147483647},
+ {"int64", 9223372036854775807},
+ {"uint64", 10223372036854775807ull},
+ {"float64", 113243.7863123}
+ };
+ std::vector<uint8_t> v = {'{',
+ 'i', 7, 'f', 'l', 'o', 'a', 't', '6', '4', 'D', 0xcf, 0x34, 0xbc, 0x94, 0xbc, 0xa5, 0xfb, 0x40,
+ 'i', 5, 'i', 'n', 't', '1', '6', 'I', 0xff, 0x7f,
+ 'i', 5, 'i', 'n', 't', '3', '2', 'l', 0xff, 0xff, 0xff, 0x7f,
+ 'i', 5, 'i', 'n', 't', '6', '4', 'L', 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f,
+ 'i', 4, 'i', 'n', 't', '8', 'i', 16,
+ 'i', 6, 'u', 'i', 'n', 't', '1', '6', 'u', 0x0F, 0xA7,
+ 'i', 6, 'u', 'i', 'n', 't', '3', '2', 'm', 0xFF, 0xC9, 0x9A, 0xBB,
+ 'i', 6, 'u', 'i', 'n', 't', '6', '4', 'M', 0xFF, 0xFF, 0x63, 0xA7, 0xB3, 0xB6, 0xE0, 0x8D,
+ 'i', 5, 'u', 'i', 'n', 't', '8', 'U', 0xff,
+ '}'
+ };
+ CHECK(json::to_bjdata(j) == v);
+ CHECK(json::from_bjdata(v) == j);
+ }
+
+ SECTION("Char Type")
+ {
+ json const j = {{"rolecode", "a"}, {"delim", ";"}};
+ std::vector<uint8_t> const v = {'{', 'i', 5, 'd', 'e', 'l', 'i', 'm', 'C', ';', 'i', 8, 'r', 'o', 'l', 'e', 'c', 'o', 'd', 'e', 'C', 'a', '}'};
+ //CHECK(json::to_bjdata(j) == v);
+ CHECK(json::from_bjdata(v) == j);
+ }
+
+ SECTION("Byte Type")
+ {
+ const auto s = std::vector<std::uint8_t>(
+ {
+ static_cast<std::uint8_t>(222),
+ static_cast<std::uint8_t>(173),
+ static_cast<std::uint8_t>(190),
+ static_cast<std::uint8_t>(239)
+ });
+ json const j = {{"binary", json::binary(s)}, {"val", 123}};
+ std::vector<uint8_t> const v = {'{', 'i', 6, 'b', 'i', 'n', 'a', 'r', 'y', '[', '$', 'B', '#', 'i', 4, 222, 173, 190, 239, 'i', 3, 'v', 'a', 'l', 'i', 123, '}'};
+ //CHECK(json::to_bjdata(j) == v); // 123 value gets encoded as uint8
+ CHECK(json::from_bjdata(v) == j);
+ }
+
+ SECTION("String Type")
+ {
+ SECTION("English")
+ {
+ json const j = "hello";
+ std::vector<uint8_t> v = {'S', 'i', 5, 'h', 'e', 'l', 'l', 'o'};
+ CHECK(json::to_bjdata(j) == v);
+ CHECK(json::from_bjdata(v) == j);
+ }
+
+ SECTION("Russian")
+ {
+ json const j = "привет";
+ std::vector<uint8_t> v = {'S', 'i', 12, 0xD0, 0xBF, 0xD1, 0x80, 0xD0, 0xB8, 0xD0, 0xB2, 0xD0, 0xB5, 0xD1, 0x82};
+ CHECK(json::to_bjdata(j) == v);
+ CHECK(json::from_bjdata(v) == j);
+ }
+
+ SECTION("Russian")
+ {
+ json const j = "مرحبا";
+ std::vector<uint8_t> v = {'S', 'i', 10, 0xD9, 0x85, 0xD8, 0xB1, 0xD8, 0xAD, 0xD8, 0xA8, 0xD8, 0xA7};
+ CHECK(json::to_bjdata(j) == v);
+ CHECK(json::from_bjdata(v) == j);
+ }
+ }
+
+ SECTION("Array Type")
+ {
+ SECTION("size=false type=false")
+ {
+ // note the float has been replaced by a double
+ json const j = {nullptr, true, false, 4782345193, 153.132, "ham"};
+ std::vector<uint8_t> v = {'[', 'Z', 'T', 'F', 'L', 0xE9, 0xCB, 0x0C, 0x1D, 0x01, 0x00, 0x00, 0x00, 'D', 0x4e, 0x62, 0x10, 0x58, 0x39, 0x24, 0x63, 0x40, 'S', 'i', 3, 'h', 'a', 'm', ']'};
+ CHECK(json::to_bjdata(j) == v);
+ CHECK(json::from_bjdata(v) == j);
+ }
+
+ SECTION("size=true type=false")
+ {
+ // note the float has been replaced by a double
+ json const j = {nullptr, true, false, 4782345193, 153.132, "ham"};
+ std::vector<uint8_t> v = {'[', '#', 'i', 6, 'Z', 'T', 'F', 'L', 0xE9, 0xCB, 0x0C, 0x1D, 0x01, 0x00, 0x00, 0x00, 'D', 0x4e, 0x62, 0x10, 0x58, 0x39, 0x24, 0x63, 0x40, 'S', 'i', 3, 'h', 'a', 'm'};
+ CHECK(json::to_bjdata(j, true) == v);
+ CHECK(json::from_bjdata(v) == j);
+ }
+
+ SECTION("size=true type=true")
+ {
+ // note the float has been replaced by a double
+ json const j = {nullptr, true, false, 4782345193, 153.132, "ham"};
+ std::vector<uint8_t> v = {'[', '#', 'i', 6, 'Z', 'T', 'F', 'L', 0xE9, 0xCB, 0x0C, 0x1D, 0x01, 0x00, 0x00, 0x00, 'D', 0x4e, 0x62, 0x10, 0x58, 0x39, 0x24, 0x63, 0x40, 'S', 'i', 3, 'h', 'a', 'm'};
+ CHECK(json::to_bjdata(j, true, true) == v);
+ CHECK(json::from_bjdata(v) == j);
+ }
+ }
+
+ SECTION("Object Type")
+ {
+ SECTION("size=false type=false")
+ {
+ json j =
+ {
+ {
+ "post", {
+ {"id", 1137},
+ {"author", "rkalla"},
+ {"timestamp", 1364482090592},
+ {"body", "I totally agree!"}
+ }
+ }
+ };
+ std::vector<uint8_t> v = {'{', 'i', 4, 'p', 'o', 's', 't', '{',
+ 'i', 6, 'a', 'u', 't', 'h', 'o', 'r', 'S', 'i', 6, 'r', 'k', 'a', 'l', 'l', 'a',
+ 'i', 4, 'b', 'o', 'd', 'y', 'S', 'i', 16, 'I', ' ', 't', 'o', 't', 'a', 'l', 'l', 'y', ' ', 'a', 'g', 'r', 'e', 'e', '!',
+ 'i', 2, 'i', 'd', 'I', 0x71, 0x04,
+ 'i', 9, 't', 'i', 'm', 'e', 's', 't', 'a', 'm', 'p', 'L', 0x60, 0x66, 0x78, 0xB1, 0x3D, 0x01, 0x00, 0x00,
+ '}', '}'
+ };
+ CHECK(json::to_bjdata(j) == v);
+ CHECK(json::from_bjdata(v) == j);
+ }
+
+ SECTION("size=true type=false")
+ {
+ json j =
+ {
+ {
+ "post", {
+ {"id", 1137},
+ {"author", "rkalla"},
+ {"timestamp", 1364482090592},
+ {"body", "I totally agree!"}
+ }
+ }
+ };
+ std::vector<uint8_t> v = {'{', '#', 'i', 1, 'i', 4, 'p', 'o', 's', 't', '{', '#', 'i', 4,
+ 'i', 6, 'a', 'u', 't', 'h', 'o', 'r', 'S', 'i', 6, 'r', 'k', 'a', 'l', 'l', 'a',
+ 'i', 4, 'b', 'o', 'd', 'y', 'S', 'i', 16, 'I', ' ', 't', 'o', 't', 'a', 'l', 'l', 'y', ' ', 'a', 'g', 'r', 'e', 'e', '!',
+ 'i', 2, 'i', 'd', 'I', 0x71, 0x04,
+ 'i', 9, 't', 'i', 'm', 'e', 's', 't', 'a', 'm', 'p', 'L', 0x60, 0x66, 0x78, 0xB1, 0x3D, 0x01, 0x00, 0x00,
+ };
+ CHECK(json::to_bjdata(j, true) == v);
+ CHECK(json::from_bjdata(v) == j);
+ }
+
+ SECTION("size=true type=true")
+ {
+ json j =
+ {
+ {
+ "post", {
+ {"id", 1137},
+ {"author", "rkalla"},
+ {"timestamp", 1364482090592},
+ {"body", "I totally agree!"}
+ }
+ }
+ };
+ std::vector<uint8_t> v = {'{', '#', 'i', 1, 'i', 4, 'p', 'o', 's', 't', '{', '#', 'i', 4,
+ 'i', 6, 'a', 'u', 't', 'h', 'o', 'r', 'S', 'i', 6, 'r', 'k', 'a', 'l', 'l', 'a',
+ 'i', 4, 'b', 'o', 'd', 'y', 'S', 'i', 16, 'I', ' ', 't', 'o', 't', 'a', 'l', 'l', 'y', ' ', 'a', 'g', 'r', 'e', 'e', '!',
+ 'i', 2, 'i', 'd', 'I', 0x71, 0x04,
+ 'i', 9, 't', 'i', 'm', 'e', 's', 't', 'a', 'm', 'p', 'L', 0x60, 0x66, 0x78, 0xB1, 0x3D, 0x01, 0x00, 0x00,
+ };
+ CHECK(json::to_bjdata(j, true, true) == v);
+ CHECK(json::from_bjdata(v) == j);
+ }
+ }
+
+ SECTION("Optimized Format")
+ {
+ SECTION("Array Example")
+ {
+ SECTION("No Optimization")
+ {
+ // note the floats have been replaced by doubles
+ json const j = {29.97, 31.13, 67.0, 2.113, 23.888};
+ std::vector<uint8_t> v = {'[',
+ 'D', 0xb8, 0x1e, 0x85, 0xeb, 0x51, 0xf8, 0x3d, 0x40,
+ 'D', 0xe1, 0x7a, 0x14, 0xae, 0x47, 0x21, 0x3f, 0x40,
+ 'D', 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x50, 0x40,
+ 'D', 0x81, 0x95, 0x43, 0x8b, 0x6c, 0xe7, 0x00, 0x40,
+ 'D', 0x17, 0xd9, 0xce, 0xf7, 0x53, 0xe3, 0x37, 0x40,
+ ']'
+ };
+ CHECK(json::to_bjdata(j) == v);
+ CHECK(json::from_bjdata(v) == j);
+ }
+
+ SECTION("Optimized with count")
+ {
+ // note the floats have been replaced by doubles
+ json const j = {29.97, 31.13, 67.0, 2.113, 23.888};
+ std::vector<uint8_t> v = {'[', '#', 'i', 5,
+ 'D', 0xb8, 0x1e, 0x85, 0xeb, 0x51, 0xf8, 0x3d, 0x40,
+ 'D', 0xe1, 0x7a, 0x14, 0xae, 0x47, 0x21, 0x3f, 0x40,
+ 'D', 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x50, 0x40,
+ 'D', 0x81, 0x95, 0x43, 0x8b, 0x6c, 0xe7, 0x00, 0x40,
+ 'D', 0x17, 0xd9, 0xce, 0xf7, 0x53, 0xe3, 0x37, 0x40,
+ };
+ CHECK(json::to_bjdata(j, true) == v);
+ CHECK(json::from_bjdata(v) == j);
+ }
+
+ SECTION("Optimized with type & count")
+ {
+ // note the floats have been replaced by doubles
+ json const j = {29.97, 31.13, 67.0, 2.113, 23.888};
+ std::vector<uint8_t> v = {'[', '$', 'D', '#', 'i', 5,
+ 0xb8, 0x1e, 0x85, 0xeb, 0x51, 0xf8, 0x3d, 0x40,
+ 0xe1, 0x7a, 0x14, 0xae, 0x47, 0x21, 0x3f, 0x40,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x50, 0x40,
+ 0x81, 0x95, 0x43, 0x8b, 0x6c, 0xe7, 0x00, 0x40,
+ 0x17, 0xd9, 0xce, 0xf7, 0x53, 0xe3, 0x37, 0x40,
+ };
+ CHECK(json::to_bjdata(j, true, true) == v);
+ CHECK(json::from_bjdata(v) == j);
+ }
+ }
+
+ SECTION("Object Example")
+ {
+ SECTION("No Optimization")
+ {
+ // note the floats have been replaced by doubles
+ json const j = { {"lat", 29.976}, {"long", 31.131}, {"alt", 67.0} };
+ std::vector<uint8_t> v = {'{',
+ 'i', 3, 'a', 'l', 't', 'D', 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x50, 0x40,
+ 'i', 3, 'l', 'a', 't', 'D', 0x60, 0xe5, 0xd0, 0x22, 0xdb, 0xf9, 0x3d, 0x40,
+ 'i', 4, 'l', 'o', 'n', 'g', 'D', 0xa8, 0xc6, 0x4b, 0x37, 0x89, 0x21, 0x3f, 0x40,
+ '}'
+ };
+ CHECK(json::to_bjdata(j) == v);
+ CHECK(json::from_bjdata(v) == j);
+ }
+
+ SECTION("Optimized with count")
+ {
+ // note the floats have been replaced by doubles
+ json const j = { {"lat", 29.976}, {"long", 31.131}, {"alt", 67.0} };
+ std::vector<uint8_t> v = {'{', '#', 'i', 3,
+ 'i', 3, 'a', 'l', 't', 'D', 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x50, 0x40,
+ 'i', 3, 'l', 'a', 't', 'D', 0x60, 0xe5, 0xd0, 0x22, 0xdb, 0xf9, 0x3d, 0x40,
+ 'i', 4, 'l', 'o', 'n', 'g', 'D', 0xa8, 0xc6, 0x4b, 0x37, 0x89, 0x21, 0x3f, 0x40,
+ };
+ CHECK(json::to_bjdata(j, true) == v);
+ CHECK(json::from_bjdata(v) == j);
+ }
+
+ SECTION("Optimized with type & count")
+ {
+ // note the floats have been replaced by doubles
+ json const j = { {"lat", 29.976}, {"long", 31.131}, {"alt", 67.0} };
+ std::vector<uint8_t> v = {'{', '$', 'D', '#', 'i', 3,
+ 'i', 3, 'a', 'l', 't', 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x50, 0x40,
+ 'i', 3, 'l', 'a', 't', 0x60, 0xe5, 0xd0, 0x22, 0xdb, 0xf9, 0x3d, 0x40,
+ 'i', 4, 'l', 'o', 'n', 'g', 0xa8, 0xc6, 0x4b, 0x37, 0x89, 0x21, 0x3f, 0x40,
+ };
+ CHECK(json::to_bjdata(j, true, true) == v);
+ CHECK(json::from_bjdata(v) == j);
+ }
+ }
+
+ SECTION("Special Cases (Null, No-Op and Boolean)")
+ {
+ SECTION("Array")
+ {
+ json _;
+ std::vector<uint8_t> const v = {'[', '$', 'N', '#', 'I', 0x00, 0x02};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x4E is not a permitted optimized array type", json::parse_error&);
+ CHECK(json::from_bjdata(v, true, false).is_discarded());
+ }
+
+ SECTION("Object")
+ {
+ json _;
+ std::vector<uint8_t> const v = {'{', '$', 'Z', '#', 'i', 3, 'i', 4, 'n', 'a', 'm', 'e', 'i', 8, 'p', 'a', 's', 's', 'w', 'o', 'r', 'd', 'i', 5, 'e', 'm', 'a', 'i', 'l'};
+ CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x5A is not a permitted optimized array type", json::parse_error&);
+ CHECK(json::from_bjdata(v, true, false).is_discarded());
+ }
+ }
+ }
+}
+
+#if !defined(JSON_NOEXCEPTION)
+TEST_CASE("all BJData first bytes")
+{
+ // these bytes will fail immediately with exception parse_error.112
+ std::set<uint8_t> supported =
+ {
+ 'T', 'F', 'Z', 'B', 'U', 'i', 'I', 'l', 'L', 'd', 'D', 'C', 'S', '[', '{', 'N', 'H', 'u', 'm', 'M', 'h'
+ };
+
+ for (auto i = 0; i < 256; ++i)
+ {
+ const auto byte = static_cast<uint8_t>(i);
+ CAPTURE(byte)
+
+ try
+ {
+ auto res = json::from_bjdata(std::vector<uint8_t>(1, byte));
+ }
+ catch (const json::parse_error& e)
+ {
+ // check that parse_error.112 is only thrown if the
+ // first byte is not in the supported set
+ INFO_WITH_TEMP(e.what());
+ if (supported.find(byte) == supported.end())
+ {
+ CHECK(e.id == 112);
+ }
+ else
+ {
+ CHECK(e.id != 112);
+ }
+ }
+ }
+}
+#endif
+
+TEST_CASE("BJData roundtrips" * doctest::skip())
+{
+ SECTION("input from self-generated BJData files")
+ {
+ for (const std::string filename :
+ {
+ TEST_DATA_DIRECTORY "/json_nlohmann_tests/all_unicode.json",
+ TEST_DATA_DIRECTORY "/json.org/1.json",
+ TEST_DATA_DIRECTORY "/json.org/2.json",
+ TEST_DATA_DIRECTORY "/json.org/3.json",
+ TEST_DATA_DIRECTORY "/json.org/4.json",
+ TEST_DATA_DIRECTORY "/json.org/5.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip01.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip02.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip03.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip04.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip05.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip06.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip07.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip08.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip09.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip10.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip11.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip12.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip13.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip14.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip15.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip16.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip17.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip18.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip19.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip20.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip21.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip22.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip23.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip24.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip25.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip26.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip27.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip28.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip29.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip30.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip31.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip32.json",
+ TEST_DATA_DIRECTORY "/json_testsuite/sample.json",
+ TEST_DATA_DIRECTORY "/json_tests/pass1.json",
+ TEST_DATA_DIRECTORY "/json_tests/pass2.json",
+ TEST_DATA_DIRECTORY "/json_tests/pass3.json"
+ })
+ {
+ CAPTURE(filename)
+
+ {
+ INFO_WITH_TEMP(filename + ": std::vector<uint8_t>");
+ // parse JSON file
+ std::ifstream f_json(filename);
+ const json j1 = json::parse(f_json);
+
+ // parse BJData file
+ auto packed = utils::read_binary_file(filename + ".bjdata");
+ json j2;
+ CHECK_NOTHROW(j2 = json::from_bjdata(packed));
+
+ // compare parsed JSON values
+ CHECK(j1 == j2);
+ }
+
+ {
+ INFO_WITH_TEMP(filename + ": std::ifstream");
+ // parse JSON file
+ std::ifstream f_json(filename);
+ const json j1 = json::parse(f_json);
+
+ // parse BJData file
+ std::ifstream f_bjdata(filename + ".bjdata", std::ios::binary);
+ json j2;
+ CHECK_NOTHROW(j2 = json::from_bjdata(f_bjdata));
+
+ // compare parsed JSON values
+ CHECK(j1 == j2);
+ }
+
+ {
+ INFO_WITH_TEMP(filename + ": output to output adapters");
+ // parse JSON file
+ std::ifstream f_json(filename);
+ json const j1 = json::parse(f_json);
+
+ // parse BJData file
+ auto packed = utils::read_binary_file(filename + ".bjdata");
+
+ {
+ INFO_WITH_TEMP(filename + ": output adapters: std::vector<uint8_t>");
+ std::vector<uint8_t> vec;
+ json::to_bjdata(j1, vec);
+ CHECK(vec == packed);
+ }
+ }
+ }
+ }
+}
diff --git a/json4cpp/tests/src/unit-bson.cpp b/json4cpp/tests/src/unit-bson.cpp
new file mode 100644
index 0000000000..eed70352e8
--- /dev/null
+++ b/json4cpp/tests/src/unit-bson.cpp
@@ -0,0 +1,1296 @@
+// __ _____ _____ _____
+// __| | __| | | | 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;
+
+#include <fstream>
+#include <limits>
+#include <sstream>
+#include "make_test_data_available.hpp"
+#include "test_utils.hpp"
+
+TEST_CASE("BSON")
+{
+ SECTION("individual values not supported")
+ {
+ SECTION("null")
+ {
+ json const j = nullptr;
+ CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.317] to serialize to BSON, top-level type must be object, but is null", json::type_error&);
+ }
+
+ SECTION("boolean")
+ {
+ SECTION("true")
+ {
+ json const j = true;
+ CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.317] to serialize to BSON, top-level type must be object, but is boolean", json::type_error&);
+ }
+
+ SECTION("false")
+ {
+ json const j = false;
+ CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.317] to serialize to BSON, top-level type must be object, but is boolean", json::type_error&);
+ }
+ }
+
+ SECTION("number")
+ {
+ json const j = 42;
+ CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.317] to serialize to BSON, top-level type must be object, but is number", json::type_error&);
+ }
+
+ SECTION("float")
+ {
+ json const j = 4.2;
+ CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.317] to serialize to BSON, top-level type must be object, but is number", json::type_error&);
+ }
+
+ SECTION("string")
+ {
+ json const j = "not supported";
+ CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.317] to serialize to BSON, top-level type must be object, but is string", json::type_error&);
+ }
+
+ SECTION("array")
+ {
+ json const j = std::vector<int> {1, 2, 3, 4, 5, 6, 7};
+ CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.317] to serialize to BSON, top-level type must be object, but is array", json::type_error&);
+ }
+ }
+
+ SECTION("keys containing code-point U+0000 cannot be serialized to BSON")
+ {
+ json const j =
+ {
+ { std::string("en\0try", 6), true }
+ };
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.out_of_range.409] (/en) BSON key cannot contain code point U+0000 (at byte 2)", json::out_of_range&);
+#else
+ CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.out_of_range.409] BSON key cannot contain code point U+0000 (at byte 2)", json::out_of_range&);
+#endif
+ }
+
+ SECTION("string length must be at least 1")
+ {
+ // from https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=11175
+ std::vector<std::uint8_t> const v =
+ {
+ 0x20, 0x20, 0x20, 0x20,
+ 0x02,
+ 0x00,
+ 0x00, 0x00, 0x00, 0x80
+ };
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.112] parse error at byte 10: syntax error while parsing BSON string: string length must be at least 1, is -2147483648", json::parse_error&);
+ }
+
+ SECTION("objects")
+ {
+ SECTION("empty object")
+ {
+ json const j = json::object();
+ std::vector<std::uint8_t> const expected =
+ {
+ 0x05, 0x00, 0x00, 0x00, // size (little endian)
+ // no entries
+ 0x00 // end marker
+ };
+
+ const auto result = json::to_bson(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bson(result) == j);
+ CHECK(json::from_bson(result, true, false) == j);
+ }
+
+ SECTION("non-empty object with bool")
+ {
+ json const j =
+ {
+ { "entry", true }
+ };
+
+ std::vector<std::uint8_t> const expected =
+ {
+ 0x0D, 0x00, 0x00, 0x00, // size (little endian)
+ 0x08, // entry: boolean
+ 'e', 'n', 't', 'r', 'y', '\x00',
+ 0x01, // value = true
+ 0x00 // end marker
+ };
+
+ const auto result = json::to_bson(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bson(result) == j);
+ CHECK(json::from_bson(result, true, false) == j);
+ }
+
+ SECTION("non-empty object with bool")
+ {
+ json const j =
+ {
+ { "entry", false }
+ };
+
+ std::vector<std::uint8_t> const expected =
+ {
+ 0x0D, 0x00, 0x00, 0x00, // size (little endian)
+ 0x08, // entry: boolean
+ 'e', 'n', 't', 'r', 'y', '\x00',
+ 0x00, // value = false
+ 0x00 // end marker
+ };
+
+ const auto result = json::to_bson(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bson(result) == j);
+ CHECK(json::from_bson(result, true, false) == j);
+ }
+
+ SECTION("non-empty object with double")
+ {
+ json const j =
+ {
+ { "entry", 4.2 }
+ };
+
+ std::vector<std::uint8_t> const expected =
+ {
+ 0x14, 0x00, 0x00, 0x00, // size (little endian)
+ 0x01, /// entry: double
+ 'e', 'n', 't', 'r', 'y', '\x00',
+ 0xcd, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x10, 0x40,
+ 0x00 // end marker
+ };
+
+ const auto result = json::to_bson(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bson(result) == j);
+ CHECK(json::from_bson(result, true, false) == j);
+ }
+
+ SECTION("non-empty object with string")
+ {
+ json const j =
+ {
+ { "entry", "bsonstr" }
+ };
+
+ std::vector<std::uint8_t> const expected =
+ {
+ 0x18, 0x00, 0x00, 0x00, // size (little endian)
+ 0x02, /// entry: string (UTF-8)
+ 'e', 'n', 't', 'r', 'y', '\x00',
+ 0x08, 0x00, 0x00, 0x00, 'b', 's', 'o', 'n', 's', 't', 'r', '\x00',
+ 0x00 // end marker
+ };
+
+ const auto result = json::to_bson(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bson(result) == j);
+ CHECK(json::from_bson(result, true, false) == j);
+ }
+
+ SECTION("non-empty object with null member")
+ {
+ json const j =
+ {
+ { "entry", nullptr }
+ };
+
+ std::vector<std::uint8_t> const expected =
+ {
+ 0x0C, 0x00, 0x00, 0x00, // size (little endian)
+ 0x0A, /// entry: null
+ 'e', 'n', 't', 'r', 'y', '\x00',
+ 0x00 // end marker
+ };
+
+ const auto result = json::to_bson(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bson(result) == j);
+ CHECK(json::from_bson(result, true, false) == j);
+ }
+
+ SECTION("non-empty object with integer (32-bit) member")
+ {
+ json const j =
+ {
+ { "entry", std::int32_t{0x12345678} }
+ };
+
+ std::vector<std::uint8_t> const expected =
+ {
+ 0x10, 0x00, 0x00, 0x00, // size (little endian)
+ 0x10, /// entry: int32
+ 'e', 'n', 't', 'r', 'y', '\x00',
+ 0x78, 0x56, 0x34, 0x12,
+ 0x00 // end marker
+ };
+
+ const auto result = json::to_bson(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bson(result) == j);
+ CHECK(json::from_bson(result, true, false) == j);
+ }
+
+ SECTION("non-empty object with integer (64-bit) member")
+ {
+ json const j =
+ {
+ { "entry", std::int64_t{0x1234567804030201} }
+ };
+
+ std::vector<std::uint8_t> const expected =
+ {
+ 0x14, 0x00, 0x00, 0x00, // size (little endian)
+ 0x12, /// entry: int64
+ 'e', 'n', 't', 'r', 'y', '\x00',
+ 0x01, 0x02, 0x03, 0x04, 0x78, 0x56, 0x34, 0x12,
+ 0x00 // end marker
+ };
+
+ const auto result = json::to_bson(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bson(result) == j);
+ CHECK(json::from_bson(result, true, false) == j);
+ }
+
+ SECTION("non-empty object with negative integer (32-bit) member")
+ {
+ json const j =
+ {
+ { "entry", std::int32_t{-1} }
+ };
+
+ std::vector<std::uint8_t> const expected =
+ {
+ 0x10, 0x00, 0x00, 0x00, // size (little endian)
+ 0x10, /// entry: int32
+ 'e', 'n', 't', 'r', 'y', '\x00',
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00 // end marker
+ };
+
+ const auto result = json::to_bson(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bson(result) == j);
+ CHECK(json::from_bson(result, true, false) == j);
+ }
+
+ SECTION("non-empty object with negative integer (64-bit) member")
+ {
+ json const j =
+ {
+ { "entry", std::int64_t{-1} }
+ };
+
+ std::vector<std::uint8_t> const expected =
+ {
+ 0x10, 0x00, 0x00, 0x00, // size (little endian)
+ 0x10, /// entry: int32
+ 'e', 'n', 't', 'r', 'y', '\x00',
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00 // end marker
+ };
+
+ const auto result = json::to_bson(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bson(result) == j);
+ CHECK(json::from_bson(result, true, false) == j);
+ }
+
+ SECTION("non-empty object with unsigned integer (64-bit) member")
+ {
+ json const j =
+ {
+ { "entry", std::uint64_t{0x1234567804030201} }
+ };
+
+ std::vector<std::uint8_t> const expected =
+ {
+ 0x14, 0x00, 0x00, 0x00, // size (little endian)
+ 0x12, /// entry: int64
+ 'e', 'n', 't', 'r', 'y', '\x00',
+ 0x01, 0x02, 0x03, 0x04, 0x78, 0x56, 0x34, 0x12,
+ 0x00 // end marker
+ };
+
+ const auto result = json::to_bson(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bson(result) == j);
+ CHECK(json::from_bson(result, true, false) == j);
+ }
+
+ SECTION("non-empty object with small unsigned integer member")
+ {
+ json const j =
+ {
+ { "entry", std::uint64_t{0x42} }
+ };
+
+ std::vector<std::uint8_t> const expected =
+ {
+ 0x10, 0x00, 0x00, 0x00, // size (little endian)
+ 0x10, /// entry: int32
+ 'e', 'n', 't', 'r', 'y', '\x00',
+ 0x42, 0x00, 0x00, 0x00,
+ 0x00 // end marker
+ };
+
+ const auto result = json::to_bson(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bson(result) == j);
+ CHECK(json::from_bson(result, true, false) == j);
+ }
+
+ SECTION("non-empty object with object member")
+ {
+ json const j =
+ {
+ { "entry", json::object() }
+ };
+
+ std::vector<std::uint8_t> const expected =
+ {
+ 0x11, 0x00, 0x00, 0x00, // size (little endian)
+ 0x03, /// entry: embedded document
+ 'e', 'n', 't', 'r', 'y', '\x00',
+
+ 0x05, 0x00, 0x00, 0x00, // size (little endian)
+ // no entries
+ 0x00, // end marker (embedded document)
+
+ 0x00 // end marker
+ };
+
+ const auto result = json::to_bson(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bson(result) == j);
+ CHECK(json::from_bson(result, true, false) == j);
+ }
+
+ SECTION("non-empty object with array member")
+ {
+ json const j =
+ {
+ { "entry", json::array() }
+ };
+
+ std::vector<std::uint8_t> const expected =
+ {
+ 0x11, 0x00, 0x00, 0x00, // size (little endian)
+ 0x04, /// entry: embedded document
+ 'e', 'n', 't', 'r', 'y', '\x00',
+
+ 0x05, 0x00, 0x00, 0x00, // size (little endian)
+ // no entries
+ 0x00, // end marker (embedded document)
+
+ 0x00 // end marker
+ };
+
+ const auto result = json::to_bson(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bson(result) == j);
+ CHECK(json::from_bson(result, true, false) == j);
+ }
+
+ SECTION("non-empty object with non-empty array member")
+ {
+ json const j =
+ {
+ { "entry", json::array({1, 2, 3, 4, 5, 6, 7, 8}) }
+ };
+
+ std::vector<std::uint8_t> const expected =
+ {
+ 0x49, 0x00, 0x00, 0x00, // size (little endian)
+ 0x04, /// entry: embedded document
+ 'e', 'n', 't', 'r', 'y', '\x00',
+
+ 0x3D, 0x00, 0x00, 0x00, // size (little endian)
+ 0x10, '0', 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x10, '1', 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x10, '2', 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x10, '3', 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x10, '4', 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x10, '5', 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x10, '6', 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x10, '7', 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x00, // end marker (embedded document)
+
+ 0x00 // end marker
+ };
+
+ const auto result = json::to_bson(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bson(result) == j);
+ CHECK(json::from_bson(result, true, false) == j);
+ }
+
+ SECTION("non-empty object with binary member")
+ {
+ const size_t N = 10;
+ const auto s = std::vector<std::uint8_t>(N, 'x');
+ json const j =
+ {
+ { "entry", json::binary(s, 0) }
+ };
+
+ std::vector<std::uint8_t> const expected =
+ {
+ 0x1B, 0x00, 0x00, 0x00, // size (little endian)
+ 0x05, // entry: binary
+ 'e', 'n', 't', 'r', 'y', '\x00',
+
+ 0x0A, 0x00, 0x00, 0x00, // size of binary (little endian)
+ 0x00, // Generic binary subtype
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+
+ 0x00 // end marker
+ };
+
+ const auto result = json::to_bson(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bson(result) == j);
+ CHECK(json::from_bson(result, true, false) == j);
+ }
+
+ SECTION("non-empty object with binary member with subtype")
+ {
+ // an MD5 hash
+ const std::vector<std::uint8_t> md5hash = {0xd7, 0x7e, 0x27, 0x54, 0xbe, 0x12, 0x37, 0xfe, 0xd6, 0x0c, 0x33, 0x98, 0x30, 0x3b, 0x8d, 0xc4};
+ json const j =
+ {
+ { "entry", json::binary(md5hash, 5) }
+ };
+
+ std::vector<std::uint8_t> const expected =
+ {
+ 0x21, 0x00, 0x00, 0x00, // size (little endian)
+ 0x05, // entry: binary
+ 'e', 'n', 't', 'r', 'y', '\x00',
+
+ 0x10, 0x00, 0x00, 0x00, // size of binary (little endian)
+ 0x05, // MD5 binary subtype
+ 0xd7, 0x7e, 0x27, 0x54, 0xbe, 0x12, 0x37, 0xfe, 0xd6, 0x0c, 0x33, 0x98, 0x30, 0x3b, 0x8d, 0xc4,
+
+ 0x00 // end marker
+ };
+
+ const auto result = json::to_bson(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bson(result) == j);
+ CHECK(json::from_bson(result, true, false) == j);
+ }
+
+ SECTION("Some more complex document")
+ {
+ json const j =
+ {
+ {"double", 42.5},
+ {"entry", 4.2},
+ {"number", 12345},
+ {"object", {{ "string", "value" }}}
+ };
+
+ std::vector<std::uint8_t> const expected =
+ {
+ /*size */ 0x4f, 0x00, 0x00, 0x00,
+ /*entry*/ 0x01, 'd', 'o', 'u', 'b', 'l', 'e', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x45, 0x40,
+ /*entry*/ 0x01, 'e', 'n', 't', 'r', 'y', 0x00, 0xcd, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x10, 0x40,
+ /*entry*/ 0x10, 'n', 'u', 'm', 'b', 'e', 'r', 0x00, 0x39, 0x30, 0x00, 0x00,
+ /*entry*/ 0x03, 'o', 'b', 'j', 'e', 'c', 't', 0x00,
+ /*entry: obj-size */ 0x17, 0x00, 0x00, 0x00,
+ /*entry: obj-entry*/0x02, 's', 't', 'r', 'i', 'n', 'g', 0x00, 0x06, 0x00, 0x00, 0x00, 'v', 'a', 'l', 'u', 'e', 0,
+ /*entry: obj-term.*/0x00,
+ /*obj-term*/ 0x00
+ };
+
+ const auto result = json::to_bson(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_bson(result) == j);
+ CHECK(json::from_bson(result, true, false) == j);
+ }
+ }
+
+ SECTION("Examples from https://bsonspec.org/faq.html")
+ {
+ SECTION("Example 1")
+ {
+ std::vector<std::uint8_t> input = {0x16, 0x00, 0x00, 0x00, 0x02, 'h', 'e', 'l', 'l', 'o', 0x00, 0x06, 0x00, 0x00, 0x00, 'w', 'o', 'r', 'l', 'd', 0x00, 0x00};
+ const json parsed = json::from_bson(input);
+ const json expected = {{"hello", "world"}};
+ CHECK(parsed == expected);
+ const auto dumped = json::to_bson(parsed);
+ CHECK(dumped == input);
+ CHECK(json::from_bson(dumped) == expected);
+ }
+
+ SECTION("Example 2")
+ {
+ std::vector<std::uint8_t> input = {0x31, 0x00, 0x00, 0x00, 0x04, 'B', 'S', 'O', 'N', 0x00, 0x26, 0x00, 0x00, 0x00, 0x02, 0x30, 0x00, 0x08, 0x00, 0x00, 0x00, 'a', 'w', 'e', 's', 'o', 'm', 'e', 0x00, 0x01, 0x31, 0x00, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x14, 0x40, 0x10, 0x32, 0x00, 0xc2, 0x07, 0x00, 0x00, 0x00, 0x00};
+ const json parsed = json::from_bson(input);
+ const json expected = {{"BSON", {"awesome", 5.05, 1986}}};
+ CHECK(parsed == expected);
+ const auto dumped = json::to_bson(parsed);
+ CHECK(dumped == input);
+ CHECK(json::from_bson(dumped) == expected);
+ }
+ }
+}
+
+TEST_CASE("BSON input/output_adapters")
+{
+ const json json_representation =
+ {
+ {"double", 42.5},
+ {"entry", 4.2},
+ {"number", 12345},
+ {"object", {{ "string", "value" }}}
+ };
+
+ const std::vector<std::uint8_t> bson_representation =
+ {
+ /*size */ 0x4f, 0x00, 0x00, 0x00,
+ /*entry*/ 0x01, 'd', 'o', 'u', 'b', 'l', 'e', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x45, 0x40,
+ /*entry*/ 0x01, 'e', 'n', 't', 'r', 'y', 0x00, 0xcd, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x10, 0x40,
+ /*entry*/ 0x10, 'n', 'u', 'm', 'b', 'e', 'r', 0x00, 0x39, 0x30, 0x00, 0x00,
+ /*entry*/ 0x03, 'o', 'b', 'j', 'e', 'c', 't', 0x00,
+ /*entry: obj-size */ 0x17, 0x00, 0x00, 0x00,
+ /*entry: obj-entry*/0x02, 's', 't', 'r', 'i', 'n', 'g', 0x00, 0x06, 0x00, 0x00, 0x00, 'v', 'a', 'l', 'u', 'e', 0,
+ /*entry: obj-term.*/0x00,
+ /*obj-term*/ 0x00
+ };
+
+ json j2;
+ CHECK_NOTHROW(j2 = json::from_bson(bson_representation));
+
+ // compare parsed JSON values
+ CHECK(json_representation == j2);
+
+ SECTION("roundtrips")
+ {
+ SECTION("std::ostringstream")
+ {
+ std::basic_ostringstream<char> ss;
+ json::to_bson(json_representation, ss);
+ const json j3 = json::from_bson(ss.str());
+ CHECK(json_representation == j3);
+ }
+
+ SECTION("std::string")
+ {
+ std::string s;
+ json::to_bson(json_representation, s);
+ const json j3 = json::from_bson(s);
+ CHECK(json_representation == j3);
+ }
+
+ SECTION("std::vector")
+ {
+ std::vector<std::uint8_t> v;
+ json::to_bson(json_representation, v);
+ const json j3 = json::from_bson(v);
+ CHECK(json_representation == j3);
+ }
+ }
+}
+
+namespace
+{
+class SaxCountdown
+{
+ public:
+ explicit SaxCountdown(const int count) : events_left(count)
+ {}
+
+ bool null()
+ {
+ return events_left-- > 0;
+ }
+
+ bool boolean(bool /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool number_integer(json::number_integer_t /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool number_unsigned(json::number_unsigned_t /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool number_float(json::number_float_t /*unused*/, const std::string& /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool string(std::string& /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool binary(std::vector<std::uint8_t>& /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool start_object(std::size_t /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool key(std::string& /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool end_object()
+ {
+ return events_left-- > 0;
+ }
+
+ bool start_array(std::size_t /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool end_array()
+ {
+ return events_left-- > 0;
+ }
+
+ bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const json::exception& /*unused*/) // NOLINT(readability-convert-member-functions-to-static)
+ {
+ return false;
+ }
+
+ private:
+ int events_left = 0;
+};
+} // namespace
+
+TEST_CASE("Incomplete BSON Input")
+{
+ SECTION("Incomplete BSON Input 1")
+ {
+ std::vector<std::uint8_t> const incomplete_bson =
+ {
+ 0x0D, 0x00, 0x00, 0x00, // size (little endian)
+ 0x08, // entry: boolean
+ 'e', 'n', 't' // unexpected EOF
+ };
+
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_bson(incomplete_bson), "[json.exception.parse_error.110] parse error at byte 9: syntax error while parsing BSON cstring: unexpected end of input", json::parse_error&);
+
+ CHECK(json::from_bson(incomplete_bson, true, false).is_discarded());
+
+ SaxCountdown scp(0);
+ CHECK(!json::sax_parse(incomplete_bson, &scp, json::input_format_t::bson));
+ }
+
+ SECTION("Incomplete BSON Input 2")
+ {
+ std::vector<std::uint8_t> const incomplete_bson =
+ {
+ 0x0D, 0x00, 0x00, 0x00, // size (little endian)
+ 0x08, // entry: boolean, unexpected EOF
+ };
+
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_bson(incomplete_bson), "[json.exception.parse_error.110] parse error at byte 6: syntax error while parsing BSON cstring: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bson(incomplete_bson, true, false).is_discarded());
+
+ SaxCountdown scp(0);
+ CHECK(!json::sax_parse(incomplete_bson, &scp, json::input_format_t::bson));
+ }
+
+ SECTION("Incomplete BSON Input 3")
+ {
+ std::vector<std::uint8_t> const incomplete_bson =
+ {
+ 0x41, 0x00, 0x00, 0x00, // size (little endian)
+ 0x04, /// entry: embedded document
+ 'e', 'n', 't', 'r', 'y', '\x00',
+
+ 0x35, 0x00, 0x00, 0x00, // size (little endian)
+ 0x10, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x02, 0x00, 0x00, 0x00
+ // missing input data...
+ };
+
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_bson(incomplete_bson), "[json.exception.parse_error.110] parse error at byte 28: syntax error while parsing BSON element list: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bson(incomplete_bson, true, false).is_discarded());
+
+ SaxCountdown scp(1);
+ CHECK(!json::sax_parse(incomplete_bson, &scp, json::input_format_t::bson));
+ }
+
+ SECTION("Incomplete BSON Input 4")
+ {
+ std::vector<std::uint8_t> const incomplete_bson =
+ {
+ 0x0D, 0x00, // size (incomplete), unexpected EOF
+ };
+
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_bson(incomplete_bson), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BSON number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_bson(incomplete_bson, true, false).is_discarded());
+
+ SaxCountdown scp(0);
+ CHECK(!json::sax_parse(incomplete_bson, &scp, json::input_format_t::bson));
+ }
+
+ SECTION("Improve coverage")
+ {
+ SECTION("key")
+ {
+ json const j = {{"key", "value"}};
+ auto bson_vec = json::to_bson(j);
+ SaxCountdown scp(2);
+ CHECK(!json::sax_parse(bson_vec, &scp, json::input_format_t::bson));
+ }
+
+ SECTION("array")
+ {
+ json const j =
+ {
+ { "entry", json::array() }
+ };
+ auto bson_vec = json::to_bson(j);
+ SaxCountdown scp(2);
+ CHECK(!json::sax_parse(bson_vec, &scp, json::input_format_t::bson));
+ }
+ }
+}
+
+TEST_CASE("Negative size of binary value")
+{
+ // invalid BSON: the size of the binary value is -1
+ std::vector<std::uint8_t> const input =
+ {
+ 0x21, 0x00, 0x00, 0x00, // size (little endian)
+ 0x05, // entry: binary
+ 'e', 'n', 't', 'r', 'y', '\x00',
+
+ 0xFF, 0xFF, 0xFF, 0xFF, // size of binary (little endian)
+ 0x05, // MD5 binary subtype
+ 0xd7, 0x7e, 0x27, 0x54, 0xbe, 0x12, 0x37, 0xfe, 0xd6, 0x0c, 0x33, 0x98, 0x30, 0x3b, 0x8d, 0xc4,
+
+ 0x00 // end marker
+ };
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.112] parse error at byte 15: syntax error while parsing BSON binary: byte array length cannot be negative, is -1", json::parse_error);
+}
+
+TEST_CASE("Unsupported BSON input")
+{
+ std::vector<std::uint8_t> const bson =
+ {
+ 0x0C, 0x00, 0x00, 0x00, // size (little endian)
+ 0xFF, // entry type: Min key (not supported yet)
+ 'e', 'n', 't', 'r', 'y', '\x00',
+ 0x00 // end marker
+ };
+
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_bson(bson), "[json.exception.parse_error.114] parse error at byte 5: Unsupported BSON record type 0xFF", json::parse_error&);
+ CHECK(json::from_bson(bson, true, false).is_discarded());
+
+ SaxCountdown scp(0);
+ CHECK(!json::sax_parse(bson, &scp, json::input_format_t::bson));
+}
+
+TEST_CASE("BSON numerical data")
+{
+ SECTION("number")
+ {
+ SECTION("signed")
+ {
+ SECTION("std::int64_t: INT64_MIN .. INT32_MIN-1")
+ {
+ std::vector<int64_t> const numbers
+ {
+ (std::numeric_limits<int64_t>::min)(),
+ -1000000000000000000LL,
+ -100000000000000000LL,
+ -10000000000000000LL,
+ -1000000000000000LL,
+ -100000000000000LL,
+ -10000000000000LL,
+ -1000000000000LL,
+ -100000000000LL,
+ -10000000000LL,
+ static_cast<std::int64_t>((std::numeric_limits<std::int32_t>::min)()) - 1,
+ };
+
+ for (const auto i : numbers)
+ {
+
+ CAPTURE(i)
+
+ json const j =
+ {
+ { "entry", i }
+ };
+ CHECK(j.at("entry").is_number_integer());
+
+ std::uint64_t const iu = *reinterpret_cast<const std::uint64_t*>(&i);
+ std::vector<std::uint8_t> const expected_bson =
+ {
+ 0x14u, 0x00u, 0x00u, 0x00u, // size (little endian)
+ 0x12u, /// entry: int64
+ 'e', 'n', 't', 'r', 'y', '\x00',
+ static_cast<std::uint8_t>((iu >> (8u * 0u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 1u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 2u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 3u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 4u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 5u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 6u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 7u)) & 0xffu),
+ 0x00u // end marker
+ };
+
+ const auto bson = json::to_bson(j);
+ CHECK(bson == expected_bson);
+
+ auto j_roundtrip = json::from_bson(bson);
+
+ CHECK(j_roundtrip.at("entry").is_number_integer());
+ CHECK(j_roundtrip == j);
+ CHECK(json::from_bson(bson, true, false) == j);
+
+ }
+ }
+
+ SECTION("signed std::int32_t: INT32_MIN .. INT32_MAX")
+ {
+ std::vector<int32_t> const numbers
+ {
+ (std::numeric_limits<int32_t>::min)(),
+ -2147483647L,
+ -1000000000L,
+ -100000000L,
+ -10000000L,
+ -1000000L,
+ -100000L,
+ -10000L,
+ -1000L,
+ -100L,
+ -10L,
+ -1L,
+ 0L,
+ 1L,
+ 10L,
+ 100L,
+ 1000L,
+ 10000L,
+ 100000L,
+ 1000000L,
+ 10000000L,
+ 100000000L,
+ 1000000000L,
+ 2147483646L,
+ (std::numeric_limits<int32_t>::max)()
+ };
+
+ for (const auto i : numbers)
+ {
+
+ CAPTURE(i)
+
+ json const j =
+ {
+ { "entry", i }
+ };
+ CHECK(j.at("entry").is_number_integer());
+
+ std::uint32_t const iu = *reinterpret_cast<const std::uint32_t*>(&i);
+ std::vector<std::uint8_t> const expected_bson =
+ {
+ 0x10u, 0x00u, 0x00u, 0x00u, // size (little endian)
+ 0x10u, /// entry: int32
+ 'e', 'n', 't', 'r', 'y', '\x00',
+ static_cast<std::uint8_t>((iu >> (8u * 0u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 1u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 2u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 3u)) & 0xffu),
+ 0x00u // end marker
+ };
+
+ const auto bson = json::to_bson(j);
+ CHECK(bson == expected_bson);
+
+ auto j_roundtrip = json::from_bson(bson);
+
+ CHECK(j_roundtrip.at("entry").is_number_integer());
+ CHECK(j_roundtrip == j);
+ CHECK(json::from_bson(bson, true, false) == j);
+
+ }
+ }
+
+ SECTION("signed std::int64_t: INT32_MAX+1 .. INT64_MAX")
+ {
+ std::vector<int64_t> const numbers
+ {
+ (std::numeric_limits<int64_t>::max)(),
+ 1000000000000000000LL,
+ 100000000000000000LL,
+ 10000000000000000LL,
+ 1000000000000000LL,
+ 100000000000000LL,
+ 10000000000000LL,
+ 1000000000000LL,
+ 100000000000LL,
+ 10000000000LL,
+ static_cast<std::int64_t>((std::numeric_limits<int32_t>::max)()) + 1,
+ };
+
+ for (const auto i : numbers)
+ {
+
+ CAPTURE(i)
+
+ json const j =
+ {
+ { "entry", i }
+ };
+ CHECK(j.at("entry").is_number_integer());
+
+ std::uint64_t const iu = *reinterpret_cast<const std::uint64_t*>(&i);
+ std::vector<std::uint8_t> const expected_bson =
+ {
+ 0x14u, 0x00u, 0x00u, 0x00u, // size (little endian)
+ 0x12u, /// entry: int64
+ 'e', 'n', 't', 'r', 'y', '\x00',
+ static_cast<std::uint8_t>((iu >> (8u * 0u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 1u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 2u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 3u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 4u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 5u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 6u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 7u)) & 0xffu),
+ 0x00u // end marker
+ };
+
+ const auto bson = json::to_bson(j);
+ CHECK(bson == expected_bson);
+
+ auto j_roundtrip = json::from_bson(bson);
+
+ CHECK(j_roundtrip.at("entry").is_number_integer());
+ CHECK(j_roundtrip == j);
+ CHECK(json::from_bson(bson, true, false) == j);
+
+ }
+ }
+ }
+
+ SECTION("unsigned")
+ {
+ SECTION("unsigned std::uint64_t: 0 .. INT32_MAX")
+ {
+ std::vector<std::uint64_t> const numbers
+ {
+ 0ULL,
+ 1ULL,
+ 10ULL,
+ 100ULL,
+ 1000ULL,
+ 10000ULL,
+ 100000ULL,
+ 1000000ULL,
+ 10000000ULL,
+ 100000000ULL,
+ 1000000000ULL,
+ 2147483646ULL,
+ static_cast<std::uint64_t>((std::numeric_limits<int32_t>::max)())
+ };
+
+ for (const auto i : numbers)
+ {
+
+ CAPTURE(i)
+
+ json const j =
+ {
+ { "entry", i }
+ };
+
+ auto iu = i;
+ std::vector<std::uint8_t> const expected_bson =
+ {
+ 0x10u, 0x00u, 0x00u, 0x00u, // size (little endian)
+ 0x10u, /// entry: int32
+ 'e', 'n', 't', 'r', 'y', '\x00',
+ static_cast<std::uint8_t>((iu >> (8u * 0u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 1u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 2u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 3u)) & 0xffu),
+ 0x00u // end marker
+ };
+
+ const auto bson = json::to_bson(j);
+ CHECK(bson == expected_bson);
+
+ auto j_roundtrip = json::from_bson(bson);
+
+ CHECK(j.at("entry").is_number_unsigned());
+ CHECK(j_roundtrip.at("entry").is_number_integer());
+ CHECK(j_roundtrip == j);
+ CHECK(json::from_bson(bson, true, false) == j);
+
+ }
+ }
+
+ SECTION("unsigned std::uint64_t: INT32_MAX+1 .. INT64_MAX")
+ {
+ std::vector<std::uint64_t> const numbers
+ {
+ static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)()) + 1,
+ 4000000000ULL,
+ static_cast<std::uint64_t>((std::numeric_limits<std::uint32_t>::max)()),
+ 10000000000ULL,
+ 100000000000ULL,
+ 1000000000000ULL,
+ 10000000000000ULL,
+ 100000000000000ULL,
+ 1000000000000000ULL,
+ 10000000000000000ULL,
+ 100000000000000000ULL,
+ 1000000000000000000ULL,
+ static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()),
+ };
+
+ for (const auto i : numbers)
+ {
+
+ CAPTURE(i)
+
+ json const j =
+ {
+ { "entry", i }
+ };
+
+ auto iu = i;
+ std::vector<std::uint8_t> const expected_bson =
+ {
+ 0x14u, 0x00u, 0x00u, 0x00u, // size (little endian)
+ 0x12u, /// entry: int64
+ 'e', 'n', 't', 'r', 'y', '\x00',
+ static_cast<std::uint8_t>((iu >> (8u * 0u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 1u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 2u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 3u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 4u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 5u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 6u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 7u)) & 0xffu),
+ 0x00u // end marker
+ };
+
+ const auto bson = json::to_bson(j);
+ CHECK(bson == expected_bson);
+
+ auto j_roundtrip = json::from_bson(bson);
+
+ CHECK(j.at("entry").is_number_unsigned());
+ CHECK(j_roundtrip.at("entry").is_number_integer());
+ CHECK(j_roundtrip == j);
+ CHECK(json::from_bson(bson, true, false) == j);
+ }
+ }
+
+ SECTION("unsigned std::uint64_t: INT64_MAX+1 .. UINT64_MAX")
+ {
+ std::vector<std::uint64_t> const numbers
+ {
+ static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()) + 1ULL,
+ 0xffffffffffffffff,
+ };
+
+ for (const auto i : numbers)
+ {
+
+ CAPTURE(i)
+
+ json const j =
+ {
+ { "entry", i }
+ };
+
+ auto iu = i;
+ std::vector<std::uint8_t> const expected_bson =
+ {
+ 0x14u, 0x00u, 0x00u, 0x00u, // size (little endian)
+ 0x11u, /// entry: uint64
+ 'e', 'n', 't', 'r', 'y', '\x00',
+ static_cast<std::uint8_t>((iu >> (8u * 0u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 1u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 2u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 3u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 4u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 5u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 6u)) & 0xffu),
+ static_cast<std::uint8_t>((iu >> (8u * 7u)) & 0xffu),
+ 0x00u // end marker
+ };
+
+ const auto bson = json::to_bson(j);
+ CHECK(bson == expected_bson);
+
+ auto j_roundtrip = json::from_bson(bson);
+
+ CHECK(j.at("entry").is_number_unsigned());
+ CHECK(j_roundtrip.at("entry").is_number_unsigned());
+ CHECK(j_roundtrip == j);
+ CHECK(json::from_bson(bson, true, false) == j);
+ }
+ }
+
+ }
+ }
+}
+
+TEST_CASE("BSON roundtrips" * doctest::skip())
+{
+ SECTION("reference files")
+ {
+ for (const std::string filename :
+ {
+ TEST_DATA_DIRECTORY "/json.org/1.json",
+ TEST_DATA_DIRECTORY "/json.org/2.json",
+ TEST_DATA_DIRECTORY "/json.org/3.json",
+ TEST_DATA_DIRECTORY "/json.org/4.json",
+ TEST_DATA_DIRECTORY "/json.org/5.json"
+ })
+ {
+ CAPTURE(filename)
+
+ {
+ INFO_WITH_TEMP(filename + ": std::vector<std::uint8_t>");
+ // parse JSON file
+ std::ifstream f_json(filename);
+ const json j1 = json::parse(f_json);
+
+ // parse BSON file
+ auto packed = utils::read_binary_file(filename + ".bson");
+ json j2;
+ CHECK_NOTHROW(j2 = json::from_bson(packed));
+
+ // compare parsed JSON values
+ CHECK(j1 == j2);
+ }
+
+ {
+ INFO_WITH_TEMP(filename + ": std::ifstream");
+ // parse JSON file
+ std::ifstream f_json(filename);
+ const json j1 = json::parse(f_json);
+
+ // parse BSON file
+ std::ifstream f_bson(filename + ".bson", std::ios::binary);
+ json j2;
+ CHECK_NOTHROW(j2 = json::from_bson(f_bson));
+
+ // compare parsed JSON values
+ CHECK(j1 == j2);
+ }
+
+ {
+ INFO_WITH_TEMP(filename + ": uint8_t* and size");
+ // parse JSON file
+ std::ifstream f_json(filename);
+ const json j1 = json::parse(f_json);
+
+ // parse BSON file
+ auto packed = utils::read_binary_file(filename + ".bson");
+ json j2;
+ CHECK_NOTHROW(j2 = json::from_bson({packed.data(), packed.size()}));
+
+ // compare parsed JSON values
+ CHECK(j1 == j2);
+ }
+
+ {
+ INFO_WITH_TEMP(filename + ": output to output adapters");
+ // parse JSON file
+ std::ifstream f_json(filename);
+ json const j1 = json::parse(f_json);
+
+ // parse BSON file
+ auto packed = utils::read_binary_file(filename + ".bson");
+
+ {
+ INFO_WITH_TEMP(filename + ": output adapters: std::vector<std::uint8_t>");
+ std::vector<std::uint8_t> vec;
+ json::to_bson(j1, vec);
+
+ if (vec != packed)
+ {
+ // the exact serializations may differ due to the order of
+ // object keys; in these cases, just compare whether both
+ // serializations create the same JSON value
+ CHECK(json::from_bson(vec) == json::from_bson(packed));
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/json4cpp/tests/src/unit-byte_container_with_subtype.cpp b/json4cpp/tests/src/unit-byte_container_with_subtype.cpp
new file mode 100644
index 0000000000..3983ba95f0
--- /dev/null
+++ b/json4cpp/tests/src/unit-byte_container_with_subtype.cpp
@@ -0,0 +1,76 @@
+// __ _____ _____ _____
+// __| | __| | | | 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;
+
+TEST_CASE("byte_container_with_subtype")
+{
+ using subtype_type = nlohmann::byte_container_with_subtype<std::vector<std::uint8_t>>::subtype_type;
+
+ SECTION("empty container")
+ {
+ nlohmann::byte_container_with_subtype<std::vector<std::uint8_t>> container;
+
+ CHECK(!container.has_subtype());
+ CHECK(container.subtype() == static_cast<subtype_type>(-1));
+
+ container.clear_subtype();
+ CHECK(!container.has_subtype());
+ CHECK(container.subtype() == static_cast<subtype_type>(-1));
+
+ container.set_subtype(42);
+ CHECK(container.has_subtype());
+ CHECK(container.subtype() == 42);
+ }
+
+ SECTION("subtyped container")
+ {
+ nlohmann::byte_container_with_subtype<std::vector<std::uint8_t>> container({}, 42);
+ CHECK(container.has_subtype());
+ CHECK(container.subtype() == 42);
+
+ container.clear_subtype();
+ CHECK(!container.has_subtype());
+ CHECK(container.subtype() == static_cast<subtype_type>(-1));
+ }
+
+ SECTION("comparisons")
+ {
+ std::vector<std::uint8_t> const bytes = {{0xCA, 0xFE, 0xBA, 0xBE}};
+ nlohmann::byte_container_with_subtype<std::vector<std::uint8_t>> container1;
+ nlohmann::byte_container_with_subtype<std::vector<std::uint8_t>> container2({}, 42);
+ nlohmann::byte_container_with_subtype<std::vector<std::uint8_t>> container3(bytes);
+ nlohmann::byte_container_with_subtype<std::vector<std::uint8_t>> container4(bytes, 42);
+
+ CHECK(container1 == container1);
+ CHECK(container1 != container2);
+ CHECK(container1 != container3);
+ CHECK(container1 != container4);
+ CHECK(container2 != container1);
+ CHECK(container2 == container2);
+ CHECK(container2 != container3);
+ CHECK(container2 != container4);
+ CHECK(container3 != container1);
+ CHECK(container3 != container2);
+ CHECK(container3 == container3);
+ CHECK(container3 != container4);
+ CHECK(container4 != container1);
+ CHECK(container4 != container2);
+ CHECK(container4 != container3);
+ CHECK(container4 == container4);
+
+ container3.clear();
+ container4.clear();
+
+ CHECK(container1 == container3);
+ CHECK(container2 == container4);
+ }
+}
diff --git a/json4cpp/tests/src/unit-capacity.cpp b/json4cpp/tests/src/unit-capacity.cpp
new file mode 100644
index 0000000000..98fcd00892
--- /dev/null
+++ b/json4cpp/tests/src/unit-capacity.cpp
@@ -0,0 +1,542 @@
+// __ _____ _____ _____
+// __| | __| | | | 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;
+
+TEST_CASE("capacity")
+{
+ SECTION("empty()")
+ {
+ SECTION("boolean")
+ {
+ json j = true; // NOLINT(misc-const-correctness)
+ const json j_const = true;
+
+ SECTION("result of empty")
+ {
+ CHECK(j.empty() == false);
+ CHECK(j_const.empty() == false);
+ }
+
+ SECTION("definition of empty")
+ {
+ CHECK(j.empty() == (j.begin() == j.end()));
+ CHECK(j_const.empty() == (j_const.begin() == j_const.end()));
+ }
+ }
+
+ SECTION("string")
+ {
+ json j = "hello world"; // NOLINT(misc-const-correctness)
+ const json j_const = "hello world";
+
+ SECTION("result of empty")
+ {
+ CHECK(j.empty() == false);
+ CHECK(j_const.empty() == false);
+ }
+
+ SECTION("definition of empty")
+ {
+ CHECK(j.empty() == (j.begin() == j.end()));
+ CHECK(j_const.empty() == (j_const.begin() == j_const.end()));
+ }
+ }
+
+ SECTION("array")
+ {
+ SECTION("empty array")
+ {
+ json j = json::array(); // NOLINT(misc-const-correctness)
+ const json j_const = json::array();
+
+ SECTION("result of empty")
+ {
+ CHECK(j.empty() == true);
+ CHECK(j_const.empty() == true);
+ }
+
+ SECTION("definition of empty")
+ {
+ CHECK(j.empty() == (j.begin() == j.end()));
+ CHECK(j_const.empty() == (j_const.begin() == j_const.end()));
+ }
+ }
+
+ SECTION("filled array")
+ {
+ json j = {1, 2, 3}; // NOLINT(misc-const-correctness)
+ const json j_const = {1, 2, 3};
+
+ SECTION("result of empty")
+ {
+ CHECK(j.empty() == false);
+ CHECK(j_const.empty() == false);
+ }
+
+ SECTION("definition of empty")
+ {
+ CHECK(j.empty() == (j.begin() == j.end()));
+ CHECK(j_const.empty() == (j_const.begin() == j_const.end()));
+ }
+ }
+ }
+
+ SECTION("object")
+ {
+ SECTION("empty object")
+ {
+ json j = json::object(); // NOLINT(misc-const-correctness)
+ const json j_const = json::object();
+
+ SECTION("result of empty")
+ {
+ CHECK(j.empty() == true);
+ CHECK(j_const.empty() == true);
+ }
+
+ SECTION("definition of empty")
+ {
+ CHECK(j.empty() == (j.begin() == j.end()));
+ CHECK(j_const.empty() == (j_const.begin() == j_const.end()));
+ }
+ }
+
+ SECTION("filled object")
+ {
+ json j = {{"one", 1}, {"two", 2}, {"three", 3}}; // NOLINT(misc-const-correctness)
+ const json j_const = {{"one", 1}, {"two", 2}, {"three", 3}};
+
+ SECTION("result of empty")
+ {
+ CHECK(j.empty() == false);
+ CHECK(j_const.empty() == false);
+ }
+
+ SECTION("definition of empty")
+ {
+ CHECK(j.empty() == (j.begin() == j.end()));
+ CHECK(j_const.empty() == (j_const.begin() == j_const.end()));
+ }
+ }
+ }
+
+ SECTION("number (integer)")
+ {
+ json j = -23; // NOLINT(misc-const-correctness)
+ const json j_const = -23;
+
+ SECTION("result of empty")
+ {
+ CHECK(j.empty() == false);
+ CHECK(j_const.empty() == false);
+ }
+
+ SECTION("definition of empty")
+ {
+ CHECK(j.empty() == (j.begin() == j.end()));
+ CHECK(j_const.empty() == (j_const.begin() == j_const.end()));
+ }
+ }
+
+ SECTION("number (unsigned)")
+ {
+ json j = 23u; // NOLINT(misc-const-correctness)
+ const json j_const = 23u;
+
+ SECTION("result of empty")
+ {
+ CHECK(j.empty() == false);
+ CHECK(j_const.empty() == false);
+ }
+
+ SECTION("definition of empty")
+ {
+ CHECK(j.empty() == (j.begin() == j.end()));
+ CHECK(j_const.empty() == (j_const.begin() == j_const.end()));
+ }
+ }
+
+ SECTION("number (float)")
+ {
+ json j = 23.42; // NOLINT(misc-const-correctness)
+ const json j_const = 23.42;
+
+ SECTION("result of empty")
+ {
+ CHECK(j.empty() == false);
+ CHECK(j_const.empty() == false);
+ }
+
+ SECTION("definition of empty")
+ {
+ CHECK(j.empty() == (j.begin() == j.end()));
+ CHECK(j_const.empty() == (j_const.begin() == j_const.end()));
+ }
+ }
+
+ SECTION("null")
+ {
+ json j = nullptr; // NOLINT(misc-const-correctness)
+ const json j_const = nullptr;
+
+ SECTION("result of empty")
+ {
+ CHECK(j.empty() == true);
+ CHECK(j_const.empty() == true);
+ }
+
+ SECTION("definition of empty")
+ {
+ CHECK(j.empty() == (j.begin() == j.end()));
+ CHECK(j_const.empty() == (j_const.begin() == j_const.end()));
+ }
+ }
+ }
+
+ SECTION("size()")
+ {
+ SECTION("boolean")
+ {
+ json j = true; // NOLINT(misc-const-correctness)
+ const json j_const = true;
+
+ SECTION("result of size")
+ {
+ CHECK(j.size() == 1);
+ CHECK(j_const.size() == 1);
+ }
+
+ SECTION("definition of size")
+ {
+ CHECK(std::distance(j.begin(), j.end()) == j.size());
+ CHECK(std::distance(j_const.begin(), j_const.end()) == j_const.size());
+ CHECK(std::distance(j.rbegin(), j.rend()) == j.size());
+ CHECK(std::distance(j_const.crbegin(), j_const.crend()) == j_const.size());
+ }
+ }
+
+ SECTION("string")
+ {
+ json j = "hello world"; // NOLINT(misc-const-correctness)
+ const json j_const = "hello world";
+
+ SECTION("result of size")
+ {
+ CHECK(j.size() == 1);
+ CHECK(j_const.size() == 1);
+ }
+
+ SECTION("definition of size")
+ {
+ CHECK(std::distance(j.begin(), j.end()) == j.size());
+ CHECK(std::distance(j_const.begin(), j_const.end()) == j_const.size());
+ CHECK(std::distance(j.rbegin(), j.rend()) == j.size());
+ CHECK(std::distance(j_const.crbegin(), j_const.crend()) == j_const.size());
+ }
+ }
+
+ SECTION("array")
+ {
+ SECTION("empty array")
+ {
+ json j = json::array(); // NOLINT(misc-const-correctness)
+ const json j_const = json::array();
+
+ SECTION("result of size")
+ {
+ CHECK(j.size() == 0);
+ CHECK(j_const.size() == 0);
+ }
+
+ SECTION("definition of size")
+ {
+ CHECK(std::distance(j.begin(), j.end()) == j.size());
+ CHECK(std::distance(j_const.begin(), j_const.end()) == j_const.size());
+ CHECK(std::distance(j.rbegin(), j.rend()) == j.size());
+ CHECK(std::distance(j_const.crbegin(), j_const.crend()) == j_const.size());
+ }
+ }
+
+ SECTION("filled array")
+ {
+ json j = {1, 2, 3}; // NOLINT(misc-const-correctness)
+ const json j_const = {1, 2, 3};
+
+ SECTION("result of size")
+ {
+ CHECK(j.size() == 3);
+ CHECK(j_const.size() == 3);
+ }
+
+ SECTION("definition of size")
+ {
+ CHECK(std::distance(j.begin(), j.end()) == j.size());
+ CHECK(std::distance(j_const.begin(), j_const.end()) == j_const.size());
+ CHECK(std::distance(j.rbegin(), j.rend()) == j.size());
+ CHECK(std::distance(j_const.crbegin(), j_const.crend()) == j_const.size());
+ }
+ }
+ }
+
+ SECTION("object")
+ {
+ SECTION("empty object")
+ {
+ json j = json::object(); // NOLINT(misc-const-correctness)
+ const json j_const = json::object();
+
+ SECTION("result of size")
+ {
+ CHECK(j.size() == 0);
+ CHECK(j_const.size() == 0);
+ }
+
+ SECTION("definition of size")
+ {
+ CHECK(std::distance(j.begin(), j.end()) == j.size());
+ CHECK(std::distance(j_const.begin(), j_const.end()) == j_const.size());
+ CHECK(std::distance(j.rbegin(), j.rend()) == j.size());
+ CHECK(std::distance(j_const.crbegin(), j_const.crend()) == j_const.size());
+ }
+ }
+
+ SECTION("filled object")
+ {
+ json j = {{"one", 1}, {"two", 2}, {"three", 3}}; // NOLINT(misc-const-correctness)
+ const json j_const = {{"one", 1}, {"two", 2}, {"three", 3}};
+
+ SECTION("result of size")
+ {
+ CHECK(j.size() == 3);
+ CHECK(j_const.size() == 3);
+ }
+
+ SECTION("definition of size")
+ {
+ CHECK(std::distance(j.begin(), j.end()) == j.size());
+ CHECK(std::distance(j_const.begin(), j_const.end()) == j_const.size());
+ CHECK(std::distance(j.rbegin(), j.rend()) == j.size());
+ CHECK(std::distance(j_const.crbegin(), j_const.crend()) == j_const.size());
+ }
+ }
+ }
+
+ SECTION("number (integer)")
+ {
+ json j = -23; // NOLINT(misc-const-correctness)
+ const json j_const = -23;
+
+ SECTION("result of size")
+ {
+ CHECK(j.size() == 1);
+ CHECK(j_const.size() == 1);
+ }
+
+ SECTION("definition of size")
+ {
+ CHECK(std::distance(j.begin(), j.end()) == j.size());
+ CHECK(std::distance(j_const.begin(), j_const.end()) == j_const.size());
+ CHECK(std::distance(j.rbegin(), j.rend()) == j.size());
+ CHECK(std::distance(j_const.crbegin(), j_const.crend()) == j_const.size());
+ }
+ }
+
+ SECTION("number (unsigned)")
+ {
+ json j = 23u; // NOLINT(misc-const-correctness)
+ const json j_const = 23u;
+
+ SECTION("result of size")
+ {
+ CHECK(j.size() == 1);
+ CHECK(j_const.size() == 1);
+ }
+
+ SECTION("definition of size")
+ {
+ CHECK(std::distance(j.begin(), j.end()) == j.size());
+ CHECK(std::distance(j_const.begin(), j_const.end()) == j_const.size());
+ CHECK(std::distance(j.rbegin(), j.rend()) == j.size());
+ CHECK(std::distance(j_const.crbegin(), j_const.crend()) == j_const.size());
+ }
+ }
+
+ SECTION("number (float)")
+ {
+ json j = 23.42; // NOLINT(misc-const-correctness)
+ const json j_const = 23.42;
+
+ SECTION("result of size")
+ {
+ CHECK(j.size() == 1);
+ CHECK(j_const.size() == 1);
+ }
+
+ SECTION("definition of size")
+ {
+ CHECK(std::distance(j.begin(), j.end()) == j.size());
+ CHECK(std::distance(j_const.begin(), j_const.end()) == j_const.size());
+ CHECK(std::distance(j.rbegin(), j.rend()) == j.size());
+ CHECK(std::distance(j_const.crbegin(), j_const.crend()) == j_const.size());
+ }
+ }
+
+ SECTION("null")
+ {
+ json j = nullptr; // NOLINT(misc-const-correctness)
+ const json j_const = nullptr;
+
+ SECTION("result of size")
+ {
+ CHECK(j.size() == 0);
+ CHECK(j_const.size() == 0);
+ }
+
+ SECTION("definition of size")
+ {
+ CHECK(std::distance(j.begin(), j.end()) == j.size());
+ CHECK(std::distance(j_const.begin(), j_const.end()) == j_const.size());
+ CHECK(std::distance(j.rbegin(), j.rend()) == j.size());
+ CHECK(std::distance(j_const.crbegin(), j_const.crend()) == j_const.size());
+ }
+ }
+ }
+
+ SECTION("max_size()")
+ {
+ SECTION("boolean")
+ {
+ json j = true; // NOLINT(misc-const-correctness)
+ const json j_const = true;
+
+ SECTION("result of max_size")
+ {
+ CHECK(j.max_size() == 1);
+ CHECK(j_const.max_size() == 1);
+ }
+ }
+
+ SECTION("string")
+ {
+ json j = "hello world"; // NOLINT(misc-const-correctness)
+ const json j_const = "hello world";
+
+ SECTION("result of max_size")
+ {
+ CHECK(j.max_size() == 1);
+ CHECK(j_const.max_size() == 1);
+ }
+ }
+
+ SECTION("array")
+ {
+ SECTION("empty array")
+ {
+ json j = json::array(); // NOLINT(misc-const-correctness)
+ const json j_const = json::array();
+
+ SECTION("result of max_size")
+ {
+ CHECK(j.max_size() >= j.size());
+ CHECK(j_const.max_size() >= j_const.size());
+ }
+ }
+
+ SECTION("filled array")
+ {
+ json j = {1, 2, 3}; // NOLINT(misc-const-correctness)
+ const json j_const = {1, 2, 3};
+
+ SECTION("result of max_size")
+ {
+ CHECK(j.max_size() >= j.size());
+ CHECK(j_const.max_size() >= j_const.size());
+ }
+ }
+ }
+
+ SECTION("object")
+ {
+ SECTION("empty object")
+ {
+ json j = json::object(); // NOLINT(misc-const-correctness)
+ const json j_const = json::object();
+
+ SECTION("result of max_size")
+ {
+ CHECK(j.max_size() >= j.size());
+ CHECK(j_const.max_size() >= j_const.size());
+ }
+ }
+
+ SECTION("filled object")
+ {
+ json j = {{"one", 1}, {"two", 2}, {"three", 3}}; // NOLINT(misc-const-correctness)
+ const json j_const = {{"one", 1}, {"two", 2}, {"three", 3}};
+
+ SECTION("result of max_size")
+ {
+ CHECK(j.max_size() >= j.size());
+ CHECK(j_const.max_size() >= j_const.size());
+ }
+ }
+ }
+
+ SECTION("number (integer)")
+ {
+ json j = -23; // NOLINT(misc-const-correctness)
+ const json j_const = -23;
+
+ SECTION("result of max_size")
+ {
+ CHECK(j.max_size() == 1);
+ CHECK(j_const.max_size() == 1);
+ }
+ }
+
+ SECTION("number (unsigned)")
+ {
+ json j = 23u; // NOLINT(misc-const-correctness)
+ const json j_const = 23u;
+
+ SECTION("result of max_size")
+ {
+ CHECK(j.max_size() == 1);
+ CHECK(j_const.max_size() == 1);
+ }
+ }
+
+ SECTION("number (float)")
+ {
+ json j = 23.42; // NOLINT(misc-const-correctness)
+ const json j_const = 23.42;
+
+ SECTION("result of max_size")
+ {
+ CHECK(j.max_size() == 1);
+ CHECK(j_const.max_size() == 1);
+ }
+ }
+
+ SECTION("null")
+ {
+ json j = nullptr; // NOLINT(misc-const-correctness)
+ const json j_const = nullptr;
+
+ SECTION("result of max_size")
+ {
+ CHECK(j.max_size() == 0);
+ CHECK(j_const.max_size() == 0);
+ }
+ }
+ }
+}
diff --git a/json4cpp/tests/src/unit-cbor.cpp b/json4cpp/tests/src/unit-cbor.cpp
new file mode 100644
index 0000000000..5da5b0c7ab
--- /dev/null
+++ b/json4cpp/tests/src/unit-cbor.cpp
@@ -0,0 +1,2780 @@
+// __ _____ _____ _____
+// __| | __| | | | 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;
+
+#include <fstream>
+#include <sstream>
+#include <iomanip>
+#include <limits>
+#include <set>
+#include "make_test_data_available.hpp"
+#include "test_utils.hpp"
+
+namespace
+{
+class SaxCountdown
+{
+ public:
+ explicit SaxCountdown(const int count) : events_left(count)
+ {}
+
+ bool null()
+ {
+ return events_left-- > 0;
+ }
+
+ bool boolean(bool /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool number_integer(json::number_integer_t /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool number_unsigned(json::number_unsigned_t /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool number_float(json::number_float_t /*unused*/, const std::string& /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool string(std::string& /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool binary(std::vector<std::uint8_t>& /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool start_object(std::size_t /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool key(std::string& /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool end_object()
+ {
+ return events_left-- > 0;
+ }
+
+ bool start_array(std::size_t /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool end_array()
+ {
+ return events_left-- > 0;
+ }
+
+ bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const json::exception& /*unused*/) // NOLINT(readability-convert-member-functions-to-static)
+ {
+ return false;
+ }
+
+ private:
+ int events_left = 0;
+};
+} // namespace
+
+TEST_CASE("CBOR")
+{
+ SECTION("individual values")
+ {
+ SECTION("discarded")
+ {
+ // discarded values are not serialized
+ json const j = json::value_t::discarded;
+ const auto result = json::to_cbor(j);
+ CHECK(result.empty());
+ }
+
+ SECTION("NaN")
+ {
+ // NaN value
+ json const j = std::numeric_limits<json::number_float_t>::quiet_NaN();
+ const std::vector<uint8_t> expected = {0xf9, 0x7e, 0x00};
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ }
+
+ SECTION("Infinity")
+ {
+ // Infinity value
+ json const j = std::numeric_limits<json::number_float_t>::infinity();
+ const std::vector<uint8_t> expected = {0xf9, 0x7c, 0x00};
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ }
+
+ SECTION("null")
+ {
+ const json j = nullptr;
+ const std::vector<uint8_t> expected = {0xf6};
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+
+ SECTION("boolean")
+ {
+ SECTION("true")
+ {
+ const json j = true;
+ const std::vector<uint8_t> expected = {0xf5};
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+
+ SECTION("false")
+ {
+ const json j = false;
+ const std::vector<uint8_t> expected = {0xf4};
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+ }
+
+ SECTION("number")
+ {
+ SECTION("signed")
+ {
+ SECTION("-9223372036854775808..-4294967297")
+ {
+ const std::vector<int64_t> numbers
+ {
+ (std::numeric_limits<int64_t>::min)(),
+ -1000000000000000000,
+ -100000000000000000,
+ -10000000000000000,
+ -1000000000000000,
+ -100000000000000,
+ -10000000000000,
+ -1000000000000,
+ -100000000000,
+ -10000000000,
+ -4294967297,
+ };
+ for (const auto i : numbers)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ const json j = i;
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ const auto positive = static_cast<uint64_t>(-1 - i);
+ const std::vector<uint8_t> expected
+ {
+ static_cast<uint8_t>(0x3b),
+ static_cast<uint8_t>((positive >> 56) & 0xff),
+ static_cast<uint8_t>((positive >> 48) & 0xff),
+ static_cast<uint8_t>((positive >> 40) & 0xff),
+ static_cast<uint8_t>((positive >> 32) & 0xff),
+ static_cast<uint8_t>((positive >> 24) & 0xff),
+ static_cast<uint8_t>((positive >> 16) & 0xff),
+ static_cast<uint8_t>((positive >> 8) & 0xff),
+ static_cast<uint8_t>(positive & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 9);
+
+ // check individual bytes
+ CHECK(result[0] == 0x3b);
+ const uint64_t restored = (static_cast<uint64_t>(result[1]) << 070) +
+ (static_cast<uint64_t>(result[2]) << 060) +
+ (static_cast<uint64_t>(result[3]) << 050) +
+ (static_cast<uint64_t>(result[4]) << 040) +
+ (static_cast<uint64_t>(result[5]) << 030) +
+ (static_cast<uint64_t>(result[6]) << 020) +
+ (static_cast<uint64_t>(result[7]) << 010) +
+ static_cast<uint64_t>(result[8]);
+ CHECK(restored == positive);
+ CHECK(-1 - static_cast<int64_t>(restored) == i);
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+ }
+
+ SECTION("-4294967296..-65537")
+ {
+ const std::vector<int64_t> numbers
+ {
+ -65537,
+ -100000,
+ -1000000,
+ -10000000,
+ -100000000,
+ -1000000000,
+ -4294967296,
+ };
+ for (const auto i : numbers)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ const json j = i;
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ auto positive = static_cast<uint32_t>(static_cast<uint64_t>(-1 - i) & 0x00000000ffffffff);
+ const std::vector<uint8_t> expected
+ {
+ static_cast<uint8_t>(0x3a),
+ static_cast<uint8_t>((positive >> 24) & 0xff),
+ static_cast<uint8_t>((positive >> 16) & 0xff),
+ static_cast<uint8_t>((positive >> 8) & 0xff),
+ static_cast<uint8_t>(positive & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 5);
+
+ // check individual bytes
+ CHECK(result[0] == 0x3a);
+ const uint32_t restored = (static_cast<uint32_t>(result[1]) << 030) +
+ (static_cast<uint32_t>(result[2]) << 020) +
+ (static_cast<uint32_t>(result[3]) << 010) +
+ static_cast<uint32_t>(result[4]);
+ CHECK(restored == positive);
+ CHECK(-1LL - restored == i);
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+ }
+
+ SECTION("-65536..-257")
+ {
+ for (int32_t i = -65536; i <= -257; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ const json j = i;
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ const auto positive = static_cast<uint16_t>(-1 - i);
+ const std::vector<uint8_t> expected
+ {
+ static_cast<uint8_t>(0x39),
+ static_cast<uint8_t>((positive >> 8) & 0xff),
+ static_cast<uint8_t>(positive & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 3);
+
+ // check individual bytes
+ CHECK(result[0] == 0x39);
+ const auto restored = static_cast<uint16_t>((static_cast<uint8_t>(result[1]) * 256) + static_cast<uint8_t>(result[2]));
+ CHECK(restored == positive);
+ CHECK(-1 - restored == i);
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+ }
+
+ SECTION("-9263 (int 16)")
+ {
+ const json j = -9263;
+ std::vector<uint8_t> expected = {0x39, 0x24, 0x2e};
+
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+
+ const auto restored = static_cast<int16_t>(-1 - ((result[1] << 8) + result[2]));
+ CHECK(restored == -9263);
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+
+ SECTION("-256..-24")
+ {
+ for (auto i = -256; i < -24; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ const json j = i;
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ const std::vector<uint8_t> expected
+ {
+ 0x38,
+ static_cast<uint8_t>(-1 - i),
+ };
+
+ // compare result + size
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 2);
+
+ // check individual bytes
+ CHECK(result[0] == 0x38);
+ CHECK(static_cast<int16_t>(-1 - result[1]) == i);
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+ }
+
+ SECTION("-24..-1")
+ {
+ for (auto i = -24; i <= -1; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ const json j = i;
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ const std::vector<uint8_t> expected
+ {
+ static_cast<uint8_t>(0x20 - 1 - static_cast<uint8_t>(i)),
+ };
+
+ // compare result + size
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 1);
+
+ // check individual bytes
+ CHECK(static_cast<int8_t>(0x20 - 1 - result[0]) == i);
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+ }
+
+ SECTION("0..23")
+ {
+ for (size_t i = 0; i <= 23; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json j = -1;
+ j.get_ref<json::number_integer_t&>() = static_cast<json::number_integer_t>(i);
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ const std::vector<uint8_t> expected
+ {
+ static_cast<uint8_t>(i),
+ };
+
+ // compare result + size
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 1);
+
+ // check individual bytes
+ CHECK(result[0] == i);
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+ }
+
+ SECTION("24..255")
+ {
+ for (size_t i = 24; i <= 255; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json j = -1;
+ j.get_ref<json::number_integer_t&>() = static_cast<json::number_integer_t>(i);
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ const std::vector<uint8_t> expected
+ {
+ static_cast<uint8_t>(0x18),
+ static_cast<uint8_t>(i),
+ };
+
+ // compare result + size
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 2);
+
+ // check individual bytes
+ CHECK(result[0] == 0x18);
+ CHECK(result[1] == i);
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+ }
+
+ SECTION("256..65535")
+ {
+ for (size_t i = 256; i <= 65535; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json j = -1;
+ j.get_ref<json::number_integer_t&>() = static_cast<json::number_integer_t>(i);
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ const std::vector<uint8_t> expected
+ {
+ static_cast<uint8_t>(0x19),
+ static_cast<uint8_t>((i >> 8) & 0xff),
+ static_cast<uint8_t>(i & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 3);
+
+ // check individual bytes
+ CHECK(result[0] == 0x19);
+ const auto restored = static_cast<uint16_t>((static_cast<uint8_t>(result[1]) * 256) + static_cast<uint8_t>(result[2]));
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+ }
+
+ SECTION("65536..4294967295")
+ {
+ for (const uint32_t i :
+ {
+ 65536u, 77777u, 1048576u
+ })
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json j = -1;
+ j.get_ref<json::number_integer_t&>() = static_cast<json::number_integer_t>(i);
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ const std::vector<uint8_t> expected
+ {
+ 0x1a,
+ static_cast<uint8_t>((i >> 24) & 0xff),
+ static_cast<uint8_t>((i >> 16) & 0xff),
+ static_cast<uint8_t>((i >> 8) & 0xff),
+ static_cast<uint8_t>(i & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 5);
+
+ // check individual bytes
+ CHECK(result[0] == 0x1a);
+ const uint32_t restored = (static_cast<uint32_t>(result[1]) << 030) +
+ (static_cast<uint32_t>(result[2]) << 020) +
+ (static_cast<uint32_t>(result[3]) << 010) +
+ static_cast<uint32_t>(result[4]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+ }
+
+ SECTION("4294967296..4611686018427387903")
+ {
+ for (const uint64_t i :
+ {
+ 4294967296ul, 4611686018427387903ul
+ })
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json j = -1;
+ j.get_ref<json::number_integer_t&>() = static_cast<json::number_integer_t>(i);
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ const std::vector<uint8_t> expected
+ {
+ 0x1b,
+ static_cast<uint8_t>((i >> 070) & 0xff),
+ static_cast<uint8_t>((i >> 060) & 0xff),
+ static_cast<uint8_t>((i >> 050) & 0xff),
+ static_cast<uint8_t>((i >> 040) & 0xff),
+ static_cast<uint8_t>((i >> 030) & 0xff),
+ static_cast<uint8_t>((i >> 020) & 0xff),
+ static_cast<uint8_t>((i >> 010) & 0xff),
+ static_cast<uint8_t>(i & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 9);
+
+ // check individual bytes
+ CHECK(result[0] == 0x1b);
+ const uint64_t restored = (static_cast<uint64_t>(result[1]) << 070) +
+ (static_cast<uint64_t>(result[2]) << 060) +
+ (static_cast<uint64_t>(result[3]) << 050) +
+ (static_cast<uint64_t>(result[4]) << 040) +
+ (static_cast<uint64_t>(result[5]) << 030) +
+ (static_cast<uint64_t>(result[6]) << 020) +
+ (static_cast<uint64_t>(result[7]) << 010) +
+ static_cast<uint64_t>(result[8]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+ }
+
+ SECTION("-32768..-129 (int 16)")
+ {
+ for (int16_t i = -32768; i <= static_cast<std::int16_t>(-129); ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ const json j = i;
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ const std::vector<uint8_t> expected
+ {
+ 0xd1,
+ static_cast<uint8_t>((i >> 8) & 0xff),
+ static_cast<uint8_t>(i & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 3);
+
+ // check individual bytes
+ CHECK(result[0] == 0xd1);
+ const auto restored = static_cast<int16_t>((result[1] << 8) + result[2]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ }
+ }
+ }
+
+ SECTION("unsigned")
+ {
+ SECTION("0..23 (Integer)")
+ {
+ for (size_t i = 0; i <= 23; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with unsigned integer number
+ const json j = i;
+
+ // check type
+ CHECK(j.is_number_unsigned());
+
+ // create expected byte vector
+ const std::vector<uint8_t> expected
+ {
+ static_cast<uint8_t>(i),
+ };
+
+ // compare result + size
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 1);
+
+ // check individual bytes
+ CHECK(result[0] == i);
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+ }
+
+ SECTION("24..255 (one-byte uint8_t)")
+ {
+ for (size_t i = 24; i <= 255; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with unsigned integer number
+ const json j = i;
+
+ // check type
+ CHECK(j.is_number_unsigned());
+
+ // create expected byte vector
+ const std::vector<uint8_t> expected
+ {
+ 0x18,
+ static_cast<uint8_t>(i),
+ };
+
+ // compare result + size
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 2);
+
+ // check individual bytes
+ CHECK(result[0] == 0x18);
+ const auto restored = static_cast<uint8_t>(result[1]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+ }
+
+ SECTION("256..65535 (two-byte uint16_t)")
+ {
+ for (size_t i = 256; i <= 65535; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with unsigned integer number
+ const json j = i;
+
+ // check type
+ CHECK(j.is_number_unsigned());
+
+ // create expected byte vector
+ const std::vector<uint8_t> expected
+ {
+ 0x19,
+ static_cast<uint8_t>((i >> 8) & 0xff),
+ static_cast<uint8_t>(i & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 3);
+
+ // check individual bytes
+ CHECK(result[0] == 0x19);
+ const auto restored = static_cast<uint16_t>((static_cast<uint8_t>(result[1]) * 256) + static_cast<uint8_t>(result[2]));
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+ }
+
+ SECTION("65536..4294967295 (four-byte uint32_t)")
+ {
+ for (const uint32_t i :
+ {
+ 65536u, 77777u, 1048576u
+ })
+ {
+ CAPTURE(i)
+
+ // create JSON value with unsigned integer number
+ const json j = i;
+
+ // check type
+ CHECK(j.is_number_unsigned());
+
+ // create expected byte vector
+ const std::vector<uint8_t> expected
+ {
+ 0x1a,
+ static_cast<uint8_t>((i >> 24) & 0xff),
+ static_cast<uint8_t>((i >> 16) & 0xff),
+ static_cast<uint8_t>((i >> 8) & 0xff),
+ static_cast<uint8_t>(i & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 5);
+
+ // check individual bytes
+ CHECK(result[0] == 0x1a);
+ const uint32_t restored = (static_cast<uint32_t>(result[1]) << 030) +
+ (static_cast<uint32_t>(result[2]) << 020) +
+ (static_cast<uint32_t>(result[3]) << 010) +
+ static_cast<uint32_t>(result[4]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+ }
+
+ SECTION("4294967296..4611686018427387903 (eight-byte uint64_t)")
+ {
+ for (const uint64_t i :
+ {
+ 4294967296ul, 4611686018427387903ul
+ })
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ const json j = i;
+
+ // check type
+ CHECK(j.is_number_unsigned());
+
+ // create expected byte vector
+ const std::vector<uint8_t> expected
+ {
+ 0x1b,
+ static_cast<uint8_t>((i >> 070) & 0xff),
+ static_cast<uint8_t>((i >> 060) & 0xff),
+ static_cast<uint8_t>((i >> 050) & 0xff),
+ static_cast<uint8_t>((i >> 040) & 0xff),
+ static_cast<uint8_t>((i >> 030) & 0xff),
+ static_cast<uint8_t>((i >> 020) & 0xff),
+ static_cast<uint8_t>((i >> 010) & 0xff),
+ static_cast<uint8_t>(i & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 9);
+
+ // check individual bytes
+ CHECK(result[0] == 0x1b);
+ const uint64_t restored = (static_cast<uint64_t>(result[1]) << 070) +
+ (static_cast<uint64_t>(result[2]) << 060) +
+ (static_cast<uint64_t>(result[3]) << 050) +
+ (static_cast<uint64_t>(result[4]) << 040) +
+ (static_cast<uint64_t>(result[5]) << 030) +
+ (static_cast<uint64_t>(result[6]) << 020) +
+ (static_cast<uint64_t>(result[7]) << 010) +
+ static_cast<uint64_t>(result[8]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+ }
+ }
+
+ SECTION("double-precision float")
+ {
+ SECTION("3.1415925")
+ {
+ double v = 3.1415925;
+ const json j = v;
+ std::vector<uint8_t> expected =
+ {
+ 0xfb, 0x40, 0x09, 0x21, 0xfb, 0x3f, 0xa6, 0xde, 0xfc
+ };
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result) == v);
+
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+ }
+
+ SECTION("single-precision float")
+ {
+ SECTION("0.5")
+ {
+ double v = 0.5;
+ const json j = v;
+ // its double-precision float binary value is
+ // {0xfb, 0x3f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
+ // but to save memory, we can store it as single-precision float.
+ const std::vector<uint8_t> expected = {0xfa, 0x3f, 0x00, 0x00, 0x00};
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result) == v);
+ }
+ SECTION("0.0")
+ {
+ double v = 0.0;
+ const json j = v;
+ // its double-precision binary value is:
+ // {0xfb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
+ const std::vector<uint8_t> expected = {0xfa, 0x00, 0x00, 0x00, 0x00};
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result) == v);
+ }
+ SECTION("-0.0")
+ {
+ double v = -0.0;
+ const json j = v;
+ // its double-precision binary value is:
+ // {0xfb, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
+ const std::vector<uint8_t> expected = {0xfa, 0x80, 0x00, 0x00, 0x00};
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result) == v);
+ }
+ SECTION("100.0")
+ {
+ double v = 100.0;
+ const json j = v;
+ // its double-precision binary value is:
+ // {0xfb, 0x40, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
+ const std::vector<uint8_t> expected = {0xfa, 0x42, 0xc8, 0x00, 0x00};
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result) == v);
+ }
+ SECTION("200.0")
+ {
+ double v = 200.0;
+ const json j = v;
+ // its double-precision binary value is:
+ // {0xfb, 0x40, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
+ const std::vector<uint8_t> expected = {0xfa, 0x43, 0x48, 0x00, 0x00};
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result) == v);
+ }
+ SECTION("3.40282e+38(max float)")
+ {
+ float v = (std::numeric_limits<float>::max)();
+ const json j = v;
+ const std::vector<uint8_t> expected =
+ {
+ 0xfa, 0x7f, 0x7f, 0xff, 0xff
+ };
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result) == v);
+ }
+ SECTION("-3.40282e+38(lowest float)")
+ {
+ auto v = static_cast<double>(std::numeric_limits<float>::lowest());
+ const json j = v;
+ const std::vector<uint8_t> expected =
+ {
+ 0xfa, 0xff, 0x7f, 0xff, 0xff
+ };
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result) == v);
+ }
+ SECTION("1 + 3.40282e+38(more than max float)")
+ {
+ double v = static_cast<double>((std::numeric_limits<float>::max)()) + 0.1e+34;
+ const json j = v;
+ const std::vector<uint8_t> expected =
+ {
+ 0xfb, 0x47, 0xf0, 0x00, 0x03, 0x04, 0xdc, 0x64, 0x49
+ };
+ // double
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result) == v);
+ }
+ SECTION("-1 - 3.40282e+38(less than lowest float)")
+ {
+ double v = static_cast<double>(std::numeric_limits<float>::lowest()) - 1.0;
+ const json j = v;
+ const std::vector<uint8_t> expected =
+ {
+ 0xfa, 0xff, 0x7f, 0xff, 0xff
+ };
+ // the same with the lowest float
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result) == v);
+ }
+
+ }
+
+ SECTION("half-precision float (edge cases)")
+ {
+ SECTION("errors")
+ {
+ SECTION("no byte follows")
+ {
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xf9})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_cbor(std::vector<uint8_t>({0xf9}), true, false).is_discarded());
+ }
+ SECTION("only one byte follows")
+ {
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xf9, 0x7c})), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_cbor(std::vector<uint8_t>({0xf9, 0x7c}), true, false).is_discarded());
+ }
+ }
+
+ SECTION("exp = 0b00000")
+ {
+ SECTION("0 (0 00000 0000000000)")
+ {
+ json const j = json::from_cbor(std::vector<uint8_t>({0xf9, 0x00, 0x00}));
+ const json::number_float_t d{j};
+ CHECK(d == 0.0);
+ }
+
+ SECTION("-0 (1 00000 0000000000)")
+ {
+ json const j = json::from_cbor(std::vector<uint8_t>({0xf9, 0x80, 0x00}));
+ const json::number_float_t d{j};
+ CHECK(d == -0.0);
+ }
+
+ SECTION("2**-24 (0 00000 0000000001)")
+ {
+ json const j = json::from_cbor(std::vector<uint8_t>({0xf9, 0x00, 0x01}));
+ const json::number_float_t d{j};
+ CHECK(d == std::pow(2.0, -24.0));
+ }
+ }
+
+ SECTION("exp = 0b11111")
+ {
+ SECTION("infinity (0 11111 0000000000)")
+ {
+ json const j = json::from_cbor(std::vector<uint8_t>({0xf9, 0x7c, 0x00}));
+ const json::number_float_t d{j};
+ CHECK(d == std::numeric_limits<json::number_float_t>::infinity());
+ CHECK(j.dump() == "null");
+ }
+
+ SECTION("-infinity (1 11111 0000000000)")
+ {
+ json const j = json::from_cbor(std::vector<uint8_t>({0xf9, 0xfc, 0x00}));
+ const json::number_float_t d{j};
+ CHECK(d == -std::numeric_limits<json::number_float_t>::infinity());
+ CHECK(j.dump() == "null");
+ }
+ }
+
+ SECTION("other values from https://en.wikipedia.org/wiki/Half-precision_floating-point_format")
+ {
+ SECTION("1 (0 01111 0000000000)")
+ {
+ json const j = json::from_cbor(std::vector<uint8_t>({0xf9, 0x3c, 0x00}));
+ const json::number_float_t d{j};
+ CHECK(d == 1);
+ }
+
+ SECTION("-2 (1 10000 0000000000)")
+ {
+ json const j = json::from_cbor(std::vector<uint8_t>({0xf9, 0xc0, 0x00}));
+ const json::number_float_t d{j};
+ CHECK(d == -2);
+ }
+
+ SECTION("65504 (0 11110 1111111111)")
+ {
+ json const j = json::from_cbor(std::vector<uint8_t>({0xf9, 0x7b, 0xff}));
+ const json::number_float_t d{j};
+ CHECK(d == 65504);
+ }
+ }
+
+ SECTION("infinity")
+ {
+ json const j = json::from_cbor(std::vector<uint8_t>({0xf9, 0x7c, 0x00}));
+ json::number_float_t const d{j};
+ CHECK(!std::isfinite(d));
+ CHECK(j.dump() == "null");
+ }
+
+ SECTION("NaN")
+ {
+ json const j = json::from_cbor(std::vector<uint8_t>({0xf9, 0x7e, 0x00}));
+ json::number_float_t const d{j};
+ CHECK(std::isnan(d));
+ CHECK(j.dump() == "null");
+ }
+ }
+ }
+
+ SECTION("string")
+ {
+ SECTION("N = 0..23")
+ {
+ for (size_t N = 0; N <= 0x17; ++N)
+ {
+ CAPTURE(N)
+
+ // create JSON value with string containing of N * 'x'
+ const auto s = std::string(N, 'x');
+ const json j = s;
+
+ // create expected byte vector
+ std::vector<uint8_t> expected;
+ expected.push_back(static_cast<uint8_t>(0x60 + N));
+ for (size_t i = 0; i < N; ++i)
+ {
+ expected.push_back('x');
+ }
+
+ // compare result + size
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 1);
+ // check that no null byte is appended
+ if (N > 0)
+ {
+ CHECK(result.back() != '\x00');
+ }
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+ }
+
+ SECTION("N = 24..255")
+ {
+ for (size_t N = 24; N <= 255; ++N)
+ {
+ CAPTURE(N)
+
+ // create JSON value with string containing of N * 'x'
+ const auto s = std::string(N, 'x');
+ const json j = s;
+
+ // create expected byte vector
+ std::vector<uint8_t> expected;
+
+ expected.push_back(0x78);
+ expected.push_back(static_cast<uint8_t>(N));
+ for (size_t i = 0; i < N; ++i)
+ {
+ expected.push_back('x');
+ }
+
+ // compare result + size
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 2);
+ // check that no null byte is appended
+ CHECK(result.back() != '\x00');
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+ }
+
+ SECTION("N = 256..65535")
+ {
+ for (const size_t N :
+ {
+ 256u, 999u, 1025u, 3333u, 2048u, 65535u
+ })
+ {
+ CAPTURE(N)
+
+ // create JSON value with string containing of N * 'x'
+ const auto s = std::string(N, 'x');
+ const json j = s;
+
+ // create expected byte vector (hack: create string first)
+ std::vector<uint8_t> expected(N, 'x');
+ // reverse order of commands, because we insert at begin()
+ expected.insert(expected.begin(), static_cast<uint8_t>(N & 0xff));
+ expected.insert(expected.begin(), static_cast<uint8_t>((N >> 8) & 0xff));
+ expected.insert(expected.begin(), 0x79);
+
+ // compare result + size
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 3);
+ // check that no null byte is appended
+ CHECK(result.back() != '\x00');
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+ }
+
+ SECTION("N = 65536..4294967295")
+ {
+ for (const size_t N :
+ {
+ 65536u, 77777u, 1048576u
+ })
+ {
+ CAPTURE(N)
+
+ // create JSON value with string containing of N * 'x'
+ const auto s = std::string(N, 'x');
+ const json j = s;
+
+ // create expected byte vector (hack: create string first)
+ std::vector<uint8_t> expected(N, 'x');
+ // reverse order of commands, because we insert at begin()
+ expected.insert(expected.begin(), static_cast<uint8_t>(N & 0xff));
+ expected.insert(expected.begin(), static_cast<uint8_t>((N >> 8) & 0xff));
+ expected.insert(expected.begin(), static_cast<uint8_t>((N >> 16) & 0xff));
+ expected.insert(expected.begin(), static_cast<uint8_t>((N >> 24) & 0xff));
+ expected.insert(expected.begin(), 0x7a);
+
+ // compare result + size
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 5);
+ // check that no null byte is appended
+ CHECK(result.back() != '\x00');
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+ }
+ }
+
+ SECTION("array")
+ {
+ SECTION("empty")
+ {
+ const json j = json::array();
+ std::vector<uint8_t> expected = {0x80};
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+
+ SECTION("[null]")
+ {
+ const json j = {nullptr};
+ const std::vector<uint8_t> expected = {0x81, 0xf6};
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+
+ SECTION("[1,2,3,4,5]")
+ {
+ const json j = json::parse("[1,2,3,4,5]");
+ const std::vector<uint8_t> expected = {0x85, 0x01, 0x02, 0x03, 0x04, 0x05};
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+
+ SECTION("[[[[]]]]")
+ {
+ const json j = json::parse("[[[[]]]]");
+ const std::vector<uint8_t> expected = {0x81, 0x81, 0x81, 0x80};
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+
+ SECTION("array with uint16_t elements")
+ {
+ const json j(257, nullptr);
+ std::vector<uint8_t> expected(j.size() + 3, 0xf6); // all null
+ expected[0] = 0x99; // array 16 bit
+ expected[1] = 0x01; // size (0x0101), byte 0
+ expected[2] = 0x01; // size (0x0101), byte 1
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+
+ SECTION("array with uint32_t elements")
+ {
+ const json j(65793, nullptr);
+ std::vector<uint8_t> expected(j.size() + 5, 0xf6); // all null
+ expected[0] = 0x9a; // array 32 bit
+ expected[1] = 0x00; // size (0x00010101), byte 0
+ expected[2] = 0x01; // size (0x00010101), byte 1
+ expected[3] = 0x01; // size (0x00010101), byte 2
+ expected[4] = 0x01; // size (0x00010101), byte 3
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+ }
+
+ SECTION("object")
+ {
+ SECTION("empty")
+ {
+ const json j = json::object();
+ const std::vector<uint8_t> expected = {0xa0};
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+
+ SECTION("{\"\":null}")
+ {
+ const json j = {{"", nullptr}};
+ const std::vector<uint8_t> expected = {0xa1, 0x60, 0xf6};
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+
+ SECTION("{\"a\": {\"b\": {\"c\": {}}}}")
+ {
+ const json j = json::parse(R"({"a": {"b": {"c": {}}}})");
+ const std::vector<uint8_t> expected =
+ {
+ 0xa1, 0x61, 0x61, 0xa1, 0x61, 0x62, 0xa1, 0x61, 0x63, 0xa0
+ };
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+
+ SECTION("object with uint8_t elements")
+ {
+ json j;
+ for (auto i = 0; i < 255; ++i)
+ {
+ // format i to a fixed width of 5
+ // each entry will need 7 bytes: 6 for string, 1 for null
+ std::stringstream ss;
+ ss << std::setw(5) << std::setfill('0') << i;
+ j.emplace(ss.str(), nullptr);
+ }
+
+ const auto result = json::to_cbor(j);
+
+ // Checking against an expected vector byte by byte is
+ // difficult, because no assumption on the order of key/value
+ // pairs are made. We therefore only check the prefix (type and
+ // size and the overall size. The rest is then handled in the
+ // roundtrip check.
+ CHECK(result.size() == 1787); // 1 type, 1 size, 255*7 content
+ CHECK(result[0] == 0xb8); // map 8 bit
+ CHECK(result[1] == 0xff); // size byte (0xff)
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+
+ SECTION("object with uint16_t elements")
+ {
+ json j;
+ for (auto i = 0; i < 256; ++i)
+ {
+ // format i to a fixed width of 5
+ // each entry will need 7 bytes: 6 for string, 1 for null
+ std::stringstream ss;
+ ss << std::setw(5) << std::setfill('0') << i;
+ j.emplace(ss.str(), nullptr);
+ }
+
+ const auto result = json::to_cbor(j);
+
+ // Checking against an expected vector byte by byte is
+ // difficult, because no assumption on the order of key/value
+ // pairs are made. We therefore only check the prefix (type and
+ // size and the overall size. The rest is then handled in the
+ // roundtrip check.
+ CHECK(result.size() == 1795); // 1 type, 2 size, 256*7 content
+ CHECK(result[0] == 0xb9); // map 16 bit
+ CHECK(result[1] == 0x01); // byte 0 of size (0x0100)
+ CHECK(result[2] == 0x00); // byte 1 of size (0x0100)
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+
+ SECTION("object with uint32_t elements")
+ {
+ json j;
+ for (auto i = 0; i < 65536; ++i)
+ {
+ // format i to a fixed width of 5
+ // each entry will need 7 bytes: 6 for string, 1 for null
+ std::stringstream ss;
+ ss << std::setw(5) << std::setfill('0') << i;
+ j.emplace(ss.str(), nullptr);
+ }
+
+ const auto result = json::to_cbor(j);
+
+ // Checking against an expected vector byte by byte is
+ // difficult, because no assumption on the order of key/value
+ // pairs are made. We therefore only check the prefix (type and
+ // size and the overall size. The rest is then handled in the
+ // roundtrip check.
+ CHECK(result.size() == 458757); // 1 type, 4 size, 65536*7 content
+ CHECK(result[0] == 0xba); // map 32 bit
+ CHECK(result[1] == 0x00); // byte 0 of size (0x00010000)
+ CHECK(result[2] == 0x01); // byte 1 of size (0x00010000)
+ CHECK(result[3] == 0x00); // byte 2 of size (0x00010000)
+ CHECK(result[4] == 0x00); // byte 3 of size (0x00010000)
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+ }
+
+ SECTION("binary")
+ {
+ SECTION("N = 0..23")
+ {
+ for (size_t N = 0; N <= 0x17; ++N)
+ {
+ CAPTURE(N)
+
+ // create JSON value with byte array containing of N * 'x'
+ const auto s = std::vector<uint8_t>(N, 'x');
+ const json j = json::binary(s);
+
+ // create expected byte vector
+ std::vector<uint8_t> expected;
+ expected.push_back(static_cast<uint8_t>(0x40 + N));
+ for (size_t i = 0; i < N; ++i)
+ {
+ expected.push_back(0x78);
+ }
+
+ // compare result + size
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 1);
+ // check that no null byte is appended
+ if (N > 0)
+ {
+ CHECK(result.back() != '\x00');
+ }
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+ }
+
+ SECTION("N = 24..255")
+ {
+ for (size_t N = 24; N <= 255; ++N)
+ {
+ CAPTURE(N)
+
+ // create JSON value with string containing of N * 'x'
+ const auto s = std::vector<uint8_t>(N, 'x');
+ const json j = json::binary(s);
+
+ // create expected byte vector
+ std::vector<uint8_t> expected;
+ expected.push_back(0x58);
+ expected.push_back(static_cast<uint8_t>(N));
+ for (size_t i = 0; i < N; ++i)
+ {
+ expected.push_back('x');
+ }
+
+ // compare result + size
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 2);
+ // check that no null byte is appended
+ CHECK(result.back() != '\x00');
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+ }
+
+ SECTION("N = 256..65535")
+ {
+ for (const size_t N :
+ {
+ 256u, 999u, 1025u, 3333u, 2048u, 65535u
+ })
+ {
+ CAPTURE(N)
+
+ // create JSON value with string containing of N * 'x'
+ const auto s = std::vector<uint8_t>(N, 'x');
+ const json j = json::binary(s);
+
+ // create expected byte vector (hack: create string first)
+ std::vector<uint8_t> expected(N, 'x');
+ // reverse order of commands, because we insert at begin()
+ expected.insert(expected.begin(), static_cast<uint8_t>(N & 0xff));
+ expected.insert(expected.begin(), static_cast<uint8_t>((N >> 8) & 0xff));
+ expected.insert(expected.begin(), 0x59);
+
+ // compare result + size
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 3);
+ // check that no null byte is appended
+ CHECK(result.back() != '\x00');
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+ }
+
+ SECTION("N = 65536..4294967295")
+ {
+ for (const size_t N :
+ {
+ 65536u, 77777u, 1048576u
+ })
+ {
+ CAPTURE(N)
+
+ // create JSON value with string containing of N * 'x'
+ const auto s = std::vector<uint8_t>(N, 'x');
+ const json j = json::binary(s);
+
+ // create expected byte vector (hack: create string first)
+ std::vector<uint8_t> expected(N, 'x');
+ // reverse order of commands, because we insert at begin()
+ expected.insert(expected.begin(), static_cast<uint8_t>(N & 0xff));
+ expected.insert(expected.begin(), static_cast<uint8_t>((N >> 8) & 0xff));
+ expected.insert(expected.begin(), static_cast<uint8_t>((N >> 16) & 0xff));
+ expected.insert(expected.begin(), static_cast<uint8_t>((N >> 24) & 0xff));
+ expected.insert(expected.begin(), 0x5a);
+
+ // compare result + size
+ const auto result = json::to_cbor(j);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 5);
+ // check that no null byte is appended
+ CHECK(result.back() != '\x00');
+
+ // roundtrip
+ CHECK(json::from_cbor(result) == j);
+ CHECK(json::from_cbor(result, true, false) == j);
+ }
+ }
+
+ SECTION("indefinite size")
+ {
+ std::vector<std::uint8_t> const input = {0x5F, 0x44, 0xaa, 0xbb, 0xcc, 0xdd, 0x43, 0xee, 0xff, 0x99, 0xFF};
+ auto j = json::from_cbor(input);
+ CHECK(j.is_binary());
+ auto k = json::binary({0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x99});
+ CAPTURE(j.dump(0, ' ', false, json::error_handler_t::strict))
+ CHECK(j == k);
+ }
+
+ SECTION("binary in array")
+ {
+ // array with three empty byte strings
+ std::vector<std::uint8_t> const input = {0x83, 0x40, 0x40, 0x40};
+ json _;
+ CHECK_NOTHROW(_ = json::from_cbor(input));
+ }
+
+ SECTION("binary in object")
+ {
+ // object mapping "foo" to empty byte string
+ std::vector<std::uint8_t> const input = {0xA1, 0x63, 0x66, 0x6F, 0x6F, 0x40};
+ json _;
+ CHECK_NOTHROW(_ = json::from_cbor(input));
+ }
+
+ SECTION("SAX callback with binary")
+ {
+ // object mapping "foo" to byte string
+ std::vector<std::uint8_t> const input = {0xA1, 0x63, 0x66, 0x6F, 0x6F, 0x41, 0x00};
+
+ // callback to set binary_seen to true if a binary value was seen
+ bool binary_seen = false;
+ auto callback = [&binary_seen](int /*depth*/, json::parse_event_t /*event*/, json & parsed) noexcept
+ {
+ if (parsed.is_binary())
+ {
+ binary_seen = true;
+ }
+ return true;
+ };
+
+ json j;
+ auto cbp = nlohmann::detail::json_sax_dom_callback_parser<json, nlohmann::detail::string_input_adapter_type>(j, callback, true);
+ CHECK(json::sax_parse(input, &cbp, json::input_format_t::cbor));
+ CHECK(j.at("foo").is_binary());
+ CHECK(binary_seen);
+ }
+ }
+ }
+
+ SECTION("additional deserialization")
+ {
+ SECTION("0x5b (byte array)")
+ {
+ std::vector<uint8_t> const given = {0x5b, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x61
+ };
+ const json j = json::from_cbor(given);
+ CHECK(j == json::binary(std::vector<uint8_t> {'a'}));
+ }
+
+ SECTION("0x7b (string)")
+ {
+ std::vector<uint8_t> const given = {0x7b, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x61
+ };
+ const json j = json::from_cbor(given);
+ CHECK(j == "a");
+ }
+
+ SECTION("0x9b (array)")
+ {
+ std::vector<uint8_t> const given = {0x9b, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0xf4
+ };
+ const json j = json::from_cbor(given);
+ CHECK(j == json::parse("[false]"));
+ }
+
+ SECTION("0xbb (map)")
+ {
+ std::vector<uint8_t> const given = {0xbb, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x60, 0xf4
+ };
+ const json j = json::from_cbor(given);
+ CHECK(j == json::parse("{\"\": false}"));
+ }
+ }
+
+ SECTION("errors")
+ {
+ SECTION("empty byte vector")
+ {
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>()), "[json.exception.parse_error.110] parse error at byte 1: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
+ CHECK(json::from_cbor(std::vector<uint8_t>(), true, false).is_discarded());
+ }
+
+ SECTION("too short byte vector")
+ {
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x18})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x19})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x19, 0x00})), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x1a})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x1a, 0x00})), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x1a, 0x00, 0x00})), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x1a, 0x00, 0x00, 0x00})), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x1b})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x1b, 0x00})), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00})), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00})), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00, 0x00})), "[json.exception.parse_error.110] parse error at byte 6: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00, 0x00, 0x00})), "[json.exception.parse_error.110] parse error at byte 7: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})), "[json.exception.parse_error.110] parse error at byte 8: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})), "[json.exception.parse_error.110] parse error at byte 9: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x38})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x39})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x39, 0x00})), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x3a})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x3a, 0x00})), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x3a, 0x00, 0x00})), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x3a, 0x00, 0x00, 0x00})), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x3b})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x3b, 0x00})), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})), "[json.exception.parse_error.110] parse error at byte 9: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x62})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x62, 0x60})), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x7F})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x7F, 0x60})), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x82, 0x01})), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x9F, 0x01})), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xBF, 0x61, 0x61, 0xF5})), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xA1, 0x61, 0X61})), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xBF, 0x61, 0X61})), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x5F})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR binary: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x5F, 0x00})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR binary: expected length specification (0x40-0x5B) or indefinite binary array type (0x5F); last byte: 0x00", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x41})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR binary: unexpected end of input", json::parse_error&);
+
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x18}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x19}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x19, 0x00}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x1a}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x1a, 0x00}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x1a, 0x00, 0x00}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x1a, 0x00, 0x00, 0x00}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x1b}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x1b, 0x00}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00, 0x00}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00, 0x00, 0x00}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x38}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x39}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x39, 0x00}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x3a}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x3a, 0x00}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x3a, 0x00, 0x00}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x3a, 0x00, 0x00, 0x00}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x3b}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x3b, 0x00}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x62}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x62, 0x60}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x7F}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x7F, 0x60}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x82, 0x01}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x9F, 0x01}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0xBF, 0x61, 0x61, 0xF5}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0xA1, 0x61, 0x61}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0xBF, 0x61, 0x61}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x5F}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x5F, 0x00}), true, false).is_discarded());
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x41}), true, false).is_discarded());
+ }
+
+ SECTION("unsupported bytes")
+ {
+ SECTION("concrete examples")
+ {
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x1c})), "[json.exception.parse_error.112] parse error at byte 1: syntax error while parsing CBOR value: invalid byte: 0x1C", json::parse_error&);
+ CHECK(json::from_cbor(std::vector<uint8_t>({0x1c}), true, false).is_discarded());
+
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xf8})), "[json.exception.parse_error.112] parse error at byte 1: syntax error while parsing CBOR value: invalid byte: 0xF8", json::parse_error&);
+ CHECK(json::from_cbor(std::vector<uint8_t>({0xf8}), true, false).is_discarded());
+ }
+
+ SECTION("all unsupported bytes")
+ {
+ for (const auto byte :
+ {
+ // ?
+ 0x1c, 0x1d, 0x1e, 0x1f,
+ // ?
+ 0x3c, 0x3d, 0x3e, 0x3f,
+ // ?
+ 0x5c, 0x5d, 0x5e,
+ // ?
+ 0x7c, 0x7d, 0x7e,
+ // ?
+ 0x9c, 0x9d, 0x9e,
+ // ?
+ 0xbc, 0xbd, 0xbe,
+ // date/time
+ 0xc0, 0xc1,
+ // bignum
+ 0xc2, 0xc3,
+ // fraction
+ 0xc4,
+ // bigfloat
+ 0xc5,
+ // tagged item
+ 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4,
+ // expected conversion
+ 0xd5, 0xd6, 0xd7,
+ // more tagged items
+ 0xd8, 0xd9, 0xda, 0xdb,
+ // ?
+ 0xdc, 0xdd, 0xde, 0xdf,
+ // (simple value)
+ 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3,
+ // undefined
+ 0xf7,
+ // simple value
+ 0xf8
+ })
+ {
+ json _;
+ CHECK_THROWS_AS(_ = json::from_cbor(std::vector<uint8_t>({static_cast<uint8_t>(byte)})), json::parse_error&);
+ CHECK(json::from_cbor(std::vector<uint8_t>({static_cast<uint8_t>(byte)}), true, false).is_discarded());
+ }
+ }
+ }
+
+ SECTION("invalid string in map")
+ {
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xa1, 0xff, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0xFF", json::parse_error&);
+ CHECK(json::from_cbor(std::vector<uint8_t>({0xa1, 0xff, 0x01}), true, false).is_discarded());
+ }
+
+ SECTION("strict mode")
+ {
+ std::vector<uint8_t> const vec = {0xf6, 0xf6};
+ SECTION("non-strict mode")
+ {
+ const auto result = json::from_cbor(vec, false);
+ CHECK(result == json());
+ CHECK(!json::from_cbor(vec, false, false).is_discarded());
+ }
+
+ SECTION("strict mode")
+ {
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR value: expected end of input; last byte: 0xF6", json::parse_error&);
+ CHECK(json::from_cbor(vec, true, false).is_discarded());
+ }
+ }
+ }
+
+ SECTION("SAX aborts")
+ {
+ SECTION("start_array(len)")
+ {
+ std::vector<uint8_t> const v = {0x83, 0x01, 0x02, 0x03};
+ SaxCountdown scp(0);
+ CHECK(!json::sax_parse(v, &scp, json::input_format_t::cbor));
+ }
+
+ SECTION("start_object(len)")
+ {
+ std::vector<uint8_t> const v = {0xA1, 0x63, 0x66, 0x6F, 0x6F, 0xF4};
+ SaxCountdown scp(0);
+ CHECK(!json::sax_parse(v, &scp, json::input_format_t::cbor));
+ }
+
+ SECTION("key()")
+ {
+ std::vector<uint8_t> const v = {0xA1, 0x63, 0x66, 0x6F, 0x6F, 0xF4};
+ SaxCountdown scp(1);
+ CHECK(!json::sax_parse(v, &scp, json::input_format_t::cbor));
+ }
+ }
+}
+
+// use this testcase outside [hide] to run it with Valgrind
+TEST_CASE("single CBOR roundtrip")
+{
+ SECTION("sample.json")
+ {
+ std::string const filename = TEST_DATA_DIRECTORY "/json_testsuite/sample.json";
+
+ // parse JSON file
+ std::ifstream f_json(filename);
+ const json j1 = json::parse(f_json);
+
+ // parse CBOR file
+ auto packed = utils::read_binary_file(filename + ".cbor");
+ json j2;
+ CHECK_NOTHROW(j2 = json::from_cbor(packed));
+
+ // compare parsed JSON values
+ CHECK(j1 == j2);
+
+ SECTION("roundtrips")
+ {
+ SECTION("std::ostringstream")
+ {
+ std::basic_ostringstream<char> ss;
+ json::to_cbor(j1, ss);
+ json j3 = json::from_cbor(ss.str());
+ CHECK(j1 == j3);
+ }
+
+ SECTION("std::string")
+ {
+ std::string s;
+ json::to_cbor(j1, s);
+ json j3 = json::from_cbor(s);
+ CHECK(j1 == j3);
+ }
+ }
+
+ // check with different start index
+ packed.insert(packed.begin(), 5, 0xff);
+ CHECK(j1 == json::from_cbor(packed.begin() + 5, packed.end()));
+ }
+}
+
+#if !defined(JSON_NOEXCEPTION)
+TEST_CASE("CBOR regressions")
+{
+ SECTION("fuzz test results")
+ {
+ /*
+ The following test cases were found during a two-day session with
+ AFL-Fuzz. As a result, empty byte vectors and excessive lengths are
+ detected.
+ */
+ for (const std::string filename :
+ {
+ TEST_DATA_DIRECTORY "/cbor_regression/test01",
+ TEST_DATA_DIRECTORY "/cbor_regression/test02",
+ TEST_DATA_DIRECTORY "/cbor_regression/test03",
+ TEST_DATA_DIRECTORY "/cbor_regression/test04",
+ TEST_DATA_DIRECTORY "/cbor_regression/test05",
+ TEST_DATA_DIRECTORY "/cbor_regression/test06",
+ TEST_DATA_DIRECTORY "/cbor_regression/test07",
+ TEST_DATA_DIRECTORY "/cbor_regression/test08",
+ TEST_DATA_DIRECTORY "/cbor_regression/test09",
+ TEST_DATA_DIRECTORY "/cbor_regression/test10",
+ TEST_DATA_DIRECTORY "/cbor_regression/test11",
+ TEST_DATA_DIRECTORY "/cbor_regression/test12",
+ TEST_DATA_DIRECTORY "/cbor_regression/test13",
+ TEST_DATA_DIRECTORY "/cbor_regression/test14",
+ TEST_DATA_DIRECTORY "/cbor_regression/test15",
+ TEST_DATA_DIRECTORY "/cbor_regression/test16",
+ TEST_DATA_DIRECTORY "/cbor_regression/test17",
+ TEST_DATA_DIRECTORY "/cbor_regression/test18",
+ TEST_DATA_DIRECTORY "/cbor_regression/test19",
+ TEST_DATA_DIRECTORY "/cbor_regression/test20",
+ TEST_DATA_DIRECTORY "/cbor_regression/test21"
+ })
+ {
+ CAPTURE(filename)
+
+ try
+ {
+ // parse CBOR file
+ auto vec1 = utils::read_binary_file(filename);
+ const json j1 = json::from_cbor(vec1);
+
+ try
+ {
+ // step 2: round trip
+ std::vector<uint8_t> const vec2 = json::to_cbor(j1);
+
+ // parse serialization
+ json j2 = json::from_cbor(vec2);
+
+ // deserializations must match
+ CHECK(j1 == j2);
+ }
+ catch (const json::parse_error&)
+ {
+ // parsing a CBOR serialization must not fail
+ CHECK(false);
+ }
+ }
+ catch (const json::parse_error&) // NOLINT(bugprone-empty-catch)
+ {
+ // parse errors are ok, because input may be random bytes
+ }
+ }
+ }
+}
+#endif
+
+TEST_CASE("CBOR roundtrips" * doctest::skip())
+{
+ SECTION("input from flynn")
+ {
+ // most of these are excluded due to differences in key order (not a real problem)
+ std::set<std::string> exclude_packed;
+ exclude_packed.insert(TEST_DATA_DIRECTORY "/json.org/1.json");
+ exclude_packed.insert(TEST_DATA_DIRECTORY "/json.org/2.json");
+ exclude_packed.insert(TEST_DATA_DIRECTORY "/json.org/3.json");
+ exclude_packed.insert(TEST_DATA_DIRECTORY "/json.org/4.json");
+ exclude_packed.insert(TEST_DATA_DIRECTORY "/json.org/5.json");
+ exclude_packed.insert(TEST_DATA_DIRECTORY "/json_testsuite/sample.json"); // kills AppVeyor
+ exclude_packed.insert(TEST_DATA_DIRECTORY "/json_tests/pass1.json");
+ exclude_packed.insert(TEST_DATA_DIRECTORY "/regression/working_file.json");
+ exclude_packed.insert(TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object.json");
+ exclude_packed.insert(TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_duplicated_key.json");
+ exclude_packed.insert(TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_long_strings.json");
+
+ for (const std::string filename :
+ {
+ TEST_DATA_DIRECTORY "/json_nlohmann_tests/all_unicode.json",
+ TEST_DATA_DIRECTORY "/json.org/1.json",
+ TEST_DATA_DIRECTORY "/json.org/2.json",
+ TEST_DATA_DIRECTORY "/json.org/3.json",
+ TEST_DATA_DIRECTORY "/json.org/4.json",
+ TEST_DATA_DIRECTORY "/json.org/5.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip01.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip02.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip03.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip04.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip05.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip06.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip07.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip08.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip09.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip10.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip11.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip12.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip13.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip14.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip15.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip16.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip17.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip18.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip19.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip20.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip21.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip22.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip23.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip24.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip25.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip26.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip27.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip28.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip29.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip30.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip31.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip32.json",
+ TEST_DATA_DIRECTORY "/json_testsuite/sample.json", // kills AppVeyor
+ TEST_DATA_DIRECTORY "/json_tests/pass1.json",
+ TEST_DATA_DIRECTORY "/json_tests/pass2.json",
+ TEST_DATA_DIRECTORY "/json_tests/pass3.json",
+ TEST_DATA_DIRECTORY "/regression/floats.json",
+ TEST_DATA_DIRECTORY "/regression/signed_ints.json",
+ TEST_DATA_DIRECTORY "/regression/unsigned_ints.json",
+ TEST_DATA_DIRECTORY "/regression/working_file.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_arraysWithSpaces.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_empty-string.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_empty.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_ending_with_newline.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_false.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_heterogeneous.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_null.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_with_1_and_newline.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_with_leading_space.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_with_several_null.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_with_trailing_space.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_0e+1.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_0e1.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_after_space.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_double_close_to_zero.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_double_huge_neg_exp.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_huge_exp.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_int_with_exp.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_minus_zero.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_negative_int.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_negative_one.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_negative_zero.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_capital_e.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_capital_e_neg_exp.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_capital_e_pos_exp.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_exponent.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_fraction_exponent.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_neg_exp.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_neg_overflow.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_pos_exponent.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_pos_overflow.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_underflow.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_simple_int.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_simple_real.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_too_big_neg_int.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_too_big_pos_int.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_very_big_negative_int.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_basic.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_duplicated_key.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_duplicated_key_and_value.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_empty.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_empty_key.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_escaped_null_in_key.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_extreme_numbers.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_long_strings.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_simple.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_string_unicode.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_with_newlines.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_1_2_3_bytes_UTF-8_sequences.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_UTF-16_Surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_accepted_surrogate_pair.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_accepted_surrogate_pairs.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_allowed_escapes.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_backslash_and_u_escaped_zero.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_backslash_doublequotes.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_comments.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_double_escape_a.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_double_escape_n.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_escaped_control_character.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_escaped_noncharacter.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_in_array.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_in_array_with_leading_space.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_last_surrogates_1_and_2.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_newline_uescaped.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+10FFFF.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+1FFFF.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+FFFF.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_null_escape.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_one-byte-utf-8.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_pi.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_simple_ascii.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_space.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_three-byte-utf-8.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_two-byte-utf-8.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_u+2028_line_sep.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_u+2029_par_sep.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_uEscape.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unescaped_char_delete.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicodeEscapedBackslash.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode_2.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode_U+2064_invisible_plus.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode_escaped_double_quote.json",
+ // TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_utf16.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_utf8.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_with_del_character.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_false.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_int.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_negative_real.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_null.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_string.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_true.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_string_empty.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_trailing_newline.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_true_in_array.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_whitespace_array.json"
+ })
+ {
+ CAPTURE(filename)
+
+ {
+ INFO_WITH_TEMP(filename + ": std::vector<uint8_t>");
+ // parse JSON file
+ std::ifstream f_json(filename);
+ const json j1 = json::parse(f_json);
+
+ // parse CBOR file
+ const auto packed = utils::read_binary_file(filename + ".cbor");
+ json j2;
+ CHECK_NOTHROW(j2 = json::from_cbor(packed));
+
+ // compare parsed JSON values
+ CHECK(j1 == j2);
+ }
+
+ {
+ INFO_WITH_TEMP(filename + ": std::ifstream");
+ // parse JSON file
+ std::ifstream f_json(filename);
+ const json j1 = json::parse(f_json);
+
+ // parse CBOR file
+ std::ifstream f_cbor(filename + ".cbor", std::ios::binary);
+ json j2;
+ CHECK_NOTHROW(j2 = json::from_cbor(f_cbor));
+
+ // compare parsed JSON values
+ CHECK(j1 == j2);
+ }
+
+ {
+ INFO_WITH_TEMP(filename + ": uint8_t* and size");
+ // parse JSON file
+ std::ifstream f_json(filename);
+ const json j1 = json::parse(f_json);
+
+ // parse CBOR file
+ const auto packed = utils::read_binary_file(filename + ".cbor");
+ json j2;
+ CHECK_NOTHROW(j2 = json::from_cbor({packed.data(), packed.size()}));
+
+ // compare parsed JSON values
+ CHECK(j1 == j2);
+ }
+
+ {
+ INFO_WITH_TEMP(filename + ": output to output adapters");
+ // parse JSON file
+ std::ifstream f_json(filename);
+ json const j1 = json::parse(f_json);
+
+ // parse CBOR file
+ const auto packed = utils::read_binary_file(filename + ".cbor");
+
+ if (exclude_packed.count(filename) == 0u)
+ {
+ {
+ INFO_WITH_TEMP(filename + ": output adapters: std::vector<uint8_t>");
+ std::vector<uint8_t> vec;
+ json::to_cbor(j1, vec);
+ CHECK(vec == packed);
+ }
+ }
+ }
+ }
+ }
+}
+
+#if !defined(JSON_NOEXCEPTION)
+TEST_CASE("all CBOR first bytes")
+{
+ // these bytes will fail immediately with exception parse_error.112
+ std::set<uint8_t> unsupported =
+ {
+ //// types not supported by this library
+
+ // date/time
+ 0xc0, 0xc1,
+ // bignum
+ 0xc2, 0xc3,
+ // decimal fracion
+ 0xc4,
+ // bigfloat
+ 0xc5,
+ // tagged item
+ 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd,
+ 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd8,
+ 0xd9, 0xda, 0xdb,
+ // expected conversion
+ 0xd5, 0xd6, 0xd7,
+ // simple value
+ 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
+ 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xef, 0xf0,
+ 0xf1, 0xf2, 0xf3,
+ 0xf8,
+ // undefined
+ 0xf7,
+
+ //// bytes not specified by CBOR
+
+ 0x1c, 0x1d, 0x1e, 0x1f,
+ 0x3c, 0x3d, 0x3e, 0x3f,
+ 0x5c, 0x5d, 0x5e,
+ 0x7c, 0x7d, 0x7e,
+ 0x9c, 0x9d, 0x9e,
+ 0xbc, 0xbd, 0xbe,
+ 0xdc, 0xdd, 0xde, 0xdf,
+ 0xee,
+ 0xfc, 0xfe, 0xfd,
+
+ /// break cannot be the first byte
+
+ 0xff
+ };
+
+ for (auto i = 0; i < 256; ++i)
+ {
+ const auto byte = static_cast<uint8_t>(i);
+
+ try
+ {
+ auto res = json::from_cbor(std::vector<uint8_t>(1, byte));
+ }
+ catch (const json::parse_error& e)
+ {
+ // check that parse_error.112 is only thrown if the
+ // first byte is in the unsupported set
+ INFO_WITH_TEMP(e.what());
+ if (unsupported.find(byte) != unsupported.end())
+ {
+ CHECK(e.id == 112);
+ }
+ else
+ {
+ CHECK(e.id != 112);
+ }
+ }
+ }
+}
+#endif
+
+TEST_CASE("examples from RFC 7049 Appendix A")
+{
+ SECTION("numbers")
+ {
+ CHECK(json::to_cbor(json::parse("0")) == std::vector<uint8_t>({0x00}));
+ CHECK(json::parse("0") == json::from_cbor(std::vector<uint8_t>({0x00})));
+
+ CHECK(json::to_cbor(json::parse("1")) == std::vector<uint8_t>({0x01}));
+ CHECK(json::parse("1") == json::from_cbor(std::vector<uint8_t>({0x01})));
+
+ CHECK(json::to_cbor(json::parse("10")) == std::vector<uint8_t>({0x0a}));
+ CHECK(json::parse("10") == json::from_cbor(std::vector<uint8_t>({0x0a})));
+
+ CHECK(json::to_cbor(json::parse("23")) == std::vector<uint8_t>({0x17}));
+ CHECK(json::parse("23") == json::from_cbor(std::vector<uint8_t>({0x17})));
+
+ CHECK(json::to_cbor(json::parse("24")) == std::vector<uint8_t>({0x18, 0x18}));
+ CHECK(json::parse("24") == json::from_cbor(std::vector<uint8_t>({0x18, 0x18})));
+
+ CHECK(json::to_cbor(json::parse("25")) == std::vector<uint8_t>({0x18, 0x19}));
+ CHECK(json::parse("25") == json::from_cbor(std::vector<uint8_t>({0x18, 0x19})));
+
+ CHECK(json::to_cbor(json::parse("100")) == std::vector<uint8_t>({0x18, 0x64}));
+ CHECK(json::parse("100") == json::from_cbor(std::vector<uint8_t>({0x18, 0x64})));
+
+ CHECK(json::to_cbor(json::parse("1000")) == std::vector<uint8_t>({0x19, 0x03, 0xe8}));
+ CHECK(json::parse("1000") == json::from_cbor(std::vector<uint8_t>({0x19, 0x03, 0xe8})));
+
+ CHECK(json::to_cbor(json::parse("1000000")) == std::vector<uint8_t>({0x1a, 0x00, 0x0f, 0x42, 0x40}));
+ CHECK(json::parse("1000000") == json::from_cbor(std::vector<uint8_t>({0x1a, 0x00, 0x0f, 0x42, 0x40})));
+
+ CHECK(json::to_cbor(json::parse("1000000000000")) == std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00, 0xe8, 0xd4, 0xa5, 0x10, 0x00}));
+ CHECK(json::parse("1000000000000") == json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00, 0xe8, 0xd4, 0xa5, 0x10, 0x00})));
+
+ CHECK(json::to_cbor(json::parse("18446744073709551615")) == std::vector<uint8_t>({0x1b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}));
+ CHECK(json::parse("18446744073709551615") == json::from_cbor(std::vector<uint8_t>({0x1b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff})));
+
+ // positive bignum is not supported
+ //CHECK(json::to_cbor(json::parse("18446744073709551616")) == std::vector<uint8_t>({0xc2, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}));
+ //CHECK(json::parse("18446744073709551616") == json::from_cbor(std::vector<uint8_t>({0xc2, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})));
+
+ //CHECK(json::to_cbor(json::parse("-18446744073709551616")) == std::vector<uint8_t>({0x3b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}));
+ //CHECK(json::parse("-18446744073709551616") == json::from_cbor(std::vector<uint8_t>({0x3b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff})));
+
+ // negative bignum is not supported
+ //CHECK(json::to_cbor(json::parse("-18446744073709551617")) == std::vector<uint8_t>({0xc3, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}));
+ //CHECK(json::parse("-18446744073709551617") == json::from_cbor(std::vector<uint8_t>({0xc3, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})));
+
+ CHECK(json::to_cbor(json::parse("-1")) == std::vector<uint8_t>({0x20}));
+ CHECK(json::parse("-1") == json::from_cbor(std::vector<uint8_t>({0x20})));
+
+ CHECK(json::to_cbor(json::parse("-10")) == std::vector<uint8_t>({0x29}));
+ CHECK(json::parse("-10") == json::from_cbor(std::vector<uint8_t>({0x29})));
+
+ CHECK(json::to_cbor(json::parse("-100")) == std::vector<uint8_t>({0x38, 0x63}));
+ CHECK(json::parse("-100") == json::from_cbor(std::vector<uint8_t>({0x38, 0x63})));
+
+ CHECK(json::to_cbor(json::parse("-1000")) == std::vector<uint8_t>({0x39, 0x03, 0xe7}));
+ CHECK(json::parse("-1000") == json::from_cbor(std::vector<uint8_t>({0x39, 0x03, 0xe7})));
+
+ // half-precision float
+ //CHECK(json::to_cbor(json::parse("0.0")) == std::vector<uint8_t>({0xf9, 0x00, 0x00}));
+ CHECK(json::parse("0.0") == json::from_cbor(std::vector<uint8_t>({0xf9, 0x00, 0x00})));
+
+ // half-precision float
+ //CHECK(json::to_cbor(json::parse("-0.0")) == std::vector<uint8_t>({0xf9, 0x80, 0x00}));
+ CHECK(json::parse("-0.0") == json::from_cbor(std::vector<uint8_t>({0xf9, 0x80, 0x00})));
+
+ // half-precision float
+ //CHECK(json::to_cbor(json::parse("1.0")) == std::vector<uint8_t>({0xf9, 0x3c, 0x00}));
+ CHECK(json::parse("1.0") == json::from_cbor(std::vector<uint8_t>({0xf9, 0x3c, 0x00})));
+
+ CHECK(json::to_cbor(json::parse("1.1")) == std::vector<uint8_t>({0xfb, 0x3f, 0xf1, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9a}));
+ CHECK(json::parse("1.1") == json::from_cbor(std::vector<uint8_t>({0xfb, 0x3f, 0xf1, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9a})));
+
+ // half-precision float
+ //CHECK(json::to_cbor(json::parse("1.5")) == std::vector<uint8_t>({0xf9, 0x3e, 0x00}));
+ CHECK(json::parse("1.5") == json::from_cbor(std::vector<uint8_t>({0xf9, 0x3e, 0x00})));
+
+ // half-precision float
+ //CHECK(json::to_cbor(json::parse("65504.0")) == std::vector<uint8_t>({0xf9, 0x7b, 0xff}));
+ CHECK(json::parse("65504.0") == json::from_cbor(std::vector<uint8_t>({0xf9, 0x7b, 0xff})));
+
+ //CHECK(json::to_cbor(json::parse("100000.0")) == std::vector<uint8_t>({0xfa, 0x47, 0xc3, 0x50, 0x00}));
+ CHECK(json::parse("100000.0") == json::from_cbor(std::vector<uint8_t>({0xfa, 0x47, 0xc3, 0x50, 0x00})));
+
+ //CHECK(json::to_cbor(json::parse("3.4028234663852886e+38")) == std::vector<uint8_t>({0xfa, 0x7f, 0x7f, 0xff, 0xff}));
+ CHECK(json::parse("3.4028234663852886e+38") == json::from_cbor(std::vector<uint8_t>({0xfa, 0x7f, 0x7f, 0xff, 0xff})));
+
+ CHECK(json::to_cbor(json::parse("1.0e+300")) == std::vector<uint8_t>({0xfb, 0x7e, 0x37, 0xe4, 0x3c, 0x88, 0x00, 0x75, 0x9c}));
+ CHECK(json::parse("1.0e+300") == json::from_cbor(std::vector<uint8_t>({0xfb, 0x7e, 0x37, 0xe4, 0x3c, 0x88, 0x00, 0x75, 0x9c})));
+
+ // half-precision float
+ //CHECK(json::to_cbor(json::parse("5.960464477539063e-8")) == std::vector<uint8_t>({0xf9, 0x00, 0x01}));
+ CHECK(json::parse("-4.0") == json::from_cbor(std::vector<uint8_t>({0xf9, 0xc4, 0x00})));
+
+ // half-precision float
+ //CHECK(json::to_cbor(json::parse("0.00006103515625")) == std::vector<uint8_t>({0xf9, 0x04, 0x00}));
+ CHECK(json::parse("-4.0") == json::from_cbor(std::vector<uint8_t>({0xf9, 0xc4, 0x00})));
+
+ // half-precision float
+ //CHECK(json::to_cbor(json::parse("-4.0")) == std::vector<uint8_t>({0xf9, 0xc4, 0x00}));
+ CHECK(json::parse("-4.0") == json::from_cbor(std::vector<uint8_t>({0xf9, 0xc4, 0x00})));
+
+ CHECK(json::to_cbor(json::parse("-4.1")) == std::vector<uint8_t>({0xfb, 0xc0, 0x10, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66}));
+ CHECK(json::parse("-4.1") == json::from_cbor(std::vector<uint8_t>({0xfb, 0xc0, 0x10, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66})));
+ }
+
+ SECTION("simple values")
+ {
+ CHECK(json::to_cbor(json::parse("false")) == std::vector<uint8_t>({0xf4}));
+ CHECK(json::parse("false") == json::from_cbor(std::vector<uint8_t>({0xf4})));
+
+ CHECK(json::to_cbor(json::parse("true")) == std::vector<uint8_t>({0xf5}));
+ CHECK(json::parse("true") == json::from_cbor(std::vector<uint8_t>({0xf5})));
+
+ CHECK(json::to_cbor(json::parse("true")) == std::vector<uint8_t>({0xf5}));
+ CHECK(json::parse("true") == json::from_cbor(std::vector<uint8_t>({0xf5})));
+ }
+
+ SECTION("strings")
+ {
+ CHECK(json::to_cbor(json::parse("\"\"")) == std::vector<uint8_t>({0x60}));
+ CHECK(json::parse("\"\"") == json::from_cbor(std::vector<uint8_t>({0x60})));
+
+ CHECK(json::to_cbor(json::parse("\"a\"")) == std::vector<uint8_t>({0x61, 0x61}));
+ CHECK(json::parse("\"a\"") == json::from_cbor(std::vector<uint8_t>({0x61, 0x61})));
+
+ CHECK(json::to_cbor(json::parse("\"IETF\"")) == std::vector<uint8_t>({0x64, 0x49, 0x45, 0x54, 0x46}));
+ CHECK(json::parse("\"IETF\"") == json::from_cbor(std::vector<uint8_t>({0x64, 0x49, 0x45, 0x54, 0x46})));
+
+ CHECK(json::to_cbor(json::parse("\"\\u00fc\"")) == std::vector<uint8_t>({0x62, 0xc3, 0xbc}));
+ CHECK(json::parse("\"\\u00fc\"") == json::from_cbor(std::vector<uint8_t>({0x62, 0xc3, 0xbc})));
+
+ CHECK(json::to_cbor(json::parse("\"\\u6c34\"")) == std::vector<uint8_t>({0x63, 0xe6, 0xb0, 0xb4}));
+ CHECK(json::parse("\"\\u6c34\"") == json::from_cbor(std::vector<uint8_t>({0x63, 0xe6, 0xb0, 0xb4})));
+
+ CHECK(json::to_cbor(json::parse("\"\\ud800\\udd51\"")) == std::vector<uint8_t>({0x64, 0xf0, 0x90, 0x85, 0x91}));
+ CHECK(json::parse("\"\\ud800\\udd51\"") == json::from_cbor(std::vector<uint8_t>({0x64, 0xf0, 0x90, 0x85, 0x91})));
+
+ // indefinite length strings
+ CHECK(json::parse("\"streaming\"") == json::from_cbor(std::vector<uint8_t>({0x7f, 0x65, 0x73, 0x74, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x67, 0xff})));
+ }
+
+ SECTION("byte arrays")
+ {
+ const auto packed = utils::read_binary_file(TEST_DATA_DIRECTORY "/binary_data/cbor_binary.cbor");
+ json j;
+ CHECK_NOTHROW(j = json::from_cbor(packed));
+
+ const auto expected = utils::read_binary_file(TEST_DATA_DIRECTORY "/binary_data/cbor_binary.out");
+ CHECK(j == json::binary(expected));
+
+ // 0xd8
+ CHECK(json::to_cbor(json::binary(std::vector<uint8_t> {}, 0x42)) == std::vector<uint8_t> {0xd8, 0x42, 0x40});
+ CHECK(!json::from_cbor(json::to_cbor(json::binary(std::vector<uint8_t> {}, 0x42)), true, true, json::cbor_tag_handler_t::ignore).get_binary().has_subtype());
+ CHECK(json::from_cbor(json::to_cbor(json::binary(std::vector<uint8_t> {}, 0x42)), true, true, json::cbor_tag_handler_t::store).get_binary().subtype() == 0x42);
+ // 0xd9
+ CHECK(json::to_cbor(json::binary(std::vector<uint8_t> {}, 1000)) == std::vector<uint8_t> {0xd9, 0x03, 0xe8, 0x40});
+ CHECK(!json::from_cbor(json::to_cbor(json::binary(std::vector<uint8_t> {}, 1000)), true, true, json::cbor_tag_handler_t::ignore).get_binary().has_subtype());
+ CHECK(json::from_cbor(json::to_cbor(json::binary(std::vector<uint8_t> {}, 1000)), true, true, json::cbor_tag_handler_t::store).get_binary().subtype() == 1000);
+ // 0xda
+ CHECK(json::to_cbor(json::binary(std::vector<uint8_t> {}, 394216)) == std::vector<uint8_t> {0xda, 0x00, 0x06, 0x03, 0xe8, 0x40});
+ CHECK(!json::from_cbor(json::to_cbor(json::binary(std::vector<uint8_t> {}, 394216)), true, true, json::cbor_tag_handler_t::ignore).get_binary().has_subtype());
+ CHECK(json::from_cbor(json::to_cbor(json::binary(std::vector<uint8_t> {}, 394216)), true, true, json::cbor_tag_handler_t::store).get_binary().subtype() == 394216);
+ // 0xdb
+ CHECK(json::to_cbor(json::binary(std::vector<uint8_t> {}, 8589934590)) == std::vector<uint8_t> {0xdb, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xfe, 0x40});
+ CHECK(!json::from_cbor(json::to_cbor(json::binary(std::vector<uint8_t> {}, 8589934590)), true, true, json::cbor_tag_handler_t::ignore).get_binary().has_subtype());
+ CHECK(json::from_cbor(json::to_cbor(json::binary(std::vector<uint8_t> {}, 8589934590)), true, true, json::cbor_tag_handler_t::store).get_binary().subtype() == 8589934590);
+ }
+
+ SECTION("arrays")
+ {
+ CHECK(json::to_cbor(json::parse("[]")) == std::vector<uint8_t>({0x80}));
+ CHECK(json::parse("[]") == json::from_cbor(std::vector<uint8_t>({0x80})));
+
+ CHECK(json::to_cbor(json::parse("[1, 2, 3]")) == std::vector<uint8_t>({0x83, 0x01, 0x02, 0x03}));
+ CHECK(json::parse("[1, 2, 3]") == json::from_cbor(std::vector<uint8_t>({0x83, 0x01, 0x02, 0x03})));
+
+ CHECK(json::to_cbor(json::parse("[1, [2, 3], [4, 5]]")) == std::vector<uint8_t>({0x83, 0x01, 0x82, 0x02, 0x03, 0x82, 0x04, 0x05}));
+ CHECK(json::parse("[1, [2, 3], [4, 5]]") == json::from_cbor(std::vector<uint8_t>({0x83, 0x01, 0x82, 0x02, 0x03, 0x82, 0x04, 0x05})));
+
+ CHECK(json::to_cbor(json::parse("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]")) == std::vector<uint8_t>({0x98, 0x19, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x18, 0x18, 0x19}));
+ CHECK(json::parse("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]") == json::from_cbor(std::vector<uint8_t>({0x98, 0x19, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x18, 0x18, 0x19})));
+
+ // indefinite length arrays
+ CHECK(json::parse("[]") == json::from_cbor(std::vector<uint8_t>({0x9f, 0xff})));
+ CHECK(json::parse("[1, [2, 3], [4, 5]] ") == json::from_cbor(std::vector<uint8_t>({0x9f, 0x01, 0x82, 0x02, 0x03, 0x9f, 0x04, 0x05, 0xff, 0xff})));
+ CHECK(json::parse("[1, [2, 3], [4, 5]]") == json::from_cbor(std::vector<uint8_t>({0x9f, 0x01, 0x82, 0x02, 0x03, 0x82, 0x04, 0x05, 0xff})));
+ CHECK(json::parse("[1, [2, 3], [4, 5]]") == json::from_cbor(std::vector<uint8_t>({0x83, 0x01, 0x82, 0x02, 0x03, 0x9f, 0x04, 0x05, 0xff})));
+ CHECK(json::parse("[1, [2, 3], [4, 5]]") == json::from_cbor(std::vector<uint8_t>({0x83, 0x01, 0x9f, 0x02, 0x03, 0xff, 0x82, 0x04, 0x05})));
+ CHECK(json::parse("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]") == json::from_cbor(std::vector<uint8_t>({0x9f, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x18, 0x18, 0x19, 0xff})));
+ }
+
+ SECTION("objects")
+ {
+ CHECK(json::to_cbor(json::parse("{}")) == std::vector<uint8_t>({0xa0}));
+ CHECK(json::parse("{}") == json::from_cbor(std::vector<uint8_t>({0xa0})));
+
+ CHECK(json::to_cbor(json::parse("{\"a\": 1, \"b\": [2, 3]}")) == std::vector<uint8_t>({0xa2, 0x61, 0x61, 0x01, 0x61, 0x62, 0x82, 0x02, 0x03}));
+ CHECK(json::parse("{\"a\": 1, \"b\": [2, 3]}") == json::from_cbor(std::vector<uint8_t>({0xa2, 0x61, 0x61, 0x01, 0x61, 0x62, 0x82, 0x02, 0x03})));
+
+ CHECK(json::to_cbor(json::parse("[\"a\", {\"b\": \"c\"}]")) == std::vector<uint8_t>({0x82, 0x61, 0x61, 0xa1, 0x61, 0x62, 0x61, 0x63}));
+ CHECK(json::parse("[\"a\", {\"b\": \"c\"}]") == json::from_cbor(std::vector<uint8_t>({0x82, 0x61, 0x61, 0xa1, 0x61, 0x62, 0x61, 0x63})));
+
+ CHECK(json::to_cbor(json::parse("{\"a\": \"A\", \"b\": \"B\", \"c\": \"C\", \"d\": \"D\", \"e\": \"E\"}")) == std::vector<uint8_t>({0xa5, 0x61, 0x61, 0x61, 0x41, 0x61, 0x62, 0x61, 0x42, 0x61, 0x63, 0x61, 0x43, 0x61, 0x64, 0x61, 0x44, 0x61, 0x65, 0x61, 0x45}));
+ CHECK(json::parse("{\"a\": \"A\", \"b\": \"B\", \"c\": \"C\", \"d\": \"D\", \"e\": \"E\"}") == json::from_cbor(std::vector<uint8_t>({0xa5, 0x61, 0x61, 0x61, 0x41, 0x61, 0x62, 0x61, 0x42, 0x61, 0x63, 0x61, 0x43, 0x61, 0x64, 0x61, 0x44, 0x61, 0x65, 0x61, 0x45})));
+
+ // indefinite length objects
+ CHECK(json::parse("{\"a\": 1, \"b\": [2, 3]}") == json::from_cbor(std::vector<uint8_t>({0xbf, 0x61, 0x61, 0x01, 0x61, 0x62, 0x9f, 0x02, 0x03, 0xff, 0xff})));
+ CHECK(json::parse("[\"a\", {\"b\": \"c\"}]") == json::from_cbor(std::vector<uint8_t>({0x82, 0x61, 0x61, 0xbf, 0x61, 0x62, 0x61, 0x63, 0xff})));
+ CHECK(json::parse("{\"Fun\": true, \"Amt\": -2}") == json::from_cbor(std::vector<uint8_t>({0xbf, 0x63, 0x46, 0x75, 0x6e, 0xf5, 0x63, 0x41, 0x6d, 0x74, 0x21, 0xff})));
+ }
+}
+
+TEST_CASE("Tagged values")
+{
+ const json j = "s";
+ auto v = json::to_cbor(j);
+
+ SECTION("0xC6..0xD4")
+ {
+ for (const auto b : std::vector<std::uint8_t>
+ {
+ 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4
+ })
+ {
+ CAPTURE(b);
+
+ // add tag to value
+ auto v_tagged = v;
+ v_tagged.insert(v_tagged.begin(), b);
+
+ // check that parsing fails in error mode
+ json _;
+ CHECK_THROWS_AS(_ = json::from_cbor(v_tagged), json::parse_error);
+ CHECK_THROWS_AS(_ = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::error), json::parse_error);
+
+ // check that parsing succeeds and gets original value in ignore mode
+ auto j_tagged = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::ignore);
+ CHECK(j_tagged == j);
+
+ auto j_tagged_stored = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::store);
+ CHECK(j_tagged_stored == j);
+ }
+ }
+
+ SECTION("0xD8 - 1 byte follows")
+ {
+ SECTION("success")
+ {
+ // add tag to value
+ auto v_tagged = v;
+ v_tagged.insert(v_tagged.begin(), 0x42); // 1 byte
+ v_tagged.insert(v_tagged.begin(), 0xD8); // tag
+
+ // check that parsing fails in error mode
+ json _;
+ CHECK_THROWS_AS(_ = json::from_cbor(v_tagged), json::parse_error);
+ CHECK_THROWS_AS(_ = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::error), json::parse_error);
+
+ // check that parsing succeeds and gets original value in ignore mode
+ auto j_tagged = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::ignore);
+ CHECK(j_tagged == j);
+ }
+
+ SECTION("missing byte after tag")
+ {
+ // add tag to value
+ auto v_tagged = v;
+ v_tagged.insert(v_tagged.begin(), 0xD8); // tag
+
+ // check that parsing fails in all modes
+ json _;
+ CHECK_THROWS_AS(_ = json::from_cbor(v_tagged), json::parse_error);
+ CHECK_THROWS_AS(_ = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::error), json::parse_error);
+ CHECK_THROWS_AS(_ = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::ignore), json::parse_error);
+ }
+ }
+
+ SECTION("0xD9 - 2 byte follow")
+ {
+ SECTION("success")
+ {
+ // add tag to value
+ auto v_tagged = v;
+ v_tagged.insert(v_tagged.begin(), 0x42); // 1 byte
+ v_tagged.insert(v_tagged.begin(), 0x23); // 1 byte
+ v_tagged.insert(v_tagged.begin(), 0xD9); // tag
+
+ // check that parsing fails in error mode
+ json _;
+ CHECK_THROWS_AS(_ = json::from_cbor(v_tagged), json::parse_error);
+ CHECK_THROWS_AS(_ = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::error), json::parse_error);
+
+ // check that parsing succeeds and gets original value in ignore mode
+ auto j_tagged = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::ignore);
+ CHECK(j_tagged == j);
+ }
+
+ SECTION("missing byte after tag")
+ {
+ // add tag to value
+ auto v_tagged = v;
+ v_tagged.insert(v_tagged.begin(), 0x23); // 1 byte
+ v_tagged.insert(v_tagged.begin(), 0xD9); // tag
+
+ // check that parsing fails in all modes
+ json _;
+ CHECK_THROWS_AS(_ = json::from_cbor(v_tagged), json::parse_error);
+ CHECK_THROWS_AS(_ = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::error), json::parse_error);
+ CHECK_THROWS_AS(_ = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::ignore), json::parse_error);
+ }
+ }
+
+ SECTION("0xDA - 4 bytes follow")
+ {
+ SECTION("success")
+ {
+ // add tag to value
+ auto v_tagged = v;
+ v_tagged.insert(v_tagged.begin(), 0x42); // 1 byte
+ v_tagged.insert(v_tagged.begin(), 0x23); // 1 byte
+ v_tagged.insert(v_tagged.begin(), 0x22); // 1 byte
+ v_tagged.insert(v_tagged.begin(), 0x11); // 1 byte
+ v_tagged.insert(v_tagged.begin(), 0xDA); // tag
+
+ // check that parsing fails in error mode
+ json _;
+ CHECK_THROWS_AS(_ = json::from_cbor(v_tagged), json::parse_error);
+ CHECK_THROWS_AS(_ = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::error), json::parse_error);
+
+ // check that parsing succeeds and gets original value in ignore mode
+ auto j_tagged = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::ignore);
+ CHECK(j_tagged == j);
+ }
+
+ SECTION("missing bytes after tag")
+ {
+ // add tag to value
+ auto v_tagged = v;
+ v_tagged.insert(v_tagged.begin(), 0x23); // 1 byte
+ v_tagged.insert(v_tagged.begin(), 0x22); // 1 byte
+ v_tagged.insert(v_tagged.begin(), 0x11); // 1 byte
+ v_tagged.insert(v_tagged.begin(), 0xDA); // tag
+
+ // check that parsing fails in all modes
+ json _;
+ CHECK_THROWS_AS(_ = json::from_cbor(v_tagged), json::parse_error);
+ CHECK_THROWS_AS(_ = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::error), json::parse_error);
+ CHECK_THROWS_AS(_ = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::ignore), json::parse_error);
+ }
+ }
+
+ SECTION("0xDB - 8 bytes follow")
+ {
+ SECTION("success")
+ {
+ // add tag to value
+ auto v_tagged = v;
+ v_tagged.insert(v_tagged.begin(), 0x42); // 1 byte
+ v_tagged.insert(v_tagged.begin(), 0x23); // 1 byte
+ v_tagged.insert(v_tagged.begin(), 0x22); // 1 byte
+ v_tagged.insert(v_tagged.begin(), 0x11); // 1 byte
+ v_tagged.insert(v_tagged.begin(), 0x42); // 1 byte
+ v_tagged.insert(v_tagged.begin(), 0x23); // 1 byte
+ v_tagged.insert(v_tagged.begin(), 0x22); // 1 byte
+ v_tagged.insert(v_tagged.begin(), 0x11); // 1 byte
+ v_tagged.insert(v_tagged.begin(), 0xDB); // tag
+
+ // check that parsing fails in error mode
+ json _;
+ CHECK_THROWS_AS(_ = json::from_cbor(v_tagged), json::parse_error);
+ CHECK_THROWS_AS(_ = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::error), json::parse_error);
+
+ // check that parsing succeeds and gets original value in ignore mode
+ auto j_tagged = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::ignore);
+ CHECK(j_tagged == j);
+ }
+
+ SECTION("missing byte after tag")
+ {
+ // add tag to value
+ auto v_tagged = v;
+ v_tagged.insert(v_tagged.begin(), 0x42); // 1 byte
+ v_tagged.insert(v_tagged.begin(), 0x23); // 1 byte
+ v_tagged.insert(v_tagged.begin(), 0x22); // 1 byte
+ v_tagged.insert(v_tagged.begin(), 0x11); // 1 byte
+ v_tagged.insert(v_tagged.begin(), 0x23); // 1 byte
+ v_tagged.insert(v_tagged.begin(), 0x22); // 1 byte
+ v_tagged.insert(v_tagged.begin(), 0x11); // 1 byte
+ v_tagged.insert(v_tagged.begin(), 0xDB); // tag
+
+ // check that parsing fails in all modes
+ json _;
+ CHECK_THROWS_AS(_ = json::from_cbor(v_tagged), json::parse_error);
+ CHECK_THROWS_AS(_ = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::error), json::parse_error);
+ CHECK_THROWS_AS(_ = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::ignore), json::parse_error);
+ }
+ }
+
+ SECTION("negative integer overflow")
+ {
+ // CBOR encodes negative integers as: result = -1 - n
+ // For type 0x3B, n is an 8-byte uint64_t. Valid range for n with
+ // the default int64_t is [0, INT64_MAX], producing results in [INT64_MIN, -1].
+ // When n > INT64_MAX, the result exceeds int64_t range and is rejected.
+
+ SECTION("n = 0 is valid (result = -1)")
+ {
+ // n = 0, result = -1 - 0 = -1 (smallest magnitude negative)
+ const std::vector<uint8_t> input = {0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ const auto result = json::from_cbor(input);
+ CHECK(result.is_number_integer());
+ CHECK(result.get<int64_t>() == -1);
+ }
+
+ SECTION("n = INT64_MAX is valid (result = INT64_MIN)")
+ {
+ // n = INT64_MAX (0x7FFFFFFFFFFFFFFF)
+ // result = -1 - INT64_MAX = INT64_MIN (-9223372036854775808)
+ const std::vector<uint8_t> input = {0x3B, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+ const auto result = json::from_cbor(input);
+ CHECK(result.is_number_integer());
+ CHECK(result.get<int64_t>() == (std::numeric_limits<int64_t>::min)());
+ }
+
+ SECTION("n = INT64_MAX + 1 is rejected (overflow)")
+ {
+ // n = INT64_MAX + 1 (0x8000000000000000)
+ // result = -1 - n = -9223372036854775809, which exceeds int64_t range
+ const std::vector<uint8_t> input = {0x3B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(input),
+ "[json.exception.parse_error.112] parse error at byte 9: syntax error while parsing CBOR value: negative integer overflow",
+ json::parse_error);
+ }
+
+ SECTION("n = UINT64_MAX is rejected (overflow)")
+ {
+ // n = UINT64_MAX (0xFFFFFFFFFFFFFFFF)
+ // result = -1 - n = -18446744073709551616, which exceeds int64_t range
+ const std::vector<uint8_t> input = {0x3B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(input),
+ "[json.exception.parse_error.112] parse error at byte 9: syntax error while parsing CBOR value: negative integer overflow",
+ json::parse_error);
+ }
+
+ SECTION("overflow with allow_exceptions=false returns discarded")
+ {
+ const std::vector<uint8_t> input = {0x3B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ const auto result = json::from_cbor(input, true, false);
+ CHECK(result.is_discarded());
+ }
+ }
+
+ SECTION("tagged binary")
+ {
+ // create a binary value of subtype 42
+ json j_binary;
+ j_binary["binary"] = json::binary({0xCA, 0xFE, 0xBA, 0xBE}, 42);
+
+ // convert to CBOR
+ const auto vec = json::to_cbor(j_binary);
+ CHECK(vec == std::vector<std::uint8_t> {0xA1, 0x66, 0x62, 0x69, 0x6E, 0x61, 0x72, 0x79, 0xD8, 0x2A, 0x44, 0xCA, 0xFE, 0xBA, 0xBE});
+
+ // parse error when parsing tagged value
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec), "[json.exception.parse_error.112] parse error at byte 9: syntax error while parsing CBOR value: invalid byte: 0xD8", json::parse_error);
+
+ // binary without subtype when tags are ignored
+ json jb = json::from_cbor(vec, true, true, json::cbor_tag_handler_t::ignore);
+ CHECK(jb.is_object());
+ CHECK(jb["binary"].is_binary());
+ CHECK(!jb["binary"].get_binary().has_subtype());
+ }
+}
diff --git a/json4cpp/tests/src/unit-class_const_iterator.cpp b/json4cpp/tests/src/unit-class_const_iterator.cpp
new file mode 100644
index 0000000000..9e5de42c78
--- /dev/null
+++ b/json4cpp/tests/src/unit-class_const_iterator.cpp
@@ -0,0 +1,393 @@
+// __ _____ _____ _____
+// __| | __| | | | 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;
+
+TEST_CASE("const_iterator class")
+{
+ SECTION("construction")
+ {
+ SECTION("constructor")
+ {
+ SECTION("null")
+ {
+ json const j(json::value_t::null);
+ json::const_iterator const it(&j);
+ }
+
+ SECTION("object")
+ {
+ json const j(json::value_t::object);
+ json::const_iterator const it(&j);
+ }
+
+ SECTION("array")
+ {
+ json const j(json::value_t::array);
+ json::const_iterator const it(&j);
+ }
+ }
+
+ SECTION("copy assignment")
+ {
+ json const j(json::value_t::null);
+ json::const_iterator const it(&j);
+ json::const_iterator it2(&j);
+ it2 = it;
+ }
+
+ SECTION("copy constructor from non-const iterator")
+ {
+ SECTION("create from uninitialized iterator")
+ {
+ const json::iterator it {};
+ json::const_iterator const cit(it);
+ }
+
+ SECTION("create from initialized iterator")
+ {
+ json j;
+ const json::iterator it = j.begin();
+ json::const_iterator const cit(it);
+ }
+ }
+ }
+
+ SECTION("initialization")
+ {
+ SECTION("set_begin")
+ {
+ SECTION("null")
+ {
+ json const j(json::value_t::null);
+ json::const_iterator it(&j);
+ it.set_begin();
+ CHECK((it == j.cbegin()));
+ }
+
+ SECTION("object")
+ {
+ json const j(json::value_t::object);
+ json::const_iterator it(&j);
+ it.set_begin();
+ CHECK((it == j.cbegin()));
+ }
+
+ SECTION("array")
+ {
+ json const j(json::value_t::array);
+ json::const_iterator it(&j);
+ it.set_begin();
+ CHECK((it == j.cbegin()));
+ }
+ }
+
+ SECTION("set_end")
+ {
+ SECTION("null")
+ {
+ json const j(json::value_t::null);
+ json::const_iterator it(&j);
+ it.set_end();
+ CHECK((it == j.cend()));
+ }
+
+ SECTION("object")
+ {
+ json const j(json::value_t::object);
+ json::const_iterator it(&j);
+ it.set_end();
+ CHECK((it == j.cend()));
+ }
+
+ SECTION("array")
+ {
+ json const j(json::value_t::array);
+ json::const_iterator it(&j);
+ it.set_end();
+ CHECK((it == j.cend()));
+ }
+ }
+ }
+
+ SECTION("element access")
+ {
+ SECTION("operator*")
+ {
+ SECTION("null")
+ {
+ json const j(json::value_t::null);
+ json::const_iterator const it = j.cbegin();
+ CHECK_THROWS_WITH_AS(*it, "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ }
+
+ SECTION("number")
+ {
+ json const j(17);
+ json::const_iterator it = j.cbegin();
+ CHECK(*it == json(17));
+ it = j.cend();
+ CHECK_THROWS_WITH_AS(*it, "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ }
+
+ SECTION("object")
+ {
+ json const j({{"foo", "bar"}});
+ json::const_iterator const it = j.cbegin();
+ CHECK(*it == json("bar"));
+ }
+
+ SECTION("array")
+ {
+ json const j({1, 2, 3, 4});
+ json::const_iterator const it = j.cbegin();
+ CHECK(*it == json(1));
+ }
+ }
+
+ SECTION("operator->")
+ {
+ SECTION("null")
+ {
+ json const j(json::value_t::null);
+ json::const_iterator const it = j.cbegin();
+ CHECK_THROWS_WITH_AS(std::string(it->type_name()), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ }
+
+ SECTION("number")
+ {
+ json const j(17);
+ json::const_iterator it = j.cbegin();
+ CHECK(std::string(it->type_name()) == "number");
+ it = j.cend();
+ CHECK_THROWS_WITH_AS(std::string(it->type_name()), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ }
+
+ SECTION("object")
+ {
+ json const j({{"foo", "bar"}});
+ json::const_iterator const it = j.cbegin();
+ CHECK(std::string(it->type_name()) == "string");
+ }
+
+ SECTION("array")
+ {
+ json const j({1, 2, 3, 4});
+ json::const_iterator const it = j.cbegin();
+ CHECK(std::string(it->type_name()) == "number");
+ }
+ }
+ }
+
+ SECTION("increment/decrement")
+ {
+ SECTION("post-increment")
+ {
+ SECTION("null")
+ {
+ json const j(json::value_t::null);
+ json::const_iterator it = j.cbegin();
+ CHECK((it.m_it.primitive_iterator.m_it == 1));
+ it++;
+ CHECK((it.m_it.primitive_iterator.m_it != 0 && it.m_it.primitive_iterator.m_it != 1));
+ }
+
+ SECTION("number")
+ {
+ json const j(17);
+ json::const_iterator it = j.cbegin();
+ CHECK((it.m_it.primitive_iterator.m_it == 0));
+ it++;
+ CHECK((it.m_it.primitive_iterator.m_it == 1));
+ it++;
+ CHECK((it.m_it.primitive_iterator.m_it != 0 && it.m_it.primitive_iterator.m_it != 1));
+ }
+
+ SECTION("object")
+ {
+ json const j({{"foo", "bar"}});
+ json::const_iterator it = j.cbegin();
+ CHECK((it.m_it.object_iterator == it.m_object->m_data.m_value.object->begin()));
+ it++;
+ CHECK((it.m_it.object_iterator == it.m_object->m_data.m_value.object->end()));
+ }
+
+ SECTION("array")
+ {
+ json const j({1, 2, 3, 4});
+ json::const_iterator it = j.cbegin();
+ CHECK((it.m_it.array_iterator == it.m_object->m_data.m_value.array->begin()));
+ it++;
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->begin()));
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->end()));
+ it++;
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->begin()));
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->end()));
+ it++;
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->begin()));
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->end()));
+ it++;
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->begin()));
+ CHECK((it.m_it.array_iterator == it.m_object->m_data.m_value.array->end()));
+ }
+ }
+
+ SECTION("pre-increment")
+ {
+ SECTION("null")
+ {
+ json const j(json::value_t::null);
+ json::const_iterator it = j.cbegin();
+ CHECK((it.m_it.primitive_iterator.m_it == 1));
+ ++it;
+ CHECK((it.m_it.primitive_iterator.m_it != 0 && it.m_it.primitive_iterator.m_it != 1));
+ }
+
+ SECTION("number")
+ {
+ json const j(17);
+ json::const_iterator it = j.cbegin();
+ CHECK((it.m_it.primitive_iterator.m_it == 0));
+ ++it;
+ CHECK((it.m_it.primitive_iterator.m_it == 1));
+ ++it;
+ CHECK((it.m_it.primitive_iterator.m_it != 0 && it.m_it.primitive_iterator.m_it != 1));
+ }
+
+ SECTION("object")
+ {
+ json const j({{"foo", "bar"}});
+ json::const_iterator it = j.cbegin();
+ CHECK((it.m_it.object_iterator == it.m_object->m_data.m_value.object->begin()));
+ ++it;
+ CHECK((it.m_it.object_iterator == it.m_object->m_data.m_value.object->end()));
+ }
+
+ SECTION("array")
+ {
+ json const j({1, 2, 3, 4});
+ json::const_iterator it = j.cbegin();
+ CHECK((it.m_it.array_iterator == it.m_object->m_data.m_value.array->begin()));
+ ++it;
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->begin()));
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->end()));
+ ++it;
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->begin()));
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->end()));
+ ++it;
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->begin()));
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->end()));
+ ++it;
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->begin()));
+ CHECK((it.m_it.array_iterator == it.m_object->m_data.m_value.array->end()));
+ }
+ }
+
+ SECTION("post-decrement")
+ {
+ SECTION("null")
+ {
+ json const j(json::value_t::null);
+ json::const_iterator const it = j.cend();
+ CHECK((it.m_it.primitive_iterator.m_it == 1));
+ }
+
+ SECTION("number")
+ {
+ json const j(17);
+ json::const_iterator it = j.cend();
+ CHECK((it.m_it.primitive_iterator.m_it == 1));
+ it--;
+ CHECK((it.m_it.primitive_iterator.m_it == 0));
+ it--;
+ CHECK((it.m_it.primitive_iterator.m_it != 0 && it.m_it.primitive_iterator.m_it != 1));
+ }
+
+ SECTION("object")
+ {
+ json const j({{"foo", "bar"}});
+ json::const_iterator it = j.cend();
+ CHECK((it.m_it.object_iterator == it.m_object->m_data.m_value.object->end()));
+ it--;
+ CHECK((it.m_it.object_iterator == it.m_object->m_data.m_value.object->begin()));
+ }
+
+ SECTION("array")
+ {
+ json const j({1, 2, 3, 4});
+ json::const_iterator it = j.cend();
+ CHECK((it.m_it.array_iterator == it.m_object->m_data.m_value.array->end()));
+ it--;
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->begin()));
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->end()));
+ it--;
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->begin()));
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->end()));
+ it--;
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->begin()));
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->end()));
+ it--;
+ CHECK((it.m_it.array_iterator == it.m_object->m_data.m_value.array->begin()));
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->end()));
+ }
+ }
+
+ SECTION("pre-decrement")
+ {
+ SECTION("null")
+ {
+ json const j(json::value_t::null);
+ json::const_iterator const it = j.cend();
+ CHECK((it.m_it.primitive_iterator.m_it == 1));
+ }
+
+ SECTION("number")
+ {
+ json const j(17);
+ json::const_iterator it = j.cend();
+ CHECK((it.m_it.primitive_iterator.m_it == 1));
+ --it;
+ CHECK((it.m_it.primitive_iterator.m_it == 0));
+ --it;
+ CHECK((it.m_it.primitive_iterator.m_it != 0 && it.m_it.primitive_iterator.m_it != 1));
+ }
+
+ SECTION("object")
+ {
+ json const j({{"foo", "bar"}});
+ json::const_iterator it = j.cend();
+ CHECK((it.m_it.object_iterator == it.m_object->m_data.m_value.object->end()));
+ --it;
+ CHECK((it.m_it.object_iterator == it.m_object->m_data.m_value.object->begin()));
+ }
+
+ SECTION("array")
+ {
+ json const j({1, 2, 3, 4});
+ json::const_iterator it = j.cend();
+ CHECK((it.m_it.array_iterator == it.m_object->m_data.m_value.array->end()));
+ --it;
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->begin()));
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->end()));
+ --it;
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->begin()));
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->end()));
+ --it;
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->begin()));
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->end()));
+ --it;
+ CHECK((it.m_it.array_iterator == it.m_object->m_data.m_value.array->begin()));
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->end()));
+ }
+ }
+ }
+}
diff --git a/json4cpp/tests/src/unit-class_iterator.cpp b/json4cpp/tests/src/unit-class_iterator.cpp
new file mode 100644
index 0000000000..2303381177
--- /dev/null
+++ b/json4cpp/tests/src/unit-class_iterator.cpp
@@ -0,0 +1,468 @@
+// __ _____ _____ _____
+// __| | __| | | | 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;
+
+template<typename Iter>
+using can_post_increment_temporary = decltype((std::declval<Iter>()++)++);
+
+template<typename Iter>
+using can_post_decrement_temporary = decltype((std::declval<Iter>()--)--);
+
+TEST_CASE("iterator class")
+{
+ SECTION("construction")
+ {
+ SECTION("constructor")
+ {
+ SECTION("null")
+ {
+ json j(json::value_t::null);
+ json::iterator const it(&j);
+ }
+
+ SECTION("object")
+ {
+ json j(json::value_t::object);
+ json::iterator const it(&j);
+ }
+
+ SECTION("array")
+ {
+ json j(json::value_t::array);
+ json::iterator const it(&j);
+ }
+ }
+
+ SECTION("copy assignment")
+ {
+ json j(json::value_t::null);
+ json::iterator const it(&j);
+ json::iterator it2(&j);
+ it2 = it;
+ }
+ }
+
+ SECTION("initialization")
+ {
+ SECTION("set_begin")
+ {
+ SECTION("null")
+ {
+ json j(json::value_t::null);
+ json::iterator it(&j);
+ it.set_begin();
+ CHECK((it == j.begin()));
+ }
+
+ SECTION("object")
+ {
+ json j(json::value_t::object);
+ json::iterator it(&j);
+ it.set_begin();
+ CHECK((it == j.begin()));
+ }
+
+ SECTION("array")
+ {
+ json j(json::value_t::array);
+ json::iterator it(&j);
+ it.set_begin();
+ CHECK((it == j.begin()));
+ }
+ }
+
+ SECTION("set_end")
+ {
+ SECTION("null")
+ {
+ json j(json::value_t::null);
+ json::iterator it(&j);
+ it.set_end();
+ CHECK((it == j.end()));
+ }
+
+ SECTION("object")
+ {
+ json j(json::value_t::object);
+ json::iterator it(&j);
+ it.set_end();
+ CHECK((it == j.end()));
+ }
+
+ SECTION("array")
+ {
+ json j(json::value_t::array);
+ json::iterator it(&j);
+ it.set_end();
+ CHECK((it == j.end()));
+ }
+ }
+ }
+
+ SECTION("element access")
+ {
+ SECTION("operator*")
+ {
+ SECTION("null")
+ {
+ json j(json::value_t::null);
+ json::iterator const it = j.begin();
+ CHECK_THROWS_WITH_AS(*it, "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ }
+
+ SECTION("number")
+ {
+ json j(17);
+ json::iterator it = j.begin();
+ CHECK(*it == json(17));
+ it = j.end();
+ CHECK_THROWS_WITH_AS(*it, "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ }
+
+ SECTION("object")
+ {
+ json j({{"foo", "bar"}});
+ json::iterator const it = j.begin();
+ CHECK(*it == json("bar"));
+ }
+
+ SECTION("array")
+ {
+ json j({1, 2, 3, 4});
+ json::iterator const it = j.begin();
+ CHECK(*it == json(1));
+ }
+ }
+
+ SECTION("operator->")
+ {
+ SECTION("null")
+ {
+ json j(json::value_t::null);
+ json::iterator const it = j.begin();
+ CHECK_THROWS_WITH_AS(std::string(it->type_name()), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ }
+
+ SECTION("number")
+ {
+ json j(17);
+ json::iterator it = j.begin();
+ CHECK(std::string(it->type_name()) == "number");
+ it = j.end();
+ CHECK_THROWS_WITH_AS(std::string(it->type_name()), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ }
+
+ SECTION("object")
+ {
+ json j({{"foo", "bar"}});
+ json::iterator const it = j.begin();
+ CHECK(std::string(it->type_name()) == "string");
+ }
+
+ SECTION("array")
+ {
+ json j({1, 2, 3, 4});
+ json::iterator const it = j.begin();
+ CHECK(std::string(it->type_name()) == "number");
+ }
+ }
+ }
+
+ SECTION("increment/decrement")
+ {
+ SECTION("post-increment")
+ {
+ SECTION("null")
+ {
+ json j(json::value_t::null);
+ json::iterator it = j.begin();
+ CHECK((it.m_it.primitive_iterator.m_it == 1));
+ it++;
+ CHECK((it.m_it.primitive_iterator.m_it != 0 && it.m_it.primitive_iterator.m_it != 1));
+ }
+
+ SECTION("number")
+ {
+ json j(17);
+ json::iterator it = j.begin();
+ CHECK((it.m_it.primitive_iterator.m_it == 0));
+ it++;
+ CHECK((it.m_it.primitive_iterator.m_it == 1));
+ it++;
+ CHECK((it.m_it.primitive_iterator.m_it != 0 && it.m_it.primitive_iterator.m_it != 1));
+ }
+
+ SECTION("object")
+ {
+ json j({{"foo", "bar"}});
+ json::iterator it = j.begin();
+ CHECK((it.m_it.object_iterator == it.m_object->m_data.m_value.object->begin()));
+ it++;
+ CHECK((it.m_it.object_iterator == it.m_object->m_data.m_value.object->end()));
+ }
+
+ SECTION("array")
+ {
+ json j({1, 2, 3, 4});
+ json::iterator it = j.begin();
+ CHECK((it.m_it.array_iterator == it.m_object->m_data.m_value.array->begin()));
+ it++;
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->begin()));
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->end()));
+ it++;
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->begin()));
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->end()));
+ it++;
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->begin()));
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->end()));
+ it++;
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->begin()));
+ CHECK((it.m_it.array_iterator == it.m_object->m_data.m_value.array->end()));
+ }
+ }
+
+ SECTION("pre-increment")
+ {
+ SECTION("null")
+ {
+ json j(json::value_t::null);
+ json::iterator it = j.begin();
+ CHECK((it.m_it.primitive_iterator.m_it == 1));
+ ++it;
+ CHECK((it.m_it.primitive_iterator.m_it != 0 && it.m_it.primitive_iterator.m_it != 1));
+ }
+
+ SECTION("number")
+ {
+ json j(17);
+ json::iterator it = j.begin();
+ CHECK((it.m_it.primitive_iterator.m_it == 0));
+ ++it;
+ CHECK((it.m_it.primitive_iterator.m_it == 1));
+ ++it;
+ CHECK((it.m_it.primitive_iterator.m_it != 0 && it.m_it.primitive_iterator.m_it != 1));
+ }
+
+ SECTION("object")
+ {
+ json j({{"foo", "bar"}});
+ json::iterator it = j.begin();
+ CHECK((it.m_it.object_iterator == it.m_object->m_data.m_value.object->begin()));
+ ++it;
+ CHECK((it.m_it.object_iterator == it.m_object->m_data.m_value.object->end()));
+ }
+
+ SECTION("array")
+ {
+ json j({1, 2, 3, 4});
+ json::iterator it = j.begin();
+ CHECK((it.m_it.array_iterator == it.m_object->m_data.m_value.array->begin()));
+ ++it;
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->begin()));
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->end()));
+ ++it;
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->begin()));
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->end()));
+ ++it;
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->begin()));
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->end()));
+ ++it;
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->begin()));
+ CHECK((it.m_it.array_iterator == it.m_object->m_data.m_value.array->end()));
+ }
+ }
+
+ SECTION("post-decrement")
+ {
+ SECTION("null")
+ {
+ json j(json::value_t::null);
+ json::iterator const it = j.end();
+ CHECK((it.m_it.primitive_iterator.m_it == 1));
+ }
+
+ SECTION("number")
+ {
+ json j(17);
+ json::iterator it = j.end();
+ CHECK((it.m_it.primitive_iterator.m_it == 1));
+ it--;
+ CHECK((it.m_it.primitive_iterator.m_it == 0));
+ it--;
+ CHECK((it.m_it.primitive_iterator.m_it != 0 && it.m_it.primitive_iterator.m_it != 1));
+ }
+
+ SECTION("object")
+ {
+ json j({{"foo", "bar"}});
+ json::iterator it = j.end();
+ CHECK((it.m_it.object_iterator == it.m_object->m_data.m_value.object->end()));
+ it--;
+ CHECK((it.m_it.object_iterator == it.m_object->m_data.m_value.object->begin()));
+ }
+
+ SECTION("array")
+ {
+ json j({1, 2, 3, 4});
+ json::iterator it = j.end();
+ CHECK((it.m_it.array_iterator == it.m_object->m_data.m_value.array->end()));
+ it--;
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->begin()));
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->end()));
+ it--;
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->begin()));
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->end()));
+ it--;
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->begin()));
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->end()));
+ it--;
+ CHECK((it.m_it.array_iterator == it.m_object->m_data.m_value.array->begin()));
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->end()));
+ }
+ }
+
+ SECTION("pre-decrement")
+ {
+ SECTION("null")
+ {
+ json j(json::value_t::null);
+ json::iterator const it = j.end();
+ CHECK((it.m_it.primitive_iterator.m_it == 1));
+ }
+
+ SECTION("number")
+ {
+ json j(17);
+ json::iterator it = j.end();
+ CHECK((it.m_it.primitive_iterator.m_it == 1));
+ --it;
+ CHECK((it.m_it.primitive_iterator.m_it == 0));
+ --it;
+ CHECK((it.m_it.primitive_iterator.m_it != 0 && it.m_it.primitive_iterator.m_it != 1));
+ }
+
+ SECTION("object")
+ {
+ json j({{"foo", "bar"}});
+ json::iterator it = j.end();
+ CHECK((it.m_it.object_iterator == it.m_object->m_data.m_value.object->end()));
+ --it;
+ CHECK((it.m_it.object_iterator == it.m_object->m_data.m_value.object->begin()));
+ }
+
+ SECTION("array")
+ {
+ json j({1, 2, 3, 4});
+ json::iterator it = j.end();
+ CHECK((it.m_it.array_iterator == it.m_object->m_data.m_value.array->end()));
+ --it;
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->begin()));
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->end()));
+ --it;
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->begin()));
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->end()));
+ --it;
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->begin()));
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->end()));
+ --it;
+ CHECK((it.m_it.array_iterator == it.m_object->m_data.m_value.array->begin()));
+ CHECK((it.m_it.array_iterator != it.m_object->m_data.m_value.array->end()));
+ }
+ }
+ }
+ SECTION("equality-preserving")
+ {
+ SECTION("post-increment")
+ {
+ SECTION("primitive_iterator_t")
+ {
+ using Iter = nlohmann::detail::primitive_iterator_t;
+ CHECK(std::is_same < decltype(std::declval<Iter&>()++), Iter >::value);
+ }
+ SECTION("iter_impl")
+ {
+ using Iter = nlohmann::detail::iter_impl<json>;
+ CHECK(std::is_same < decltype(std::declval<Iter&>()++), Iter >::value);
+ }
+ SECTION("json_reverse_iterator")
+ {
+ using Base = nlohmann::detail::iter_impl<json>;
+ using Iter = nlohmann::detail::json_reverse_iterator<Base>;
+ CHECK(std::is_same < decltype(std::declval<Iter&>()++), Iter >::value);
+ }
+ }
+ SECTION("post-decrement")
+ {
+ SECTION("primitive_iterator_t")
+ {
+ using Iter = nlohmann::detail::primitive_iterator_t;
+ CHECK(std::is_same < decltype(std::declval<Iter&>()--), Iter >::value);
+ }
+ SECTION("iter_impl")
+ {
+ using Iter = nlohmann::detail::iter_impl<json>;
+ CHECK(std::is_same < decltype(std::declval<Iter&>()--), Iter >::value );
+ }
+ SECTION("json_reverse_iterator")
+ {
+ using Base = nlohmann::detail::iter_impl<json>;
+ using Iter = nlohmann::detail::json_reverse_iterator<Base>;
+ CHECK(std::is_same < decltype(std::declval<Iter&>()--), Iter >::value );
+ }
+ }
+ }
+ // prevent "accidental mutation of a temporary object"
+ SECTION("cert-dcl21-cpp")
+ {
+ using nlohmann::detail::is_detected;
+ SECTION("post-increment")
+ {
+ SECTION("primitive_iterator_t")
+ {
+ using Iter = nlohmann::detail::primitive_iterator_t;
+ CHECK_FALSE(is_detected<can_post_increment_temporary, Iter&>::value);
+ }
+ SECTION("iter_impl")
+ {
+ using Iter = nlohmann::detail::iter_impl<json>;
+ CHECK_FALSE(is_detected<can_post_increment_temporary, Iter&>::value);
+ }
+ SECTION("json_reverse_iterator")
+ {
+ using Base = nlohmann::detail::iter_impl<json>;
+ using Iter = nlohmann::detail::json_reverse_iterator<Base>;
+ CHECK_FALSE(is_detected<can_post_increment_temporary, Iter&>::value);
+ }
+ }
+ SECTION("post-decrement")
+ {
+ SECTION("primitive_iterator_t")
+ {
+ using Iter = nlohmann::detail::primitive_iterator_t;
+ CHECK_FALSE(is_detected<can_post_decrement_temporary, Iter&>::value);
+ }
+ SECTION("iter_impl")
+ {
+ using Iter = nlohmann::detail::iter_impl<json>;
+ CHECK_FALSE(is_detected<can_post_decrement_temporary, Iter&>::value);
+ }
+ SECTION("json_reverse_iterator")
+ {
+ using Base = nlohmann::detail::iter_impl<json>;
+ using Iter = nlohmann::detail::json_reverse_iterator<Base>;
+ CHECK_FALSE(is_detected<can_post_decrement_temporary, Iter&>::value);
+ }
+
+ }
+ }
+}
diff --git a/json4cpp/tests/src/unit-class_lexer.cpp b/json4cpp/tests/src/unit-class_lexer.cpp
new file mode 100644
index 0000000000..64baf3da6d
--- /dev/null
+++ b/json4cpp/tests/src/unit-class_lexer.cpp
@@ -0,0 +1,226 @@
+// __ _____ _____ _____
+// __| | __| | | | 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;
+
+namespace
+{
+// shortcut to scan a string literal
+json::lexer::token_type scan_string(const char* s, bool ignore_comments = false);
+json::lexer::token_type scan_string(const char* s, const bool ignore_comments)
+{
+ auto ia = nlohmann::detail::input_adapter(s);
+ return nlohmann::detail::lexer<json, decltype(ia)>(std::move(ia), ignore_comments).scan(); // NOLINT(hicpp-move-const-arg,performance-move-const-arg)
+}
+} // namespace
+
+std::string get_error_message(const char* s, bool ignore_comments = false); // NOLINT(misc-use-internal-linkage)
+std::string get_error_message(const char* s, const bool ignore_comments)
+{
+ auto ia = nlohmann::detail::input_adapter(s);
+ auto lexer = nlohmann::detail::lexer<json, decltype(ia)>(std::move(ia), ignore_comments); // NOLINT(hicpp-move-const-arg,performance-move-const-arg)
+ lexer.scan();
+ return lexer.get_error_message();
+}
+
+TEST_CASE("lexer class")
+{
+ SECTION("scan")
+ {
+ SECTION("structural characters")
+ {
+ CHECK((scan_string("[") == json::lexer::token_type::begin_array));
+ CHECK((scan_string("]") == json::lexer::token_type::end_array));
+ CHECK((scan_string("{") == json::lexer::token_type::begin_object));
+ CHECK((scan_string("}") == json::lexer::token_type::end_object));
+ CHECK((scan_string(",") == json::lexer::token_type::value_separator));
+ CHECK((scan_string(":") == json::lexer::token_type::name_separator));
+ }
+
+ SECTION("literal names")
+ {
+ CHECK((scan_string("null") == json::lexer::token_type::literal_null));
+ CHECK((scan_string("true") == json::lexer::token_type::literal_true));
+ CHECK((scan_string("false") == json::lexer::token_type::literal_false));
+ }
+
+ SECTION("numbers")
+ {
+ CHECK((scan_string("0") == json::lexer::token_type::value_unsigned));
+ CHECK((scan_string("1") == json::lexer::token_type::value_unsigned));
+ CHECK((scan_string("2") == json::lexer::token_type::value_unsigned));
+ CHECK((scan_string("3") == json::lexer::token_type::value_unsigned));
+ CHECK((scan_string("4") == json::lexer::token_type::value_unsigned));
+ CHECK((scan_string("5") == json::lexer::token_type::value_unsigned));
+ CHECK((scan_string("6") == json::lexer::token_type::value_unsigned));
+ CHECK((scan_string("7") == json::lexer::token_type::value_unsigned));
+ CHECK((scan_string("8") == json::lexer::token_type::value_unsigned));
+ CHECK((scan_string("9") == json::lexer::token_type::value_unsigned));
+
+ CHECK((scan_string("-0") == json::lexer::token_type::value_integer));
+ CHECK((scan_string("-1") == json::lexer::token_type::value_integer));
+
+ CHECK((scan_string("1.1") == json::lexer::token_type::value_float));
+ CHECK((scan_string("-1.1") == json::lexer::token_type::value_float));
+ CHECK((scan_string("1E10") == json::lexer::token_type::value_float));
+ }
+
+ SECTION("whitespace")
+ {
+ // result is end_of_input, because not token is following
+ CHECK((scan_string(" ") == json::lexer::token_type::end_of_input));
+ CHECK((scan_string("\t") == json::lexer::token_type::end_of_input));
+ CHECK((scan_string("\n") == json::lexer::token_type::end_of_input));
+ CHECK((scan_string("\r") == json::lexer::token_type::end_of_input));
+ CHECK((scan_string(" \t\n\r\n\t ") == json::lexer::token_type::end_of_input));
+ }
+ }
+
+ SECTION("token_type_name")
+ {
+ CHECK((std::string(json::lexer::token_type_name(json::lexer::token_type::uninitialized)) == "<uninitialized>"));
+ CHECK((std::string(json::lexer::token_type_name(json::lexer::token_type::literal_true)) == "true literal"));
+ CHECK((std::string(json::lexer::token_type_name(json::lexer::token_type::literal_false)) == "false literal"));
+ CHECK((std::string(json::lexer::token_type_name(json::lexer::token_type::literal_null)) == "null literal"));
+ CHECK((std::string(json::lexer::token_type_name(json::lexer::token_type::value_string)) == "string literal"));
+ CHECK((std::string(json::lexer::token_type_name(json::lexer::token_type::value_unsigned)) == "number literal"));
+ CHECK((std::string(json::lexer::token_type_name(json::lexer::token_type::value_integer)) == "number literal"));
+ CHECK((std::string(json::lexer::token_type_name(json::lexer::token_type::value_float)) == "number literal"));
+ CHECK((std::string(json::lexer::token_type_name(json::lexer::token_type::begin_array)) == "'['"));
+ CHECK((std::string(json::lexer::token_type_name(json::lexer::token_type::begin_object)) == "'{'"));
+ CHECK((std::string(json::lexer::token_type_name(json::lexer::token_type::end_array)) == "']'"));
+ CHECK((std::string(json::lexer::token_type_name(json::lexer::token_type::end_object)) == "'}'"));
+ CHECK((std::string(json::lexer::token_type_name(json::lexer::token_type::name_separator)) == "':'"));
+ CHECK((std::string(json::lexer::token_type_name(json::lexer::token_type::value_separator)) == "','"));
+ CHECK((std::string(json::lexer::token_type_name(json::lexer::token_type::parse_error)) == "<parse error>"));
+ CHECK((std::string(json::lexer::token_type_name(json::lexer::token_type::end_of_input)) == "end of input"));
+ }
+
+ SECTION("parse errors on first character")
+ {
+ for (int c = 1; c < 128; ++c)
+ {
+ // create string from the ASCII code
+ const auto s = std::string(1, static_cast<char>(c));
+ // store scan() result
+ const auto res = scan_string(s.c_str());
+
+ CAPTURE(s)
+
+ switch (c)
+ {
+ // single characters that are valid tokens
+ case ('['):
+ case (']'):
+ case ('{'):
+ case ('}'):
+ case (','):
+ case (':'):
+ case ('0'):
+ case ('1'):
+ case ('2'):
+ case ('3'):
+ case ('4'):
+ case ('5'):
+ case ('6'):
+ case ('7'):
+ case ('8'):
+ case ('9'):
+ {
+ CHECK((res != json::lexer::token_type::parse_error));
+ break;
+ }
+
+ // whitespace
+ case (' '):
+ case ('\t'):
+ case ('\n'):
+ case ('\r'):
+ {
+ CHECK((res == json::lexer::token_type::end_of_input));
+ break;
+ }
+
+ // anything else is not expected
+ default:
+ {
+ CHECK((res == json::lexer::token_type::parse_error));
+ break;
+ }
+ }
+ }
+ }
+
+ SECTION("very large string")
+ {
+ // strings larger than 1024 bytes yield a resize of the lexer's yytext buffer
+ std::string s("\"");
+ s += std::string(2048, 'x');
+ s += "\"";
+ CHECK((scan_string(s.c_str()) == json::lexer::token_type::value_string));
+ }
+
+ SECTION("fail on comments")
+ {
+ CHECK((scan_string("/", false) == json::lexer::token_type::parse_error));
+ CHECK(get_error_message("/", false) == "invalid literal");
+
+ CHECK((scan_string("/!", false) == json::lexer::token_type::parse_error));
+ CHECK(get_error_message("/!", false) == "invalid literal");
+ CHECK((scan_string("/*", false) == json::lexer::token_type::parse_error));
+ CHECK(get_error_message("/*", false) == "invalid literal");
+ CHECK((scan_string("/**", false) == json::lexer::token_type::parse_error));
+ CHECK(get_error_message("/**", false) == "invalid literal");
+
+ CHECK((scan_string("//", false) == json::lexer::token_type::parse_error));
+ CHECK(get_error_message("//", false) == "invalid literal");
+ CHECK((scan_string("/**/", false) == json::lexer::token_type::parse_error));
+ CHECK(get_error_message("/**/", false) == "invalid literal");
+ CHECK((scan_string("/** /", false) == json::lexer::token_type::parse_error));
+ CHECK(get_error_message("/** /", false) == "invalid literal");
+
+ CHECK((scan_string("/***/", false) == json::lexer::token_type::parse_error));
+ CHECK(get_error_message("/***/", false) == "invalid literal");
+ CHECK((scan_string("/* true */", false) == json::lexer::token_type::parse_error));
+ CHECK(get_error_message("/* true */", false) == "invalid literal");
+ CHECK((scan_string("/*/**/", false) == json::lexer::token_type::parse_error));
+ CHECK(get_error_message("/*/**/", false) == "invalid literal");
+ CHECK((scan_string("/*/* */", false) == json::lexer::token_type::parse_error));
+ CHECK(get_error_message("/*/* */", false) == "invalid literal");
+ }
+
+ SECTION("ignore comments")
+ {
+ CHECK((scan_string("/", true) == json::lexer::token_type::parse_error));
+ CHECK(get_error_message("/", true) == "invalid comment; expecting '/' or '*' after '/'");
+
+ CHECK((scan_string("/!", true) == json::lexer::token_type::parse_error));
+ CHECK(get_error_message("/!", true) == "invalid comment; expecting '/' or '*' after '/'");
+ CHECK((scan_string("/*", true) == json::lexer::token_type::parse_error));
+ CHECK(get_error_message("/*", true) == "invalid comment; missing closing '*/'");
+ CHECK((scan_string("/**", true) == json::lexer::token_type::parse_error));
+ CHECK(get_error_message("/**", true) == "invalid comment; missing closing '*/'");
+
+ CHECK((scan_string("//", true) == json::lexer::token_type::end_of_input));
+ CHECK((scan_string("/**/", true) == json::lexer::token_type::end_of_input));
+ CHECK((scan_string("/** /", true) == json::lexer::token_type::parse_error));
+ CHECK(get_error_message("/** /", true) == "invalid comment; missing closing '*/'");
+
+ CHECK((scan_string("/***/", true) == json::lexer::token_type::end_of_input));
+ CHECK((scan_string("/* true */", true) == json::lexer::token_type::end_of_input));
+ CHECK((scan_string("/*/**/", true) == json::lexer::token_type::end_of_input));
+ CHECK((scan_string("/*/* */", true) == json::lexer::token_type::end_of_input));
+
+ CHECK((scan_string("//\n//\n", true) == json::lexer::token_type::end_of_input));
+ CHECK((scan_string("/**//**//**/", true) == json::lexer::token_type::end_of_input));
+ }
+}
diff --git a/json4cpp/tests/src/unit-class_parser.cpp b/json4cpp/tests/src/unit-class_parser.cpp
new file mode 100644
index 0000000000..9c5fadc295
--- /dev/null
+++ b/json4cpp/tests/src/unit-class_parser.cpp
@@ -0,0 +1,1754 @@
+// __ _____ _____ _____
+// __| | __| | | | 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 <valarray>
+
+namespace
+{
+class SaxEventLogger
+{
+ public:
+ bool null()
+ {
+ events.emplace_back("null()");
+ return true;
+ }
+
+ bool boolean(bool val)
+ {
+ events.emplace_back(val ? "boolean(true)" : "boolean(false)");
+ return true;
+ }
+
+ bool number_integer(json::number_integer_t val)
+ {
+ events.push_back("number_integer(" + std::to_string(val) + ")");
+ return true;
+ }
+
+ bool number_unsigned(json::number_unsigned_t val)
+ {
+ events.push_back("number_unsigned(" + std::to_string(val) + ")");
+ return true;
+ }
+
+ bool number_float(json::number_float_t /*unused*/, const std::string& s)
+ {
+ events.push_back("number_float(" + s + ")");
+ return true;
+ }
+
+ bool string(std::string& val)
+ {
+ events.push_back("string(" + val + ")");
+ return true;
+ }
+
+ bool binary(json::binary_t& val)
+ {
+ std::string binary_contents = "binary(";
+ std::string comma_space;
+ for (auto b : val)
+ {
+ binary_contents.append(comma_space);
+ binary_contents.append(std::to_string(static_cast<int>(b)));
+ comma_space = ", ";
+ }
+ binary_contents.append(")");
+ events.push_back(binary_contents);
+ return true;
+ }
+
+ bool start_object(std::size_t elements)
+ {
+ if (elements == (std::numeric_limits<std::size_t>::max)())
+ {
+ events.emplace_back("start_object()");
+ }
+ else
+ {
+ events.push_back("start_object(" + std::to_string(elements) + ")");
+ }
+ return true;
+ }
+
+ bool key(std::string& val)
+ {
+ events.push_back("key(" + val + ")");
+ return true;
+ }
+
+ bool end_object()
+ {
+ events.emplace_back("end_object()");
+ return true;
+ }
+
+ bool start_array(std::size_t elements)
+ {
+ if (elements == (std::numeric_limits<std::size_t>::max)())
+ {
+ events.emplace_back("start_array()");
+ }
+ else
+ {
+ events.push_back("start_array(" + std::to_string(elements) + ")");
+ }
+ return true;
+ }
+
+ bool end_array()
+ {
+ events.emplace_back("end_array()");
+ return true;
+ }
+
+ bool parse_error(std::size_t position, const std::string& /*unused*/, const json::exception& /*unused*/)
+ {
+ errored = true;
+ events.push_back("parse_error(" + std::to_string(position) + ")");
+ return false;
+ }
+
+ std::vector<std::string> events {}; // NOLINT(readability-redundant-member-init)
+ bool errored = false;
+};
+
+class SaxCountdown : public nlohmann::json::json_sax_t
+{
+ public:
+ explicit SaxCountdown(const int count) : events_left(count)
+ {}
+
+ bool null() override
+ {
+ return events_left-- > 0;
+ }
+
+ bool boolean(bool /*val*/) override
+ {
+ return events_left-- > 0;
+ }
+
+ bool number_integer(json::number_integer_t /*val*/) override
+ {
+ return events_left-- > 0;
+ }
+
+ bool number_unsigned(json::number_unsigned_t /*val*/) override
+ {
+ return events_left-- > 0;
+ }
+
+ bool number_float(json::number_float_t /*val*/, const std::string& /*s*/) override
+ {
+ return events_left-- > 0;
+ }
+
+ bool string(std::string& /*val*/) override
+ {
+ return events_left-- > 0;
+ }
+
+ bool binary(json::binary_t& /*val*/) override
+ {
+ return events_left-- > 0;
+ }
+
+ bool start_object(std::size_t /*elements*/) override
+ {
+ return events_left-- > 0;
+ }
+
+ bool key(std::string& /*val*/) override
+ {
+ return events_left-- > 0;
+ }
+
+ bool end_object() override
+ {
+ return events_left-- > 0;
+ }
+
+ bool start_array(std::size_t /*elements*/) override
+ {
+ return events_left-- > 0;
+ }
+
+ bool end_array() override
+ {
+ return events_left-- > 0;
+ }
+
+ bool parse_error(std::size_t /*position*/, const std::string& /*last_token*/, const json::exception& /*ex*/) override
+ {
+ return false;
+ }
+
+ private:
+ int events_left = 0;
+};
+
+json parser_helper(const std::string& s);
+bool accept_helper(const std::string& s);
+void comments_helper(const std::string& s);
+void trailing_comma_helper(const std::string& s);
+
+json parser_helper(const std::string& s)
+{
+ json j;
+ json::parser(nlohmann::detail::input_adapter(s)).parse(true, j);
+
+ // if this line was reached, no exception occurred
+ // -> check if result is the same without exceptions
+ json j_nothrow;
+ CHECK_NOTHROW(json::parser(nlohmann::detail::input_adapter(s), nullptr, false).parse(true, j_nothrow));
+ CHECK(j_nothrow == j);
+
+ json j_sax;
+ nlohmann::detail::json_sax_dom_parser<json, nlohmann::detail::string_input_adapter_type> sdp(j_sax);
+ json::sax_parse(s, &sdp);
+ CHECK(j_sax == j);
+
+ comments_helper(s);
+
+ trailing_comma_helper(s);
+
+ return j;
+}
+
+bool accept_helper(const std::string& s)
+{
+ CAPTURE(s)
+
+ // 1. parse s without exceptions
+ json j;
+ CHECK_NOTHROW(json::parser(nlohmann::detail::input_adapter(s), nullptr, false).parse(true, j));
+ const bool ok_noexcept = !j.is_discarded();
+
+ // 2. accept s
+ const bool ok_accept = json::parser(nlohmann::detail::input_adapter(s)).accept(true);
+
+ // 3. check if both approaches come to the same result
+ CHECK(ok_noexcept == ok_accept);
+
+ // 4. parse with SAX (compare with relaxed accept result)
+ SaxEventLogger el;
+ CHECK_NOTHROW(json::sax_parse(s, &el, json::input_format_t::json, false));
+ CHECK(json::parser(nlohmann::detail::input_adapter(s)).accept(false) == !el.errored);
+
+ // 5. parse with simple callback
+ json::parser_callback_t const cb = [](int /*unused*/, json::parse_event_t /*unused*/, json& /*unused*/) noexcept
+ {
+ return true;
+ };
+ json const j_cb = json::parse(s, cb, false);
+ const bool ok_noexcept_cb = !j_cb.is_discarded();
+
+ // 6. check if this approach came to the same result
+ CHECK(ok_noexcept == ok_noexcept_cb);
+
+ // 7. check if comments or trailing commas are properly ignored
+ if (ok_accept)
+ {
+ comments_helper(s);
+ trailing_comma_helper(s);
+ }
+
+ // 8. return result
+ return ok_accept;
+}
+
+void comments_helper(const std::string& s)
+{
+ json _;
+
+ // parse/accept with default parser
+ CHECK_NOTHROW(_ = json::parse(s));
+ CHECK(json::accept(s));
+
+ // parse/accept while skipping comments
+ CHECK_NOTHROW(_ = json::parse(s, nullptr, false, true));
+ CHECK(json::accept(s, true));
+
+ std::vector<std::string> json_with_comments;
+
+ // start with a comment
+ json_with_comments.push_back(std::string("// this is a comment\n") + s);
+ json_with_comments.push_back(std::string("/* this is a comment */") + s);
+ // end with a comment
+ json_with_comments.push_back(s + "// this is a comment");
+ json_with_comments.push_back(s + "/* this is a comment */");
+
+ // check all strings
+ for (const auto& json_with_comment : json_with_comments)
+ {
+ CAPTURE(json_with_comment)
+ CHECK_THROWS_AS(_ = json::parse(json_with_comment), json::parse_error);
+ CHECK(!json::accept(json_with_comment));
+
+ CHECK_NOTHROW(_ = json::parse(json_with_comment, nullptr, true, true));
+ CHECK(json::accept(json_with_comment, true));
+ }
+}
+
+void trailing_comma_helper(const std::string& s)
+{
+ json _;
+
+ // parse/accept with default parser
+ CHECK_NOTHROW(_ = json::parse(s));
+ CHECK(json::accept(s));
+
+ // parse/accept while allowing trailing commas
+ CHECK_NOTHROW(_ = json::parse(s, nullptr, false, false, true));
+ CHECK(json::accept(s, false, true));
+
+ // note: [,] and {,} are not allowed
+ if (s.size() > 1 && (s.back() == ']' || s.back() == '}') && !_.empty())
+ {
+ std::vector<std::string> json_with_trailing_commas;
+ json_with_trailing_commas.push_back(s.substr(0, s.size() - 1) + " ," + s.back());
+ json_with_trailing_commas.push_back(s.substr(0, s.size() - 1) + "," + s.back());
+ json_with_trailing_commas.push_back(s.substr(0, s.size() - 1) + ", " + s.back());
+
+ for (const auto& json_with_trailing_comma : json_with_trailing_commas)
+ {
+ CAPTURE(json_with_trailing_comma)
+ CHECK_THROWS_AS(_ = json::parse(json_with_trailing_comma), json::parse_error);
+ CHECK(!json::accept(json_with_trailing_comma));
+
+ CHECK_NOTHROW(_ = json::parse(json_with_trailing_comma, nullptr, true, false, true));
+ CHECK(json::accept(json_with_trailing_comma, false, true));
+ }
+ }
+}
+
+} // namespace
+
+TEST_CASE("parser class")
+{
+ SECTION("parse")
+ {
+ SECTION("null")
+ {
+ CHECK(parser_helper("null") == json(nullptr));
+ }
+
+ SECTION("true")
+ {
+ CHECK(parser_helper("true") == json(true));
+ }
+
+ SECTION("false")
+ {
+ CHECK(parser_helper("false") == json(false));
+ }
+
+ SECTION("array")
+ {
+ SECTION("empty array")
+ {
+ CHECK(parser_helper("[]") == json(json::value_t::array));
+ CHECK(parser_helper("[ ]") == json(json::value_t::array));
+ }
+
+ SECTION("nonempty array")
+ {
+ CHECK(parser_helper("[true, false, null]") == json({true, false, nullptr}));
+ }
+ }
+
+ SECTION("object")
+ {
+ SECTION("empty object")
+ {
+ CHECK(parser_helper("{}") == json(json::value_t::object));
+ CHECK(parser_helper("{ }") == json(json::value_t::object));
+ }
+
+ SECTION("nonempty object")
+ {
+ CHECK(parser_helper("{\"\": true, \"one\": 1, \"two\": null}") == json({{"", true}, {"one", 1}, {"two", nullptr}}));
+ }
+ }
+
+ SECTION("string")
+ {
+ // empty string
+ CHECK(parser_helper("\"\"") == json(json::value_t::string));
+
+ SECTION("errors")
+ {
+ // error: tab in string
+ CHECK_THROWS_WITH_AS(parser_helper("\"\t\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t; last read: '\"<U+0009>'", json::parse_error&);
+ // error: newline in string
+ CHECK_THROWS_WITH_AS(parser_helper("\"\n\""), "[json.exception.parse_error.101] parse error at line 2, column 0: syntax error while parsing value - invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n; last read: '\"<U+000A>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\r\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r; last read: '\"<U+000D>'", json::parse_error&);
+ // error: backspace in string
+ CHECK_THROWS_WITH_AS(parser_helper("\"\b\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b; last read: '\"<U+0008>'", json::parse_error&);
+ // improve code coverage
+ CHECK_THROWS_AS(parser_helper("\uFF01"), json::parse_error&);
+ CHECK_THROWS_AS(parser_helper("[-4:1,]"), json::parse_error&);
+ // unescaped control characters
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x00\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: missing closing quote; last read: '\"'", json::parse_error&); // NOLINT(bugprone-string-literal-with-embedded-nul)
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x01\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0001 (SOH) must be escaped to \\u0001; last read: '\"<U+0001>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x02\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0002 (STX) must be escaped to \\u0002; last read: '\"<U+0002>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x03\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0003 (ETX) must be escaped to \\u0003; last read: '\"<U+0003>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x04\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0004 (EOT) must be escaped to \\u0004; last read: '\"<U+0004>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x05\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0005 (ENQ) must be escaped to \\u0005; last read: '\"<U+0005>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x06\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0006 (ACK) must be escaped to \\u0006; last read: '\"<U+0006>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x07\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0007 (BEL) must be escaped to \\u0007; last read: '\"<U+0007>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x08\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b; last read: '\"<U+0008>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x09\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t; last read: '\"<U+0009>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x0a\""), "[json.exception.parse_error.101] parse error at line 2, column 0: syntax error while parsing value - invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n; last read: '\"<U+000A>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x0b\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+000B (VT) must be escaped to \\u000B; last read: '\"<U+000B>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x0c\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+000C (FF) must be escaped to \\u000C or \\f; last read: '\"<U+000C>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x0d\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r; last read: '\"<U+000D>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x0e\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+000E (SO) must be escaped to \\u000E; last read: '\"<U+000E>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x0f\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+000F (SI) must be escaped to \\u000F; last read: '\"<U+000F>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x10\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0010 (DLE) must be escaped to \\u0010; last read: '\"<U+0010>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x11\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0011 (DC1) must be escaped to \\u0011; last read: '\"<U+0011>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x12\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0012 (DC2) must be escaped to \\u0012; last read: '\"<U+0012>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x13\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0013 (DC3) must be escaped to \\u0013; last read: '\"<U+0013>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x14\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0014 (DC4) must be escaped to \\u0014; last read: '\"<U+0014>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x15\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0015 (NAK) must be escaped to \\u0015; last read: '\"<U+0015>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x16\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0016 (SYN) must be escaped to \\u0016; last read: '\"<U+0016>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x17\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0017 (ETB) must be escaped to \\u0017; last read: '\"<U+0017>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x18\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0018 (CAN) must be escaped to \\u0018; last read: '\"<U+0018>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x19\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0019 (EM) must be escaped to \\u0019; last read: '\"<U+0019>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x1a\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+001A (SUB) must be escaped to \\u001A; last read: '\"<U+001A>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x1b\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+001B (ESC) must be escaped to \\u001B; last read: '\"<U+001B>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x1c\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+001C (FS) must be escaped to \\u001C; last read: '\"<U+001C>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x1d\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+001D (GS) must be escaped to \\u001D; last read: '\"<U+001D>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x1e\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+001E (RS) must be escaped to \\u001E; last read: '\"<U+001E>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x1f\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+001F (US) must be escaped to \\u001F; last read: '\"<U+001F>'", json::parse_error&);
+
+ SECTION("additional test for null byte")
+ {
+ // The test above for the null byte is wrong, because passing
+ // a string to the parser only reads int until it encounters
+ // a null byte. This test inserts the null byte later on and
+ // uses an iterator range.
+ std::string s = "\"1\"";
+ s[1] = '\0';
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::parse(s.begin(), s.end()), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0000 (NUL) must be escaped to \\u0000; last read: '\"<U+0000>'", json::parse_error&);
+ }
+ }
+
+ SECTION("escaped")
+ {
+ // quotation mark "\""
+ auto r1 = R"("\"")"_json;
+ CHECK(parser_helper("\"\\\"\"") == r1);
+ // reverse solidus "\\"
+ auto r2 = R"("\\")"_json;
+ CHECK(parser_helper("\"\\\\\"") == r2);
+ // solidus
+ CHECK(parser_helper("\"\\/\"") == R"("/")"_json);
+ // backspace
+ CHECK(parser_helper("\"\\b\"") == json("\b"));
+ // formfeed
+ CHECK(parser_helper("\"\\f\"") == json("\f"));
+ // newline
+ CHECK(parser_helper("\"\\n\"") == json("\n"));
+ // carriage return
+ CHECK(parser_helper("\"\\r\"") == json("\r"));
+ // horizontal tab
+ CHECK(parser_helper("\"\\t\"") == json("\t"));
+
+ CHECK(parser_helper("\"\\u0001\"").get<json::string_t>() == "\x01");
+ CHECK(parser_helper("\"\\u000a\"").get<json::string_t>() == "\n");
+ CHECK(parser_helper("\"\\u00b0\"").get<json::string_t>() == "°");
+ CHECK(parser_helper("\"\\u0c00\"").get<json::string_t>() == "ఀ");
+ CHECK(parser_helper("\"\\ud000\"").get<json::string_t>() == "퀀");
+ CHECK(parser_helper("\"\\u000E\"").get<json::string_t>() == "\x0E");
+ CHECK(parser_helper("\"\\u00F0\"").get<json::string_t>() == "ð");
+ CHECK(parser_helper("\"\\u0100\"").get<json::string_t>() == "Ā");
+ CHECK(parser_helper("\"\\u2000\"").get<json::string_t>() == " ");
+ CHECK(parser_helper("\"\\uFFFF\"").get<json::string_t>() == "￿");
+ CHECK(parser_helper("\"\\u20AC\"").get<json::string_t>() == "€");
+ CHECK(parser_helper("\"€\"").get<json::string_t>() == "€");
+ CHECK(parser_helper("\"🎈\"").get<json::string_t>() == "🎈");
+
+ CHECK(parser_helper("\"\\ud80c\\udc60\"").get<json::string_t>() == "\xf0\x93\x81\xa0");
+ CHECK(parser_helper("\"\\ud83c\\udf1e\"").get<json::string_t>() == "🌞");
+ }
+ }
+
+ SECTION("number")
+ {
+ SECTION("integers")
+ {
+ SECTION("without exponent")
+ {
+ CHECK(parser_helper("-128") == json(-128));
+ CHECK(parser_helper("-0") == json(-0));
+ CHECK(parser_helper("0") == json(0));
+ CHECK(parser_helper("128") == json(128));
+ }
+
+ SECTION("with exponent")
+ {
+ CHECK(parser_helper("0e1") == json(0e1));
+ CHECK(parser_helper("0E1") == json(0e1));
+
+ CHECK(parser_helper("10000E-4") == json(10000e-4));
+ CHECK(parser_helper("10000E-3") == json(10000e-3));
+ CHECK(parser_helper("10000E-2") == json(10000e-2));
+ CHECK(parser_helper("10000E-1") == json(10000e-1));
+ CHECK(parser_helper("10000E0") == json(10000e0));
+ CHECK(parser_helper("10000E1") == json(10000e1));
+ CHECK(parser_helper("10000E2") == json(10000e2));
+ CHECK(parser_helper("10000E3") == json(10000e3));
+ CHECK(parser_helper("10000E4") == json(10000e4));
+
+ CHECK(parser_helper("10000e-4") == json(10000e-4));
+ CHECK(parser_helper("10000e-3") == json(10000e-3));
+ CHECK(parser_helper("10000e-2") == json(10000e-2));
+ CHECK(parser_helper("10000e-1") == json(10000e-1));
+ CHECK(parser_helper("10000e0") == json(10000e0));
+ CHECK(parser_helper("10000e1") == json(10000e1));
+ CHECK(parser_helper("10000e2") == json(10000e2));
+ CHECK(parser_helper("10000e3") == json(10000e3));
+ CHECK(parser_helper("10000e4") == json(10000e4));
+
+ CHECK(parser_helper("-0e1") == json(-0e1));
+ CHECK(parser_helper("-0E1") == json(-0e1));
+ CHECK(parser_helper("-0E123") == json(-0e123));
+
+ // numbers after exponent
+ CHECK(parser_helper("10E0") == json(10e0));
+ CHECK(parser_helper("10E1") == json(10e1));
+ CHECK(parser_helper("10E2") == json(10e2));
+ CHECK(parser_helper("10E3") == json(10e3));
+ CHECK(parser_helper("10E4") == json(10e4));
+ CHECK(parser_helper("10E5") == json(10e5));
+ CHECK(parser_helper("10E6") == json(10e6));
+ CHECK(parser_helper("10E7") == json(10e7));
+ CHECK(parser_helper("10E8") == json(10e8));
+ CHECK(parser_helper("10E9") == json(10e9));
+ CHECK(parser_helper("10E+0") == json(10e0));
+ CHECK(parser_helper("10E+1") == json(10e1));
+ CHECK(parser_helper("10E+2") == json(10e2));
+ CHECK(parser_helper("10E+3") == json(10e3));
+ CHECK(parser_helper("10E+4") == json(10e4));
+ CHECK(parser_helper("10E+5") == json(10e5));
+ CHECK(parser_helper("10E+6") == json(10e6));
+ CHECK(parser_helper("10E+7") == json(10e7));
+ CHECK(parser_helper("10E+8") == json(10e8));
+ CHECK(parser_helper("10E+9") == json(10e9));
+ CHECK(parser_helper("10E-1") == json(10e-1));
+ CHECK(parser_helper("10E-2") == json(10e-2));
+ CHECK(parser_helper("10E-3") == json(10e-3));
+ CHECK(parser_helper("10E-4") == json(10e-4));
+ CHECK(parser_helper("10E-5") == json(10e-5));
+ CHECK(parser_helper("10E-6") == json(10e-6));
+ CHECK(parser_helper("10E-7") == json(10e-7));
+ CHECK(parser_helper("10E-8") == json(10e-8));
+ CHECK(parser_helper("10E-9") == json(10e-9));
+ }
+
+ SECTION("edge cases")
+ {
+ // From RFC8259, Section 6:
+ // Note that when such software is used, numbers that are
+ // integers and are in the range [-(2**53)+1, (2**53)-1]
+ // are interoperable in the sense that implementations will
+ // agree exactly on their numeric values.
+
+ // -(2**53)+1
+ CHECK(parser_helper("-9007199254740991").get<int64_t>() == -9007199254740991);
+ // (2**53)-1
+ CHECK(parser_helper("9007199254740991").get<int64_t>() == 9007199254740991);
+ }
+
+ SECTION("over the edge cases") // issue #178 - Integer conversion to unsigned (incorrect handling of 64-bit integers)
+ {
+ // While RFC8259, Section 6 specifies a preference for support
+ // for ranges in range of IEEE 754-2008 binary64 (double precision)
+ // this does not accommodate 64-bit integers without loss of accuracy.
+ // As 64-bit integers are now widely used in software, it is desirable
+ // to expand support to the full 64 bit (signed and unsigned) range
+ // i.e. -(2**63) -> (2**64)-1.
+
+ // -(2**63) ** Note: compilers see negative literals as negated positive numbers (hence the -1))
+ CHECK(parser_helper("-9223372036854775808").get<int64_t>() == -9223372036854775807 - 1);
+ // (2**63)-1
+ CHECK(parser_helper("9223372036854775807").get<int64_t>() == 9223372036854775807);
+ // (2**64)-1
+ CHECK(parser_helper("18446744073709551615").get<uint64_t>() == 18446744073709551615u);
+ }
+ }
+
+ SECTION("floating-point")
+ {
+ SECTION("without exponent")
+ {
+ CHECK(parser_helper("-128.5") == json(-128.5));
+ CHECK(parser_helper("0.999") == json(0.999));
+ CHECK(parser_helper("128.5") == json(128.5));
+ CHECK(parser_helper("-0.0") == json(-0.0));
+ }
+
+ SECTION("with exponent")
+ {
+ CHECK(parser_helper("-128.5E3") == json(-128.5E3));
+ CHECK(parser_helper("-128.5E-3") == json(-128.5E-3));
+ CHECK(parser_helper("-0.0e1") == json(-0.0e1));
+ CHECK(parser_helper("-0.0E1") == json(-0.0e1));
+ }
+ }
+
+ SECTION("overflow")
+ {
+ // overflows during parsing yield an exception
+ CHECK_THROWS_WITH_AS(parser_helper("1.18973e+4932").empty(), "[json.exception.out_of_range.406] number overflow parsing '1.18973e+4932'", json::out_of_range&);
+ }
+
+ SECTION("invalid numbers")
+ {
+ // numbers must not begin with "+"
+ CHECK_THROWS_AS(parser_helper("+1"), json::parse_error&);
+ CHECK_THROWS_AS(parser_helper("+0"), json::parse_error&);
+
+ CHECK_THROWS_WITH_AS(parser_helper("01"),
+ "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - unexpected number literal; expected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("-01"),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - unexpected number literal; expected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("--1"),
+ "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid number; expected digit after '-'; last read: '--'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("1."),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid number; expected digit after '.'; last read: '1.'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("1E"),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid number; expected '+', '-', or digit after exponent; last read: '1E'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("1E-"),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid number; expected digit after exponent sign; last read: '1E-'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("1.E1"),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid number; expected digit after '.'; last read: '1.E'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("-1E"),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid number; expected '+', '-', or digit after exponent; last read: '-1E'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("-0E#"),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid number; expected '+', '-', or digit after exponent; last read: '-0E#'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("-0E-#"),
+ "[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - invalid number; expected digit after exponent sign; last read: '-0E-#'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("-0#"),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid literal; last read: '-0#'; expected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("-0.0:"),
+ "[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - unexpected ':'; expected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("-0.0Z"),
+ "[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - invalid literal; last read: '-0.0Z'; expected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("-0E123:"),
+ "[json.exception.parse_error.101] parse error at line 1, column 7: syntax error while parsing value - unexpected ':'; expected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("-0e0-:"),
+ "[json.exception.parse_error.101] parse error at line 1, column 6: syntax error while parsing value - invalid number; expected digit after '-'; last read: '-:'; expected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("-0e-:"),
+ "[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - invalid number; expected digit after exponent sign; last read: '-0e-:'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("-0f"),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid literal; last read: '-0f'; expected end of input", json::parse_error&);
+ }
+ }
+ }
+
+ SECTION("accept")
+ {
+ SECTION("null")
+ {
+ CHECK(accept_helper("null"));
+ }
+
+ SECTION("true")
+ {
+ CHECK(accept_helper("true"));
+ }
+
+ SECTION("false")
+ {
+ CHECK(accept_helper("false"));
+ }
+
+ SECTION("array")
+ {
+ SECTION("empty array")
+ {
+ CHECK(accept_helper("[]"));
+ CHECK(accept_helper("[ ]"));
+ }
+
+ SECTION("nonempty array")
+ {
+ CHECK(accept_helper("[true, false, null]"));
+ }
+ }
+
+ SECTION("object")
+ {
+ SECTION("empty object")
+ {
+ CHECK(accept_helper("{}"));
+ CHECK(accept_helper("{ }"));
+ }
+
+ SECTION("nonempty object")
+ {
+ CHECK(accept_helper("{\"\": true, \"one\": 1, \"two\": null}"));
+ }
+ }
+
+ SECTION("string")
+ {
+ // empty string
+ CHECK(accept_helper("\"\""));
+
+ SECTION("errors")
+ {
+ // error: tab in string
+ CHECK(accept_helper("\"\t\"") == false);
+ // error: newline in string
+ CHECK(accept_helper("\"\n\"") == false);
+ CHECK(accept_helper("\"\r\"") == false);
+ // error: backspace in string
+ CHECK(accept_helper("\"\b\"") == false);
+ // improve code coverage
+ CHECK(accept_helper("\uFF01") == false);
+ CHECK(accept_helper("[-4:1,]") == false);
+ // unescaped control characters
+ CHECK(accept_helper("\"\x00\"") == false); // NOLINT(bugprone-string-literal-with-embedded-nul)
+ CHECK(accept_helper("\"\x01\"") == false);
+ CHECK(accept_helper("\"\x02\"") == false);
+ CHECK(accept_helper("\"\x03\"") == false);
+ CHECK(accept_helper("\"\x04\"") == false);
+ CHECK(accept_helper("\"\x05\"") == false);
+ CHECK(accept_helper("\"\x06\"") == false);
+ CHECK(accept_helper("\"\x07\"") == false);
+ CHECK(accept_helper("\"\x08\"") == false);
+ CHECK(accept_helper("\"\x09\"") == false);
+ CHECK(accept_helper("\"\x0a\"") == false);
+ CHECK(accept_helper("\"\x0b\"") == false);
+ CHECK(accept_helper("\"\x0c\"") == false);
+ CHECK(accept_helper("\"\x0d\"") == false);
+ CHECK(accept_helper("\"\x0e\"") == false);
+ CHECK(accept_helper("\"\x0f\"") == false);
+ CHECK(accept_helper("\"\x10\"") == false);
+ CHECK(accept_helper("\"\x11\"") == false);
+ CHECK(accept_helper("\"\x12\"") == false);
+ CHECK(accept_helper("\"\x13\"") == false);
+ CHECK(accept_helper("\"\x14\"") == false);
+ CHECK(accept_helper("\"\x15\"") == false);
+ CHECK(accept_helper("\"\x16\"") == false);
+ CHECK(accept_helper("\"\x17\"") == false);
+ CHECK(accept_helper("\"\x18\"") == false);
+ CHECK(accept_helper("\"\x19\"") == false);
+ CHECK(accept_helper("\"\x1a\"") == false);
+ CHECK(accept_helper("\"\x1b\"") == false);
+ CHECK(accept_helper("\"\x1c\"") == false);
+ CHECK(accept_helper("\"\x1d\"") == false);
+ CHECK(accept_helper("\"\x1e\"") == false);
+ CHECK(accept_helper("\"\x1f\"") == false);
+ }
+
+ SECTION("escaped")
+ {
+ // quotation mark "\""
+ auto r1 = R"("\"")"_json;
+ CHECK(accept_helper("\"\\\"\""));
+ // reverse solidus "\\"
+ auto r2 = R"("\\")"_json;
+ CHECK(accept_helper("\"\\\\\""));
+ // solidus
+ CHECK(accept_helper("\"\\/\""));
+ // backspace
+ CHECK(accept_helper("\"\\b\""));
+ // formfeed
+ CHECK(accept_helper("\"\\f\""));
+ // newline
+ CHECK(accept_helper("\"\\n\""));
+ // carriage return
+ CHECK(accept_helper("\"\\r\""));
+ // horizontal tab
+ CHECK(accept_helper("\"\\t\""));
+
+ CHECK(accept_helper("\"\\u0001\""));
+ CHECK(accept_helper("\"\\u000a\""));
+ CHECK(accept_helper("\"\\u00b0\""));
+ CHECK(accept_helper("\"\\u0c00\""));
+ CHECK(accept_helper("\"\\ud000\""));
+ CHECK(accept_helper("\"\\u000E\""));
+ CHECK(accept_helper("\"\\u00F0\""));
+ CHECK(accept_helper("\"\\u0100\""));
+ CHECK(accept_helper("\"\\u2000\""));
+ CHECK(accept_helper("\"\\uFFFF\""));
+ CHECK(accept_helper("\"\\u20AC\""));
+ CHECK(accept_helper("\"€\""));
+ CHECK(accept_helper("\"🎈\""));
+
+ CHECK(accept_helper("\"\\ud80c\\udc60\""));
+ CHECK(accept_helper("\"\\ud83c\\udf1e\""));
+ }
+ }
+
+ SECTION("number")
+ {
+ SECTION("integers")
+ {
+ SECTION("without exponent")
+ {
+ CHECK(accept_helper("-128"));
+ CHECK(accept_helper("-0"));
+ CHECK(accept_helper("0"));
+ CHECK(accept_helper("128"));
+ }
+
+ SECTION("with exponent")
+ {
+ CHECK(accept_helper("0e1"));
+ CHECK(accept_helper("0E1"));
+
+ CHECK(accept_helper("10000E-4"));
+ CHECK(accept_helper("10000E-3"));
+ CHECK(accept_helper("10000E-2"));
+ CHECK(accept_helper("10000E-1"));
+ CHECK(accept_helper("10000E0"));
+ CHECK(accept_helper("10000E1"));
+ CHECK(accept_helper("10000E2"));
+ CHECK(accept_helper("10000E3"));
+ CHECK(accept_helper("10000E4"));
+
+ CHECK(accept_helper("10000e-4"));
+ CHECK(accept_helper("10000e-3"));
+ CHECK(accept_helper("10000e-2"));
+ CHECK(accept_helper("10000e-1"));
+ CHECK(accept_helper("10000e0"));
+ CHECK(accept_helper("10000e1"));
+ CHECK(accept_helper("10000e2"));
+ CHECK(accept_helper("10000e3"));
+ CHECK(accept_helper("10000e4"));
+
+ CHECK(accept_helper("-0e1"));
+ CHECK(accept_helper("-0E1"));
+ CHECK(accept_helper("-0E123"));
+ }
+
+ SECTION("edge cases")
+ {
+ // From RFC8259, Section 6:
+ // Note that when such software is used, numbers that are
+ // integers and are in the range [-(2**53)+1, (2**53)-1]
+ // are interoperable in the sense that implementations will
+ // agree exactly on their numeric values.
+
+ // -(2**53)+1
+ CHECK(accept_helper("-9007199254740991"));
+ // (2**53)-1
+ CHECK(accept_helper("9007199254740991"));
+ }
+
+ SECTION("over the edge cases") // issue #178 - Integer conversion to unsigned (incorrect handling of 64-bit integers)
+ {
+ // While RFC8259, Section 6 specifies a preference for support
+ // for ranges in range of IEEE 754-2008 binary64 (double precision)
+ // this does not accommodate 64 bit integers without loss of accuracy.
+ // As 64 bit integers are now widely used in software, it is desirable
+ // to expand support to the full 64 bit (signed and unsigned) range
+ // i.e. -(2**63) -> (2**64)-1.
+
+ // -(2**63) ** Note: compilers see negative literals as negated positive numbers (hence the -1))
+ CHECK(accept_helper("-9223372036854775808"));
+ // (2**63)-1
+ CHECK(accept_helper("9223372036854775807"));
+ // (2**64)-1
+ CHECK(accept_helper("18446744073709551615"));
+ }
+ }
+
+ SECTION("floating-point")
+ {
+ SECTION("without exponent")
+ {
+ CHECK(accept_helper("-128.5"));
+ CHECK(accept_helper("0.999"));
+ CHECK(accept_helper("128.5"));
+ CHECK(accept_helper("-0.0"));
+ }
+
+ SECTION("with exponent")
+ {
+ CHECK(accept_helper("-128.5E3"));
+ CHECK(accept_helper("-128.5E-3"));
+ CHECK(accept_helper("-0.0e1"));
+ CHECK(accept_helper("-0.0E1"));
+ }
+ }
+
+ SECTION("overflow")
+ {
+ // overflows during parsing
+ CHECK(!accept_helper("1.18973e+4932"));
+ }
+
+ SECTION("invalid numbers")
+ {
+ CHECK(accept_helper("01") == false);
+ CHECK(accept_helper("--1") == false);
+ CHECK(accept_helper("1.") == false);
+ CHECK(accept_helper("1E") == false);
+ CHECK(accept_helper("1E-") == false);
+ CHECK(accept_helper("1.E1") == false);
+ CHECK(accept_helper("-1E") == false);
+ CHECK(accept_helper("-0E#") == false);
+ CHECK(accept_helper("-0E-#") == false);
+ CHECK(accept_helper("-0#") == false);
+ CHECK(accept_helper("-0.0:") == false);
+ CHECK(accept_helper("-0.0Z") == false);
+ CHECK(accept_helper("-0E123:") == false);
+ CHECK(accept_helper("-0e0-:") == false);
+ CHECK(accept_helper("-0e-:") == false);
+ CHECK(accept_helper("-0f") == false);
+
+ // numbers must not begin with "+"
+ CHECK(accept_helper("+1") == false);
+ CHECK(accept_helper("+0") == false);
+ }
+ }
+ }
+
+ SECTION("parse errors")
+ {
+ // unexpected end of number
+ CHECK_THROWS_WITH_AS(parser_helper("0."),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid number; expected digit after '.'; last read: '0.'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("-"),
+ "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid number; expected digit after '-'; last read: '-'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("--"),
+ "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid number; expected digit after '-'; last read: '--'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("-0."),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid number; expected digit after '.'; last read: '-0.'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("-."),
+ "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid number; expected digit after '-'; last read: '-.'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("-:"),
+ "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid number; expected digit after '-'; last read: '-:'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("0.:"),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid number; expected digit after '.'; last read: '0.:'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("e."),
+ "[json.exception.parse_error.101] parse error at line 1, column 1: syntax error while parsing value - invalid literal; last read: 'e'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("1e."),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid number; expected '+', '-', or digit after exponent; last read: '1e.'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("1e/"),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid number; expected '+', '-', or digit after exponent; last read: '1e/'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("1e:"),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid number; expected '+', '-', or digit after exponent; last read: '1e:'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("1E."),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid number; expected '+', '-', or digit after exponent; last read: '1E.'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("1E/"),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid number; expected '+', '-', or digit after exponent; last read: '1E/'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("1E:"),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid number; expected '+', '-', or digit after exponent; last read: '1E:'", json::parse_error&);
+
+ // unexpected end of null
+ CHECK_THROWS_WITH_AS(parser_helper("n"),
+ "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid literal; last read: 'n'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("nu"),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid literal; last read: 'nu'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("nul"),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid literal; last read: 'nul'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("nulk"),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid literal; last read: 'nulk'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("nulm"),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid literal; last read: 'nulm'", json::parse_error&);
+
+ // unexpected end of true
+ CHECK_THROWS_WITH_AS(parser_helper("t"),
+ "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid literal; last read: 't'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("tr"),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid literal; last read: 'tr'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("tru"),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid literal; last read: 'tru'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("trud"),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid literal; last read: 'trud'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("truf"),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid literal; last read: 'truf'", json::parse_error&);
+
+ // unexpected end of false
+ CHECK_THROWS_WITH_AS(parser_helper("f"),
+ "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid literal; last read: 'f'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("fa"),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid literal; last read: 'fa'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("fal"),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid literal; last read: 'fal'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("fals"),
+ "[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - invalid literal; last read: 'fals'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("falsd"),
+ "[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - invalid literal; last read: 'falsd'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("falsf"),
+ "[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - invalid literal; last read: 'falsf'", json::parse_error&);
+
+ // missing/unexpected end of array
+ CHECK_THROWS_WITH_AS(parser_helper("["),
+ "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - unexpected end of input; expected '[', '{', or a literal", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("[1"),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing array - unexpected end of input; expected ']'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("[1,"),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - unexpected end of input; expected '[', '{', or a literal", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("[1,]"),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - unexpected ']'; expected '[', '{', or a literal", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("]"),
+ "[json.exception.parse_error.101] parse error at line 1, column 1: syntax error while parsing value - unexpected ']'; expected '[', '{', or a literal", json::parse_error&);
+
+ // missing/unexpected end of object
+ CHECK_THROWS_WITH_AS(parser_helper("{"),
+ "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing object key - unexpected end of input; expected string literal", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("{\"foo\""),
+ "[json.exception.parse_error.101] parse error at line 1, column 7: syntax error while parsing object separator - unexpected end of input; expected ':'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("{\"foo\":"),
+ "[json.exception.parse_error.101] parse error at line 1, column 8: syntax error while parsing value - unexpected end of input; expected '[', '{', or a literal", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("{\"foo\":}"),
+ "[json.exception.parse_error.101] parse error at line 1, column 8: syntax error while parsing value - unexpected '}'; expected '[', '{', or a literal", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("{\"foo\":1,}"),
+ "[json.exception.parse_error.101] parse error at line 1, column 10: syntax error while parsing object key - unexpected '}'; expected string literal", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("}"),
+ "[json.exception.parse_error.101] parse error at line 1, column 1: syntax error while parsing value - unexpected '}'; expected '[', '{', or a literal", json::parse_error&);
+
+ // missing/unexpected end of string
+ CHECK_THROWS_WITH_AS(parser_helper("\""),
+ "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: missing closing quote; last read: '\"'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\\\""),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid string: missing closing quote; last read: '\"\\\"'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\\u\""),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid string: '\\u' must be followed by 4 hex digits; last read: '\"\\u\"'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\\u0\""),
+ "[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - invalid string: '\\u' must be followed by 4 hex digits; last read: '\"\\u0\"'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\\u01\""),
+ "[json.exception.parse_error.101] parse error at line 1, column 6: syntax error while parsing value - invalid string: '\\u' must be followed by 4 hex digits; last read: '\"\\u01\"'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\\u012\""),
+ "[json.exception.parse_error.101] parse error at line 1, column 7: syntax error while parsing value - invalid string: '\\u' must be followed by 4 hex digits; last read: '\"\\u012\"'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\\u"),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid string: '\\u' must be followed by 4 hex digits; last read: '\"\\u'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\\u0"),
+ "[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - invalid string: '\\u' must be followed by 4 hex digits; last read: '\"\\u0'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\\u01"),
+ "[json.exception.parse_error.101] parse error at line 1, column 6: syntax error while parsing value - invalid string: '\\u' must be followed by 4 hex digits; last read: '\"\\u01'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\\u012"),
+ "[json.exception.parse_error.101] parse error at line 1, column 7: syntax error while parsing value - invalid string: '\\u' must be followed by 4 hex digits; last read: '\"\\u012'", json::parse_error&);
+
+ // invalid escapes
+ for (int c = 1; c < 128; ++c)
+ {
+ auto s = std::string("\"\\") + std::string(1, static_cast<char>(c)) + "\"";
+
+ switch (c)
+ {
+ // valid escapes
+ case ('"'):
+ case ('\\'):
+ case ('/'):
+ case ('b'):
+ case ('f'):
+ case ('n'):
+ case ('r'):
+ case ('t'):
+ {
+ CHECK_NOTHROW(parser_helper(s));
+ break;
+ }
+
+ // \u must be followed with four numbers, so we skip it here
+ case ('u'):
+ {
+ break;
+ }
+
+ // any other combination of backslash and character is invalid
+ default:
+ {
+ CHECK_THROWS_AS(parser_helper(s), json::parse_error&);
+ // only check error message if c is not a control character
+ if (c > 0x1f)
+ {
+ CHECK_THROWS_WITH_STD_STR(parser_helper(s),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid string: forbidden character after backslash; last read: '\"\\" + std::string(1, static_cast<char>(c)) + "'");
+ }
+ break;
+ }
+ }
+ }
+
+ // invalid \uxxxx escapes
+ {
+ // check whether character is a valid hex character
+ const auto valid = [](int c)
+ {
+ switch (c)
+ {
+ case ('0'):
+ case ('1'):
+ case ('2'):
+ case ('3'):
+ case ('4'):
+ case ('5'):
+ case ('6'):
+ case ('7'):
+ case ('8'):
+ case ('9'):
+ case ('a'):
+ case ('b'):
+ case ('c'):
+ case ('d'):
+ case ('e'):
+ case ('f'):
+ case ('A'):
+ case ('B'):
+ case ('C'):
+ case ('D'):
+ case ('E'):
+ case ('F'):
+ {
+ return true;
+ }
+
+ default:
+ {
+ return false;
+ }
+ }
+ };
+
+ for (int c = 1; c < 128; ++c)
+ {
+ std::string const s = "\"\\u";
+
+ // create a string with the iterated character at each position
+ auto s1 = s + "000" + std::string(1, static_cast<char>(c)) + "\"";
+ auto s2 = s + "00" + std::string(1, static_cast<char>(c)) + "0\"";
+ auto s3 = s + "0" + std::string(1, static_cast<char>(c)) + "00\"";
+ auto s4 = s + std::string(1, static_cast<char>(c)) + "000\"";
+
+ if (valid(c))
+ {
+ CAPTURE(s1)
+ CHECK_NOTHROW(parser_helper(s1));
+ CAPTURE(s2)
+ CHECK_NOTHROW(parser_helper(s2));
+ CAPTURE(s3)
+ CHECK_NOTHROW(parser_helper(s3));
+ CAPTURE(s4)
+ CHECK_NOTHROW(parser_helper(s4));
+ }
+ else
+ {
+ CAPTURE(s1)
+ CHECK_THROWS_AS(parser_helper(s1), json::parse_error&);
+ // only check error message if c is not a control character
+ if (c > 0x1f)
+ {
+ CHECK_THROWS_WITH_STD_STR(parser_helper(s1),
+ "[json.exception.parse_error.101] parse error at line 1, column 7: syntax error while parsing value - invalid string: '\\u' must be followed by 4 hex digits; last read: '" + s1.substr(0, 7) + "'");
+ }
+
+ CAPTURE(s2)
+ CHECK_THROWS_AS(parser_helper(s2), json::parse_error&);
+ // only check error message if c is not a control character
+ if (c > 0x1f)
+ {
+ CHECK_THROWS_WITH_STD_STR(parser_helper(s2),
+ "[json.exception.parse_error.101] parse error at line 1, column 6: syntax error while parsing value - invalid string: '\\u' must be followed by 4 hex digits; last read: '" + s2.substr(0, 6) + "'");
+ }
+
+ CAPTURE(s3)
+ CHECK_THROWS_AS(parser_helper(s3), json::parse_error&);
+ // only check error message if c is not a control character
+ if (c > 0x1f)
+ {
+ CHECK_THROWS_WITH_STD_STR(parser_helper(s3),
+ "[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - invalid string: '\\u' must be followed by 4 hex digits; last read: '" + s3.substr(0, 5) + "'");
+ }
+
+ CAPTURE(s4)
+ CHECK_THROWS_AS(parser_helper(s4), json::parse_error&);
+ // only check error message if c is not a control character
+ if (c > 0x1f)
+ {
+ CHECK_THROWS_WITH_STD_STR(parser_helper(s4),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid string: '\\u' must be followed by 4 hex digits; last read: '" + s4.substr(0, 4) + "'");
+ }
+ }
+ }
+ }
+
+ json _;
+
+ // missing part of a surrogate pair
+ CHECK_THROWS_WITH_AS(_ = json::parse("\"\\uD80C\""), "[json.exception.parse_error.101] parse error at line 1, column 8: syntax error while parsing value - invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF; last read: '\"\\uD80C\"'", json::parse_error&);
+ // invalid surrogate pair
+ CHECK_THROWS_WITH_AS(_ = json::parse("\"\\uD80C\\uD80C\""),
+ "[json.exception.parse_error.101] parse error at line 1, column 13: syntax error while parsing value - invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF; last read: '\"\\uD80C\\uD80C'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::parse("\"\\uD80C\\u0000\""),
+ "[json.exception.parse_error.101] parse error at line 1, column 13: syntax error while parsing value - invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF; last read: '\"\\uD80C\\u0000'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::parse("\"\\uD80C\\uFFFF\""),
+ "[json.exception.parse_error.101] parse error at line 1, column 13: syntax error while parsing value - invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF; last read: '\"\\uD80C\\uFFFF'", json::parse_error&);
+ }
+
+ SECTION("parse errors (accept)")
+ {
+ // unexpected end of number
+ CHECK(accept_helper("0.") == false);
+ CHECK(accept_helper("-") == false);
+ CHECK(accept_helper("--") == false);
+ CHECK(accept_helper("-0.") == false);
+ CHECK(accept_helper("-.") == false);
+ CHECK(accept_helper("-:") == false);
+ CHECK(accept_helper("0.:") == false);
+ CHECK(accept_helper("e.") == false);
+ CHECK(accept_helper("1e.") == false);
+ CHECK(accept_helper("1e/") == false);
+ CHECK(accept_helper("1e:") == false);
+ CHECK(accept_helper("1E.") == false);
+ CHECK(accept_helper("1E/") == false);
+ CHECK(accept_helper("1E:") == false);
+
+ // unexpected end of null
+ CHECK(accept_helper("n") == false);
+ CHECK(accept_helper("nu") == false);
+ CHECK(accept_helper("nul") == false);
+
+ // unexpected end of true
+ CHECK(accept_helper("t") == false);
+ CHECK(accept_helper("tr") == false);
+ CHECK(accept_helper("tru") == false);
+
+ // unexpected end of false
+ CHECK(accept_helper("f") == false);
+ CHECK(accept_helper("fa") == false);
+ CHECK(accept_helper("fal") == false);
+ CHECK(accept_helper("fals") == false);
+
+ // missing/unexpected end of array
+ CHECK(accept_helper("[") == false);
+ CHECK(accept_helper("[1") == false);
+ CHECK(accept_helper("[1,") == false);
+ CHECK(accept_helper("[1,]") == false);
+ CHECK(accept_helper("]") == false);
+
+ // missing/unexpected end of object
+ CHECK(accept_helper("{") == false);
+ CHECK(accept_helper("{\"foo\"") == false);
+ CHECK(accept_helper("{\"foo\":") == false);
+ CHECK(accept_helper("{\"foo\":}") == false);
+ CHECK(accept_helper("{\"foo\":1,}") == false);
+ CHECK(accept_helper("}") == false);
+
+ // missing/unexpected end of string
+ CHECK(accept_helper("\"") == false);
+ CHECK(accept_helper("\"\\\"") == false);
+ CHECK(accept_helper("\"\\u\"") == false);
+ CHECK(accept_helper("\"\\u0\"") == false);
+ CHECK(accept_helper("\"\\u01\"") == false);
+ CHECK(accept_helper("\"\\u012\"") == false);
+ CHECK(accept_helper("\"\\u") == false);
+ CHECK(accept_helper("\"\\u0") == false);
+ CHECK(accept_helper("\"\\u01") == false);
+ CHECK(accept_helper("\"\\u012") == false);
+
+ // unget of newline
+ CHECK(parser_helper("\n123\n") == 123);
+
+ // invalid escapes
+ for (int c = 1; c < 128; ++c)
+ {
+ auto s = std::string("\"\\") + std::string(1, static_cast<char>(c)) + "\"";
+
+ switch (c)
+ {
+ // valid escapes
+ case ('"'):
+ case ('\\'):
+ case ('/'):
+ case ('b'):
+ case ('f'):
+ case ('n'):
+ case ('r'):
+ case ('t'):
+ {
+ CHECK(json::parser(nlohmann::detail::input_adapter(s)).accept());
+ break;
+ }
+
+ // \u must be followed with four numbers, so we skip it here
+ case ('u'):
+ {
+ break;
+ }
+
+ // any other combination of backslash and character is invalid
+ default:
+ {
+ CHECK(json::parser(nlohmann::detail::input_adapter(s)).accept() == false);
+ break;
+ }
+ }
+ }
+
+ // invalid \uxxxx escapes
+ {
+ // check whether character is a valid hex character
+ const auto valid = [](int c)
+ {
+ switch (c)
+ {
+ case ('0'):
+ case ('1'):
+ case ('2'):
+ case ('3'):
+ case ('4'):
+ case ('5'):
+ case ('6'):
+ case ('7'):
+ case ('8'):
+ case ('9'):
+ case ('a'):
+ case ('b'):
+ case ('c'):
+ case ('d'):
+ case ('e'):
+ case ('f'):
+ case ('A'):
+ case ('B'):
+ case ('C'):
+ case ('D'):
+ case ('E'):
+ case ('F'):
+ {
+ return true;
+ }
+
+ default:
+ {
+ return false;
+ }
+ }
+ };
+
+ for (int c = 1; c < 128; ++c)
+ {
+ std::string const s = "\"\\u";
+
+ // create a string with the iterated character at each position
+ const auto s1 = s + "000" + std::string(1, static_cast<char>(c)) + "\"";
+ const auto s2 = s + "00" + std::string(1, static_cast<char>(c)) + "0\"";
+ const auto s3 = s + "0" + std::string(1, static_cast<char>(c)) + "00\"";
+ const auto s4 = s + std::string(1, static_cast<char>(c)) + "000\"";
+
+ if (valid(c))
+ {
+ CAPTURE(s1)
+ CHECK(json::parser(nlohmann::detail::input_adapter(s1)).accept());
+ CAPTURE(s2)
+ CHECK(json::parser(nlohmann::detail::input_adapter(s2)).accept());
+ CAPTURE(s3)
+ CHECK(json::parser(nlohmann::detail::input_adapter(s3)).accept());
+ CAPTURE(s4)
+ CHECK(json::parser(nlohmann::detail::input_adapter(s4)).accept());
+ }
+ else
+ {
+ CAPTURE(s1)
+ CHECK(json::parser(nlohmann::detail::input_adapter(s1)).accept() == false);
+
+ CAPTURE(s2)
+ CHECK(json::parser(nlohmann::detail::input_adapter(s2)).accept() == false);
+
+ CAPTURE(s3)
+ CHECK(json::parser(nlohmann::detail::input_adapter(s3)).accept() == false);
+
+ CAPTURE(s4)
+ CHECK(json::parser(nlohmann::detail::input_adapter(s4)).accept() == false);
+ }
+ }
+ }
+
+ // missing part of a surrogate pair
+ CHECK(accept_helper("\"\\uD80C\"") == false);
+ // invalid surrogate pair
+ CHECK(accept_helper("\"\\uD80C\\uD80C\"") == false);
+ CHECK(accept_helper("\"\\uD80C\\u0000\"") == false);
+ CHECK(accept_helper("\"\\uD80C\\uFFFF\"") == false);
+ }
+
+ SECTION("tests found by mutate++")
+ {
+ // test case to make sure no comma precedes the first key
+ CHECK_THROWS_WITH_AS(parser_helper("{,\"key\": false}"), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing object key - unexpected ','; expected string literal", json::parse_error&);
+ // test case to make sure an object is properly closed
+ CHECK_THROWS_WITH_AS(parser_helper("[{\"key\": false true]"), "[json.exception.parse_error.101] parse error at line 1, column 19: syntax error while parsing object - unexpected true literal; expected '}'", json::parse_error&);
+
+ // test case to make sure the callback is properly evaluated after reading a key
+ {
+ json::parser_callback_t const cb = [](int /*unused*/, json::parse_event_t event, json& /*unused*/) noexcept
+ {
+ return event != json::parse_event_t::key;
+ };
+
+ const json x = json::parse("{\"key\": false}", cb);
+ CHECK(x == json::object());
+ }
+ }
+
+ SECTION("callback function")
+ {
+ const auto* s_object = R"(
+ {
+ "foo": 2,
+ "bar": {
+ "baz": 1
+ }
+ }
+ )";
+
+ const auto* s_array = R"(
+ [1,2,[3,4,5],4,5]
+ )";
+
+ const auto* structured_array = R"(
+ [
+ 1,
+ {
+ "foo": "bar"
+ },
+ {
+ "qux": "baz"
+ }
+ ]
+ )";
+
+ SECTION("filter nothing")
+ {
+ const json j_object = json::parse(s_object, [](int /*unused*/, json::parse_event_t /*unused*/, const json& /*unused*/) noexcept
+ {
+ return true;
+ });
+
+ CHECK (j_object == json({{"foo", 2}, {"bar", {{"baz", 1}}}}));
+
+ const json j_array = json::parse(s_array, [](int /*unused*/, json::parse_event_t /*unused*/, const json& /*unused*/) noexcept
+ {
+ return true;
+ });
+
+ CHECK (j_array == json({1, 2, {3, 4, 5}, 4, 5}));
+ }
+
+ SECTION("filter everything")
+ {
+ json const j_object = json::parse(s_object, [](int /*unused*/, json::parse_event_t /*unused*/, const json& /*unused*/) noexcept
+ {
+ return false;
+ });
+
+ // the top-level object will be discarded, leaving a null
+ CHECK (j_object.is_null());
+
+ json const j_array = json::parse(s_array, [](int /*unused*/, json::parse_event_t /*unused*/, const json& /*unused*/) noexcept
+ {
+ return false;
+ });
+
+ // the top-level array will be discarded, leaving a null
+ CHECK (j_array.is_null());
+ }
+
+ SECTION("filter specific element")
+ {
+ const json j_object = json::parse(s_object, [](int /*unused*/, json::parse_event_t event, const json & j) noexcept
+ {
+ // filter all number(2) elements
+ return event != json::parse_event_t::value || j != json(2);
+ });
+
+ CHECK (j_object == json({{"bar", {{"baz", 1}}}}));
+
+ const json j_array = json::parse(s_array, [](int /*unused*/, json::parse_event_t event, const json & j) noexcept
+ {
+ return event != json::parse_event_t::value || j != json(2);
+ });
+
+ CHECK (j_array == json({1, {3, 4, 5}, 4, 5}));
+ }
+
+ SECTION("filter object in array")
+ {
+ const json j_filtered1 = json::parse(structured_array, [](int /*unused*/, json::parse_event_t e, const json & parsed)
+ {
+ return !(e == json::parse_event_t::object_end && parsed.contains("foo"));
+ });
+
+ // the specified object will be discarded, and removed.
+ CHECK (j_filtered1.size() == 2);
+ CHECK (j_filtered1 == json({1, {{"qux", "baz"}}}));
+
+ const json j_filtered2 = json::parse(structured_array, [](int /*unused*/, json::parse_event_t e, const json& /*parsed*/) noexcept
+ {
+ return e != json::parse_event_t::object_end;
+ });
+
+ // removed all objects in array.
+ CHECK (j_filtered2.size() == 1);
+ CHECK (j_filtered2 == json({1}));
+ }
+
+ SECTION("filter specific events")
+ {
+ SECTION("first closing event")
+ {
+ {
+ const json j_object = json::parse(s_object, [](int /*unused*/, json::parse_event_t e, const json& /*unused*/) noexcept
+ {
+ static bool first = true;
+ if (e == json::parse_event_t::object_end && first)
+ {
+ first = false;
+ return false;
+ }
+
+ return true;
+ });
+
+ // the first completed object will be discarded
+ CHECK (j_object == json({{"foo", 2}}));
+ }
+
+ {
+ const json j_array = json::parse(s_array, [](int /*unused*/, json::parse_event_t e, const json& /*unused*/) noexcept
+ {
+ static bool first = true;
+ if (e == json::parse_event_t::array_end && first)
+ {
+ first = false;
+ return false;
+ }
+
+ return true;
+ });
+
+ // the first completed array will be discarded
+ CHECK (j_array == json({1, 2, 4, 5}));
+ }
+ }
+ }
+
+ SECTION("special cases")
+ {
+ // the following test cases cover the situation in which an empty
+ // object and array is discarded only after the closing character
+ // has been read
+
+ const json j_empty_object = json::parse("{}", [](int /*unused*/, json::parse_event_t e, const json& /*unused*/) noexcept
+ {
+ return e != json::parse_event_t::object_end;
+ });
+ CHECK(j_empty_object == json());
+
+ const json j_empty_array = json::parse("[]", [](int /*unused*/, json::parse_event_t e, const json& /*unused*/) noexcept
+ {
+ return e != json::parse_event_t::array_end;
+ });
+ CHECK(j_empty_array == json());
+ }
+ }
+
+ SECTION("constructing from contiguous containers")
+ {
+ SECTION("from std::vector")
+ {
+ std::vector<uint8_t> v = {'t', 'r', 'u', 'e'};
+ json j;
+ json::parser(nlohmann::detail::input_adapter(std::begin(v), std::end(v))).parse(true, j);
+ CHECK(j == json(true));
+ }
+
+ SECTION("from std::array")
+ {
+ std::array<uint8_t, 5> v { {'t', 'r', 'u', 'e'} };
+ json j;
+ json::parser(nlohmann::detail::input_adapter(std::begin(v), std::end(v))).parse(true, j);
+ CHECK(j == json(true));
+ }
+
+ SECTION("from array")
+ {
+ uint8_t v[] = {'t', 'r', 'u', 'e'}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
+ json j;
+ json::parser(nlohmann::detail::input_adapter(std::begin(v), std::end(v))).parse(true, j);
+ CHECK(j == json(true));
+ }
+
+ SECTION("from char literal")
+ {
+ CHECK(parser_helper("true") == json(true));
+ }
+
+ SECTION("from std::string")
+ {
+ std::string v = {'t', 'r', 'u', 'e'};
+ json j;
+ json::parser(nlohmann::detail::input_adapter(std::begin(v), std::end(v))).parse(true, j);
+ CHECK(j == json(true));
+ }
+
+ SECTION("from std::initializer_list")
+ {
+ std::initializer_list<uint8_t> const v = {'t', 'r', 'u', 'e'};
+ json j;
+ json::parser(nlohmann::detail::input_adapter(std::begin(v), std::end(v))).parse(true, j);
+ CHECK(j == json(true));
+ }
+
+ SECTION("from std::valarray")
+ {
+ std::valarray<uint8_t> v = {'t', 'r', 'u', 'e'};
+ json j;
+ json::parser(nlohmann::detail::input_adapter(std::begin(v), std::end(v))).parse(true, j);
+ CHECK(j == json(true));
+ }
+ }
+
+ SECTION("improve test coverage")
+ {
+ SECTION("parser with callback")
+ {
+ json::parser_callback_t const cb = [](int /*unused*/, json::parse_event_t /*unused*/, json& /*unused*/) noexcept
+ {
+ return true;
+ };
+
+ CHECK(json::parse("{\"foo\": true:", cb, false).is_discarded());
+
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::parse("{\"foo\": true:", cb), "[json.exception.parse_error.101] parse error at line 1, column 13: syntax error while parsing object - unexpected ':'; expected '}'", json::parse_error&);
+
+ CHECK_THROWS_WITH_AS(_ = json::parse("1.18973e+4932", cb), "[json.exception.out_of_range.406] number overflow parsing '1.18973e+4932'", json::out_of_range&);
+ }
+
+ SECTION("SAX parser")
+ {
+ SECTION("null sax handler")
+ {
+# if defined(__has_feature)
+#if !__has_feature(undefined_behavior_sanitizer)
+ const std::string s = "some_string";
+ SaxCountdown* p = nullptr;
+ CHECK_THROWS_WITH_AS(json::sax_parse(s, p), "[json.exception.other_error.502] SAX handler must not be null", json::other_error&); // NOLINT(clang-analyzer-core.NonNullParamChecker)
+ CHECK_THROWS_WITH_AS(json::sax_parse(s.begin(), s.end(), p), "[json.exception.other_error.502] SAX handler must not be null", json::other_error&); // NOLINT(clang-analyzer-core.NonNullParamChecker)
+ CHECK_THROWS_WITH_AS(json::sax_parse(nlohmann::detail::span_input_adapter(s.c_str(), s.size()), p), "[json.exception.other_error.502] SAX handler must not be null", json::other_error&); // NOLINT(clang-analyzer-core.NonNullParamChecker)
+#endif
+#else
+ const std::string s = "some_string";
+ SaxCountdown* p = nullptr;
+ CHECK_THROWS_WITH_AS(json::sax_parse(s, p), "[json.exception.other_error.502] SAX handler must not be null", json::other_error&); // NOLINT(clang-analyzer-core.NonNullParamChecker)
+ CHECK_THROWS_WITH_AS(json::sax_parse(s.begin(), s.end(), p), "[json.exception.other_error.502] SAX handler must not be null", json::other_error&); // NOLINT(clang-analyzer-core.NonNullParamChecker)
+ CHECK_THROWS_WITH_AS(json::sax_parse(nlohmann::detail::span_input_adapter(s.c_str(), s.size()), p), "[json.exception.other_error.502] SAX handler must not be null", json::other_error&); // NOLINT(clang-analyzer-core.NonNullParamChecker)
+#endif
+ }
+
+ SECTION("valid sax handler")
+ {
+ const std::string str = "some_string";
+ SaxCountdown s(1);
+ CHECK(json::sax_parse(str, &s) == false);
+ CHECK(json::sax_parse(nlohmann::detail::span_input_adapter(str.c_str(), str.size()), &s) == false);
+ }
+
+ SECTION("} without value")
+ {
+ SaxCountdown s(1);
+ CHECK(json::sax_parse("{}", &s) == false);
+ }
+
+ SECTION("} with value")
+ {
+ SaxCountdown s(3);
+ CHECK(json::sax_parse("{\"k1\": true}", &s) == false);
+ }
+
+ SECTION("second key")
+ {
+ SaxCountdown s(3);
+ CHECK(json::sax_parse("{\"k1\": true, \"k2\": false}", &s) == false);
+ }
+
+ SECTION("] without value")
+ {
+ SaxCountdown s(1);
+ CHECK(json::sax_parse("[]", &s) == false);
+ }
+
+ SECTION("] with value")
+ {
+ SaxCountdown s(2);
+ CHECK(json::sax_parse("[1]", &s) == false);
+ }
+
+ SECTION("float")
+ {
+ SaxCountdown s(0);
+ CHECK(json::sax_parse("3.14", &s) == false);
+ }
+
+ SECTION("false")
+ {
+ SaxCountdown s(0);
+ CHECK(json::sax_parse("false", &s) == false);
+ }
+
+ SECTION("null")
+ {
+ SaxCountdown s(0);
+ CHECK(json::sax_parse("null", &s) == false);
+ }
+
+ SECTION("true")
+ {
+ SaxCountdown s(0);
+ CHECK(json::sax_parse("true", &s) == false);
+ }
+
+ SECTION("unsigned")
+ {
+ SaxCountdown s(0);
+ CHECK(json::sax_parse("12", &s) == false);
+ }
+
+ SECTION("integer")
+ {
+ SaxCountdown s(0);
+ CHECK(json::sax_parse("-12", &s) == false);
+ }
+
+ SECTION("string")
+ {
+ SaxCountdown s(0);
+ CHECK(json::sax_parse("\"foo\"", &s) == false);
+ }
+ }
+ }
+
+ SECTION("error messages for comments")
+ {
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::parse("/a", nullptr, true, true), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid comment; expecting '/' or '*' after '/'; last read: '/a'", json::parse_error);
+ CHECK_THROWS_WITH_AS(_ = json::parse("/*", nullptr, true, true), "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid comment; missing closing '*/'; last read: '/*<U+0000>'", json::parse_error);
+ }
+}
diff --git a/json4cpp/tests/src/unit-class_parser_diagnostic_positions.cpp b/json4cpp/tests/src/unit-class_parser_diagnostic_positions.cpp
new file mode 100644
index 0000000000..2697ecf8af
--- /dev/null
+++ b/json4cpp/tests/src/unit-class_parser_diagnostic_positions.cpp
@@ -0,0 +1,1957 @@
+// __ _____ _____ _____
+// __| | __| | | | 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
+#ifdef JSON_DIAGNOSTIC_POSITIONS
+ #undef JSON_DIAGNOSTIC_POSITIONS
+#endif
+
+#define JSON_DIAGNOSTIC_POSITIONS 1
+#include <nlohmann/json.hpp>
+using nlohmann::json;
+
+#ifdef JSON_TEST_NO_GLOBAL_UDLS
+ using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
+#endif
+
+#include <valarray>
+
+namespace
+{
+class SaxEventLogger
+{
+ public:
+ bool null()
+ {
+ events.emplace_back("null()");
+ return true;
+ }
+
+ bool boolean(bool val)
+ {
+ events.emplace_back(val ? "boolean(true)" : "boolean(false)");
+ return true;
+ }
+
+ bool number_integer(json::number_integer_t val)
+ {
+ events.push_back("number_integer(" + std::to_string(val) + ")");
+ return true;
+ }
+
+ bool number_unsigned(json::number_unsigned_t val)
+ {
+ events.push_back("number_unsigned(" + std::to_string(val) + ")");
+ return true;
+ }
+
+ bool number_float(json::number_float_t /*unused*/, const std::string& s)
+ {
+ events.push_back("number_float(" + s + ")");
+ return true;
+ }
+
+ bool string(std::string& val)
+ {
+ events.push_back("string(" + val + ")");
+ return true;
+ }
+
+ bool binary(json::binary_t& val)
+ {
+ std::string binary_contents = "binary(";
+ std::string comma_space;
+ for (auto b : val)
+ {
+ binary_contents.append(comma_space);
+ binary_contents.append(std::to_string(static_cast<int>(b)));
+ comma_space = ", ";
+ }
+ binary_contents.append(")");
+ events.push_back(binary_contents);
+ return true;
+ }
+
+ bool start_object(std::size_t elements)
+ {
+ if (elements == (std::numeric_limits<std::size_t>::max)())
+ {
+ events.emplace_back("start_object()");
+ }
+ else
+ {
+ events.push_back("start_object(" + std::to_string(elements) + ")");
+ }
+ return true;
+ }
+
+ bool key(std::string& val)
+ {
+ events.push_back("key(" + val + ")");
+ return true;
+ }
+
+ bool end_object()
+ {
+ events.emplace_back("end_object()");
+ return true;
+ }
+
+ bool start_array(std::size_t elements)
+ {
+ if (elements == (std::numeric_limits<std::size_t>::max)())
+ {
+ events.emplace_back("start_array()");
+ }
+ else
+ {
+ events.push_back("start_array(" + std::to_string(elements) + ")");
+ }
+ return true;
+ }
+
+ bool end_array()
+ {
+ events.emplace_back("end_array()");
+ return true;
+ }
+
+ bool parse_error(std::size_t position, const std::string& /*unused*/, const json::exception& /*unused*/)
+ {
+ errored = true;
+ events.push_back("parse_error(" + std::to_string(position) + ")");
+ return false;
+ }
+
+ std::vector<std::string> events {}; // NOLINT(readability-redundant-member-init)
+ bool errored = false;
+};
+
+class SaxCountdown : public nlohmann::json::json_sax_t
+{
+ public:
+ explicit SaxCountdown(const int count) : events_left(count)
+ {}
+
+ bool null() override
+ {
+ return events_left-- > 0;
+ }
+
+ bool boolean(bool /*val*/) override
+ {
+ return events_left-- > 0;
+ }
+
+ bool number_integer(json::number_integer_t /*val*/) override
+ {
+ return events_left-- > 0;
+ }
+
+ bool number_unsigned(json::number_unsigned_t /*val*/) override
+ {
+ return events_left-- > 0;
+ }
+
+ bool number_float(json::number_float_t /*val*/, const std::string& /*s*/) override
+ {
+ return events_left-- > 0;
+ }
+
+ bool string(std::string& /*val*/) override
+ {
+ return events_left-- > 0;
+ }
+
+ bool binary(json::binary_t& /*val*/) override
+ {
+ return events_left-- > 0;
+ }
+
+ bool start_object(std::size_t /*elements*/) override
+ {
+ return events_left-- > 0;
+ }
+
+ bool key(std::string& /*val*/) override
+ {
+ return events_left-- > 0;
+ }
+
+ bool end_object() override
+ {
+ return events_left-- > 0;
+ }
+
+ bool start_array(std::size_t /*elements*/) override
+ {
+ return events_left-- > 0;
+ }
+
+ bool end_array() override
+ {
+ return events_left-- > 0;
+ }
+
+ bool parse_error(std::size_t /*position*/, const std::string& /*last_token*/, const json::exception& /*ex*/) override
+ {
+ return false;
+ }
+
+ private:
+ int events_left = 0;
+};
+
+json parser_helper(const std::string& s);
+bool accept_helper(const std::string& s);
+void comments_helper(const std::string& s);
+
+json parser_helper(const std::string& s)
+{
+ json j;
+ json::parser(nlohmann::detail::input_adapter(s)).parse(true, j);
+
+ // if this line was reached, no exception occurred
+ // -> check if result is the same without exceptions
+ json j_nothrow;
+ CHECK_NOTHROW(json::parser(nlohmann::detail::input_adapter(s), nullptr, false).parse(true, j_nothrow));
+ CHECK(j_nothrow == j);
+
+ json j_sax;
+ nlohmann::detail::json_sax_dom_parser<json, nlohmann::detail::string_input_adapter_type> sdp(j_sax);
+ json::sax_parse(s, &sdp);
+ CHECK(j_sax == j);
+
+ comments_helper(s);
+
+ return j;
+}
+
+bool accept_helper(const std::string& s)
+{
+ CAPTURE(s)
+
+ // 1. parse s without exceptions
+ json j;
+ CHECK_NOTHROW(json::parser(nlohmann::detail::input_adapter(s), nullptr, false).parse(true, j));
+ const bool ok_noexcept = !j.is_discarded();
+
+ // 2. accept s
+ const bool ok_accept = json::parser(nlohmann::detail::input_adapter(s)).accept(true);
+
+ // 3. check if both approaches come to the same result
+ CHECK(ok_noexcept == ok_accept);
+
+ // 4. parse with SAX (compare with relaxed accept result)
+ SaxEventLogger el;
+ CHECK_NOTHROW(json::sax_parse(s, &el, json::input_format_t::json, false));
+ CHECK(json::parser(nlohmann::detail::input_adapter(s)).accept(false) == !el.errored);
+
+ // 5. parse with simple callback
+ json::parser_callback_t const cb = [](int /*unused*/, json::parse_event_t /*unused*/, json& /*unused*/) noexcept
+ {
+ return true;
+ };
+ json const j_cb = json::parse(s, cb, false);
+ const bool ok_noexcept_cb = !j_cb.is_discarded();
+
+ // 6. check if this approach came to the same result
+ CHECK(ok_noexcept == ok_noexcept_cb);
+
+ // 7. check if comments are properly ignored
+ if (ok_accept)
+ {
+ comments_helper(s);
+ }
+
+ // 8. return result
+ return ok_accept;
+}
+
+void comments_helper(const std::string& s)
+{
+ json _;
+
+ // parse/accept with default parser
+ CHECK_NOTHROW(_ = json::parse(s));
+ CHECK(json::accept(s));
+
+ // parse/accept while skipping comments
+ CHECK_NOTHROW(_ = json::parse(s, nullptr, false, true));
+ CHECK(json::accept(s, true));
+
+ std::vector<std::string> json_with_comments;
+
+ // start with a comment
+ json_with_comments.push_back(std::string("// this is a comment\n") + s);
+ json_with_comments.push_back(std::string("/* this is a comment */") + s);
+ // end with a comment
+ json_with_comments.push_back(s + "// this is a comment");
+ json_with_comments.push_back(s + "/* this is a comment */");
+
+ // check all strings
+ for (const auto& json_with_comment : json_with_comments)
+ {
+ CAPTURE(json_with_comment)
+ CHECK_THROWS_AS(_ = json::parse(json_with_comment), json::parse_error);
+ CHECK(!json::accept(json_with_comment));
+
+ CHECK_NOTHROW(_ = json::parse(json_with_comment, nullptr, true, true));
+ CHECK(json::accept(json_with_comment, true));
+ }
+}
+
+/**
+ * Validates that the generated JSON object is the same as expected
+ * Validates that the start position and end position match the start and end of the string
+ *
+ * This check assumes that there is no whitespace around the json object in the original string.
+ */
+void validate_generated_json_and_start_end_pos_helper(const std::string& original_string, const json& j, const json& check)
+{
+ CHECK(j == check);
+ CHECK(j.start_pos() == 0);
+ CHECK(j.end_pos() == original_string.size());
+}
+
+/**
+ * Parses the root object from the given root string and validates that the start and end positions for the nested object are correct.
+ *
+ * This checks that whitespace around the nested object is included in the start and end positions of the root object.
+ */
+void validate_start_end_pos_for_nested_obj_helper(const std::string& nested_type_json_str, const std::string& root_type_json_str, const json& expected_json, const json::parser_callback_t& cb = nullptr)
+{
+ json j;
+
+ // 1. If callback is provided, use callback version of parse()
+ if (cb)
+ {
+ j = json::parse(root_type_json_str, cb);
+ }
+ else
+ {
+ j = json::parse(root_type_json_str);
+ }
+
+ // 2. Check if the generated JSON is as expected
+ // Assumptions: The root_type_json_str does not have any whitespace around the json object
+ validate_generated_json_and_start_end_pos_helper(root_type_json_str, j, expected_json);
+
+ // 3. Get the nested object
+ const auto& nested = j["nested"];
+ // 4. Check if the start and end positions are generated correctly for nested objects and arrays
+ CHECK(nested_type_json_str == root_type_json_str.substr(nested.start_pos(), nested.end_pos() - nested.start_pos()));
+}
+
+} // namespace
+
+TEST_CASE("parser class")
+{
+ SECTION("parse")
+ {
+ SECTION("null")
+ {
+ CHECK(parser_helper("null") == json(nullptr));
+ }
+
+ SECTION("true")
+ {
+ CHECK(parser_helper("true") == json(true));
+ }
+
+ SECTION("false")
+ {
+ CHECK(parser_helper("false") == json(false));
+ }
+
+ SECTION("array")
+ {
+ SECTION("empty array")
+ {
+ CHECK(parser_helper("[]") == json(json::value_t::array));
+ CHECK(parser_helper("[ ]") == json(json::value_t::array));
+ }
+
+ SECTION("nonempty array")
+ {
+ CHECK(parser_helper("[true, false, null]") == json({true, false, nullptr}));
+ }
+ }
+
+ SECTION("object")
+ {
+ SECTION("empty object")
+ {
+ CHECK(parser_helper("{}") == json(json::value_t::object));
+ CHECK(parser_helper("{ }") == json(json::value_t::object));
+ }
+
+ SECTION("nonempty object")
+ {
+ CHECK(parser_helper("{\"\": true, \"one\": 1, \"two\": null}") == json({{"", true}, {"one", 1}, {"two", nullptr}}));
+ }
+ }
+
+ SECTION("string")
+ {
+ // empty string
+ CHECK(parser_helper("\"\"") == json(json::value_t::string));
+
+ SECTION("errors")
+ {
+ // error: tab in string
+ CHECK_THROWS_WITH_AS(parser_helper("\"\t\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t; last read: '\"<U+0009>'", json::parse_error&);
+ // error: newline in string
+ CHECK_THROWS_WITH_AS(parser_helper("\"\n\""), "[json.exception.parse_error.101] parse error at line 2, column 0: syntax error while parsing value - invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n; last read: '\"<U+000A>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\r\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r; last read: '\"<U+000D>'", json::parse_error&);
+ // error: backspace in string
+ CHECK_THROWS_WITH_AS(parser_helper("\"\b\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b; last read: '\"<U+0008>'", json::parse_error&);
+ // improve code coverage
+ CHECK_THROWS_AS(parser_helper("\uFF01"), json::parse_error&);
+ CHECK_THROWS_AS(parser_helper("[-4:1,]"), json::parse_error&);
+ // unescaped control characters
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x00\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: missing closing quote; last read: '\"'", json::parse_error&); // NOLINT(bugprone-string-literal-with-embedded-nul)
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x01\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0001 (SOH) must be escaped to \\u0001; last read: '\"<U+0001>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x02\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0002 (STX) must be escaped to \\u0002; last read: '\"<U+0002>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x03\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0003 (ETX) must be escaped to \\u0003; last read: '\"<U+0003>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x04\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0004 (EOT) must be escaped to \\u0004; last read: '\"<U+0004>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x05\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0005 (ENQ) must be escaped to \\u0005; last read: '\"<U+0005>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x06\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0006 (ACK) must be escaped to \\u0006; last read: '\"<U+0006>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x07\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0007 (BEL) must be escaped to \\u0007; last read: '\"<U+0007>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x08\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b; last read: '\"<U+0008>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x09\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t; last read: '\"<U+0009>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x0a\""), "[json.exception.parse_error.101] parse error at line 2, column 0: syntax error while parsing value - invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n; last read: '\"<U+000A>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x0b\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+000B (VT) must be escaped to \\u000B; last read: '\"<U+000B>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x0c\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+000C (FF) must be escaped to \\u000C or \\f; last read: '\"<U+000C>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x0d\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r; last read: '\"<U+000D>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x0e\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+000E (SO) must be escaped to \\u000E; last read: '\"<U+000E>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x0f\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+000F (SI) must be escaped to \\u000F; last read: '\"<U+000F>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x10\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0010 (DLE) must be escaped to \\u0010; last read: '\"<U+0010>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x11\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0011 (DC1) must be escaped to \\u0011; last read: '\"<U+0011>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x12\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0012 (DC2) must be escaped to \\u0012; last read: '\"<U+0012>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x13\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0013 (DC3) must be escaped to \\u0013; last read: '\"<U+0013>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x14\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0014 (DC4) must be escaped to \\u0014; last read: '\"<U+0014>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x15\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0015 (NAK) must be escaped to \\u0015; last read: '\"<U+0015>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x16\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0016 (SYN) must be escaped to \\u0016; last read: '\"<U+0016>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x17\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0017 (ETB) must be escaped to \\u0017; last read: '\"<U+0017>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x18\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0018 (CAN) must be escaped to \\u0018; last read: '\"<U+0018>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x19\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0019 (EM) must be escaped to \\u0019; last read: '\"<U+0019>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x1a\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+001A (SUB) must be escaped to \\u001A; last read: '\"<U+001A>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x1b\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+001B (ESC) must be escaped to \\u001B; last read: '\"<U+001B>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x1c\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+001C (FS) must be escaped to \\u001C; last read: '\"<U+001C>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x1d\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+001D (GS) must be escaped to \\u001D; last read: '\"<U+001D>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x1e\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+001E (RS) must be escaped to \\u001E; last read: '\"<U+001E>'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\x1f\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+001F (US) must be escaped to \\u001F; last read: '\"<U+001F>'", json::parse_error&);
+
+ SECTION("additional test for null byte")
+ {
+ // The test above for the null byte is wrong, because passing
+ // a string to the parser only reads int until it encounters
+ // a null byte. This test inserts the null byte later on and
+ // uses an iterator range.
+ std::string s = "\"1\"";
+ s[1] = '\0';
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::parse(s.begin(), s.end()), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0000 (NUL) must be escaped to \\u0000; last read: '\"<U+0000>'", json::parse_error&);
+ }
+ }
+
+ SECTION("escaped")
+ {
+ // quotation mark "\""
+ auto r1 = R"("\"")"_json;
+ CHECK(parser_helper("\"\\\"\"") == r1);
+ // reverse solidus "\\"
+ auto r2 = R"("\\")"_json;
+ CHECK(parser_helper("\"\\\\\"") == r2);
+ // solidus
+ CHECK(parser_helper("\"\\/\"") == R"("/")"_json);
+ // backspace
+ CHECK(parser_helper("\"\\b\"") == json("\b"));
+ // formfeed
+ CHECK(parser_helper("\"\\f\"") == json("\f"));
+ // newline
+ CHECK(parser_helper("\"\\n\"") == json("\n"));
+ // carriage return
+ CHECK(parser_helper("\"\\r\"") == json("\r"));
+ // horizontal tab
+ CHECK(parser_helper("\"\\t\"") == json("\t"));
+
+ CHECK(parser_helper("\"\\u0001\"").get<json::string_t>() == "\x01");
+ CHECK(parser_helper("\"\\u000a\"").get<json::string_t>() == "\n");
+ CHECK(parser_helper("\"\\u00b0\"").get<json::string_t>() == "°");
+ CHECK(parser_helper("\"\\u0c00\"").get<json::string_t>() == "ఀ");
+ CHECK(parser_helper("\"\\ud000\"").get<json::string_t>() == "퀀");
+ CHECK(parser_helper("\"\\u000E\"").get<json::string_t>() == "\x0E");
+ CHECK(parser_helper("\"\\u00F0\"").get<json::string_t>() == "ð");
+ CHECK(parser_helper("\"\\u0100\"").get<json::string_t>() == "Ā");
+ CHECK(parser_helper("\"\\u2000\"").get<json::string_t>() == " ");
+ CHECK(parser_helper("\"\\uFFFF\"").get<json::string_t>() == "￿");
+ CHECK(parser_helper("\"\\u20AC\"").get<json::string_t>() == "€");
+ CHECK(parser_helper("\"€\"").get<json::string_t>() == "€");
+ CHECK(parser_helper("\"🎈\"").get<json::string_t>() == "🎈");
+
+ CHECK(parser_helper("\"\\ud80c\\udc60\"").get<json::string_t>() == "\xf0\x93\x81\xa0");
+ CHECK(parser_helper("\"\\ud83c\\udf1e\"").get<json::string_t>() == "🌞");
+ }
+ }
+
+ SECTION("number")
+ {
+ SECTION("integers")
+ {
+ SECTION("without exponent")
+ {
+ CHECK(parser_helper("-128") == json(-128));
+ CHECK(parser_helper("-0") == json(-0));
+ CHECK(parser_helper("0") == json(0));
+ CHECK(parser_helper("128") == json(128));
+ }
+
+ SECTION("with exponent")
+ {
+ CHECK(parser_helper("0e1") == json(0e1));
+ CHECK(parser_helper("0E1") == json(0e1));
+
+ CHECK(parser_helper("10000E-4") == json(10000e-4));
+ CHECK(parser_helper("10000E-3") == json(10000e-3));
+ CHECK(parser_helper("10000E-2") == json(10000e-2));
+ CHECK(parser_helper("10000E-1") == json(10000e-1));
+ CHECK(parser_helper("10000E0") == json(10000e0));
+ CHECK(parser_helper("10000E1") == json(10000e1));
+ CHECK(parser_helper("10000E2") == json(10000e2));
+ CHECK(parser_helper("10000E3") == json(10000e3));
+ CHECK(parser_helper("10000E4") == json(10000e4));
+
+ CHECK(parser_helper("10000e-4") == json(10000e-4));
+ CHECK(parser_helper("10000e-3") == json(10000e-3));
+ CHECK(parser_helper("10000e-2") == json(10000e-2));
+ CHECK(parser_helper("10000e-1") == json(10000e-1));
+ CHECK(parser_helper("10000e0") == json(10000e0));
+ CHECK(parser_helper("10000e1") == json(10000e1));
+ CHECK(parser_helper("10000e2") == json(10000e2));
+ CHECK(parser_helper("10000e3") == json(10000e3));
+ CHECK(parser_helper("10000e4") == json(10000e4));
+
+ CHECK(parser_helper("-0e1") == json(-0e1));
+ CHECK(parser_helper("-0E1") == json(-0e1));
+ CHECK(parser_helper("-0E123") == json(-0e123));
+
+ // numbers after exponent
+ CHECK(parser_helper("10E0") == json(10e0));
+ CHECK(parser_helper("10E1") == json(10e1));
+ CHECK(parser_helper("10E2") == json(10e2));
+ CHECK(parser_helper("10E3") == json(10e3));
+ CHECK(parser_helper("10E4") == json(10e4));
+ CHECK(parser_helper("10E5") == json(10e5));
+ CHECK(parser_helper("10E6") == json(10e6));
+ CHECK(parser_helper("10E7") == json(10e7));
+ CHECK(parser_helper("10E8") == json(10e8));
+ CHECK(parser_helper("10E9") == json(10e9));
+ CHECK(parser_helper("10E+0") == json(10e0));
+ CHECK(parser_helper("10E+1") == json(10e1));
+ CHECK(parser_helper("10E+2") == json(10e2));
+ CHECK(parser_helper("10E+3") == json(10e3));
+ CHECK(parser_helper("10E+4") == json(10e4));
+ CHECK(parser_helper("10E+5") == json(10e5));
+ CHECK(parser_helper("10E+6") == json(10e6));
+ CHECK(parser_helper("10E+7") == json(10e7));
+ CHECK(parser_helper("10E+8") == json(10e8));
+ CHECK(parser_helper("10E+9") == json(10e9));
+ CHECK(parser_helper("10E-1") == json(10e-1));
+ CHECK(parser_helper("10E-2") == json(10e-2));
+ CHECK(parser_helper("10E-3") == json(10e-3));
+ CHECK(parser_helper("10E-4") == json(10e-4));
+ CHECK(parser_helper("10E-5") == json(10e-5));
+ CHECK(parser_helper("10E-6") == json(10e-6));
+ CHECK(parser_helper("10E-7") == json(10e-7));
+ CHECK(parser_helper("10E-8") == json(10e-8));
+ CHECK(parser_helper("10E-9") == json(10e-9));
+ }
+
+ SECTION("edge cases")
+ {
+ // From RFC8259, Section 6:
+ // Note that when such software is used, numbers that are
+ // integers and are in the range [-(2**53)+1, (2**53)-1]
+ // are interoperable in the sense that implementations will
+ // agree exactly on their numeric values.
+
+ // -(2**53)+1
+ CHECK(parser_helper("-9007199254740991").get<int64_t>() == -9007199254740991);
+ // (2**53)-1
+ CHECK(parser_helper("9007199254740991").get<int64_t>() == 9007199254740991);
+ }
+
+ SECTION("over the edge cases") // issue #178 - Integer conversion to unsigned (incorrect handling of 64-bit integers)
+ {
+ // While RFC8259, Section 6 specifies a preference for support
+ // for ranges in range of IEEE 754-2008 binary64 (double precision)
+ // this does not accommodate 64-bit integers without loss of accuracy.
+ // As 64-bit integers are now widely used in software, it is desirable
+ // to expand support to the full 64 bit (signed and unsigned) range
+ // i.e. -(2**63) -> (2**64)-1.
+
+ // -(2**63) ** Note: compilers see negative literals as negated positive numbers (hence the -1))
+ CHECK(parser_helper("-9223372036854775808").get<int64_t>() == -9223372036854775807 - 1);
+ // (2**63)-1
+ CHECK(parser_helper("9223372036854775807").get<int64_t>() == 9223372036854775807);
+ // (2**64)-1
+ CHECK(parser_helper("18446744073709551615").get<uint64_t>() == 18446744073709551615u);
+ }
+ }
+
+ SECTION("floating-point")
+ {
+ SECTION("without exponent")
+ {
+ CHECK(parser_helper("-128.5") == json(-128.5));
+ CHECK(parser_helper("0.999") == json(0.999));
+ CHECK(parser_helper("128.5") == json(128.5));
+ CHECK(parser_helper("-0.0") == json(-0.0));
+ }
+
+ SECTION("with exponent")
+ {
+ CHECK(parser_helper("-128.5E3") == json(-128.5E3));
+ CHECK(parser_helper("-128.5E-3") == json(-128.5E-3));
+ CHECK(parser_helper("-0.0e1") == json(-0.0e1));
+ CHECK(parser_helper("-0.0E1") == json(-0.0e1));
+ }
+ }
+
+ SECTION("overflow")
+ {
+ // overflows during parsing yield an exception
+ CHECK_THROWS_WITH_AS(parser_helper("1.18973e+4932").empty(), "[json.exception.out_of_range.406] number overflow parsing '1.18973e+4932'", json::out_of_range&);
+ }
+
+ SECTION("invalid numbers")
+ {
+ // numbers must not begin with "+"
+ CHECK_THROWS_AS(parser_helper("+1"), json::parse_error&);
+ CHECK_THROWS_AS(parser_helper("+0"), json::parse_error&);
+
+ CHECK_THROWS_WITH_AS(parser_helper("01"),
+ "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - unexpected number literal; expected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("-01"),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - unexpected number literal; expected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("--1"),
+ "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid number; expected digit after '-'; last read: '--'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("1."),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid number; expected digit after '.'; last read: '1.'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("1E"),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid number; expected '+', '-', or digit after exponent; last read: '1E'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("1E-"),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid number; expected digit after exponent sign; last read: '1E-'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("1.E1"),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid number; expected digit after '.'; last read: '1.E'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("-1E"),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid number; expected '+', '-', or digit after exponent; last read: '-1E'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("-0E#"),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid number; expected '+', '-', or digit after exponent; last read: '-0E#'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("-0E-#"),
+ "[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - invalid number; expected digit after exponent sign; last read: '-0E-#'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("-0#"),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid literal; last read: '-0#'; expected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("-0.0:"),
+ "[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - unexpected ':'; expected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("-0.0Z"),
+ "[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - invalid literal; last read: '-0.0Z'; expected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("-0E123:"),
+ "[json.exception.parse_error.101] parse error at line 1, column 7: syntax error while parsing value - unexpected ':'; expected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("-0e0-:"),
+ "[json.exception.parse_error.101] parse error at line 1, column 6: syntax error while parsing value - invalid number; expected digit after '-'; last read: '-:'; expected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("-0e-:"),
+ "[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - invalid number; expected digit after exponent sign; last read: '-0e-:'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("-0f"),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid literal; last read: '-0f'; expected end of input", json::parse_error&);
+ }
+ }
+ }
+
+ SECTION("accept")
+ {
+ SECTION("null")
+ {
+ CHECK(accept_helper("null"));
+ }
+
+ SECTION("true")
+ {
+ CHECK(accept_helper("true"));
+ }
+
+ SECTION("false")
+ {
+ CHECK(accept_helper("false"));
+ }
+
+ SECTION("array")
+ {
+ SECTION("empty array")
+ {
+ CHECK(accept_helper("[]"));
+ CHECK(accept_helper("[ ]"));
+ }
+
+ SECTION("nonempty array")
+ {
+ CHECK(accept_helper("[true, false, null]"));
+ }
+ }
+
+ SECTION("object")
+ {
+ SECTION("empty object")
+ {
+ CHECK(accept_helper("{}"));
+ CHECK(accept_helper("{ }"));
+ }
+
+ SECTION("nonempty object")
+ {
+ CHECK(accept_helper("{\"\": true, \"one\": 1, \"two\": null}"));
+ }
+ }
+
+ SECTION("string")
+ {
+ // empty string
+ CHECK(accept_helper("\"\""));
+
+ SECTION("errors")
+ {
+ // error: tab in string
+ CHECK(accept_helper("\"\t\"") == false);
+ // error: newline in string
+ CHECK(accept_helper("\"\n\"") == false);
+ CHECK(accept_helper("\"\r\"") == false);
+ // error: backspace in string
+ CHECK(accept_helper("\"\b\"") == false);
+ // improve code coverage
+ CHECK(accept_helper("\uFF01") == false);
+ CHECK(accept_helper("[-4:1,]") == false);
+ // unescaped control characters
+ CHECK(accept_helper("\"\x00\"") == false); // NOLINT(bugprone-string-literal-with-embedded-nul)
+ CHECK(accept_helper("\"\x01\"") == false);
+ CHECK(accept_helper("\"\x02\"") == false);
+ CHECK(accept_helper("\"\x03\"") == false);
+ CHECK(accept_helper("\"\x04\"") == false);
+ CHECK(accept_helper("\"\x05\"") == false);
+ CHECK(accept_helper("\"\x06\"") == false);
+ CHECK(accept_helper("\"\x07\"") == false);
+ CHECK(accept_helper("\"\x08\"") == false);
+ CHECK(accept_helper("\"\x09\"") == false);
+ CHECK(accept_helper("\"\x0a\"") == false);
+ CHECK(accept_helper("\"\x0b\"") == false);
+ CHECK(accept_helper("\"\x0c\"") == false);
+ CHECK(accept_helper("\"\x0d\"") == false);
+ CHECK(accept_helper("\"\x0e\"") == false);
+ CHECK(accept_helper("\"\x0f\"") == false);
+ CHECK(accept_helper("\"\x10\"") == false);
+ CHECK(accept_helper("\"\x11\"") == false);
+ CHECK(accept_helper("\"\x12\"") == false);
+ CHECK(accept_helper("\"\x13\"") == false);
+ CHECK(accept_helper("\"\x14\"") == false);
+ CHECK(accept_helper("\"\x15\"") == false);
+ CHECK(accept_helper("\"\x16\"") == false);
+ CHECK(accept_helper("\"\x17\"") == false);
+ CHECK(accept_helper("\"\x18\"") == false);
+ CHECK(accept_helper("\"\x19\"") == false);
+ CHECK(accept_helper("\"\x1a\"") == false);
+ CHECK(accept_helper("\"\x1b\"") == false);
+ CHECK(accept_helper("\"\x1c\"") == false);
+ CHECK(accept_helper("\"\x1d\"") == false);
+ CHECK(accept_helper("\"\x1e\"") == false);
+ CHECK(accept_helper("\"\x1f\"") == false);
+ }
+
+ SECTION("escaped")
+ {
+ // quotation mark "\""
+ auto r1 = R"("\"")"_json;
+ CHECK(accept_helper("\"\\\"\""));
+ // reverse solidus "\\"
+ auto r2 = R"("\\")"_json;
+ CHECK(accept_helper("\"\\\\\""));
+ // solidus
+ CHECK(accept_helper("\"\\/\""));
+ // backspace
+ CHECK(accept_helper("\"\\b\""));
+ // formfeed
+ CHECK(accept_helper("\"\\f\""));
+ // newline
+ CHECK(accept_helper("\"\\n\""));
+ // carriage return
+ CHECK(accept_helper("\"\\r\""));
+ // horizontal tab
+ CHECK(accept_helper("\"\\t\""));
+
+ CHECK(accept_helper("\"\\u0001\""));
+ CHECK(accept_helper("\"\\u000a\""));
+ CHECK(accept_helper("\"\\u00b0\""));
+ CHECK(accept_helper("\"\\u0c00\""));
+ CHECK(accept_helper("\"\\ud000\""));
+ CHECK(accept_helper("\"\\u000E\""));
+ CHECK(accept_helper("\"\\u00F0\""));
+ CHECK(accept_helper("\"\\u0100\""));
+ CHECK(accept_helper("\"\\u2000\""));
+ CHECK(accept_helper("\"\\uFFFF\""));
+ CHECK(accept_helper("\"\\u20AC\""));
+ CHECK(accept_helper("\"€\""));
+ CHECK(accept_helper("\"🎈\""));
+
+ CHECK(accept_helper("\"\\ud80c\\udc60\""));
+ CHECK(accept_helper("\"\\ud83c\\udf1e\""));
+ }
+ }
+
+ SECTION("number")
+ {
+ SECTION("integers")
+ {
+ SECTION("without exponent")
+ {
+ CHECK(accept_helper("-128"));
+ CHECK(accept_helper("-0"));
+ CHECK(accept_helper("0"));
+ CHECK(accept_helper("128"));
+ }
+
+ SECTION("with exponent")
+ {
+ CHECK(accept_helper("0e1"));
+ CHECK(accept_helper("0E1"));
+
+ CHECK(accept_helper("10000E-4"));
+ CHECK(accept_helper("10000E-3"));
+ CHECK(accept_helper("10000E-2"));
+ CHECK(accept_helper("10000E-1"));
+ CHECK(accept_helper("10000E0"));
+ CHECK(accept_helper("10000E1"));
+ CHECK(accept_helper("10000E2"));
+ CHECK(accept_helper("10000E3"));
+ CHECK(accept_helper("10000E4"));
+
+ CHECK(accept_helper("10000e-4"));
+ CHECK(accept_helper("10000e-3"));
+ CHECK(accept_helper("10000e-2"));
+ CHECK(accept_helper("10000e-1"));
+ CHECK(accept_helper("10000e0"));
+ CHECK(accept_helper("10000e1"));
+ CHECK(accept_helper("10000e2"));
+ CHECK(accept_helper("10000e3"));
+ CHECK(accept_helper("10000e4"));
+
+ CHECK(accept_helper("-0e1"));
+ CHECK(accept_helper("-0E1"));
+ CHECK(accept_helper("-0E123"));
+ }
+
+ SECTION("edge cases")
+ {
+ // From RFC8259, Section 6:
+ // Note that when such software is used, numbers that are
+ // integers and are in the range [-(2**53)+1, (2**53)-1]
+ // are interoperable in the sense that implementations will
+ // agree exactly on their numeric values.
+
+ // -(2**53)+1
+ CHECK(accept_helper("-9007199254740991"));
+ // (2**53)-1
+ CHECK(accept_helper("9007199254740991"));
+ }
+
+ SECTION("over the edge cases") // issue #178 - Integer conversion to unsigned (incorrect handling of 64-bit integers)
+ {
+ // While RFC8259, Section 6 specifies a preference for support
+ // for ranges in range of IEEE 754-2008 binary64 (double precision)
+ // this does not accommodate 64 bit integers without loss of accuracy.
+ // As 64 bit integers are now widely used in software, it is desirable
+ // to expand support to the full 64 bit (signed and unsigned) range
+ // i.e. -(2**63) -> (2**64)-1.
+
+ // -(2**63) ** Note: compilers see negative literals as negated positive numbers (hence the -1))
+ CHECK(accept_helper("-9223372036854775808"));
+ // (2**63)-1
+ CHECK(accept_helper("9223372036854775807"));
+ // (2**64)-1
+ CHECK(accept_helper("18446744073709551615"));
+ }
+ }
+
+ SECTION("floating-point")
+ {
+ SECTION("without exponent")
+ {
+ CHECK(accept_helper("-128.5"));
+ CHECK(accept_helper("0.999"));
+ CHECK(accept_helper("128.5"));
+ CHECK(accept_helper("-0.0"));
+ }
+
+ SECTION("with exponent")
+ {
+ CHECK(accept_helper("-128.5E3"));
+ CHECK(accept_helper("-128.5E-3"));
+ CHECK(accept_helper("-0.0e1"));
+ CHECK(accept_helper("-0.0E1"));
+ }
+ }
+
+ SECTION("overflow")
+ {
+ // overflows during parsing
+ CHECK(!accept_helper("1.18973e+4932"));
+ }
+
+ SECTION("invalid numbers")
+ {
+ CHECK(accept_helper("01") == false);
+ CHECK(accept_helper("--1") == false);
+ CHECK(accept_helper("1.") == false);
+ CHECK(accept_helper("1E") == false);
+ CHECK(accept_helper("1E-") == false);
+ CHECK(accept_helper("1.E1") == false);
+ CHECK(accept_helper("-1E") == false);
+ CHECK(accept_helper("-0E#") == false);
+ CHECK(accept_helper("-0E-#") == false);
+ CHECK(accept_helper("-0#") == false);
+ CHECK(accept_helper("-0.0:") == false);
+ CHECK(accept_helper("-0.0Z") == false);
+ CHECK(accept_helper("-0E123:") == false);
+ CHECK(accept_helper("-0e0-:") == false);
+ CHECK(accept_helper("-0e-:") == false);
+ CHECK(accept_helper("-0f") == false);
+
+ // numbers must not begin with "+"
+ CHECK(accept_helper("+1") == false);
+ CHECK(accept_helper("+0") == false);
+ }
+ }
+ }
+
+ SECTION("parse errors")
+ {
+ // unexpected end of number
+ CHECK_THROWS_WITH_AS(parser_helper("0."),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid number; expected digit after '.'; last read: '0.'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("-"),
+ "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid number; expected digit after '-'; last read: '-'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("--"),
+ "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid number; expected digit after '-'; last read: '--'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("-0."),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid number; expected digit after '.'; last read: '-0.'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("-."),
+ "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid number; expected digit after '-'; last read: '-.'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("-:"),
+ "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid number; expected digit after '-'; last read: '-:'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("0.:"),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid number; expected digit after '.'; last read: '0.:'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("e."),
+ "[json.exception.parse_error.101] parse error at line 1, column 1: syntax error while parsing value - invalid literal; last read: 'e'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("1e."),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid number; expected '+', '-', or digit after exponent; last read: '1e.'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("1e/"),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid number; expected '+', '-', or digit after exponent; last read: '1e/'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("1e:"),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid number; expected '+', '-', or digit after exponent; last read: '1e:'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("1E."),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid number; expected '+', '-', or digit after exponent; last read: '1E.'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("1E/"),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid number; expected '+', '-', or digit after exponent; last read: '1E/'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("1E:"),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid number; expected '+', '-', or digit after exponent; last read: '1E:'", json::parse_error&);
+
+ // unexpected end of null
+ CHECK_THROWS_WITH_AS(parser_helper("n"),
+ "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid literal; last read: 'n'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("nu"),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid literal; last read: 'nu'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("nul"),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid literal; last read: 'nul'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("nulk"),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid literal; last read: 'nulk'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("nulm"),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid literal; last read: 'nulm'", json::parse_error&);
+
+ // unexpected end of true
+ CHECK_THROWS_WITH_AS(parser_helper("t"),
+ "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid literal; last read: 't'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("tr"),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid literal; last read: 'tr'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("tru"),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid literal; last read: 'tru'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("trud"),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid literal; last read: 'trud'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("truf"),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid literal; last read: 'truf'", json::parse_error&);
+
+ // unexpected end of false
+ CHECK_THROWS_WITH_AS(parser_helper("f"),
+ "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid literal; last read: 'f'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("fa"),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid literal; last read: 'fa'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("fal"),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid literal; last read: 'fal'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("fals"),
+ "[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - invalid literal; last read: 'fals'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("falsd"),
+ "[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - invalid literal; last read: 'falsd'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("falsf"),
+ "[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - invalid literal; last read: 'falsf'", json::parse_error&);
+
+ // missing/unexpected end of array
+ CHECK_THROWS_WITH_AS(parser_helper("["),
+ "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - unexpected end of input; expected '[', '{', or a literal", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("[1"),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing array - unexpected end of input; expected ']'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("[1,"),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - unexpected end of input; expected '[', '{', or a literal", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("[1,]"),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - unexpected ']'; expected '[', '{', or a literal", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("]"),
+ "[json.exception.parse_error.101] parse error at line 1, column 1: syntax error while parsing value - unexpected ']'; expected '[', '{', or a literal", json::parse_error&);
+
+ // missing/unexpected end of object
+ CHECK_THROWS_WITH_AS(parser_helper("{"),
+ "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing object key - unexpected end of input; expected string literal", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("{\"foo\""),
+ "[json.exception.parse_error.101] parse error at line 1, column 7: syntax error while parsing object separator - unexpected end of input; expected ':'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("{\"foo\":"),
+ "[json.exception.parse_error.101] parse error at line 1, column 8: syntax error while parsing value - unexpected end of input; expected '[', '{', or a literal", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("{\"foo\":}"),
+ "[json.exception.parse_error.101] parse error at line 1, column 8: syntax error while parsing value - unexpected '}'; expected '[', '{', or a literal", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("{\"foo\":1,}"),
+ "[json.exception.parse_error.101] parse error at line 1, column 10: syntax error while parsing object key - unexpected '}'; expected string literal", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("}"),
+ "[json.exception.parse_error.101] parse error at line 1, column 1: syntax error while parsing value - unexpected '}'; expected '[', '{', or a literal", json::parse_error&);
+
+ // missing/unexpected end of string
+ CHECK_THROWS_WITH_AS(parser_helper("\""),
+ "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: missing closing quote; last read: '\"'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\\\""),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid string: missing closing quote; last read: '\"\\\"'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\\u\""),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid string: '\\u' must be followed by 4 hex digits; last read: '\"\\u\"'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\\u0\""),
+ "[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - invalid string: '\\u' must be followed by 4 hex digits; last read: '\"\\u0\"'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\\u01\""),
+ "[json.exception.parse_error.101] parse error at line 1, column 6: syntax error while parsing value - invalid string: '\\u' must be followed by 4 hex digits; last read: '\"\\u01\"'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\\u012\""),
+ "[json.exception.parse_error.101] parse error at line 1, column 7: syntax error while parsing value - invalid string: '\\u' must be followed by 4 hex digits; last read: '\"\\u012\"'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\\u"),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid string: '\\u' must be followed by 4 hex digits; last read: '\"\\u'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\\u0"),
+ "[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - invalid string: '\\u' must be followed by 4 hex digits; last read: '\"\\u0'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\\u01"),
+ "[json.exception.parse_error.101] parse error at line 1, column 6: syntax error while parsing value - invalid string: '\\u' must be followed by 4 hex digits; last read: '\"\\u01'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(parser_helper("\"\\u012"),
+ "[json.exception.parse_error.101] parse error at line 1, column 7: syntax error while parsing value - invalid string: '\\u' must be followed by 4 hex digits; last read: '\"\\u012'", json::parse_error&);
+
+ // invalid escapes
+ for (int c = 1; c < 128; ++c)
+ {
+ auto s = std::string("\"\\") + std::string(1, static_cast<char>(c)) + "\"";
+
+ switch (c)
+ {
+ // valid escapes
+ case ('"'):
+ case ('\\'):
+ case ('/'):
+ case ('b'):
+ case ('f'):
+ case ('n'):
+ case ('r'):
+ case ('t'):
+ {
+ CHECK_NOTHROW(parser_helper(s));
+ break;
+ }
+
+ // \u must be followed with four numbers, so we skip it here
+ case ('u'):
+ {
+ break;
+ }
+
+ // any other combination of backslash and character is invalid
+ default:
+ {
+ CHECK_THROWS_AS(parser_helper(s), json::parse_error&);
+ // only check error message if c is not a control character
+ if (c > 0x1f)
+ {
+ CHECK_THROWS_WITH_STD_STR(parser_helper(s),
+ "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid string: forbidden character after backslash; last read: '\"\\" + std::string(1, static_cast<char>(c)) + "'");
+ }
+ break;
+ }
+ }
+ }
+
+ // invalid \uxxxx escapes
+ {
+ // check whether character is a valid hex character
+ const auto valid = [](int c)
+ {
+ switch (c)
+ {
+ case ('0'):
+ case ('1'):
+ case ('2'):
+ case ('3'):
+ case ('4'):
+ case ('5'):
+ case ('6'):
+ case ('7'):
+ case ('8'):
+ case ('9'):
+ case ('a'):
+ case ('b'):
+ case ('c'):
+ case ('d'):
+ case ('e'):
+ case ('f'):
+ case ('A'):
+ case ('B'):
+ case ('C'):
+ case ('D'):
+ case ('E'):
+ case ('F'):
+ {
+ return true;
+ }
+
+ default:
+ {
+ return false;
+ }
+ }
+ };
+
+ for (int c = 1; c < 128; ++c)
+ {
+ std::string const s = "\"\\u";
+
+ // create a string with the iterated character at each position
+ auto s1 = s + "000" + std::string(1, static_cast<char>(c)) + "\"";
+ auto s2 = s + "00" + std::string(1, static_cast<char>(c)) + "0\"";
+ auto s3 = s + "0" + std::string(1, static_cast<char>(c)) + "00\"";
+ auto s4 = s + std::string(1, static_cast<char>(c)) + "000\"";
+
+ if (valid(c))
+ {
+ CAPTURE(s1)
+ CHECK_NOTHROW(parser_helper(s1));
+ CAPTURE(s2)
+ CHECK_NOTHROW(parser_helper(s2));
+ CAPTURE(s3)
+ CHECK_NOTHROW(parser_helper(s3));
+ CAPTURE(s4)
+ CHECK_NOTHROW(parser_helper(s4));
+ }
+ else
+ {
+ CAPTURE(s1)
+ CHECK_THROWS_AS(parser_helper(s1), json::parse_error&);
+ // only check error message if c is not a control character
+ if (c > 0x1f)
+ {
+ CHECK_THROWS_WITH_STD_STR(parser_helper(s1),
+ "[json.exception.parse_error.101] parse error at line 1, column 7: syntax error while parsing value - invalid string: '\\u' must be followed by 4 hex digits; last read: '" + s1.substr(0, 7) + "'");
+ }
+
+ CAPTURE(s2)
+ CHECK_THROWS_AS(parser_helper(s2), json::parse_error&);
+ // only check error message if c is not a control character
+ if (c > 0x1f)
+ {
+ CHECK_THROWS_WITH_STD_STR(parser_helper(s2),
+ "[json.exception.parse_error.101] parse error at line 1, column 6: syntax error while parsing value - invalid string: '\\u' must be followed by 4 hex digits; last read: '" + s2.substr(0, 6) + "'");
+ }
+
+ CAPTURE(s3)
+ CHECK_THROWS_AS(parser_helper(s3), json::parse_error&);
+ // only check error message if c is not a control character
+ if (c > 0x1f)
+ {
+ CHECK_THROWS_WITH_STD_STR(parser_helper(s3),
+ "[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - invalid string: '\\u' must be followed by 4 hex digits; last read: '" + s3.substr(0, 5) + "'");
+ }
+
+ CAPTURE(s4)
+ CHECK_THROWS_AS(parser_helper(s4), json::parse_error&);
+ // only check error message if c is not a control character
+ if (c > 0x1f)
+ {
+ CHECK_THROWS_WITH_STD_STR(parser_helper(s4),
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid string: '\\u' must be followed by 4 hex digits; last read: '" + s4.substr(0, 4) + "'");
+ }
+ }
+ }
+ }
+
+ json _;
+
+ // missing part of a surrogate pair
+ CHECK_THROWS_WITH_AS(_ = json::parse("\"\\uD80C\""), "[json.exception.parse_error.101] parse error at line 1, column 8: syntax error while parsing value - invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF; last read: '\"\\uD80C\"'", json::parse_error&);
+ // invalid surrogate pair
+ CHECK_THROWS_WITH_AS(_ = json::parse("\"\\uD80C\\uD80C\""),
+ "[json.exception.parse_error.101] parse error at line 1, column 13: syntax error while parsing value - invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF; last read: '\"\\uD80C\\uD80C'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::parse("\"\\uD80C\\u0000\""),
+ "[json.exception.parse_error.101] parse error at line 1, column 13: syntax error while parsing value - invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF; last read: '\"\\uD80C\\u0000'", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::parse("\"\\uD80C\\uFFFF\""),
+ "[json.exception.parse_error.101] parse error at line 1, column 13: syntax error while parsing value - invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF; last read: '\"\\uD80C\\uFFFF'", json::parse_error&);
+ }
+
+ SECTION("parse errors (accept)")
+ {
+ // unexpected end of number
+ CHECK(accept_helper("0.") == false);
+ CHECK(accept_helper("-") == false);
+ CHECK(accept_helper("--") == false);
+ CHECK(accept_helper("-0.") == false);
+ CHECK(accept_helper("-.") == false);
+ CHECK(accept_helper("-:") == false);
+ CHECK(accept_helper("0.:") == false);
+ CHECK(accept_helper("e.") == false);
+ CHECK(accept_helper("1e.") == false);
+ CHECK(accept_helper("1e/") == false);
+ CHECK(accept_helper("1e:") == false);
+ CHECK(accept_helper("1E.") == false);
+ CHECK(accept_helper("1E/") == false);
+ CHECK(accept_helper("1E:") == false);
+
+ // unexpected end of null
+ CHECK(accept_helper("n") == false);
+ CHECK(accept_helper("nu") == false);
+ CHECK(accept_helper("nul") == false);
+
+ // unexpected end of true
+ CHECK(accept_helper("t") == false);
+ CHECK(accept_helper("tr") == false);
+ CHECK(accept_helper("tru") == false);
+
+ // unexpected end of false
+ CHECK(accept_helper("f") == false);
+ CHECK(accept_helper("fa") == false);
+ CHECK(accept_helper("fal") == false);
+ CHECK(accept_helper("fals") == false);
+
+ // missing/unexpected end of array
+ CHECK(accept_helper("[") == false);
+ CHECK(accept_helper("[1") == false);
+ CHECK(accept_helper("[1,") == false);
+ CHECK(accept_helper("[1,]") == false);
+ CHECK(accept_helper("]") == false);
+
+ // missing/unexpected end of object
+ CHECK(accept_helper("{") == false);
+ CHECK(accept_helper("{\"foo\"") == false);
+ CHECK(accept_helper("{\"foo\":") == false);
+ CHECK(accept_helper("{\"foo\":}") == false);
+ CHECK(accept_helper("{\"foo\":1,}") == false);
+ CHECK(accept_helper("}") == false);
+
+ // missing/unexpected end of string
+ CHECK(accept_helper("\"") == false);
+ CHECK(accept_helper("\"\\\"") == false);
+ CHECK(accept_helper("\"\\u\"") == false);
+ CHECK(accept_helper("\"\\u0\"") == false);
+ CHECK(accept_helper("\"\\u01\"") == false);
+ CHECK(accept_helper("\"\\u012\"") == false);
+ CHECK(accept_helper("\"\\u") == false);
+ CHECK(accept_helper("\"\\u0") == false);
+ CHECK(accept_helper("\"\\u01") == false);
+ CHECK(accept_helper("\"\\u012") == false);
+
+ // unget of newline
+ CHECK(parser_helper("\n123\n") == 123);
+
+ // invalid escapes
+ for (int c = 1; c < 128; ++c)
+ {
+ auto s = std::string("\"\\") + std::string(1, static_cast<char>(c)) + "\"";
+
+ switch (c)
+ {
+ // valid escapes
+ case ('"'):
+ case ('\\'):
+ case ('/'):
+ case ('b'):
+ case ('f'):
+ case ('n'):
+ case ('r'):
+ case ('t'):
+ {
+ CHECK(json::parser(nlohmann::detail::input_adapter(s)).accept());
+ break;
+ }
+
+ // \u must be followed with four numbers, so we skip it here
+ case ('u'):
+ {
+ break;
+ }
+
+ // any other combination of backslash and character is invalid
+ default:
+ {
+ CHECK(json::parser(nlohmann::detail::input_adapter(s)).accept() == false);
+ break;
+ }
+ }
+ }
+
+ // invalid \uxxxx escapes
+ {
+ // check whether character is a valid hex character
+ const auto valid = [](int c)
+ {
+ switch (c)
+ {
+ case ('0'):
+ case ('1'):
+ case ('2'):
+ case ('3'):
+ case ('4'):
+ case ('5'):
+ case ('6'):
+ case ('7'):
+ case ('8'):
+ case ('9'):
+ case ('a'):
+ case ('b'):
+ case ('c'):
+ case ('d'):
+ case ('e'):
+ case ('f'):
+ case ('A'):
+ case ('B'):
+ case ('C'):
+ case ('D'):
+ case ('E'):
+ case ('F'):
+ {
+ return true;
+ }
+
+ default:
+ {
+ return false;
+ }
+ }
+ };
+
+ for (int c = 1; c < 128; ++c)
+ {
+ std::string const s = "\"\\u";
+
+ // create a string with the iterated character at each position
+ const auto s1 = s + "000" + std::string(1, static_cast<char>(c)) + "\"";
+ const auto s2 = s + "00" + std::string(1, static_cast<char>(c)) + "0\"";
+ const auto s3 = s + "0" + std::string(1, static_cast<char>(c)) + "00\"";
+ const auto s4 = s + std::string(1, static_cast<char>(c)) + "000\"";
+
+ if (valid(c))
+ {
+ CAPTURE(s1)
+ CHECK(json::parser(nlohmann::detail::input_adapter(s1)).accept());
+ CAPTURE(s2)
+ CHECK(json::parser(nlohmann::detail::input_adapter(s2)).accept());
+ CAPTURE(s3)
+ CHECK(json::parser(nlohmann::detail::input_adapter(s3)).accept());
+ CAPTURE(s4)
+ CHECK(json::parser(nlohmann::detail::input_adapter(s4)).accept());
+ }
+ else
+ {
+ CAPTURE(s1)
+ CHECK(json::parser(nlohmann::detail::input_adapter(s1)).accept() == false);
+
+ CAPTURE(s2)
+ CHECK(json::parser(nlohmann::detail::input_adapter(s2)).accept() == false);
+
+ CAPTURE(s3)
+ CHECK(json::parser(nlohmann::detail::input_adapter(s3)).accept() == false);
+
+ CAPTURE(s4)
+ CHECK(json::parser(nlohmann::detail::input_adapter(s4)).accept() == false);
+ }
+ }
+ }
+
+ // missing part of a surrogate pair
+ CHECK(accept_helper("\"\\uD80C\"") == false);
+ // invalid surrogate pair
+ CHECK(accept_helper("\"\\uD80C\\uD80C\"") == false);
+ CHECK(accept_helper("\"\\uD80C\\u0000\"") == false);
+ CHECK(accept_helper("\"\\uD80C\\uFFFF\"") == false);
+ }
+
+ SECTION("tests found by mutate++")
+ {
+ // test case to make sure no comma precedes the first key
+ CHECK_THROWS_WITH_AS(parser_helper("{,\"key\": false}"), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing object key - unexpected ','; expected string literal", json::parse_error&);
+ // test case to make sure an object is properly closed
+ CHECK_THROWS_WITH_AS(parser_helper("[{\"key\": false true]"), "[json.exception.parse_error.101] parse error at line 1, column 19: syntax error while parsing object - unexpected true literal; expected '}'", json::parse_error&);
+
+ // test case to make sure the callback is properly evaluated after reading a key
+ {
+ json::parser_callback_t const cb = [](int /*unused*/, json::parse_event_t event, json& /*unused*/) noexcept
+ {
+ return event != json::parse_event_t::key;
+ };
+
+ const json x = json::parse("{\"key\": false}", cb);
+ CHECK(x == json::object());
+ }
+ }
+
+ SECTION("callback function")
+ {
+ const auto* s_object = R"(
+ {
+ "foo": 2,
+ "bar": {
+ "baz": 1
+ }
+ }
+ )";
+
+ const auto* s_array = R"(
+ [1,2,[3,4,5],4,5]
+ )";
+
+ const auto* structured_array = R"(
+ [
+ 1,
+ {
+ "foo": "bar"
+ },
+ {
+ "qux": "baz"
+ }
+ ]
+ )";
+
+ SECTION("filter nothing")
+ {
+ const json j_object = json::parse(s_object, [](int /*unused*/, json::parse_event_t /*unused*/, const json& /*unused*/) noexcept
+ {
+ return true;
+ });
+
+ CHECK (j_object == json({{"foo", 2}, {"bar", {{"baz", 1}}}}));
+
+ const json j_array = json::parse(s_array, [](int /*unused*/, json::parse_event_t /*unused*/, const json& /*unused*/) noexcept
+ {
+ return true;
+ });
+
+ CHECK (j_array == json({1, 2, {3, 4, 5}, 4, 5}));
+ }
+
+ SECTION("filter everything")
+ {
+ json const j_object = json::parse(s_object, [](int /*unused*/, json::parse_event_t /*unused*/, const json& /*unused*/) noexcept
+ {
+ return false;
+ });
+
+ // the top-level object will be discarded, leaving a null
+ CHECK (j_object.is_null());
+
+ json const j_array = json::parse(s_array, [](int /*unused*/, json::parse_event_t /*unused*/, const json& /*unused*/) noexcept
+ {
+ return false;
+ });
+
+ // the top-level array will be discarded, leaving a null
+ CHECK (j_array.is_null());
+ }
+
+ SECTION("filter specific element")
+ {
+ const json j_object = json::parse(s_object, [](int /*unused*/, json::parse_event_t event, const json & j) noexcept
+ {
+ // filter all number(2) elements
+ return event != json::parse_event_t::value || j != json(2);
+ });
+
+ CHECK (j_object == json({{"bar", {{"baz", 1}}}}));
+
+ const json j_array = json::parse(s_array, [](int /*unused*/, json::parse_event_t event, const json & j) noexcept
+ {
+ return event != json::parse_event_t::value || j != json(2);
+ });
+
+ CHECK (j_array == json({1, {3, 4, 5}, 4, 5}));
+ }
+
+ SECTION("filter object in array")
+ {
+ const json j_filtered1 = json::parse(structured_array, [](int /*unused*/, json::parse_event_t e, const json & parsed)
+ {
+ return !(e == json::parse_event_t::object_end && parsed.contains("foo"));
+ });
+
+ // the specified object will be discarded, and removed.
+ CHECK (j_filtered1.size() == 2);
+ CHECK (j_filtered1 == json({1, {{"qux", "baz"}}}));
+
+ const json j_filtered2 = json::parse(structured_array, [](int /*unused*/, json::parse_event_t e, const json& /*parsed*/) noexcept
+ {
+ return e != json::parse_event_t::object_end;
+ });
+
+ // removed all objects in array.
+ CHECK (j_filtered2.size() == 1);
+ CHECK (j_filtered2 == json({1}));
+ }
+
+ SECTION("filter specific events")
+ {
+ SECTION("first closing event")
+ {
+ {
+ const json j_object = json::parse(s_object, [](int /*unused*/, json::parse_event_t e, const json& /*unused*/) noexcept
+ {
+ static bool first = true;
+ if (e == json::parse_event_t::object_end && first)
+ {
+ first = false;
+ return false;
+ }
+
+ return true;
+ });
+
+ // the first completed object will be discarded
+ CHECK (j_object == json({{"foo", 2}}));
+ }
+
+ {
+ const json j_array = json::parse(s_array, [](int /*unused*/, json::parse_event_t e, const json& /*unused*/) noexcept
+ {
+ static bool first = true;
+ if (e == json::parse_event_t::array_end && first)
+ {
+ first = false;
+ return false;
+ }
+
+ return true;
+ });
+
+ // the first completed array will be discarded
+ CHECK (j_array == json({1, 2, 4, 5}));
+ }
+ }
+ }
+
+ SECTION("special cases")
+ {
+ // the following test cases cover the situation in which an empty
+ // object and array is discarded only after the closing character
+ // has been read
+
+ const json j_empty_object = json::parse("{}", [](int /*unused*/, json::parse_event_t e, const json& /*unused*/) noexcept
+ {
+ return e != json::parse_event_t::object_end;
+ });
+ CHECK(j_empty_object == json());
+
+ const json j_empty_array = json::parse("[]", [](int /*unused*/, json::parse_event_t e, const json& /*unused*/) noexcept
+ {
+ return e != json::parse_event_t::array_end;
+ });
+ CHECK(j_empty_array == json());
+ }
+ }
+
+ SECTION("constructing from contiguous containers")
+ {
+ SECTION("from std::vector")
+ {
+ std::vector<uint8_t> v = {'t', 'r', 'u', 'e'};
+ json j;
+ json::parser(nlohmann::detail::input_adapter(std::begin(v), std::end(v))).parse(true, j);
+ CHECK(j == json(true));
+ }
+
+ SECTION("from std::array")
+ {
+ std::array<uint8_t, 5> v { {'t', 'r', 'u', 'e'} };
+ json j;
+ json::parser(nlohmann::detail::input_adapter(std::begin(v), std::end(v))).parse(true, j);
+ CHECK(j == json(true));
+ }
+
+ SECTION("from array")
+ {
+ uint8_t v[] = {'t', 'r', 'u', 'e'}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
+ json j;
+ json::parser(nlohmann::detail::input_adapter(std::begin(v), std::end(v))).parse(true, j);
+ CHECK(j == json(true));
+ }
+
+ SECTION("from char literal")
+ {
+ CHECK(parser_helper("true") == json(true));
+ }
+
+ SECTION("from std::string")
+ {
+ std::string v = {'t', 'r', 'u', 'e'};
+ json j;
+ json::parser(nlohmann::detail::input_adapter(std::begin(v), std::end(v))).parse(true, j);
+ CHECK(j == json(true));
+ }
+
+ SECTION("from std::initializer_list")
+ {
+ std::initializer_list<uint8_t> const v = {'t', 'r', 'u', 'e'};
+ json j;
+ json::parser(nlohmann::detail::input_adapter(std::begin(v), std::end(v))).parse(true, j);
+ CHECK(j == json(true));
+ }
+
+ SECTION("from std::valarray")
+ {
+ std::valarray<uint8_t> v = {'t', 'r', 'u', 'e'};
+ json j;
+ json::parser(nlohmann::detail::input_adapter(std::begin(v), std::end(v))).parse(true, j);
+ CHECK(j == json(true));
+ }
+ }
+
+ SECTION("improve test coverage")
+ {
+ SECTION("parser with callback")
+ {
+ json::parser_callback_t const cb = [](int /*unused*/, json::parse_event_t /*unused*/, json& /*unused*/) noexcept
+ {
+ return true;
+ };
+
+ CHECK(json::parse("{\"foo\": true:", cb, false).is_discarded());
+
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::parse("{\"foo\": true:", cb), "[json.exception.parse_error.101] parse error at line 1, column 13: syntax error while parsing object - unexpected ':'; expected '}'", json::parse_error&);
+
+ CHECK_THROWS_WITH_AS(_ = json::parse("1.18973e+4932", cb), "[json.exception.out_of_range.406] number overflow parsing '1.18973e+4932'", json::out_of_range&);
+ }
+
+ SECTION("SAX parser")
+ {
+ SECTION("} without value")
+ {
+ SaxCountdown s(1);
+ CHECK(json::sax_parse("{}", &s) == false);
+ }
+
+ SECTION("} with value")
+ {
+ SaxCountdown s(3);
+ CHECK(json::sax_parse("{\"k1\": true}", &s) == false);
+ }
+
+ SECTION("second key")
+ {
+ SaxCountdown s(3);
+ CHECK(json::sax_parse("{\"k1\": true, \"k2\": false}", &s) == false);
+ }
+
+ SECTION("] without value")
+ {
+ SaxCountdown s(1);
+ CHECK(json::sax_parse("[]", &s) == false);
+ }
+
+ SECTION("] with value")
+ {
+ SaxCountdown s(2);
+ CHECK(json::sax_parse("[1]", &s) == false);
+ }
+
+ SECTION("float")
+ {
+ SaxCountdown s(0);
+ CHECK(json::sax_parse("3.14", &s) == false);
+ }
+
+ SECTION("false")
+ {
+ SaxCountdown s(0);
+ CHECK(json::sax_parse("false", &s) == false);
+ }
+
+ SECTION("null")
+ {
+ SaxCountdown s(0);
+ CHECK(json::sax_parse("null", &s) == false);
+ }
+
+ SECTION("true")
+ {
+ SaxCountdown s(0);
+ CHECK(json::sax_parse("true", &s) == false);
+ }
+
+ SECTION("unsigned")
+ {
+ SaxCountdown s(0);
+ CHECK(json::sax_parse("12", &s) == false);
+ }
+
+ SECTION("integer")
+ {
+ SaxCountdown s(0);
+ CHECK(json::sax_parse("-12", &s) == false);
+ }
+
+ SECTION("string")
+ {
+ SaxCountdown s(0);
+ CHECK(json::sax_parse("\"foo\"", &s) == false);
+ }
+ }
+ }
+
+ SECTION("error messages for comments")
+ {
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::parse("/a", nullptr, true, true), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid comment; expecting '/' or '*' after '/'; last read: '/a'", json::parse_error);
+ CHECK_THROWS_WITH_AS(_ = json::parse("/*", nullptr, true, true), "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid comment; missing closing '*/'; last read: '/*<U+0000>'", json::parse_error);
+ }
+
+ // Macro for all test cases for start_pos and end_pos
+#define SETUP_TESTCASES() \
+ SECTION("with callback") \
+ { \
+ SECTION("filter nothing") \
+ { \
+ json::parser_callback_t const cb = [](int /*unused*/, json::parse_event_t /*unused*/, json& /*unused*/) noexcept \
+ { \
+ return true; \
+ }; \
+ validate_start_end_pos_for_nested_obj_helper(nested_type_json_str, root_type_json_str, expected, cb); \
+ } \
+ SECTION("filter element") \
+ { \
+ json::parser_callback_t const cb = [](int /*unused*/, json::parse_event_t event, json& j) noexcept \
+ { \
+ return (event != json::parse_event_t::key && event != json::parse_event_t::value) || j != json("a"); \
+ }; \
+ validate_start_end_pos_for_nested_obj_helper(nested_type_json_str, root_type_json_str, filteredExpected, cb); \
+ } \
+ } \
+ SECTION("without callback") \
+ { \
+ validate_start_end_pos_for_nested_obj_helper(nested_type_json_str, root_type_json_str, expected); \
+ }
+
+ SECTION("retrieve start position and end position")
+ {
+ SECTION("for object")
+ {
+ // Create an object with spaces to test the start and end positions. Spaces will not be included in the
+ // JSON object, however, the start and end positions should include the spaces from the input JSON string.
+ const std::string nested_type_json_str = R"({ "a": 1,"b" : "test1"})";
+ const std::string root_type_json_str = R"({ "nested": )" + nested_type_json_str + R"(, "anotherValue": "test2"})";
+ auto expected = json({{"nested", {{"a", 1}, {"b", "test1"}}}, {"anotherValue", "test2"}});
+ auto filteredExpected = expected;
+ filteredExpected["nested"].erase("a");
+
+ SETUP_TESTCASES()
+ }
+
+ SECTION("for array")
+ {
+ const std::string nested_type_json_str = R"(["a", "test", 45])";
+ const std::string root_type_json_str = R"({ "nested": )" + nested_type_json_str + R"(, "anotherValue": "test" })";
+ auto expected = json({{"nested", {"a", "test", 45}}, {"anotherValue", "test"}});
+ auto filteredExpected = expected;
+ filteredExpected["nested"] = json({"test", 45});
+ SETUP_TESTCASES()
+ }
+
+ SECTION("for array with objects")
+ {
+ const std::string nested_type_json_str = R"([{"a": 1, "b": "test"}, {"c": 2, "d": "test2"}])";
+ const std::string root_type_json_str = R"({ "nested": )" + nested_type_json_str + R"(, "anotherValue": "test" })";
+ auto expected = json({{"nested", {{{"a", 1}, {"b", "test"}}, {{"c", 2}, {"d", "test2"}}}}, {"anotherValue", "test"}});
+ auto filteredExpected = expected;
+ filteredExpected["nested"][0].erase("a");
+ SETUP_TESTCASES()
+
+ auto j = json::parse(root_type_json_str);
+ auto nested_array = j["nested"];
+ const auto& nested_obj = nested_array[0];
+ CHECK(nested_type_json_str.substr(1, 21) == root_type_json_str.substr(nested_obj.start_pos(), nested_obj.end_pos() - nested_obj.start_pos()));
+ CHECK(nested_type_json_str.substr(24, 22) == root_type_json_str.substr(nested_array[1].start_pos(), nested_array[1].end_pos() - nested_array[1].start_pos()));
+ }
+
+ SECTION("for two levels of nesting objects")
+ {
+ const std::string nested_type_json_str = R"({"nested2": {"b": "test"}})";
+ const std::string root_type_json_str = R"({ "a": 2, "nested": )" + nested_type_json_str + R"(, "anotherValue": "test" })";
+ auto expected = json({{"a", 2}, {"nested", {{"nested2", {{"b", "test"}}}}}, {"anotherValue", "test"}});
+ auto filteredExpected = expected;
+ filteredExpected.erase("a");
+ SETUP_TESTCASES()
+
+ auto j = json::parse(root_type_json_str);
+ auto nested_obj = j["nested"]["nested2"];
+ CHECK(nested_type_json_str.substr(12, 13) == root_type_json_str.substr(nested_obj.start_pos(), nested_obj.end_pos() - nested_obj.start_pos()));
+ }
+
+ SECTION("for simple types")
+ {
+ SECTION("no nested")
+ {
+ SECTION("with callback")
+ {
+ json::parser_callback_t const cb = [](int /*unused*/, json::parse_event_t /*unused*/, json& /*unused*/) noexcept
+ {
+ return true;
+ };
+
+ // 1. string type
+ std::string json_str = R"("test")";
+ auto j = json::parse(json_str, cb);
+ validate_generated_json_and_start_end_pos_helper(json_str, j, "test");
+
+ // 2. number type
+ json_str = R"(1)";
+ j = json::parse(json_str, cb);
+ validate_generated_json_and_start_end_pos_helper(json_str, j, 1);
+
+ // 3. boolean type
+ json_str = R"(true)";
+ j = json::parse(json_str, cb);
+ validate_generated_json_and_start_end_pos_helper(json_str, j, true);
+
+ // 4. null type
+ json_str = R"(null)";
+ j = json::parse(json_str, cb);
+ validate_generated_json_and_start_end_pos_helper(json_str, j, nullptr);
+ }
+
+ SECTION("without callback")
+ {
+ // 1. string type
+ std::string json_str = R"("test")";
+ auto j = json::parse(json_str);
+ validate_generated_json_and_start_end_pos_helper(json_str, j, "test");
+
+ // 2. number type
+ json_str = R"(1)";
+ j = json::parse(json_str);
+ validate_generated_json_and_start_end_pos_helper(json_str, j, 1);
+
+ json_str = R"(1.001239923)";
+ j = json::parse(json_str);
+ validate_generated_json_and_start_end_pos_helper(json_str, j, 1.001239923);
+
+ json_str = R"(1.123812389000000)";
+ j = json::parse(json_str);
+ validate_generated_json_and_start_end_pos_helper(json_str, j, 1.123812389);
+
+ // 3. boolean type
+ json_str = R"(true)";
+ j = json::parse(json_str);
+ validate_generated_json_and_start_end_pos_helper(json_str, j, true);
+
+ json_str = R"(false)";
+ j = json::parse(json_str);
+ validate_generated_json_and_start_end_pos_helper(json_str, j, false);
+
+ // 4. null type
+ json_str = R"(null)";
+ j = json::parse(json_str);
+ validate_generated_json_and_start_end_pos_helper(json_str, j, nullptr);
+ }
+ }
+
+ SECTION("string type")
+ {
+ const std::string nested_type_json_str = R"("test")";
+ const std::string root_type_json_str = R"({ "a": 1, "nested": )" + nested_type_json_str + R"(, "anotherValue": "test" })";
+ auto expected = json({{"nested", "test"}, {"anotherValue", "test"}, {"a", 1}});
+ auto filteredExpected = expected;
+ filteredExpected.erase("a");
+ SETUP_TESTCASES()
+ }
+
+ SECTION("number type")
+ {
+ const std::string nested_type_json_str = R"(2)";
+ const std::string root_type_json_str = R"({ "a": 1, "nested": )" + nested_type_json_str + R"(, "anotherValue": "test" })";
+ auto expected = json({{"nested", 2}, {"anotherValue", "test"}, {"a", 1}});
+ auto filteredExpected = expected;
+ filteredExpected.erase("a");
+ SETUP_TESTCASES()
+ }
+
+ SECTION("boolean type")
+ {
+ const std::string nested_type_json_str = R"(true)";
+ const std::string root_type_json_str = R"({ "a": 1, "nested": )" + nested_type_json_str + R"(, "anotherValue": "test" })";
+ auto expected = json({{"nested", true}, {"anotherValue", "test"}, {"a", 1}});
+ auto filteredExpected = expected;
+ filteredExpected.erase("a");
+ SETUP_TESTCASES()
+ }
+
+ SECTION("null type")
+ {
+ const std::string nested_type_json_str = R"(null)";
+ const std::string root_type_json_str = R"({ "a": 1, "nested": )" + nested_type_json_str + R"(, "anotherValue": "test" })";
+ auto expected = json({{"nested", nullptr}, {"anotherValue", "test"}, {"a", 1}});
+ auto filteredExpected = expected;
+ filteredExpected.erase("a");
+ SETUP_TESTCASES()
+ }
+ }
+ SECTION("with leading whitespace and newlines around root JSON")
+ {
+ const std::string initial_whitespace = R"(
+
+ )";
+ const std::string nested_type_json_str = R"({
+ "a": 1,
+ "nested": {
+ "b": "test"
+ },
+ "anotherValue": "test"
+ })";
+ const std::string end_whitespace = R"(
+
+ )";
+ const std::string root_type_json_str = initial_whitespace + nested_type_json_str + end_whitespace;
+
+ auto expected = json({{"a", 1}, {"nested", {{"b", "test"}}}, {"anotherValue", "test"}});
+
+ auto j = json::parse(root_type_json_str);
+
+ // 2. Check if the generated JSON is as expected
+ CHECK(j == expected);
+
+ // 3. Check if the start and end positions do not include the surrounding whitespace
+ CHECK(j.start_pos() == initial_whitespace.size());
+ CHECK(j.end_pos() == root_type_json_str.size() - end_whitespace.size());
+ }
+ }
+}
diff --git a/json4cpp/tests/src/unit-comparison.cpp b/json4cpp/tests/src/unit-comparison.cpp
new file mode 100644
index 0000000000..befaa04042
--- /dev/null
+++ b/json4cpp/tests/src/unit-comparison.cpp
@@ -0,0 +1,596 @@
+// __ _____ _____ _____
+// __| | __| | | | 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
+
+// cmake/test.cmake selects the C++ standard versions with which to build a
+// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
+// When using macros that are only defined for particular versions of the standard
+// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
+// version macro in a comment close by, like this:
+// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
+
+#include "doctest_compatibility.h"
+
+#define JSON_TESTS_PRIVATE
+#include <nlohmann/json.hpp>
+using nlohmann::json;
+
+#if JSON_HAS_THREE_WAY_COMPARISON
+// this can be replaced with the doctest stl extension header in version 2.5
+namespace doctest
+{
+template<> struct StringMaker<std::partial_ordering>
+{
+ static String convert(const std::partial_ordering& order)
+ {
+ if (order == std::partial_ordering::less)
+ {
+ return "std::partial_ordering::less";
+ }
+ if (order == std::partial_ordering::equivalent)
+ {
+ return "std::partial_ordering::equivalent";
+ }
+ if (order == std::partial_ordering::greater)
+ {
+ return "std::partial_ordering::greater";
+ }
+ if (order == std::partial_ordering::unordered)
+ {
+ return "std::partial_ordering::unordered";
+ }
+ return "{?}";
+ }
+};
+} // namespace doctest
+
+#endif
+
+namespace
+{
+// helper function to check std::less<json::value_t>
+// see https://en.cppreference.com/w/cpp/utility/functional/less
+template <typename A, typename B, typename U = std::less<json::value_t>>
+bool f(A a, B b, U u = U())
+{
+ return u(a, b);
+}
+} // namespace
+
+TEST_CASE("lexicographical comparison operators")
+{
+ constexpr auto f_ = false;
+ constexpr auto _t = true;
+ constexpr auto nan = std::numeric_limits<json::number_float_t>::quiet_NaN();
+#if JSON_HAS_THREE_WAY_COMPARISON
+ constexpr auto lt = std::partial_ordering::less;
+ constexpr auto gt = std::partial_ordering::greater;
+ constexpr auto eq = std::partial_ordering::equivalent;
+ constexpr auto un = std::partial_ordering::unordered;
+#endif
+
+#if JSON_HAS_THREE_WAY_COMPARISON
+ INFO("using 3-way comparison");
+#endif
+
+#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
+ INFO("using legacy comparison");
+#endif
+
+ //REQUIRE(std::numeric_limits<json::number_float_t>::has_quiet_NaN);
+ REQUIRE(std::isnan(nan));
+
+ SECTION("types")
+ {
+ std::vector<json::value_t> j_types =
+ {
+ json::value_t::null,
+ json::value_t::boolean,
+ json::value_t::number_integer,
+ json::value_t::number_unsigned,
+ json::value_t::number_float,
+ json::value_t::object,
+ json::value_t::array,
+ json::value_t::string,
+ json::value_t::binary,
+ json::value_t::discarded
+ };
+
+ std::vector<std::vector<bool>> expected_lt =
+ {
+ //0 1 2 3 4 5 6 7 8 9
+ {f_, _t, _t, _t, _t, _t, _t, _t, _t, f_}, // 0
+ {f_, f_, _t, _t, _t, _t, _t, _t, _t, f_}, // 1
+ {f_, f_, f_, f_, f_, _t, _t, _t, _t, f_}, // 2
+ {f_, f_, f_, f_, f_, _t, _t, _t, _t, f_}, // 3
+ {f_, f_, f_, f_, f_, _t, _t, _t, _t, f_}, // 4
+ {f_, f_, f_, f_, f_, f_, _t, _t, _t, f_}, // 5
+ {f_, f_, f_, f_, f_, f_, f_, _t, _t, f_}, // 6
+ {f_, f_, f_, f_, f_, f_, f_, f_, _t, f_}, // 7
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 8
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 9
+ };
+
+ SECTION("comparison: less")
+ {
+ REQUIRE(expected_lt.size() == j_types.size());
+ for (size_t i = 0; i < j_types.size(); ++i)
+ {
+ REQUIRE(expected_lt[i].size() == j_types.size());
+ for (size_t j = 0; j < j_types.size(); ++j)
+ {
+ CAPTURE(i)
+ CAPTURE(j)
+ // check precomputed values
+#if JSON_HAS_THREE_WAY_COMPARISON
+ // JSON_HAS_CPP_20 (do not remove; see note at top of file)
+ CHECK((j_types[i] < j_types[j]) == expected_lt[i][j]);
+#else
+ CHECK(operator<(j_types[i], j_types[j]) == expected_lt[i][j]);
+#endif
+ CHECK(f(j_types[i], j_types[j]) == expected_lt[i][j]);
+ }
+ }
+ }
+#if JSON_HAS_THREE_WAY_COMPARISON
+ // JSON_HAS_CPP_20 (do not remove; see note at top of file)
+ SECTION("comparison: 3-way")
+ {
+ std::vector<std::vector<std::partial_ordering>> expected =
+ {
+ //0 1 2 3 4 5 6 7 8 9
+ {eq, lt, lt, lt, lt, lt, lt, lt, lt, un}, // 0
+ {gt, eq, lt, lt, lt, lt, lt, lt, lt, un}, // 1
+ {gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 2
+ {gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 3
+ {gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 4
+ {gt, gt, gt, gt, gt, eq, lt, lt, lt, un}, // 5
+ {gt, gt, gt, gt, gt, gt, eq, lt, lt, un}, // 6
+ {gt, gt, gt, gt, gt, gt, gt, eq, lt, un}, // 7
+ {gt, gt, gt, gt, gt, gt, gt, gt, eq, un}, // 8
+ {un, un, un, un, un, un, un, un, un, un}, // 9
+ };
+
+ // check expected partial_ordering against expected boolean
+ REQUIRE(expected.size() == expected_lt.size());
+ for (size_t i = 0; i < expected.size(); ++i)
+ {
+ REQUIRE(expected[i].size() == expected_lt[i].size());
+ for (size_t j = 0; j < expected[i].size(); ++j)
+ {
+ CAPTURE(i)
+ CAPTURE(j)
+ CHECK(std::is_lt(expected[i][j]) == expected_lt[i][j]);
+ }
+ }
+
+ // check 3-way comparison against expected partial_ordering
+ REQUIRE(expected.size() == j_types.size());
+ for (size_t i = 0; i < j_types.size(); ++i)
+ {
+ REQUIRE(expected[i].size() == j_types.size());
+ for (size_t j = 0; j < j_types.size(); ++j)
+ {
+ CAPTURE(i)
+ CAPTURE(j)
+ CHECK((j_types[i] <=> j_types[j]) == expected[i][j]); // *NOPAD*
+ }
+ }
+ }
+#endif
+ }
+
+ SECTION("values")
+ {
+ json j_values =
+ {
+ nullptr, nullptr, // 0 1
+ -17, 42, // 2 3
+ 8u, 13u, // 4 5
+ 3.14159, 23.42, // 6 7
+ nan, nan, // 8 9
+ "foo", "bar", // 10 11
+ true, false, // 12 13
+ {1, 2, 3}, {"one", "two", "three"}, // 14 15
+ {{"first", 1}, {"second", 2}}, {{"a", "A"}, {"b", {"B"}}}, // 16 17
+ json::binary({1, 2, 3}), json::binary({1, 2, 4}), // 18 19
+ json(json::value_t::discarded), json(json::value_t::discarded) // 20 21
+ };
+
+ std::vector<std::vector<bool>> expected_eq =
+ {
+ //0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
+ {_t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 0
+ {_t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 1
+ {f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 2
+ {f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 3
+ {f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 4
+ {f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 5
+ {f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 6
+ {f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 7
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 8
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 9
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 10
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 11
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 12
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_}, // 13
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_}, // 14
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_}, // 15
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_}, // 16
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_}, // 17
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_}, // 18
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_}, // 19
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 20
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 21
+ };
+
+ std::vector<std::vector<bool>> expected_lt =
+ {
+ //0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
+ {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_}, // 0
+ {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_}, // 1
+ {f_, f_, f_, _t, _t, _t, _t, _t, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 2
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 3
+ {f_, f_, f_, _t, f_, _t, f_, _t, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 4
+ {f_, f_, f_, _t, f_, f_, f_, _t, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 5
+ {f_, f_, f_, _t, _t, _t, f_, _t, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 6
+ {f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 7
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 8
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 9
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_}, // 10
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_}, // 11
+ {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 12
+ {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 13
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, _t, f_, f_, _t, _t, f_, f_}, // 14
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_}, // 15
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, f_, f_, _t, _t, f_, f_}, // 16
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, f_, _t, _t, f_, f_}, // 17
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_}, // 18
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 19
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 20
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 21
+ };
+
+ SECTION("compares unordered")
+ {
+ std::vector<std::vector<bool>> expected =
+ {
+ //0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 0
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 1
+ {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 2
+ {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 3
+ {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 4
+ {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 5
+ {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 6
+ {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 7
+ {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 8
+ {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 9
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 10
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 11
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 12
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 13
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 14
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 15
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 16
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 17
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 18
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 19
+ {_t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t}, // 20
+ {_t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t}, // 21
+ };
+
+ // check if two values compare unordered as expected
+ REQUIRE(expected.size() == j_values.size());
+ for (size_t i = 0; i < j_values.size(); ++i)
+ {
+ REQUIRE(expected[i].size() == j_values.size());
+ for (size_t j = 0; j < j_values.size(); ++j)
+ {
+ CAPTURE(i)
+ CAPTURE(j)
+ CHECK(json::compares_unordered(j_values[i], j_values[j]) == expected[i][j]);
+ }
+ }
+ }
+
+#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
+ SECTION("compares unordered (inverse)")
+ {
+ std::vector<std::vector<bool>> expected =
+ {
+ //0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 0
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 1
+ {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 2
+ {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 3
+ {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 4
+ {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 5
+ {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 6
+ {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 7
+ {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 8
+ {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 9
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 10
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 11
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 12
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 13
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 14
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 15
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 16
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 17
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 18
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 19
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 20
+ {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 21
+ };
+
+ // check that two values compare unordered as expected (with legacy-mode enabled)
+ REQUIRE(expected.size() == j_values.size());
+ for (size_t i = 0; i < j_values.size(); ++i)
+ {
+ REQUIRE(expected[i].size() == j_values.size());
+ for (size_t j = 0; j < j_values.size(); ++j)
+ {
+ CAPTURE(i)
+ CAPTURE(j)
+ CAPTURE(j_values[i])
+ CAPTURE(j_values[j])
+ CHECK(json::compares_unordered(j_values[i], j_values[j], true) == expected[i][j]);
+ }
+ }
+ }
+#endif
+
+ SECTION("comparison: equal")
+ {
+ // check that two values compare equal
+ REQUIRE(expected_eq.size() == j_values.size());
+ for (size_t i = 0; i < j_values.size(); ++i)
+ {
+ REQUIRE(expected_eq[i].size() == j_values.size());
+ for (size_t j = 0; j < j_values.size(); ++j)
+ {
+ CAPTURE(i)
+ CAPTURE(j)
+ CHECK((j_values[i] == j_values[j]) == expected_eq[i][j]);
+ }
+ }
+
+ // compare with null pointer
+ json j_null;
+ CHECK(j_null == nullptr);
+ CHECK(nullptr == j_null);
+ }
+
+ SECTION("comparison: not equal")
+ {
+ // check that two values compare unequal as expected
+ for (size_t i = 0; i < j_values.size(); ++i)
+ {
+ for (size_t j = 0; j < j_values.size(); ++j)
+ {
+ CAPTURE(i)
+ CAPTURE(j)
+
+ if (json::compares_unordered(j_values[i], j_values[j], true))
+ {
+ // if two values compare unordered,
+ // check that the boolean comparison result is always false
+ CHECK_FALSE(j_values[i] != j_values[j]);
+ }
+ else
+ {
+ // otherwise, check that they compare according to their definition
+ // as the inverse of equal
+ CHECK((j_values[i] != j_values[j]) == !(j_values[i] == j_values[j]));
+ }
+ }
+ }
+
+ // compare with null pointer
+ const json j_null;
+ CHECK((j_null != nullptr) == false);
+ CHECK((nullptr != j_null) == false);
+ CHECK((j_null != nullptr) == !(j_null == nullptr));
+ CHECK((nullptr != j_null) == !(nullptr == j_null));
+ }
+
+ SECTION("comparison: less")
+ {
+ // check that two values compare less than as expected
+ REQUIRE(expected_lt.size() == j_values.size());
+ for (size_t i = 0; i < j_values.size(); ++i)
+ {
+ REQUIRE(expected_lt[i].size() == j_values.size());
+ for (size_t j = 0; j < j_values.size(); ++j)
+ {
+ CAPTURE(i)
+ CAPTURE(j)
+ CHECK((j_values[i] < j_values[j]) == expected_lt[i][j]);
+ }
+ }
+ }
+
+ SECTION("comparison: less than or equal equal")
+ {
+ // check that two values compare less than or equal as expected
+ for (size_t i = 0; i < j_values.size(); ++i)
+ {
+ for (size_t j = 0; j < j_values.size(); ++j)
+ {
+ CAPTURE(i)
+ CAPTURE(j)
+ if (json::compares_unordered(j_values[i], j_values[j], true))
+ {
+ // if two values compare unordered,
+ // check that the boolean comparison result is always false
+ CHECK_FALSE(j_values[i] <= j_values[j]);
+ }
+ else
+ {
+ // otherwise, check that they compare according to their definition
+ // as the inverse of less than with the operand order reversed
+ CHECK((j_values[i] <= j_values[j]) == !(j_values[j] < j_values[i]));
+ }
+ }
+ }
+ }
+
+ SECTION("comparison: greater than")
+ {
+ // check that two values compare greater than as expected
+ for (size_t i = 0; i < j_values.size(); ++i)
+ {
+ for (size_t j = 0; j < j_values.size(); ++j)
+ {
+ CAPTURE(i)
+ CAPTURE(j)
+ if (json::compares_unordered(j_values[i], j_values[j]))
+ {
+ // if two values compare unordered,
+ // check that the boolean comparison result is always false
+ CHECK_FALSE(j_values[i] > j_values[j]);
+ }
+ else
+ {
+ // otherwise, check that they compare according to their definition
+ // as the inverse of less than or equal which is defined as
+ // the inverse of less than with the operand order reversed
+ CHECK((j_values[i] > j_values[j]) == !(j_values[i] <= j_values[j]));
+ CHECK((j_values[i] > j_values[j]) == !!(j_values[j] < j_values[i]));
+ }
+ }
+ }
+ }
+
+ SECTION("comparison: greater than or equal")
+ {
+ // check that two values compare greater than or equal as expected
+ for (size_t i = 0; i < j_values.size(); ++i)
+ {
+ for (size_t j = 0; j < j_values.size(); ++j)
+ {
+ CAPTURE(i)
+ CAPTURE(j)
+ if (json::compares_unordered(j_values[i], j_values[j], true))
+ {
+ // if two values compare unordered,
+ // check that the boolean result is always false
+ CHECK_FALSE(j_values[i] >= j_values[j]);
+ }
+ else
+ {
+ // otherwise, check that they compare according to their definition
+ // as the inverse of less than
+ CHECK((j_values[i] >= j_values[j]) == !(j_values[i] < j_values[j]));
+ }
+ }
+ }
+ }
+
+#if JSON_HAS_THREE_WAY_COMPARISON
+ // JSON_HAS_CPP_20 (do not remove; see note at top of file)
+ SECTION("comparison: 3-way")
+ {
+ std::vector<std::vector<std::partial_ordering>> expected =
+ {
+ //0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
+ {eq, eq, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, un, un}, // 0
+ {eq, eq, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, un, un}, // 1
+ {gt, gt, eq, lt, lt, lt, lt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 2
+ {gt, gt, gt, eq, gt, gt, gt, gt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 3
+ {gt, gt, gt, lt, eq, lt, gt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 4
+ {gt, gt, gt, lt, gt, eq, gt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 5
+ {gt, gt, gt, lt, lt, lt, eq, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 6
+ {gt, gt, gt, lt, gt, gt, gt, eq, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 7
+ {gt, gt, un, un, un, un, un, un, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 8
+ {gt, gt, un, un, un, un, un, un, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 9
+ {gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, gt, gt, gt, gt, gt, gt, gt, lt, lt, un, un}, // 10
+ {gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, eq, gt, gt, gt, gt, gt, gt, lt, lt, un, un}, // 11
+ {gt, gt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, eq, gt, lt, lt, lt, lt, lt, lt, un, un}, // 12
+ {gt, gt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, eq, lt, lt, lt, lt, lt, lt, un, un}, // 13
+ {gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, eq, lt, gt, gt, lt, lt, un, un}, // 14
+ {gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, gt, eq, gt, gt, lt, lt, un, un}, // 15
+ {gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, lt, lt, eq, gt, lt, lt, un, un}, // 16
+ {gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, lt, lt, lt, eq, lt, lt, un, un}, // 17
+ {gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, lt, un, un}, // 18
+ {gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, un, un}, // 19
+ {un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un}, // 20
+ {un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un}, // 21
+ };
+
+ // check expected partial_ordering against expected booleans
+ REQUIRE(expected.size() == expected_eq.size());
+ REQUIRE(expected.size() == expected_lt.size());
+ for (size_t i = 0; i < expected.size(); ++i)
+ {
+ REQUIRE(expected[i].size() == expected_eq[i].size());
+ REQUIRE(expected[i].size() == expected_lt[i].size());
+ for (size_t j = 0; j < expected[i].size(); ++j)
+ {
+ CAPTURE(i)
+ CAPTURE(j)
+ CHECK(std::is_eq(expected[i][j]) == expected_eq[i][j]);
+ CHECK(std::is_lt(expected[i][j]) == expected_lt[i][j]);
+ if (std::is_gt(expected[i][j]))
+ {
+ CHECK((!expected_eq[i][j] && !expected_lt[i][j]));
+ }
+ }
+ }
+
+ // check that two values compare according to their expected ordering
+ REQUIRE(expected.size() == j_values.size());
+ for (size_t i = 0; i < j_values.size(); ++i)
+ {
+ REQUIRE(expected[i].size() == j_values.size());
+ for (size_t j = 0; j < j_values.size(); ++j)
+ {
+ CAPTURE(i)
+ CAPTURE(j)
+ CHECK((j_values[i] <=> j_values[j]) == expected[i][j]); // *NOPAD*
+ }
+ }
+ }
+#endif
+ }
+
+#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
+ SECTION("parser callback regression")
+ {
+ SECTION("filter specific element")
+ {
+ const auto* s_object = R"(
+ {
+ "foo": 2,
+ "bar": {
+ "baz": 1
+ }
+ }
+ )";
+ const auto* s_array = R"(
+ [1,2,[3,4,5],4,5]
+ )";
+
+ const json j_object = json::parse(s_object, [](int /*unused*/, json::parse_event_t /*unused*/, const json & j) noexcept
+ {
+ // filter all number(2) elements
+ return j != json(2);
+ });
+
+ CHECK (j_object == json({{"bar", {{"baz", 1}}}}));
+
+ const json j_array = json::parse(s_array, [](int /*unused*/, json::parse_event_t /*unused*/, const json & j) noexcept
+ {
+ return j != json(2);
+ });
+
+ CHECK (j_array == json({1, {3, 4, 5}, 4, 5}));
+ }
+ }
+#endif
+}
diff --git a/json4cpp/tests/src/unit-concepts.cpp b/json4cpp/tests/src/unit-concepts.cpp
new file mode 100644
index 0000000000..10153ffc4c
--- /dev/null
+++ b/json4cpp/tests/src/unit-concepts.cpp
@@ -0,0 +1,149 @@
+// __ _____ _____ _____
+// __| | __| | | | 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;
+
+TEST_CASE("concepts")
+{
+ SECTION("container requirements for json")
+ {
+ // X: container class: json
+ // T: type of objects: json
+ // a, b: values of type X: json
+
+ // TABLE 96 - Container Requirements
+ // X::value_type must return T
+ CHECK((std::is_same<json::value_type, json>::value));
+
+ // X::reference must return lvalue of T
+ CHECK((std::is_same<json::reference, json&>::value));
+
+ // X::const_reference must return const lvalue of T
+ CHECK((std::is_same<json::const_reference, const json&>::value));
+
+ // X::iterator must return iterator whose value_type is T
+ CHECK((std::is_same<json::iterator::value_type, json>::value));
+ // X::iterator must meet the forward iterator requirements
+ CHECK((std::is_base_of<std::forward_iterator_tag, typename std::iterator_traits<json::iterator>::iterator_category>::value));
+ // X::iterator must be convertible to X::const_iterator
+ CHECK((std::is_convertible<json::iterator, json::const_iterator>::value));
+
+ // X::const_iterator must return iterator whose value_type is T
+ CHECK((std::is_same<json::const_iterator::value_type, json>::value));
+ // X::const_iterator must meet the forward iterator requirements
+ CHECK((std::is_base_of<std::forward_iterator_tag, typename std::iterator_traits<json::const_iterator>::iterator_category>::value));
+
+ // X::difference_type must return a signed integer
+ CHECK((std::is_signed<json::difference_type>::value));
+ // X::difference_type must be identical to X::iterator::difference_type
+ CHECK((std::is_same<json::difference_type, json::iterator::difference_type>::value));
+ // X::difference_type must be identical to X::const_iterator::difference_type
+ CHECK((std::is_same<json::difference_type, json::const_iterator::difference_type>::value));
+
+ // X::size_type must return an unsigned integer
+ CHECK((std::is_unsigned<json::size_type>::value));
+ // X::size_type can represent any non-negative value of X::difference_type
+ CHECK(static_cast<json::size_type>((std::numeric_limits<json::difference_type>::max)()) <=
+ (std::numeric_limits<json::size_type>::max)());
+
+ // the expression "X u" has the post-condition "u.empty()"
+ {
+ const json u;
+ CHECK(u.empty());
+ }
+
+ // the expression "X()" has the post-condition "X().empty()"
+ CHECK(json().empty());
+ }
+
+ SECTION("class json")
+ {
+ SECTION("DefaultConstructible")
+ {
+ CHECK(std::is_nothrow_default_constructible<json>::value);
+ }
+
+ SECTION("MoveConstructible")
+ {
+ CHECK(std::is_move_constructible<json>::value);
+ CHECK(std::is_nothrow_move_constructible<json>::value);
+ }
+
+ SECTION("CopyConstructible")
+ {
+ CHECK(std::is_copy_constructible<json>::value);
+ }
+
+ SECTION("MoveAssignable")
+ {
+ CHECK(std::is_nothrow_move_assignable<json>::value);
+ }
+
+ SECTION("CopyAssignable")
+ {
+ CHECK(std::is_copy_assignable<json>::value);
+ }
+
+ SECTION("Destructible")
+ {
+ CHECK(std::is_nothrow_destructible<json>::value);
+ }
+
+ SECTION("StandardLayoutType")
+ {
+ CHECK(std::is_standard_layout<json>::value);
+ }
+ }
+
+ SECTION("class iterator")
+ {
+ SECTION("CopyConstructible")
+ {
+ CHECK(std::is_nothrow_copy_constructible<json::iterator>::value);
+ CHECK(std::is_nothrow_copy_constructible<json::const_iterator>::value);
+ }
+
+ SECTION("CopyAssignable")
+ {
+ // STL iterators used by json::iterator don't pass this test in Debug mode
+#if !defined(_MSC_VER) || (_ITERATOR_DEBUG_LEVEL == 0)
+ CHECK(std::is_nothrow_copy_assignable<json::iterator>::value);
+ CHECK(std::is_nothrow_copy_assignable<json::const_iterator>::value);
+#endif
+ }
+
+ SECTION("Destructible")
+ {
+ CHECK(std::is_nothrow_destructible<json::iterator>::value);
+ CHECK(std::is_nothrow_destructible<json::const_iterator>::value);
+ }
+
+ SECTION("Swappable")
+ {
+ {
+ json j {1, 2, 3};
+ json::iterator it1 = j.begin();
+ json::iterator it2 = j.end();
+ swap(it1, it2);
+ CHECK(it1 == j.end());
+ CHECK(it2 == j.begin());
+ }
+ {
+ json j {1, 2, 3};
+ json::const_iterator it1 = j.cbegin();
+ json::const_iterator it2 = j.cend();
+ swap(it1, it2);
+ CHECK(it1 == j.end());
+ CHECK(it2 == j.begin());
+ }
+ }
+ }
+}
diff --git a/json4cpp/tests/src/unit-constructor1.cpp b/json4cpp/tests/src/unit-constructor1.cpp
new file mode 100644
index 0000000000..9917cd358f
--- /dev/null
+++ b/json4cpp/tests/src/unit-constructor1.cpp
@@ -0,0 +1,1654 @@
+// __ _____ _____ _____
+// __| | __| | | | 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;
+
+#include <deque>
+#include <forward_list>
+#include <fstream>
+#include <list>
+#include <set>
+#include <unordered_map>
+#include <unordered_set>
+#include <valarray>
+
+TEST_CASE("constructors")
+{
+ SECTION("create an empty value with a given type")
+ {
+ SECTION("null")
+ {
+ auto const t = json::value_t::null;
+ json const j(t);
+ CHECK(j.type() == t);
+ }
+
+ SECTION("discarded")
+ {
+ auto const t = json::value_t::discarded;
+ json const j(t);
+ CHECK(j.type() == t);
+ }
+
+ SECTION("object")
+ {
+ auto const t = json::value_t::object;
+ json const j(t);
+ CHECK(j.type() == t);
+ }
+
+ SECTION("array")
+ {
+ auto const t = json::value_t::array;
+ json const j(t);
+ CHECK(j.type() == t);
+ }
+
+ SECTION("boolean")
+ {
+ auto const t = json::value_t::boolean;
+ json const j(t);
+ CHECK(j.type() == t);
+ CHECK(j == false);
+ }
+
+ SECTION("string")
+ {
+ auto const t = json::value_t::string;
+ json const j(t);
+ CHECK(j.type() == t);
+ CHECK(j == "");
+ }
+
+ SECTION("number_integer")
+ {
+ auto const t = json::value_t::number_integer;
+ json const j(t);
+ CHECK(j.type() == t);
+ CHECK(j == 0);
+ }
+
+ SECTION("number_unsigned")
+ {
+ auto const t = json::value_t::number_unsigned;
+ json const j(t);
+ CHECK(j.type() == t);
+ CHECK(j == 0);
+ }
+
+ SECTION("number_float")
+ {
+ auto const t = json::value_t::number_float;
+ json const j(t);
+ CHECK(j.type() == t);
+ CHECK(j == 0.0);
+ }
+
+ SECTION("binary")
+ {
+ auto const t = json::value_t::binary;
+ json const j(t);
+ CHECK(j.type() == t);
+ CHECK(j == json::binary({}));
+ }
+ }
+
+ SECTION("create a null object (implicitly)")
+ {
+ SECTION("no parameter")
+ {
+ json const j{};
+ CHECK(j.type() == json::value_t::null);
+ }
+ }
+
+ SECTION("create a null object (explicitly)")
+ {
+ SECTION("parameter")
+ {
+ json const j(nullptr);
+ CHECK(j.type() == json::value_t::null);
+ }
+ }
+
+ SECTION("create an object (explicit)")
+ {
+ SECTION("empty object")
+ {
+ json::object_t const o{};
+ json const j(o);
+ CHECK(j.type() == json::value_t::object);
+ }
+
+ SECTION("filled object")
+ {
+ json::object_t const o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}};
+ json const j(o);
+ CHECK(j.type() == json::value_t::object);
+ }
+ }
+
+ SECTION("create an object (implicit)")
+ {
+ // reference object
+ json::object_t const o_reference {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}};
+ json const j_reference(o_reference);
+
+ SECTION("std::map<json::string_t, json>")
+ {
+ std::map<json::string_t, json> const o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}};
+ json const j(o);
+ CHECK(j.type() == json::value_t::object);
+ CHECK(j == j_reference);
+ }
+
+ SECTION("std::map<std::string, std::string> #600")
+ {
+ const std::map<std::string, std::string> m
+ {
+ {"a", "b"},
+ {"c", "d"},
+ {"e", "f"},
+ };
+
+ json const j(m);
+ CHECK((j.get<decltype(m)>() == m));
+ }
+
+ SECTION("std::map<const char*, json>")
+ {
+ std::map<const char*, json> const o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}};
+ json const j(o);
+ CHECK(j.type() == json::value_t::object);
+ CHECK(j == j_reference);
+ }
+
+ SECTION("std::multimap<json::string_t, json>")
+ {
+ std::multimap<json::string_t, json> const o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}};
+ json const j(o);
+ CHECK(j.type() == json::value_t::object);
+ CHECK(j == j_reference);
+ }
+
+ SECTION("std::unordered_map<json::string_t, json>")
+ {
+ std::unordered_map<json::string_t, json> const o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}};
+ json const j(o);
+ CHECK(j.type() == json::value_t::object);
+ CHECK(j == j_reference);
+ }
+
+ SECTION("std::unordered_multimap<json::string_t, json>")
+ {
+ std::unordered_multimap<json::string_t, json> const o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}};
+ json const j(o);
+ CHECK(j.type() == json::value_t::object);
+ CHECK(j == j_reference);
+ }
+
+ SECTION("associative container literal")
+ {
+ json const j({{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}});
+ CHECK(j.type() == json::value_t::object);
+ CHECK(j == j_reference);
+ }
+ }
+
+ SECTION("create an array (explicit)")
+ {
+ SECTION("empty array")
+ {
+ json::array_t const a{};
+ json const j(a);
+ CHECK(j.type() == json::value_t::array);
+ }
+
+ SECTION("filled array")
+ {
+ json::array_t const a {json(1), json(1u), json(2.2), json(false), json("string"), json()};
+ json const j(a);
+ CHECK(j.type() == json::value_t::array);
+ }
+ }
+
+ SECTION("create an array (implicit)")
+ {
+ // reference array
+ json::array_t const a_reference {json(1), json(1u), json(2.2), json(false), json("string"), json()};
+ json const j_reference(a_reference);
+
+ SECTION("std::list<json>")
+ {
+ std::list<json> const a {json(1), json(1u), json(2.2), json(false), json("string"), json()};
+ json const j(a);
+ CHECK(j.type() == json::value_t::array);
+ CHECK(j == j_reference);
+ }
+
+ SECTION("std::pair")
+ {
+ std::pair<float, std::string> const p{1.0f, "string"};
+ json const j(p);
+
+ CHECK(j.type() == json::value_t::array);
+ CHECK(j.get<decltype(p)>() == p);
+ REQUIRE(j.size() == 2);
+ CHECK(j[0] == std::get<0>(p));
+ CHECK(j[1] == std::get<1>(p));
+ }
+
+ SECTION("std::pair with discarded values")
+ {
+ json const j{1, 2.0, "string"};
+
+ const auto p = j.get<std::pair<int, float>>();
+ CHECK(p.first == j[0]);
+ CHECK(p.second == j[1]);
+ }
+
+ SECTION("std::tuple")
+ {
+ const auto t = std::make_tuple(1.0, std::string{"string"}, 42, std::vector<int> {0, 1});
+ json const j(t);
+
+ CHECK(j.type() == json::value_t::array);
+ REQUIRE(j.size() == 4);
+ CHECK(j.get<decltype(t)>() == t);
+ CHECK(j[0] == std::get<0>(t));
+ CHECK(j[1] == std::get<1>(t));
+ CHECK(j[2] == std::get<2>(t));
+ CHECK(j[3][0] == 0);
+ CHECK(j[3][1] == 1);
+ }
+
+ SECTION("std::tuple with discarded values")
+ {
+ json const j{1, 2.0, "string", 42};
+
+ const auto t = j.get<std::tuple<int, float, std::string>>();
+ CHECK(std::get<0>(t) == j[0]);
+ CHECK(std::get<1>(t) == j[1]);
+ // CHECK(std::get<2>(t) == j[2]); // commented out due to CI issue, see https://github.com/nlohmann/json/pull/3985 and https://github.com/nlohmann/json/issues/4025
+ }
+
+ SECTION("std::tuple tie")
+ {
+ const auto a = 1.0;
+ const auto* const b = "string";
+ const auto c = 42;
+ const auto d = std::vector<int> {0, 2};
+ const size_t e = 1234;
+ auto t = std::tie(a, b, c, d, e);
+ json const j(t);
+
+ double a_out = 0;
+ std::string b_out;
+ int c_out = 0;
+ std::vector<int> d_out;
+ int64_t e_out = 0;
+ auto t_out = std::tie(a_out, b_out, c_out, d_out, e_out);
+ j.get_to(t_out);
+ CHECK(a_out == a);
+ CHECK(b_out == b);
+ CHECK(c_out == c);
+ CHECK(d_out == d);
+ CHECK(e_out == e);
+ }
+
+ SECTION("std::tuple of references to elements")
+ {
+ const auto a = 1.0;
+ const auto* const b = "string";
+ const auto c = 42;
+ const size_t d = 1234;
+ const auto t = std::tie(a, b, c, d);
+ json const j(t);
+
+ auto t_out = j.get<std::tuple<const json::number_float_t&,
+ const json::string_t&,
+ const json::number_integer_t&,
+ const json::number_unsigned_t&>>();
+ CHECK(&std::get<0>(t_out) == j[0].get_ptr<const json::number_float_t*>());
+ CHECK(&std::get<1>(t_out) == j[1].get_ptr<const json::string_t*>());
+ CHECK(&std::get<2>(t_out) == j[2].get_ptr<const json::number_integer_t*>());
+ CHECK(&std::get<3>(t_out) == j[3].get_ptr<const json::number_unsigned_t*>());
+ CHECK(std::get<0>(t_out) == a);
+ CHECK(std::get<1>(t_out) == b);
+ CHECK(std::get<2>(t_out) == c);
+ CHECK(std::get<3>(t_out) == d);
+ }
+
+ SECTION("std::tuple mixed arithmetic types")
+ {
+ using j_float_t = json::number_float_t;
+ using j_int_t = json::number_integer_t;
+ using j_uint_t = json::number_unsigned_t;
+ const j_float_t a = 1.0;
+ const j_int_t b = 1234;
+ const j_uint_t c = 42;
+ json const j(std::tie(a, b, c, c));
+
+ auto t1 = j.get<std::tuple<j_int_t, j_uint_t, j_float_t, const j_uint_t&>>();
+ j_uint_t a2 = 0;
+ j_float_t b2 = 0;
+ j_int_t c2 = 0;
+ auto t2 = std::tie(a2, b2, c2);
+ j.get_to(t2);
+
+ CHECK(std::get<0>(t1) == static_cast<j_int_t>(a));
+ CHECK(std::get<1>(t1) == static_cast<j_uint_t>(b));
+ CHECK(std::get<2>(t1) == static_cast<j_float_t>(c));
+ // t1[3] exists only to force usage of the no-default-constructor version
+ CHECK(a2 == static_cast<j_uint_t>(a));
+ CHECK(b2 == static_cast<j_float_t>(b));
+ CHECK(c2 == static_cast<j_int_t>(c));
+ }
+
+ SECTION("std::pair/tuple/array failures")
+ {
+ json const j{1};
+
+ CHECK_THROWS_WITH_AS((j.get<std::pair<int, int>>()), "[json.exception.out_of_range.401] array index 1 is out of range", json::out_of_range&);
+ CHECK_THROWS_WITH_AS((j.get<std::tuple<int, int>>()), "[json.exception.out_of_range.401] array index 1 is out of range", json::out_of_range&);
+ CHECK_THROWS_WITH_AS((j.get<std::array<int, 3>>()), "[json.exception.out_of_range.401] array index 1 is out of range", json::out_of_range&);
+ }
+
+ SECTION("std::forward_list<json>")
+ {
+ std::forward_list<json> const a {json(1), json(1u), json(2.2), json(false), json("string"), json()};
+ json const j(a);
+ CHECK(j.type() == json::value_t::array);
+ CHECK(j == j_reference);
+ }
+
+ SECTION("std::array<json, 6>")
+ {
+ std::array<json, 6> const a {{json(1), json(1u), json(2.2), json(false), json("string"), json()}};
+ json const j(a);
+ CHECK(j.type() == json::value_t::array);
+ CHECK(j == j_reference);
+
+ const auto a2 = j.get<std::array<json, 6>>();
+ CHECK(a2 == a);
+ }
+
+ SECTION("std::valarray<int>")
+ {
+ std::valarray<int> const va = {1, 2, 3, 4, 5};
+ json const j(va);
+ CHECK(j.type() == json::value_t::array);
+ CHECK(j == json({1, 2, 3, 4, 5}));
+
+ auto jva = j.get<std::valarray<int>>();
+ CHECK(jva.size() == va.size());
+ for (size_t i = 0; i < jva.size(); ++i)
+ {
+ CHECK(va[i] == jva[i]);
+ }
+ }
+
+ SECTION("std::valarray<double>")
+ {
+ std::valarray<double> const va = {1.2, 2.3, 3.4, 4.5, 5.6};
+ json const j(va);
+ CHECK(j.type() == json::value_t::array);
+ CHECK(j == json({1.2, 2.3, 3.4, 4.5, 5.6}));
+
+ auto jva = j.get<std::valarray<double>>();
+ CHECK(jva.size() == va.size());
+ for (size_t i = 0; i < jva.size(); ++i)
+ {
+ CHECK(va[i] == jva[i]);
+ }
+ }
+
+ SECTION("std::vector<json>")
+ {
+ std::vector<json> const a {json(1), json(1u), json(2.2), json(false), json("string"), json()};
+ json const j(a);
+ CHECK(j.type() == json::value_t::array);
+ CHECK(j == j_reference);
+ }
+
+ SECTION("std::deque<json>")
+ {
+ std::deque<json> const a {json(1), json(1u), json(2.2), json(false), json("string"), json()};
+ json const j(a);
+ CHECK(j.type() == json::value_t::array);
+ CHECK(j == j_reference);
+ }
+
+ SECTION("std::set<json>")
+ {
+ std::set<json> const a {json(1), json(1u), json(2.2), json(false), json("string"), json()};
+ json const j(a);
+ CHECK(j.type() == json::value_t::array);
+ // we cannot really check for equality here
+ }
+
+ SECTION("std::unordered_set<json>")
+ {
+ std::unordered_set<json> const a {json(1), json(1u), json(2.2), json(false), json("string"), json()};
+ json const j(a);
+ CHECK(j.type() == json::value_t::array);
+ // we cannot really check for equality here
+ }
+
+ SECTION("sequence container literal")
+ {
+ json const j({json(1), json(1u), json(2.2), json(false), json("string"), json()});
+ CHECK(j.type() == json::value_t::array);
+ CHECK(j == j_reference);
+ }
+ }
+
+ SECTION("create a string (explicit)")
+ {
+ SECTION("empty string")
+ {
+ json::string_t const s{};
+ json const j(s);
+ CHECK(j.type() == json::value_t::string);
+ }
+
+ SECTION("filled string")
+ {
+ json::string_t const s {"Hello world"};
+ json const j(s);
+ CHECK(j.type() == json::value_t::string);
+ }
+ }
+
+ SECTION("create a string (implicit)")
+ {
+ // reference string
+ json::string_t const s_reference {"Hello world"};
+ json const j_reference(s_reference);
+
+ SECTION("std::string")
+ {
+ std::string const s {"Hello world"};
+ json const j(s);
+ CHECK(j.type() == json::value_t::string);
+ CHECK(j == j_reference);
+ }
+
+ SECTION("char[]")
+ {
+ const char s[] {"Hello world"}; // NOLINT(misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
+ json const j(s);
+ CHECK(j.type() == json::value_t::string);
+ CHECK(j == j_reference);
+ }
+
+ SECTION("const char*")
+ {
+ const char* s {"Hello world"};
+ json const j(s);
+ CHECK(j.type() == json::value_t::string);
+ CHECK(j == j_reference);
+ }
+
+ SECTION("string literal")
+ {
+ json const j("Hello world");
+ CHECK(j.type() == json::value_t::string);
+ CHECK(j == j_reference);
+ }
+ }
+
+ SECTION("create a boolean (explicit)")
+ {
+ SECTION("empty boolean")
+ {
+ json::boolean_t const b{};
+ json const j(b);
+ CHECK(j.type() == json::value_t::boolean);
+ }
+
+ SECTION("filled boolean (true)")
+ {
+ json const j(true);
+ CHECK(j.type() == json::value_t::boolean);
+ }
+
+ SECTION("filled boolean (false)")
+ {
+ json const j(false);
+ CHECK(j.type() == json::value_t::boolean);
+ }
+
+ SECTION("from std::vector<bool>::reference")
+ {
+ std::vector<bool> v{true};
+ json const j(v[0]);
+ CHECK(std::is_same<decltype(v[0]), std::vector<bool>::reference>::value);
+ CHECK(j.type() == json::value_t::boolean);
+ }
+
+ SECTION("from std::vector<bool>::const_reference")
+ {
+ const std::vector<bool> v{true};
+ json const j(v[0]);
+ CHECK(std::is_same<decltype(v[0]), std::vector<bool>::const_reference>::value);
+ CHECK(j.type() == json::value_t::boolean);
+ }
+ }
+
+ SECTION("create a binary (explicit)")
+ {
+ SECTION("empty binary")
+ {
+ json::binary_t const b{};
+ json const j(b);
+ CHECK(j.type() == json::value_t::binary);
+ }
+
+ SECTION("filled binary")
+ {
+ json::binary_t const b({1, 2, 3});
+ json const j(b);
+ CHECK(j.type() == json::value_t::binary);
+ }
+ }
+
+ SECTION("create an integer number (explicit)")
+ {
+ SECTION("uninitialized value")
+ {
+ json::number_integer_t const n{};
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_integer);
+ }
+
+ SECTION("initialized value")
+ {
+ json::number_integer_t const n(42);
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_integer);
+ }
+ }
+
+ SECTION("create an integer number (implicit)")
+ {
+ // reference objects
+ json::number_integer_t const n_reference = 42;
+ json const j_reference(n_reference);
+ json::number_unsigned_t const n_unsigned_reference = 42;
+ json const j_unsigned_reference(n_unsigned_reference);
+
+ SECTION("short")
+ {
+ short const n = 42;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_integer);
+ CHECK(j == j_reference);
+ }
+
+ SECTION("unsigned short")
+ {
+ unsigned short const n = 42;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_unsigned);
+ CHECK(j == j_unsigned_reference);
+ }
+
+ SECTION("int")
+ {
+ int const n = 42;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_integer);
+ CHECK(j == j_reference);
+ }
+
+ SECTION("unsigned int")
+ {
+ unsigned int const n = 42;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_unsigned);
+ CHECK(j == j_unsigned_reference);
+ }
+
+ SECTION("long")
+ {
+ long const n = 42;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_integer);
+ CHECK(j == j_reference);
+ }
+
+ SECTION("unsigned long")
+ {
+ unsigned long const n = 42;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_unsigned);
+ CHECK(j == j_unsigned_reference);
+ }
+
+ SECTION("long long")
+ {
+ long long const n = 42;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_integer);
+ CHECK(j == j_reference);
+ }
+
+ SECTION("unsigned long long")
+ {
+ unsigned long long const n = 42;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_unsigned);
+ CHECK(j == j_unsigned_reference);
+ }
+
+ SECTION("int8_t")
+ {
+ int8_t const n = 42;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_integer);
+ CHECK(j == j_reference);
+ }
+
+ SECTION("int16_t")
+ {
+ int16_t const n = 42;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_integer);
+ CHECK(j == j_reference);
+ }
+
+ SECTION("int32_t")
+ {
+ int32_t const n = 42;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_integer);
+ CHECK(j == j_reference);
+ }
+
+ SECTION("int64_t")
+ {
+ int64_t const n = 42;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_integer);
+ CHECK(j == j_reference);
+ }
+
+ SECTION("int_fast8_t")
+ {
+ int_fast8_t const n = 42;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_integer);
+ CHECK(j == j_reference);
+ }
+
+ SECTION("int_fast16_t")
+ {
+ int_fast16_t const n = 42;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_integer);
+ CHECK(j == j_reference);
+ }
+
+ SECTION("int_fast32_t")
+ {
+ int_fast32_t const n = 42;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_integer);
+ CHECK(j == j_reference);
+ }
+
+ SECTION("int_fast64_t")
+ {
+ int_fast64_t const n = 42;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_integer);
+ CHECK(j == j_reference);
+ }
+
+ SECTION("int_least8_t")
+ {
+ int_least8_t const n = 42;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_integer);
+ CHECK(j == j_reference);
+ }
+
+ SECTION("int_least16_t")
+ {
+ int_least16_t const n = 42;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_integer);
+ CHECK(j == j_reference);
+ }
+
+ SECTION("int_least32_t")
+ {
+ int_least32_t const n = 42;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_integer);
+ CHECK(j == j_reference);
+ }
+
+ SECTION("int_least64_t")
+ {
+ int_least64_t const n = 42;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_integer);
+ CHECK(j == j_reference);
+ }
+
+ SECTION("uint8_t")
+ {
+ uint8_t const n = 42;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_unsigned);
+ CHECK(j == j_unsigned_reference);
+ }
+
+ SECTION("uint16_t")
+ {
+ uint16_t const n = 42;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_unsigned);
+ CHECK(j == j_unsigned_reference);
+ }
+
+ SECTION("uint32_t")
+ {
+ uint32_t const n = 42;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_unsigned);
+ CHECK(j == j_unsigned_reference);
+ }
+
+ SECTION("uint64_t")
+ {
+ uint64_t const n = 42;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_unsigned);
+ CHECK(j == j_unsigned_reference);
+ }
+
+ SECTION("uint_fast8_t")
+ {
+ uint_fast8_t const n = 42;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_unsigned);
+ CHECK(j == j_unsigned_reference);
+ }
+
+ SECTION("uint_fast16_t")
+ {
+ uint_fast16_t const n = 42;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_unsigned);
+ CHECK(j == j_unsigned_reference);
+ }
+
+ SECTION("uint_fast32_t")
+ {
+ uint_fast32_t const n = 42;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_unsigned);
+ CHECK(j == j_unsigned_reference);
+ }
+
+ SECTION("uint_fast64_t")
+ {
+ uint_fast64_t const n = 42;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_unsigned);
+ CHECK(j == j_unsigned_reference);
+ }
+
+ SECTION("uint_least8_t")
+ {
+ uint_least8_t const n = 42;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_unsigned);
+ CHECK(j == j_unsigned_reference);
+ }
+
+ SECTION("uint_least16_t")
+ {
+ uint_least16_t const n = 42;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_unsigned);
+ CHECK(j == j_unsigned_reference);
+ }
+
+ SECTION("uint_least32_t")
+ {
+ uint_least32_t const n = 42;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_unsigned);
+ CHECK(j == j_unsigned_reference);
+ }
+
+ SECTION("uint_least64_t")
+ {
+ uint_least64_t const n = 42;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_unsigned);
+ CHECK(j == j_unsigned_reference);
+ }
+
+ SECTION("integer literal without suffix")
+ {
+ json const j(42);
+ CHECK(j.type() == json::value_t::number_integer);
+ CHECK(j == j_reference);
+ }
+
+ SECTION("integer literal with u suffix")
+ {
+ const json j(42u);
+ CHECK(j.type() == json::value_t::number_unsigned);
+ CHECK(j == j_unsigned_reference);
+ }
+
+ SECTION("integer literal with l suffix")
+ {
+ json const j(42L);
+ CHECK(j.type() == json::value_t::number_integer);
+ CHECK(j == j_reference);
+ }
+
+ SECTION("integer literal with ul suffix")
+ {
+ const json j(42ul);
+ CHECK(j.type() == json::value_t::number_unsigned);
+ CHECK(j == j_unsigned_reference);
+ }
+
+ SECTION("integer literal with ll suffix")
+ {
+ json const j(42LL);
+ CHECK(j.type() == json::value_t::number_integer);
+ CHECK(j == j_reference);
+ }
+
+ SECTION("integer literal with ull suffix")
+ {
+ const json j(42ull);
+ CHECK(j.type() == json::value_t::number_unsigned);
+ CHECK(j == j_unsigned_reference);
+ }
+ }
+
+ SECTION("create a floating-point number (explicit)")
+ {
+ SECTION("uninitialized value")
+ {
+ json::number_float_t const n{};
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_float);
+ }
+
+ SECTION("initialized value")
+ {
+ json::number_float_t const n(42.23);
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_float);
+ }
+
+ SECTION("NaN")
+ {
+ // NaN is stored properly, but serialized to null
+ json::number_float_t const n(std::numeric_limits<json::number_float_t>::quiet_NaN());
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_float);
+
+ // check round trip of NaN
+ json::number_float_t const d{j};
+ CHECK((std::isnan(d) && std::isnan(n)) == true);
+
+ // check that NaN is serialized to null
+ CHECK(j.dump() == "null");
+ }
+
+ SECTION("infinity")
+ {
+ // infinity is stored properly, but serialized to null
+ json::number_float_t const n(std::numeric_limits<json::number_float_t>::infinity());
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_float);
+
+ // check round trip of infinity
+ json::number_float_t const d{j};
+ CHECK(d == n);
+
+ // check that inf is serialized to null
+ CHECK(j.dump() == "null");
+ }
+ }
+
+ SECTION("create a floating-point number (implicit)")
+ {
+ // reference object
+ json::number_float_t const n_reference = 42.23;
+ json const j_reference(n_reference);
+
+ SECTION("float")
+ {
+ float const n = 42.23f;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_float);
+ CHECK(j.m_data.m_value.number_float == Approx(j_reference.m_data.m_value.number_float));
+ }
+
+ SECTION("double")
+ {
+ double const n = 42.23;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_float);
+ CHECK(j.m_data.m_value.number_float == Approx(j_reference.m_data.m_value.number_float));
+ }
+
+ SECTION("long double")
+ {
+ long double const n = 42.23L;
+ json const j(n);
+ CHECK(j.type() == json::value_t::number_float);
+ CHECK(j.m_data.m_value.number_float == Approx(j_reference.m_data.m_value.number_float));
+ }
+
+ SECTION("floating-point literal without suffix")
+ {
+ json const j(42.23);
+ CHECK(j.type() == json::value_t::number_float);
+ CHECK(j.m_data.m_value.number_float == Approx(j_reference.m_data.m_value.number_float));
+ }
+
+ SECTION("integer literal with f suffix")
+ {
+ json const j(42.23f);
+ CHECK(j.type() == json::value_t::number_float);
+ CHECK(j.m_data.m_value.number_float == Approx(j_reference.m_data.m_value.number_float));
+ }
+
+ SECTION("integer literal with l suffix")
+ {
+ json const j(42.23L);
+ CHECK(j.type() == json::value_t::number_float);
+ CHECK(j.m_data.m_value.number_float == Approx(j_reference.m_data.m_value.number_float));
+ }
+ }
+
+ SECTION("create a container (array or object) from an initializer list")
+ {
+ SECTION("empty initializer list")
+ {
+ SECTION("explicit")
+ {
+ json const j(json::initializer_list_t {});
+ CHECK(j.type() == json::value_t::object);
+ }
+
+ SECTION("implicit")
+ {
+ json const j {};
+ CHECK(j.type() == json::value_t::null);
+ }
+ }
+
+ SECTION("one element")
+ {
+ SECTION("array")
+ {
+ SECTION("explicit")
+ {
+ json const j(json::initializer_list_t {json(json::array_t())});
+ CHECK(j.type() == json::value_t::array);
+ }
+
+ SECTION("implicit")
+ {
+ json const j {json::array_t()};
+ CHECK(j.type() == json::value_t::array);
+ }
+ }
+
+ SECTION("object")
+ {
+ SECTION("explicit")
+ {
+ json const j(json::initializer_list_t {json(json::object_t())});
+ CHECK(j.type() == json::value_t::array);
+ }
+
+ SECTION("implicit")
+ {
+ json const j {json::object_t()};
+ CHECK(j.type() == json::value_t::array);
+ }
+ }
+
+ SECTION("string")
+ {
+ SECTION("explicit")
+ {
+ json const j(json::initializer_list_t {json("Hello world")});
+ CHECK(j.type() == json::value_t::array);
+ }
+
+ SECTION("implicit")
+ {
+ json const j {"Hello world"};
+ CHECK(j.type() == json::value_t::array);
+ }
+ }
+
+ SECTION("boolean")
+ {
+ SECTION("explicit")
+ {
+ json const j(json::initializer_list_t {json(true)});
+ CHECK(j.type() == json::value_t::array);
+ }
+
+ SECTION("implicit")
+ {
+ json const j {true};
+ CHECK(j.type() == json::value_t::array);
+ }
+ }
+
+ SECTION("number (integer)")
+ {
+ SECTION("explicit")
+ {
+ json const j(json::initializer_list_t {json(1)});
+ CHECK(j.type() == json::value_t::array);
+ }
+
+ SECTION("implicit")
+ {
+ json const j {1};
+ CHECK(j.type() == json::value_t::array);
+ }
+ }
+
+ SECTION("number (unsigned)")
+ {
+ SECTION("explicit")
+ {
+ json const j(json::initializer_list_t {json(1u)});
+ CHECK(j.type() == json::value_t::array);
+ }
+
+ SECTION("implicit")
+ {
+ json const j {1u};
+ CHECK(j.type() == json::value_t::array);
+ }
+ }
+
+ SECTION("number (floating-point)")
+ {
+ SECTION("explicit")
+ {
+ json const j(json::initializer_list_t {json(42.23)});
+ CHECK(j.type() == json::value_t::array);
+ }
+
+ SECTION("implicit")
+ {
+ json const j {42.23};
+ CHECK(j.type() == json::value_t::array);
+ }
+ }
+ }
+
+ SECTION("more elements")
+ {
+ SECTION("explicit")
+ {
+ json const j(json::initializer_list_t {1, 1u, 42.23, true, nullptr, json::object_t(), json::array_t()});
+ CHECK(j.type() == json::value_t::array);
+ }
+
+ SECTION("implicit")
+ {
+ json const j {1, 1u, 42.23, true, nullptr, json::object_t(), json::array_t()};
+ CHECK(j.type() == json::value_t::array);
+ }
+ }
+
+ SECTION("implicit type deduction")
+ {
+ SECTION("object")
+ {
+ json const j { {"one", 1}, {"two", 1u}, {"three", 2.2}, {"four", false} };
+ CHECK(j.type() == json::value_t::object);
+ }
+
+ SECTION("array")
+ {
+ json const j { {"one", 1}, {"two", 1u}, {"three", 2.2}, {"four", false}, 13 };
+ CHECK(j.type() == json::value_t::array);
+ }
+ }
+
+ SECTION("explicit type deduction")
+ {
+ SECTION("empty object")
+ {
+ json const j = json::object();
+ CHECK(j.type() == json::value_t::object);
+ }
+
+ SECTION("object")
+ {
+ json const j = json::object({ {"one", 1}, {"two", 1u}, {"three", 2.2}, {"four", false} });
+ CHECK(j.type() == json::value_t::object);
+ }
+
+ SECTION("object with error")
+ {
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::object({ {"one", 1}, {"two", 1u}, {"three", 2.2}, {"four", false}, 13 }), "[json.exception.type_error.301] cannot create object from initializer list", json::type_error&);
+ }
+
+ SECTION("empty array")
+ {
+ json const j = json::array();
+ CHECK(j.type() == json::value_t::array);
+ }
+
+ SECTION("array")
+ {
+ json const j = json::array({ {"one", 1}, {"two", 1u}, {"three", 2.2}, {"four", false} });
+ CHECK(j.type() == json::value_t::array);
+ }
+ }
+
+ SECTION("move from initializer_list")
+ {
+ SECTION("string")
+ {
+ SECTION("constructor with implicit types (array)")
+ {
+ // This should break through any short string optimization in std::string
+ std::string source(1024, '!');
+ const auto* source_addr = source.data();
+ json j = {std::move(source)};
+ const auto* target_addr = j[0].get_ref<std::string const&>().data();
+ const bool success = (target_addr == source_addr);
+ CHECK(success);
+ }
+
+ SECTION("constructor with implicit types (object)")
+ {
+ // This should break through any short string optimization in std::string
+ std::string source(1024, '!');
+ const auto* source_addr = source.data();
+ json j = {{"key", std::move(source)}};
+ const auto* target_addr = j["key"].get_ref<std::string const&>().data();
+ const bool success = (target_addr == source_addr);
+ CHECK(success);
+ }
+
+ SECTION("constructor with implicit types (object key)")
+ {
+ // This should break through any short string optimization in std::string
+ std::string source(1024, '!');
+ const auto* source_addr = source.data();
+ json j = {{std::move(source), 42}};
+ const auto* target_addr = j.get_ref<json::object_t&>().begin()->first.data();
+ const bool success = (target_addr == source_addr);
+ CHECK(success);
+ }
+ }
+
+ SECTION("array")
+ {
+ SECTION("constructor with implicit types (array)")
+ {
+ json::array_t source = {1, 2, 3};
+ const auto* source_addr = source.data();
+ json j {std::move(source)};
+ const auto* target_addr = j[0].get_ref<json::array_t const&>().data();
+ const bool success = (target_addr == source_addr);
+ CHECK(success);
+ }
+
+ SECTION("constructor with implicit types (object)")
+ {
+ json::array_t source = {1, 2, 3};
+ const auto* source_addr = source.data();
+ json const j {{"key", std::move(source)}};
+ const auto* target_addr = j["key"].get_ref<json::array_t const&>().data();
+ const bool success = (target_addr == source_addr);
+ CHECK(success);
+ }
+
+ SECTION("assignment with implicit types (array)")
+ {
+ json::array_t source = {1, 2, 3};
+ const auto* source_addr = source.data();
+ json j = {std::move(source)};
+ const auto* target_addr = j[0].get_ref<json::array_t const&>().data();
+ const bool success = (target_addr == source_addr);
+ CHECK(success);
+ }
+
+ SECTION("assignment with implicit types (object)")
+ {
+ json::array_t source = {1, 2, 3};
+ const auto* source_addr = source.data();
+ json j = {{"key", std::move(source)}};
+ const auto* target_addr = j["key"].get_ref<json::array_t const&>().data();
+ const bool success = (target_addr == source_addr);
+ CHECK(success);
+ }
+ }
+
+ SECTION("object")
+ {
+ SECTION("constructor with implicit types (array)")
+ {
+ json::object_t source = {{"hello", "world"}};
+ const json* source_addr = &source.at("hello");
+ json j {std::move(source)};
+ CHECK(&(j[0].get_ref<json::object_t const&>().at("hello")) == source_addr);
+ }
+
+ SECTION("constructor with implicit types (object)")
+ {
+ json::object_t source = {{"hello", "world"}};
+ const json* source_addr = &source.at("hello");
+ json j {{"key", std::move(source)}};
+ CHECK(&(j["key"].get_ref<json::object_t const&>().at("hello")) == source_addr);
+ }
+
+ SECTION("assignment with implicit types (array)")
+ {
+ json::object_t source = {{"hello", "world"}};
+ const json* source_addr = &source.at("hello");
+ json j = {std::move(source)};
+ CHECK(&(j[0].get_ref<json::object_t const&>().at("hello")) == source_addr);
+ }
+
+ SECTION("assignment with implicit types (object)")
+ {
+ json::object_t source = {{"hello", "world"}};
+ const json* source_addr = &source.at("hello");
+ json j = {{"key", std::move(source)}};
+ CHECK(&(j["key"].get_ref<json::object_t const&>().at("hello")) == source_addr);
+ }
+ }
+
+ SECTION("json")
+ {
+ SECTION("constructor with implicit types (array)")
+ {
+ json source {1, 2, 3};
+ const json* source_addr = &source[0];
+ json j {std::move(source), {}};
+ CHECK(&j[0][0] == source_addr);
+ }
+
+ SECTION("constructor with implicit types (object)")
+ {
+ json source {1, 2, 3};
+ const json* source_addr = &source[0];
+ json j {{"key", std::move(source)}};
+ CHECK(&j["key"][0] == source_addr);
+ }
+
+ SECTION("assignment with implicit types (array)")
+ {
+ json source {1, 2, 3};
+ const json* source_addr = &source[0];
+ json j = {std::move(source), {}};
+ CHECK(&j[0][0] == source_addr);
+ }
+
+ SECTION("assignment with implicit types (object)")
+ {
+ json source {1, 2, 3};
+ const json* source_addr = &source[0];
+ json j = {{"key", std::move(source)}};
+ CHECK(&j["key"][0] == source_addr);
+ }
+ }
+
+ }
+ }
+
+ SECTION("create an array of n copies of a given value")
+ {
+ SECTION("cnt = 0")
+ {
+ json const v = {1, "foo", 34.23, {1, 2, 3}, {{"A", 1}, {"B", 2u}}};
+ json const arr(0, v);
+ CHECK(arr.size() == 0);
+ }
+
+ SECTION("cnt = 1")
+ {
+ json const v = {1, "foo", 34.23, {1, 2, 3}, {{"A", 1}, {"B", 2u}}};
+ json const arr(1, v);
+ CHECK(arr.size() == 1);
+ for (const auto& x : arr)
+ {
+ CHECK(x == v);
+ }
+ }
+
+ SECTION("cnt = 3")
+ {
+ json const v = {1, "foo", 34.23, {1, 2, 3}, {{"A", 1}, {"B", 2u}}};
+ json const arr(3, v);
+ CHECK(arr.size() == 3);
+ for (const auto& x : arr)
+ {
+ CHECK(x == v);
+ }
+ }
+ }
+
+ SECTION("create a JSON container from an iterator range")
+ {
+ SECTION("object")
+ {
+ SECTION("json(begin(), end())")
+ {
+ {
+ json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
+ json const j_new(jobject.begin(), jobject.end());
+ CHECK(j_new == jobject);
+ }
+ {
+ json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
+ json const j_new(jobject.cbegin(), jobject.cend());
+ CHECK(j_new == jobject);
+ }
+ }
+
+ SECTION("json(begin(), begin())")
+ {
+ {
+ json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
+ json const j_new(jobject.begin(), jobject.begin());
+ CHECK(j_new == json::object());
+ }
+ {
+ json const jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
+ json const j_new(jobject.cbegin(), jobject.cbegin());
+ CHECK(j_new == json::object());
+ }
+ }
+
+ SECTION("construct from subrange")
+ {
+ json const jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}, {"d", false}, {"e", true}};
+ json const j_new(jobject.find("b"), jobject.find("e"));
+ CHECK(j_new == json({{"b", 1}, {"c", 17u}, {"d", false}}));
+ }
+
+ SECTION("incompatible iterators")
+ {
+ {
+ json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}, {"d", false}, {"e", true}};
+ json jobject2 = {{"a", "a"}, {"b", 1}, {"c", 17u}};
+ CHECK_THROWS_WITH_AS(json(jobject.begin(), jobject2.end()), "[json.exception.invalid_iterator.201] iterators are not compatible", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(json(jobject2.begin(), jobject.end()), "[json.exception.invalid_iterator.201] iterators are not compatible", json::invalid_iterator&);
+ }
+ {
+ json const jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}, {"d", false}, {"e", true}};
+ json const jobject2 = {{"a", "a"}, {"b", 1}, {"c", 17u}};
+ CHECK_THROWS_WITH_AS(json(jobject.cbegin(), jobject2.cend()), "[json.exception.invalid_iterator.201] iterators are not compatible", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(json(jobject2.cbegin(), jobject.cend()), "[json.exception.invalid_iterator.201] iterators are not compatible", json::invalid_iterator&);
+ }
+ }
+ }
+
+ SECTION("array")
+ {
+ SECTION("json(begin(), end())")
+ {
+ {
+ json jarray = {1, 2, 3, 4, 5};
+ json const j_new(jarray.begin(), jarray.end());
+ CHECK(j_new == jarray);
+ }
+ {
+ json const jarray = {1, 2, 3, 4, 5};
+ json const j_new(jarray.cbegin(), jarray.cend());
+ CHECK(j_new == jarray);
+ }
+ }
+
+ SECTION("json(begin(), begin())")
+ {
+ {
+ json jarray = {1, 2, 3, 4, 5};
+ const json j_new(jarray.begin(), jarray.begin());
+ CHECK(j_new == json::array());
+ }
+ {
+ json const jarray = {1, 2, 3, 4, 5};
+ json const j_new(jarray.cbegin(), jarray.cbegin());
+ CHECK(j_new == json::array());
+ }
+ }
+
+ SECTION("construct from subrange")
+ {
+ {
+ json jarray = {1, 2, 3, 4, 5};
+ json const j_new(jarray.begin() + 1, jarray.begin() + 3);
+ CHECK(j_new == json({2, 3}));
+ }
+ {
+ json const jarray = {1, 2, 3, 4, 5};
+ json const j_new(jarray.cbegin() + 1, jarray.cbegin() + 3);
+ CHECK(j_new == json({2, 3}));
+ }
+ }
+
+ SECTION("incompatible iterators")
+ {
+ {
+ json jarray = {1, 2, 3, 4};
+ json jarray2 = {2, 3, 4, 5};
+ CHECK_THROWS_WITH_AS(json(jarray.begin(), jarray2.end()), "[json.exception.invalid_iterator.201] iterators are not compatible", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(json(jarray2.begin(), jarray.end()), "[json.exception.invalid_iterator.201] iterators are not compatible", json::invalid_iterator&);
+ }
+ {
+ json const jarray = {1, 2, 3, 4};
+ json const jarray2 = {2, 3, 4, 5};
+ CHECK_THROWS_WITH_AS(json(jarray.cbegin(), jarray2.cend()), "[json.exception.invalid_iterator.201] iterators are not compatible", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(json(jarray2.cbegin(), jarray.cend()), "[json.exception.invalid_iterator.201] iterators are not compatible", json::invalid_iterator&);
+ }
+ }
+ }
+
+ SECTION("other values")
+ {
+ SECTION("construct with two valid iterators")
+ {
+ SECTION("null")
+ {
+ {
+ json j;
+ CHECK_THROWS_WITH_AS(json(j.begin(), j.end()), "[json.exception.invalid_iterator.206] cannot construct with iterators from null", json::invalid_iterator&);
+ }
+ {
+ json const j;
+ CHECK_THROWS_WITH_AS(json(j.cbegin(), j.cend()), "[json.exception.invalid_iterator.206] cannot construct with iterators from null", json::invalid_iterator&);
+ }
+ }
+
+ SECTION("string")
+ {
+ {
+ json j = "foo";
+ json const j_new(j.begin(), j.end());
+ CHECK(j == j_new);
+ }
+ {
+ json const j = "bar";
+ json const j_new(j.cbegin(), j.cend());
+ CHECK(j == j_new);
+ }
+ }
+
+ SECTION("number (boolean)")
+ {
+ {
+ json j = false;
+ json const j_new(j.begin(), j.end());
+ CHECK(j == j_new);
+ }
+ {
+ json const j = true;
+ json const j_new(j.cbegin(), j.cend());
+ CHECK(j == j_new);
+ }
+ }
+
+ SECTION("number (integer)")
+ {
+ {
+ json j = 17;
+ json const j_new(j.begin(), j.end());
+ CHECK(j == j_new);
+ }
+ {
+ json const j = 17;
+ json const j_new(j.cbegin(), j.cend());
+ CHECK(j == j_new);
+ }
+ }
+
+ SECTION("number (unsigned)")
+ {
+ {
+ json j = 17u;
+ json const j_new(j.begin(), j.end());
+ CHECK(j == j_new);
+ }
+ {
+ json const j = 17u;
+ json const j_new(j.cbegin(), j.cend());
+ CHECK(j == j_new);
+ }
+ }
+
+ SECTION("number (floating point)")
+ {
+ {
+ json j = 23.42;
+ json const j_new(j.begin(), j.end());
+ CHECK(j == j_new);
+ }
+ {
+ json const j = 23.42;
+ json const j_new(j.cbegin(), j.cend());
+ CHECK(j == j_new);
+ }
+ }
+
+ SECTION("binary")
+ {
+ {
+ json j = json::binary({1, 2, 3});
+ json const j_new(j.begin(), j.end());
+ CHECK((j == j_new));
+ }
+ {
+ json const j = json::binary({1, 2, 3});
+ json const j_new(j.cbegin(), j.cend());
+ CHECK((j == j_new));
+ }
+ }
+ }
+
+ SECTION("construct with two invalid iterators")
+ {
+ SECTION("string")
+ {
+ {
+ json j = "foo";
+ CHECK_THROWS_WITH_AS(json(j.end(), j.end()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(json(j.begin(), j.begin()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ }
+ {
+ json const j = "bar";
+ CHECK_THROWS_WITH_AS(json(j.cend(), j.cend()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(json(j.cbegin(), j.cbegin()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ }
+ }
+
+ SECTION("number (boolean)")
+ {
+ {
+ json j = false;
+ CHECK_THROWS_WITH_AS(json(j.end(), j.end()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(json(j.begin(), j.begin()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ }
+ {
+ json const j = true;
+ CHECK_THROWS_WITH_AS(json(j.cend(), j.cend()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(json(j.cbegin(), j.cbegin()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ }
+ }
+
+ SECTION("number (integer)")
+ {
+ {
+ json j = 17;
+ CHECK_THROWS_WITH_AS(json(j.end(), j.end()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(json(j.begin(), j.begin()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ }
+ {
+ json const j = 17;
+ CHECK_THROWS_WITH_AS(json(j.cend(), j.cend()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(json(j.cbegin(), j.cbegin()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ }
+ }
+
+ SECTION("number (integer)")
+ {
+ {
+ json j = 17u;
+ CHECK_THROWS_WITH_AS(json(j.end(), j.end()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(json(j.begin(), j.begin()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ }
+ {
+ json const j = 17u;
+ CHECK_THROWS_WITH_AS(json(j.cend(), j.cend()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(json(j.cbegin(), j.cbegin()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ }
+ }
+
+ SECTION("number (floating point)")
+ {
+ {
+ json j = 23.42;
+ CHECK_THROWS_WITH_AS(json(j.end(), j.end()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(json(j.begin(), j.begin()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ }
+ {
+ json const j = 23.42;
+ CHECK_THROWS_WITH_AS(json(j.cend(), j.cend()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(json(j.cbegin(), j.cbegin()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/json4cpp/tests/src/unit-constructor2.cpp b/json4cpp/tests/src/unit-constructor2.cpp
new file mode 100644
index 0000000000..a2c7e19bfe
--- /dev/null
+++ b/json4cpp/tests/src/unit-constructor2.cpp
@@ -0,0 +1,186 @@
+// __ _____ _____ _____
+// __| | __| | | | 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;
+
+TEST_CASE("other constructors and destructor")
+{
+ SECTION("copy constructor")
+ {
+ SECTION("object")
+ {
+ const json j {{"foo", 1}, {"bar", false}};
+ json k(j); // NOLINT(performance-unnecessary-copy-initialization)
+ CHECK(j == k);
+ }
+
+ SECTION("array")
+ {
+ const json j {"foo", 1, 42.23, false};
+ json k(j); // NOLINT(performance-unnecessary-copy-initialization)
+ CHECK(j == k);
+ }
+
+ SECTION("null")
+ {
+ const json j(nullptr);
+ json k(j); // NOLINT(performance-unnecessary-copy-initialization)
+ CHECK(j == k);
+ }
+
+ SECTION("boolean")
+ {
+ const json j(true);
+ json k(j); // NOLINT(performance-unnecessary-copy-initialization)
+ CHECK(j == k);
+ }
+
+ SECTION("string")
+ {
+ const json j("Hello world");
+ json k(j); // NOLINT(performance-unnecessary-copy-initialization)
+ CHECK(j == k);
+ }
+
+ SECTION("number (integer)")
+ {
+ const json j(42);
+ json k(j); // NOLINT(performance-unnecessary-copy-initialization)
+ CHECK(j == k);
+ }
+
+ SECTION("number (unsigned)")
+ {
+ const json j(42u);
+ json k(j); // NOLINT(performance-unnecessary-copy-initialization)
+ CHECK(j == k);
+ }
+
+ SECTION("number (floating-point)")
+ {
+ const json j(42.23);
+ json k(j); // NOLINT(performance-unnecessary-copy-initialization)
+ CHECK(j == k);
+ }
+
+ SECTION("binary")
+ {
+ const json j = json::binary({1, 2, 3});
+ json k(j); // NOLINT(performance-unnecessary-copy-initialization)
+ CHECK(j == k);
+ }
+ }
+
+ SECTION("move constructor")
+ {
+ json j {{"foo", "bar"}, {"baz", {1, 2, 3, 4}}, {"a", 42u}, {"b", 42.23}, {"c", nullptr}};
+ CHECK(j.type() == json::value_t::object);
+ const json k(std::move(j));
+ CHECK(k.type() == json::value_t::object);
+ CHECK(j.type() == json::value_t::null); // NOLINT: access after move is OK here
+ }
+
+ SECTION("copy assignment")
+ {
+ SECTION("object")
+ {
+ const json j {{"foo", 1}, {"bar", false}};
+ json k;
+ k = j;
+ CHECK(j == k);
+ }
+
+ SECTION("array")
+ {
+ const json j {"foo", 1, 42.23, false};
+ json k;
+ k = j;
+ CHECK(j == k);
+ }
+
+ SECTION("null")
+ {
+ const json j(nullptr);
+ json k;
+ k = j;
+ CHECK(j == k);
+ }
+
+ SECTION("boolean")
+ {
+ const json j(true);
+ json k;
+ k = j;
+ CHECK(j == k);
+ }
+
+ SECTION("string")
+ {
+ const json j("Hello world");
+ json k;
+ k = j;
+ CHECK(j == k);
+ }
+
+ SECTION("number (integer)")
+ {
+ const json j(42);
+ json k;
+ k = j;
+ CHECK(j == k);
+ }
+
+ SECTION("number (unsigned)")
+ {
+ const json j(42u);
+ json k;
+ k = j;
+ CHECK(j == k);
+ }
+
+ SECTION("number (floating-point)")
+ {
+ const json j(42.23);
+ json k;
+ k = j;
+ CHECK(j == k);
+ }
+
+ SECTION("binary")
+ {
+ const json j = json::binary({1, 2, 3});
+ json k;
+ k = j;
+ CHECK(j == k);
+ }
+ }
+
+ SECTION("destructor")
+ {
+ SECTION("object")
+ {
+ auto* j = new json {{"foo", 1}, {"bar", false}}; // NOLINT(cppcoreguidelines-owning-memory)
+ delete j; // NOLINT(cppcoreguidelines-owning-memory)
+ }
+
+ SECTION("array")
+ {
+ auto* j = new json {"foo", 1, 1u, false, 23.42}; // NOLINT(cppcoreguidelines-owning-memory)
+ delete j; // NOLINT(cppcoreguidelines-owning-memory)
+ }
+
+ SECTION("string")
+ {
+ auto* j = new json("Hello world"); // NOLINT(cppcoreguidelines-owning-memory)
+ delete j; // NOLINT(cppcoreguidelines-owning-memory)
+ }
+ }
+}
diff --git a/json4cpp/tests/src/unit-convenience.cpp b/json4cpp/tests/src/unit-convenience.cpp
new file mode 100644
index 0000000000..f5104855f0
--- /dev/null
+++ b/json4cpp/tests/src/unit-convenience.cpp
@@ -0,0 +1,205 @@
+// __ _____ _____ _____
+// __| | __| | | | 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;
+
+#include <sstream>
+
+namespace
+{
+struct alt_string_iter
+{
+ alt_string_iter() = default;
+ alt_string_iter(const char* cstr)
+ : impl(cstr)
+ {}
+
+ void reserve(std::size_t s)
+ {
+ impl.reserve(s);
+ }
+
+ template<typename Iter>
+ void append(Iter first, Iter last)
+ {
+ impl.append(first, last);
+ }
+
+ std::string::const_iterator begin() const noexcept
+ {
+ return impl.begin();
+ }
+
+ std::string::const_iterator end() const noexcept
+ {
+ return impl.end();
+ }
+
+ std::size_t size() const noexcept
+ {
+ return impl.size();
+ }
+
+ alt_string_iter& operator+=(const char c)
+ {
+ impl += c;
+ return *this;
+ }
+
+ std::string impl{}; // NOLINT(readability-redundant-member-init)
+};
+
+struct alt_string_data
+{
+ alt_string_data() = default;
+ alt_string_data(const char* cstr)
+ : impl(cstr)
+ {}
+
+ void reserve(std::size_t s)
+ {
+ impl.reserve(s);
+ }
+
+ void append(const char* p, std::size_t s)
+ {
+ impl.append(p, s);
+ }
+
+ const char* data() const
+ {
+ return impl.data();
+ }
+
+ std::size_t size() const
+ {
+ return impl.size();
+ }
+
+ alt_string_data& operator+=(const char c)
+ {
+ impl += c;
+ return *this;
+ }
+
+ std::string impl{}; // NOLINT(readability-redundant-member-init)
+};
+
+void check_escaped(const char* original, const char* escaped = "", bool ensure_ascii = false);
+void check_escaped(const char* original, const char* escaped, const bool ensure_ascii)
+{
+ std::stringstream ss;
+ json::serializer s(nlohmann::detail::output_adapter<char>(ss), ' ');
+ s.dump_escaped(original, ensure_ascii);
+ CHECK(ss.str() == escaped);
+}
+} // namespace
+
+TEST_CASE("convenience functions")
+{
+ SECTION("type name as string")
+ {
+ CHECK(std::string(json(json::value_t::null).type_name()) == "null");
+ CHECK(std::string(json(json::value_t::object).type_name()) == "object");
+ CHECK(std::string(json(json::value_t::array).type_name()) == "array");
+ CHECK(std::string(json(json::value_t::number_integer).type_name()) == "number");
+ CHECK(std::string(json(json::value_t::number_unsigned).type_name()) == "number");
+ CHECK(std::string(json(json::value_t::number_float).type_name()) == "number");
+ CHECK(std::string(json(json::value_t::binary).type_name()) == "binary");
+ CHECK(std::string(json(json::value_t::boolean).type_name()) == "boolean");
+ CHECK(std::string(json(json::value_t::string).type_name()) == "string");
+ CHECK(std::string(json(json::value_t::discarded).type_name()) == "discarded");
+ }
+
+ SECTION("string escape")
+ {
+ check_escaped("\"", "\\\"");
+ check_escaped("\\", "\\\\");
+ check_escaped("\b", "\\b");
+ check_escaped("\f", "\\f");
+ check_escaped("\n", "\\n");
+ check_escaped("\r", "\\r");
+ check_escaped("\t", "\\t");
+
+ check_escaped("\x01", "\\u0001");
+ check_escaped("\x02", "\\u0002");
+ check_escaped("\x03", "\\u0003");
+ check_escaped("\x04", "\\u0004");
+ check_escaped("\x05", "\\u0005");
+ check_escaped("\x06", "\\u0006");
+ check_escaped("\x07", "\\u0007");
+ check_escaped("\x08", "\\b");
+ check_escaped("\x09", "\\t");
+ check_escaped("\x0a", "\\n");
+ check_escaped("\x0b", "\\u000b");
+ check_escaped("\x0c", "\\f");
+ check_escaped("\x0d", "\\r");
+ check_escaped("\x0e", "\\u000e");
+ check_escaped("\x0f", "\\u000f");
+ check_escaped("\x10", "\\u0010");
+ check_escaped("\x11", "\\u0011");
+ check_escaped("\x12", "\\u0012");
+ check_escaped("\x13", "\\u0013");
+ check_escaped("\x14", "\\u0014");
+ check_escaped("\x15", "\\u0015");
+ check_escaped("\x16", "\\u0016");
+ check_escaped("\x17", "\\u0017");
+ check_escaped("\x18", "\\u0018");
+ check_escaped("\x19", "\\u0019");
+ check_escaped("\x1a", "\\u001a");
+ check_escaped("\x1b", "\\u001b");
+ check_escaped("\x1c", "\\u001c");
+ check_escaped("\x1d", "\\u001d");
+ check_escaped("\x1e", "\\u001e");
+ check_escaped("\x1f", "\\u001f");
+
+ // invalid UTF-8 characters
+ CHECK_THROWS_WITH_AS(check_escaped("ä\xA9ü"), "[json.exception.type_error.316] invalid UTF-8 byte at index 2: 0xA9", json::type_error&);
+
+ CHECK_THROWS_WITH_AS(check_escaped("\xC2"), "[json.exception.type_error.316] incomplete UTF-8 string; last byte: 0xC2", json::type_error&);
+ }
+
+ SECTION("string concat")
+ {
+ using nlohmann::detail::concat;
+
+ const char* expected = "Hello, world!";
+ alt_string_iter const hello_iter{"Hello, "};
+ alt_string_data const hello_data{"Hello, "};
+ std::string const world = "world";
+
+ SECTION("std::string")
+ {
+ const std::string str1 = concat(hello_iter, world, '!');
+ const std::string str2 = concat(hello_data, world, '!');
+ const std::string str3 = concat("Hello, ", world, '!');
+
+ CHECK(str1 == expected);
+ CHECK(str2 == expected);
+ CHECK(str3 == expected);
+ }
+
+ SECTION("alt_string_iter")
+ {
+ const alt_string_iter str = concat<alt_string_iter>(hello_iter, world, '!');
+
+ CHECK(str.impl == expected);
+ }
+
+ SECTION("alt_string_data")
+ {
+ const alt_string_data str = concat<alt_string_data>(hello_data, world, '!');
+
+ CHECK(str.impl == expected);
+ }
+ }
+}
diff --git a/json4cpp/tests/src/unit-conversions.cpp b/json4cpp/tests/src/unit-conversions.cpp
new file mode 100644
index 0000000000..81b8608fb8
--- /dev/null
+++ b/json4cpp/tests/src/unit-conversions.cpp
@@ -0,0 +1,1753 @@
+// __ _____ _____ _____
+// __| | __| | | | 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
+
+// cmake/test.cmake selects the C++ standard versions with which to build a
+// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
+// When using macros that are only defined for particular versions of the standard
+// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
+// version macro in a comment close by, like this:
+// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
+
+#include "doctest_compatibility.h"
+
+// skip tests if JSON_DisableEnumSerialization=ON (#4384)
+#if defined(JSON_DISABLE_ENUM_SERIALIZATION) && (JSON_DISABLE_ENUM_SERIALIZATION == 1)
+ #define SKIP_TESTS_FOR_ENUM_SERIALIZATION
+#endif
+
+#define JSON_TESTS_PRIVATE
+#include <nlohmann/json.hpp>
+using nlohmann::json;
+
+#include <deque>
+#include <forward_list>
+#include <list>
+#include <set>
+#include <unordered_map>
+#include <unordered_set>
+#include <valarray>
+
+// NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair
+DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
+
+#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464
+ #define JSON_HAS_CPP_17
+ #define JSON_HAS_CPP_14
+#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1)
+ #define JSON_HAS_CPP_14
+#endif
+
+#ifdef JSON_HAS_CPP_17
+ #if __has_include(<optional>)
+ #include <optional>
+ #elif __has_include(<experimental/optional>)
+ #include <experimental/optional>
+ #endif
+#endif
+
+#if defined(JSON_HAS_CPP_17)
+ #include <string_view>
+#endif
+
+TEST_CASE("value conversion")
+{
+ SECTION("get an object (explicit)")
+ {
+ const json::object_t o_reference = {{"object", json::object()},
+ {"array", {1, 2, 3, 4}},
+ {"number", 42},
+ {"boolean", false},
+ {"null", nullptr},
+ {"string", "Hello world"}
+ };
+ json j(o_reference);
+
+ SECTION("json::object_t")
+ {
+ json::object_t const o = j.get<json::object_t>();
+ CHECK(json(o) == j);
+ }
+
+ SECTION("std::map<json::string_t, json>")
+ {
+ const std::map<json::string_t, json> o =
+ j.get<std::map<json::string_t, json>>();
+ CHECK(json(o) == j);
+ }
+
+ SECTION("std::multimap<json::string_t, json>")
+ {
+ const std::multimap<json::string_t, json> o =
+ j.get<std::multimap<json::string_t, json>>();
+ CHECK(json(o) == j);
+ }
+
+ SECTION("std::unordered_map<json::string_t, json>")
+ {
+ const std::unordered_map<json::string_t, json> o =
+ j.get<std::unordered_map<json::string_t, json>>();
+ CHECK(json(o) == j);
+ }
+
+ SECTION("std::unordered_multimap<json::string_t, json>")
+ {
+ const std::unordered_multimap<json::string_t, json> o =
+ j.get<std::unordered_multimap<json::string_t, json>>();
+ CHECK(json(o) == j);
+ }
+
+ SECTION("exception in case of a non-object type")
+ {
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::null).get<json::object_t>(),
+ "[json.exception.type_error.302] type must be object, but is null", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::array).get<json::object_t>(),
+ "[json.exception.type_error.302] type must be object, but is array", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::string).get<json::object_t>(),
+ "[json.exception.type_error.302] type must be object, but is string", json::type_error&);
+ CHECK_THROWS_WITH_AS(json(json::value_t::boolean).get<json::object_t>(),
+ "[json.exception.type_error.302] type must be object, "
+ "but is boolean", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::number_integer).get<json::object_t>(),
+ "[json.exception.type_error.302] type must be object, but is number", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::number_unsigned).get<json::object_t>(),
+ "[json.exception.type_error.302] type must be object, but is number", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::number_float).get<json::object_t>(),
+ "[json.exception.type_error.302] type must be object, but is number", json::type_error&);
+ }
+ }
+
+ SECTION("get an object (explicit, get_to)")
+ {
+ const json::object_t o_reference = {{"object", json::object()},
+ {"array", {1, 2, 3, 4}},
+ {"number", 42},
+ {"boolean", false},
+ {"null", nullptr},
+ {"string", "Hello world"}
+ };
+ json j(o_reference);
+
+ SECTION("json::object_t")
+ {
+ json::object_t o = {{"previous", "value"}};
+ j.get_to(o);
+ CHECK(json(o) == j);
+ }
+
+ SECTION("std::map<json::string_t, json>")
+ {
+ std::map<json::string_t, json> o{{"previous", "value"}};
+ j.get_to(o);
+ CHECK(json(o) == j);
+ }
+
+ SECTION("std::multimap<json::string_t, json>")
+ {
+ std::multimap<json::string_t, json> o{{"previous", "value"}};
+ j.get_to(o);
+ CHECK(json(o) == j);
+ }
+
+ SECTION("std::unordered_map<json::string_t, json>")
+ {
+ std::unordered_map<json::string_t, json> o{{"previous", "value"}};
+ j.get_to(o);
+ CHECK(json(o) == j);
+ }
+
+ SECTION("std::unordered_multimap<json::string_t, json>")
+ {
+ std::unordered_multimap<json::string_t, json> o{{"previous", "value"}};
+ j.get_to(o);
+ CHECK(json(o) == j);
+ }
+ }
+
+#if JSON_USE_IMPLICIT_CONVERSIONS
+
+ SECTION("get an object (implicit)")
+ {
+ const json::object_t o_reference = {{"object", json::object()},
+ {"array", {1, 2, 3, 4}},
+ {"number", 42},
+ {"boolean", false},
+ {"null", nullptr},
+ {"string", "Hello world"}
+ };
+ json j(o_reference);
+
+ SECTION("json::object_t")
+ {
+ const json::object_t o = j;
+ CHECK(json(o) == j);
+ }
+
+ SECTION("std::map<json::string_t, json>")
+ {
+ const std::map<json::string_t, json> o = j;
+ CHECK(json(o) == j);
+ }
+
+ SECTION("std::multimap<json::string_t, json>")
+ {
+ const std::multimap<json::string_t, json> o = j;
+ CHECK(json(o) == j);
+ }
+
+ SECTION("std::unordered_map<json::string_t, json>")
+ {
+ const std::unordered_map<json::string_t, json> o = j;
+ CHECK(json(o) == j);
+ }
+
+ SECTION("std::unordered_multimap<json::string_t, json>")
+ {
+ const std::unordered_multimap<json::string_t, json> o = j;
+ CHECK(json(o) == j);
+ }
+ }
+#endif
+
+ SECTION("get an array (explicit)")
+ {
+ const json::array_t a_reference{json(1), json(1u), json(2.2),
+ json(false), json("string"), json()};
+ json j(a_reference);
+
+ SECTION("json::array_t")
+ {
+ const json::array_t a = j.get<json::array_t>();
+ CHECK(json(a) == j);
+ }
+
+ SECTION("std::list<json>")
+ {
+ const std::list<json> a = j.get<std::list<json>>();
+ CHECK(json(a) == j);
+ }
+
+ SECTION("std::forward_list<json>")
+ {
+ const std::forward_list<json> a = j.get<std::forward_list<json>>();
+ CHECK(json(a) == j);
+
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::null).get<std::forward_list<json>>(),
+ "[json.exception.type_error.302] type must be array, but is null", json::type_error&);
+ }
+
+ SECTION("std::vector<json>")
+ {
+ const std::vector<json> a = j.get<std::vector<json>>();
+ CHECK(json(a) == j);
+
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::null).get<std::vector<json>>(),
+ "[json.exception.type_error.302] type must be array, but is null", json::type_error&);
+
+#if !defined(JSON_NOEXCEPTION)
+ SECTION("reserve is called on containers that supports it")
+ {
+ // make sure all values are properly copied
+ const json j2({1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
+ auto v2 = j2.get<std::vector<int>>();
+ CHECK(v2.size() == 10);
+ }
+#endif
+ }
+
+ SECTION("built-in arrays")
+ {
+ const char str[] = "a string"; // NOLINT(misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
+ const int nbs[] = {0, 1, 2}; // NOLINT(misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
+
+ const json j2 = nbs;
+ const json j3 = str;
+
+ auto v = j2.get<std::vector<int>>();
+ auto s = j3.get<std::string>();
+ CHECK(std::equal(v.begin(), v.end(), std::begin(nbs)));
+ CHECK(s == str);
+ }
+
+ SECTION("std::deque<json>")
+ {
+ const std::deque<json> a = j.get<std::deque<json>>();
+ CHECK(json(a) == j);
+ }
+
+ SECTION("exception in case of a non-array type")
+ {
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::object).get<std::vector<int>>(),
+ "[json.exception.type_error.302] type must be array, but is object", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::null).get<json::array_t>(),
+ "[json.exception.type_error.302] type must be array, but is null", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::object).get<json::array_t>(),
+ "[json.exception.type_error.302] type must be array, but is object", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::string).get<json::array_t>(),
+ "[json.exception.type_error.302] type must be array, but is string", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::boolean).get<json::array_t>(),
+ "[json.exception.type_error.302] type must be array, but is boolean", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::number_integer).get<json::array_t>(),
+ "[json.exception.type_error.302] type must be array, but is number", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::number_unsigned).get<json::array_t>(),
+ "[json.exception.type_error.302] type must be array, but is number", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::number_float).get<json::array_t>(),
+ "[json.exception.type_error.302] type must be array, but is number", json::type_error&);
+ }
+ }
+
+ SECTION("get an array (explicit, get_to)")
+ {
+ const json::array_t a_reference{json(1), json(1u), json(2.2),
+ json(false), json("string"), json()};
+ json j(a_reference);
+
+ SECTION("json::array_t")
+ {
+ json::array_t a{"previous", "value"};
+ j.get_to(a);
+ CHECK(json(a) == j);
+ }
+
+ SECTION("std::valarray<json>")
+ {
+ std::valarray<json> a{"previous", "value"};
+ j.get_to(a);
+ CHECK(json(a) == j);
+ }
+
+ SECTION("std::list<json>")
+ {
+ std::list<json> a{"previous", "value"};
+ j.get_to(a);
+ CHECK(json(a) == j);
+ }
+
+ SECTION("std::forward_list<json>")
+ {
+ std::forward_list<json> a{"previous", "value"};
+ j.get_to(a);
+ CHECK(json(a) == j);
+ }
+
+ SECTION("std::vector<json>")
+ {
+ std::vector<json> a{"previous", "value"};
+ j.get_to(a);
+ CHECK(json(a) == j);
+ }
+
+ SECTION("built-in arrays")
+ {
+ const int nbs[] = {0, 1, 2}; // NOLINT(misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
+ int nbs2[] = {0, 0, 0}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
+
+ const json j2 = nbs;
+ j2.get_to(nbs2);
+ CHECK(std::equal(std::begin(nbs), std::end(nbs), std::begin(nbs2)));
+ }
+
+ SECTION("built-in arrays: 2D")
+ {
+ const int nbs[][3] = {{0, 1, 2}, {3, 4, 5}}; // NOLINT(misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
+ int nbs2[][3] = {{0, 0, 0}, {0, 0, 0}}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
+
+ const json j2 = nbs;
+ j2.get_to(nbs2);
+ CHECK(std::equal(std::begin(nbs[0]), std::end(nbs[1]), std::begin(nbs2[0])));
+ }
+
+ SECTION("built-in arrays: 3D")
+ {
+ // NOLINTBEGIN(misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
+ const int nbs[][2][3] = {\
+ {{0, 1, 2}, {3, 4, 5}}, \
+ {{10, 11, 12}, {13, 14, 15}}\
+ };
+ int nbs2[][2][3] = {\
+ {{0, 0, 0}, {0, 0, 0}}, \
+ {{0, 0, 0}, {0, 0, 0}}\
+ };
+ // NOLINTEND(misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
+
+ const json j2 = nbs;
+ j2.get_to(nbs2);
+ CHECK(std::equal(std::begin(nbs[0][0]), std::end(nbs[1][1]), std::begin(nbs2[0][0])));
+ }
+
+ SECTION("built-in arrays: 4D")
+ {
+ // NOLINTBEGIN(misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
+ const int nbs[][2][2][3] = {\
+ {
+ \
+ {{0, 1, 2}, {3, 4, 5}}, \
+ {{10, 11, 12}, {13, 14, 15}}\
+ }, \
+ {
+ \
+ {{20, 21, 22}, {23, 24, 25}}, \
+ {{30, 31, 32}, {33, 34, 35}}\
+ }\
+ };
+ int nbs2[][2][2][3] = {\
+ {
+ \
+ {{0, 0, 0}, {0, 0, 0}}, \
+ {{0, 0, 0}, {0, 0, 0}}\
+ }, \
+ {
+ \
+ {{0, 0, 0}, {0, 0, 0}}, \
+ {{0, 0, 0}, {0, 0, 0}}\
+ }\
+ };
+ // NOLINTEND(misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
+
+ const json j2 = nbs;
+ j2.get_to(nbs2);
+ CHECK(std::equal(std::begin(nbs[0][0][0]), std::end(nbs[1][1][1]), std::begin(nbs2[0][0][0])));
+ }
+
+ SECTION("std::deque<json>")
+ {
+ std::deque<json> a{"previous", "value"};
+ j.get_to(a);
+ CHECK(json(a) == j);
+ }
+ }
+
+#if JSON_USE_IMPLICIT_CONVERSIONS
+ SECTION("get an array (implicit)")
+ {
+ const json::array_t a_reference{json(1), json(1u), json(2.2),
+ json(false), json("string"), json()};
+ json j(a_reference);
+
+ SECTION("json::array_t")
+ {
+ const json::array_t a = j;
+ CHECK(json(a) == j);
+ }
+
+ SECTION("std::list<json>")
+ {
+ const std::list<json> a = j;
+ CHECK(json(a) == j);
+ }
+
+ SECTION("std::forward_list<json>")
+ {
+ const std::forward_list<json> a = j;
+ CHECK(json(a) == j);
+ }
+
+ SECTION("std::vector<json>")
+ {
+ const std::vector<json> a = j;
+ CHECK(json(a) == j);
+ }
+
+ SECTION("std::deque<json>")
+ {
+ const std::deque<json> a = j;
+ CHECK(json(a) == j);
+ }
+ }
+#endif
+
+ SECTION("get a string (explicit)")
+ {
+ const json::string_t s_reference{"Hello world"};
+ json j(s_reference);
+
+ SECTION("string_t")
+ {
+ const json::string_t s = j.get<json::string_t>();
+ CHECK(json(s) == j);
+ }
+
+ SECTION("std::string")
+ {
+ const std::string s = j.get<std::string>();
+ CHECK(json(s) == j);
+ }
+#if defined(JSON_HAS_CPP_17)
+ SECTION("std::string_view")
+ {
+ std::string_view const s = j.get<std::string_view>();
+ CHECK(json(s) == j);
+ }
+#endif
+
+ SECTION("exception in case of a non-string type")
+ {
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::null).get<json::string_t>(),
+ "[json.exception.type_error.302] type must be string, but is null", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::object).get<json::string_t>(),
+ "[json.exception.type_error.302] type must be string, but is object", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::array).get<json::string_t>(),
+ "[json.exception.type_error.302] type must be string, but is array", json::type_error&);
+ CHECK_THROWS_WITH_AS(json(json::value_t::boolean).get<json::string_t>(),
+ "[json.exception.type_error.302] type must be string, "
+ "but is boolean", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::number_integer).get<json::string_t>(),
+ "[json.exception.type_error.302] type must be string, but is number", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::number_unsigned).get<json::string_t>(),
+ "[json.exception.type_error.302] type must be string, but is number", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::number_float).get<json::string_t>(),
+ "[json.exception.type_error.302] type must be string, but is number", json::type_error&);
+ }
+
+#if defined(JSON_HAS_CPP_17)
+ SECTION("exception in case of a non-string type using string_view")
+ {
+ CHECK_THROWS_WITH_AS(json(json::value_t::null).get<std::string_view>(),
+ "[json.exception.type_error.302] type must be string, but is null", json::type_error&);
+ CHECK_THROWS_WITH_AS(json(json::value_t::object).get<std::string_view>(),
+ "[json.exception.type_error.302] type must be string, but is object", json::type_error&);
+ CHECK_THROWS_WITH_AS(json(json::value_t::array).get<std::string_view>(),
+ "[json.exception.type_error.302] type must be string, but is array", json::type_error&);
+ CHECK_THROWS_WITH_AS(json(json::value_t::boolean).get<std::string_view>(),
+ "[json.exception.type_error.302] type must be string, but is boolean", json::type_error&);
+ CHECK_THROWS_WITH_AS(json(json::value_t::number_integer).get<std::string_view>(),
+ "[json.exception.type_error.302] type must be string, but is number", json::type_error&);
+ CHECK_THROWS_WITH_AS(json(json::value_t::number_unsigned).get<std::string_view>(),
+ "[json.exception.type_error.302] type must be string, but is number", json::type_error&);
+ CHECK_THROWS_WITH_AS(json(json::value_t::number_float).get<std::string_view>(),
+ "[json.exception.type_error.302] type must be string, but is number", json::type_error&);
+ }
+#endif
+ }
+
+ SECTION("get a string (explicit, get_to)")
+ {
+ const json::string_t s_reference{"Hello world"};
+ json j(s_reference);
+
+ SECTION("string_t")
+ {
+ json::string_t s = "previous value";
+ j.get_to(s);
+ CHECK(json(s) == j);
+ }
+
+ SECTION("std::string")
+ {
+ std::string s = "previous value";
+ j.get_to(s);
+ CHECK(json(s) == j);
+ }
+#if defined(JSON_HAS_CPP_17)
+ SECTION("std::string_view")
+ {
+ std::string const s = "previous value";
+ std::string_view sv = s;
+ j.get_to(sv);
+ CHECK(json(sv) == j);
+ }
+#endif
+ }
+
+ SECTION("get null (explicit)")
+ {
+ std::nullptr_t n = nullptr;
+ const json j(n);
+
+ auto n2 = j.get<std::nullptr_t>();
+ CHECK(n2 == n);
+
+ CHECK_THROWS_WITH_AS(json(json::value_t::string).get<std::nullptr_t>(),
+ "[json.exception.type_error.302] type must be null, but is string", json::type_error&);
+ CHECK_THROWS_WITH_AS(json(json::value_t::object).get<std::nullptr_t>(),
+ "[json.exception.type_error.302] type must be null, but is object", json::type_error&);
+ CHECK_THROWS_WITH_AS(json(json::value_t::array).get<std::nullptr_t>(),
+ "[json.exception.type_error.302] type must be null, but is array", json::type_error&);
+ CHECK_THROWS_WITH_AS(json(json::value_t::boolean).get<std::nullptr_t>(),
+ "[json.exception.type_error.302] type must be null, but is boolean", json::type_error&);
+ CHECK_THROWS_WITH_AS(json(json::value_t::number_integer).get<std::nullptr_t>(),
+ "[json.exception.type_error.302] type must be null, but is number", json::type_error&);
+ CHECK_THROWS_WITH_AS(json(json::value_t::number_unsigned).get<std::nullptr_t>(),
+ "[json.exception.type_error.302] type must be null, but is number", json::type_error&);
+ CHECK_THROWS_WITH_AS(json(json::value_t::number_float).get<std::nullptr_t>(),
+ "[json.exception.type_error.302] type must be null, but is number", json::type_error&);
+ }
+
+#if JSON_USE_IMPLICIT_CONVERSIONS
+ SECTION("get a string (implicit)")
+ {
+ const json::string_t s_reference{"Hello world"};
+ json j(s_reference);
+
+ SECTION("string_t")
+ {
+ const json::string_t s = j;
+ CHECK(json(s) == j);
+ }
+
+#if defined(JSON_HAS_CPP_17)
+ SECTION("std::string_view")
+ {
+ std::string_view const s = j.get<std::string_view>();
+ CHECK(json(s) == j);
+ }
+#endif
+
+ SECTION("std::string")
+ {
+ const std::string s = j;
+ CHECK(json(s) == j);
+ }
+ }
+#endif
+
+ SECTION("get a boolean (explicit)")
+ {
+ const json::boolean_t b_reference{true};
+ json j(b_reference);
+
+ SECTION("boolean_t")
+ {
+ auto b = j.get<json::boolean_t>();
+ CHECK(json(b) == j);
+ }
+
+ SECTION("uint8_t")
+ {
+ auto n = j.get<uint8_t>();
+ CHECK(n == 1);
+ }
+
+ SECTION("bool")
+ {
+ const bool b = j.get<bool>();
+ CHECK(json(b) == j);
+ }
+
+ SECTION("exception in case of a non-number type")
+ {
+ CHECK_THROWS_AS(json(json::value_t::string).get<uint8_t>(),
+ json::type_error&);
+
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::null).get<json::boolean_t>(),
+ "[json.exception.type_error.302] type must be boolean, but is null", json::type_error&);
+ CHECK_THROWS_WITH_AS(json(json::value_t::object).get<json::boolean_t>(),
+ "[json.exception.type_error.302] type must be boolean, "
+ "but is object", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::array).get<json::boolean_t>(),
+ "[json.exception.type_error.302] type must be boolean, but is array", json::type_error&);
+ CHECK_THROWS_WITH_AS(json(json::value_t::string).get<json::boolean_t>(),
+ "[json.exception.type_error.302] type must be boolean, "
+ "but is string", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::number_integer).get<json::boolean_t>(),
+ "[json.exception.type_error.302] type must be boolean, but is "
+ "number", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::number_unsigned).get<json::boolean_t>(),
+ "[json.exception.type_error.302] type must be boolean, but is "
+ "number", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::number_float).get<json::boolean_t>(),
+ "[json.exception.type_error.302] type must be boolean, but is "
+ "number", json::type_error&);
+ }
+ }
+
+#if JSON_USE_IMPLICIT_CONVERSIONS
+ SECTION("get a boolean (implicit)")
+ {
+ const json::boolean_t b_reference{true};
+ json j(b_reference);
+
+ SECTION("boolean_t")
+ {
+ const json::boolean_t b = j;
+ CHECK(json(b) == j);
+ }
+
+ SECTION("bool")
+ {
+ const bool b = j;
+ CHECK(json(b) == j);
+ }
+ }
+#endif
+
+ SECTION("get an integer number (explicit)")
+ {
+ const json::number_integer_t n_reference{42};
+ json j(n_reference);
+ const json::number_unsigned_t n_unsigned_reference{42u};
+ json j_unsigned(n_unsigned_reference);
+
+ SECTION("number_integer_t")
+ {
+ auto n = j.get<json::number_integer_t>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("number_unsigned_t")
+ {
+ auto n = j_unsigned.get<json::number_unsigned_t>();
+ CHECK(json(n) == j_unsigned);
+ }
+
+ SECTION("short")
+ {
+ auto n = j.get<short>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("unsigned short")
+ {
+ auto n = j.get<unsigned short>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("int")
+ {
+ const int n = j.get<int>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("unsigned int")
+ {
+ auto n = j.get<unsigned int>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("long")
+ {
+ const long n = j.get<long>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("unsigned long")
+ {
+ auto n = j.get<unsigned long>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("long long")
+ {
+ auto n = j.get<long long>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("unsigned long long")
+ {
+ auto n = j.get<unsigned long long>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("int8_t")
+ {
+ auto n = j.get<int8_t>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("int16_t")
+ {
+ auto n = j.get<int16_t>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("int32_t")
+ {
+ auto n = j.get<int32_t>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("int64_t")
+ {
+ auto n = j.get<int64_t>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("int8_fast_t")
+ {
+ auto n = j.get<int_fast8_t>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("int16_fast_t")
+ {
+ auto n = j.get<int_fast16_t>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("int32_fast_t")
+ {
+ auto n = j.get<int_fast32_t>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("int64_fast_t")
+ {
+ auto n = j.get<int_fast64_t>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("int8_least_t")
+ {
+ auto n = j.get<int_least8_t>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("int16_least_t")
+ {
+ auto n = j.get<int_least16_t>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("int32_least_t")
+ {
+ auto n = j.get<int_least32_t>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("int64_least_t")
+ {
+ auto n = j.get<int_least64_t>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("uint8_t")
+ {
+ auto n = j.get<uint8_t>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("uint16_t")
+ {
+ auto n = j.get<uint16_t>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("uint32_t")
+ {
+ auto n = j.get<uint32_t>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("uint64_t")
+ {
+ auto n = j.get<uint64_t>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("uint8_fast_t")
+ {
+ auto n = j.get<uint_fast8_t>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("uint16_fast_t")
+ {
+ auto n = j.get<uint_fast16_t>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("uint32_fast_t")
+ {
+ auto n = j.get<uint_fast32_t>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("uint64_fast_t")
+ {
+ auto n = j.get<uint_fast64_t>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("uint8_least_t")
+ {
+ auto n = j.get<uint_least8_t>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("uint16_least_t")
+ {
+ auto n = j.get<uint_least16_t>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("uint32_least_t")
+ {
+ auto n = j.get<uint_least32_t>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("uint64_least_t")
+ {
+ auto n = j.get<uint_least64_t>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("exception in case of a non-number type")
+ {
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::null).get<json::number_integer_t>(),
+ "[json.exception.type_error.302] type must be number, but is null", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::object).get<json::number_integer_t>(),
+ "[json.exception.type_error.302] type must be number, but is object", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::array).get<json::number_integer_t>(),
+ "[json.exception.type_error.302] type must be number, but is array", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::string).get<json::number_integer_t>(),
+ "[json.exception.type_error.302] type must be number, but is string", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::boolean).get<json::number_integer_t>(),
+ "[json.exception.type_error.302] type must be number, but is "
+ "boolean", json::type_error&);
+
+ CHECK_NOTHROW(
+ json(json::value_t::number_float).get<json::number_integer_t>());
+ CHECK_NOTHROW(
+ json(json::value_t::number_float).get<json::number_unsigned_t>());
+ }
+ }
+
+#if JSON_USE_IMPLICIT_CONVERSIONS
+ SECTION("get an integer number (implicit)")
+ {
+ json::number_integer_t const n_reference{42};
+ json j(n_reference);
+ json::number_unsigned_t const n_unsigned_reference{42u};
+ json j_unsigned(n_unsigned_reference);
+
+ SECTION("number_integer_t")
+ {
+ auto n = j.get<json::number_integer_t>();
+ CHECK(json(n) == j);
+ }
+
+ SECTION("number_unsigned_t")
+ {
+ auto n = j_unsigned.get<json::number_unsigned_t>();
+ CHECK(json(n) == j_unsigned);
+ }
+
+ SECTION("short")
+ {
+ short const n = j;
+ CHECK(json(n) == j);
+ }
+
+ SECTION("unsigned short")
+ {
+ unsigned short const n = j_unsigned;
+ CHECK(json(n) == j_unsigned);
+ }
+
+ SECTION("int")
+ {
+ int const n = j;
+ CHECK(json(n) == j);
+ }
+
+ SECTION("unsigned int")
+ {
+ unsigned int const n = j_unsigned;
+ CHECK(json(n) == j_unsigned);
+ }
+
+ SECTION("long")
+ {
+ long const n = j;
+ CHECK(json(n) == j);
+ }
+
+ SECTION("unsigned long")
+ {
+ unsigned long const n = j_unsigned;
+ CHECK(json(n) == j_unsigned);
+ }
+
+ SECTION("long long")
+ {
+ long long const n = j;
+ CHECK(json(n) == j);
+ }
+
+ SECTION("unsigned long long")
+ {
+ unsigned long long const n = j_unsigned;
+ CHECK(json(n) == j_unsigned);
+ }
+
+ SECTION("int8_t")
+ {
+ int8_t const n = j;
+ CHECK(json(n) == j);
+ }
+
+ SECTION("int16_t")
+ {
+ int16_t const n = j;
+ CHECK(json(n) == j);
+ }
+
+ SECTION("int32_t")
+ {
+ int32_t const n = j;
+ CHECK(json(n) == j);
+ }
+
+ SECTION("int64_t")
+ {
+ int64_t const n = j;
+ CHECK(json(n) == j);
+ }
+
+ SECTION("int8_fast_t")
+ {
+ int_fast8_t const n = j;
+ CHECK(json(n) == j);
+ }
+
+ SECTION("int16_fast_t")
+ {
+ int_fast16_t const n = j;
+ CHECK(json(n) == j);
+ }
+
+ SECTION("int32_fast_t")
+ {
+ int_fast32_t const n = j;
+ CHECK(json(n) == j);
+ }
+
+ SECTION("int64_fast_t")
+ {
+ int_fast64_t const n = j;
+ CHECK(json(n) == j);
+ }
+
+ SECTION("int8_least_t")
+ {
+ int_least8_t const n = j;
+ CHECK(json(n) == j);
+ }
+
+ SECTION("int16_least_t")
+ {
+ int_least16_t const n = j;
+ CHECK(json(n) == j);
+ }
+
+ SECTION("int32_least_t")
+ {
+ int_least32_t const n = j;
+ CHECK(json(n) == j);
+ }
+
+ SECTION("int64_least_t")
+ {
+ int_least64_t const n = j;
+ CHECK(json(n) == j);
+ }
+
+ SECTION("uint8_t")
+ {
+ uint8_t const n = j_unsigned;
+ CHECK(json(n) == j_unsigned);
+ }
+
+ SECTION("uint16_t")
+ {
+ uint16_t const n = j_unsigned;
+ CHECK(json(n) == j_unsigned);
+ }
+
+ SECTION("uint32_t")
+ {
+ uint32_t const n = j_unsigned;
+ CHECK(json(n) == j_unsigned);
+ }
+
+ SECTION("uint64_t")
+ {
+ uint64_t const n = j_unsigned;
+ CHECK(json(n) == j_unsigned);
+ }
+
+ SECTION("uint8_fast_t")
+ {
+ uint_fast8_t const n = j_unsigned;
+ CHECK(json(n) == j_unsigned);
+ }
+
+ SECTION("uint16_fast_t")
+ {
+ uint_fast16_t const n = j_unsigned;
+ CHECK(json(n) == j_unsigned);
+ }
+
+ SECTION("uint32_fast_t")
+ {
+ uint_fast32_t const n = j_unsigned;
+ CHECK(json(n) == j_unsigned);
+ }
+
+ SECTION("uint64_fast_t")
+ {
+ uint_fast64_t const n = j_unsigned;
+ CHECK(json(n) == j_unsigned);
+ }
+
+ SECTION("uint8_least_t")
+ {
+ uint_least8_t const n = j_unsigned;
+ CHECK(json(n) == j_unsigned);
+ }
+
+ SECTION("uint16_least_t")
+ {
+ uint_least16_t const n = j_unsigned;
+ CHECK(json(n) == j_unsigned);
+ }
+
+ SECTION("uint32_least_t")
+ {
+ uint_least32_t const n = j_unsigned;
+ CHECK(json(n) == j_unsigned);
+ }
+
+ SECTION("uint64_least_t")
+ {
+ uint_least64_t const n = j_unsigned;
+ CHECK(json(n) == j_unsigned);
+ }
+ }
+#endif
+
+ SECTION("get a floating-point number (explicit)")
+ {
+ json::number_float_t const n_reference{42.23};
+ json const j(n_reference);
+
+ SECTION("number_float_t")
+ {
+ auto n = j.get<json::number_float_t>();
+ CHECK(json(n).m_data.m_value.number_float == Approx(j.m_data.m_value.number_float));
+ }
+
+ SECTION("float")
+ {
+ auto n = j.get<float>();
+ CHECK(json(n).m_data.m_value.number_float == Approx(j.m_data.m_value.number_float));
+ }
+
+ SECTION("double")
+ {
+ auto n = j.get<double>();
+ CHECK(json(n).m_data.m_value.number_float == Approx(j.m_data.m_value.number_float));
+ }
+
+ SECTION("exception in case of a non-string type")
+ {
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::null).get<json::number_float_t>(),
+ "[json.exception.type_error.302] type must be number, but is null", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::object).get<json::number_float_t>(),
+ "[json.exception.type_error.302] type must be number, but is object", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::array).get<json::number_float_t>(),
+ "[json.exception.type_error.302] type must be number, but is array", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::string).get<json::number_float_t>(),
+ "[json.exception.type_error.302] type must be number, but is string", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ json(json::value_t::boolean).get<json::number_float_t>(),
+ "[json.exception.type_error.302] type must be number, but is "
+ "boolean", json::type_error&);
+
+ CHECK_NOTHROW(
+ json(json::value_t::number_integer).get<json::number_float_t>());
+ CHECK_NOTHROW(
+ json(json::value_t::number_unsigned).get<json::number_float_t>());
+ }
+ }
+
+#if JSON_USE_IMPLICIT_CONVERSIONS
+ SECTION("get a floating-point number (implicit)")
+ {
+ json::number_float_t const n_reference{42.23};
+ json const j(n_reference);
+
+ SECTION("number_float_t")
+ {
+ json::number_float_t const n = j;
+ CHECK(json(n).m_data.m_value.number_float == Approx(j.m_data.m_value.number_float));
+ }
+
+ SECTION("float")
+ {
+ float const n = j;
+ CHECK(json(n).m_data.m_value.number_float == Approx(j.m_data.m_value.number_float));
+ }
+
+ SECTION("double")
+ {
+ double const n = j;
+ CHECK(json(n).m_data.m_value.number_float == Approx(j.m_data.m_value.number_float));
+ }
+ }
+#endif
+
+ SECTION("get a binary value (explicit)")
+ {
+ json::binary_t const n_reference{{1, 2, 3}};
+ json j(n_reference);
+
+ SECTION("binary_t")
+ {
+ json::binary_t const b = j.get<json::binary_t>();
+ CHECK(*json(b).m_data.m_value.binary == *j.m_data.m_value.binary);
+ }
+
+ SECTION("get_binary()")
+ {
+ SECTION("non-const")
+ {
+ auto& b = j.get_binary();
+ CHECK(*json(b).m_data.m_value.binary == *j.m_data.m_value.binary);
+ }
+
+ SECTION("non-const")
+ {
+ const json j_const = j; // NOLINT(performance-unnecessary-copy-initialization)
+ const auto& b = j_const.get_binary();
+ CHECK(*json(b).m_data.m_value.binary == *j.m_data.m_value.binary);
+ }
+ }
+
+ SECTION("exception in case of a non-string type")
+ {
+ json j_null(json::value_t::null);
+ json j_object(json::value_t::object);
+ json j_array(json::value_t::array);
+ json j_string(json::value_t::string);
+ json j_boolean(json::value_t::boolean);
+ const json j_null_const(json::value_t::null);
+ const json j_object_const(json::value_t::object);
+ const json j_array_const(json::value_t::array);
+ const json j_string_const(json::value_t::string);
+ const json j_boolean_const(json::value_t::boolean);
+
+ CHECK_THROWS_WITH_AS(j_null.get<json::binary_t>(),
+ "[json.exception.type_error.302] type must be binary, but is null",
+ json::type_error&);
+ CHECK_THROWS_WITH_AS(j_object.get<json::binary_t>(),
+ "[json.exception.type_error.302] type must be binary, but is object",
+ json::type_error&);
+ CHECK_THROWS_WITH_AS(j_array.get<json::binary_t>(),
+ "[json.exception.type_error.302] type must be binary, but is array",
+ json::type_error&);
+ CHECK_THROWS_WITH_AS(j_string.get<json::binary_t>(),
+ "[json.exception.type_error.302] type must be binary, but is string",
+ json::type_error&);
+ CHECK_THROWS_WITH_AS(j_boolean.get<json::binary_t>(),
+ "[json.exception.type_error.302] type must be binary, but is boolean",
+ json::type_error&);
+
+ CHECK_THROWS_WITH_AS(j_null_const.get<json::binary_t>(),
+ "[json.exception.type_error.302] type must be binary, but is null",
+ json::type_error&);
+ CHECK_THROWS_WITH_AS(j_object_const.get<json::binary_t>(),
+ "[json.exception.type_error.302] type must be binary, but is object",
+ json::type_error&);
+ CHECK_THROWS_WITH_AS(j_array_const.get<json::binary_t>(),
+ "[json.exception.type_error.302] type must be binary, but is array",
+ json::type_error&);
+ CHECK_THROWS_WITH_AS(j_string_const.get<json::binary_t>(),
+ "[json.exception.type_error.302] type must be binary, but is string",
+ json::type_error&);
+ CHECK_THROWS_WITH_AS(j_boolean_const.get<json::binary_t>(),
+ "[json.exception.type_error.302] type must be binary, but is boolean",
+ json::type_error&);
+
+ CHECK_THROWS_WITH_AS(j_null.get_binary(),
+ "[json.exception.type_error.302] type must be binary, but is null",
+ json::type_error&);
+ CHECK_THROWS_WITH_AS(j_object.get_binary(),
+ "[json.exception.type_error.302] type must be binary, but is object",
+ json::type_error&);
+ CHECK_THROWS_WITH_AS(j_array.get_binary(),
+ "[json.exception.type_error.302] type must be binary, but is array",
+ json::type_error&);
+ CHECK_THROWS_WITH_AS(j_string.get_binary(),
+ "[json.exception.type_error.302] type must be binary, but is string",
+ json::type_error&);
+ CHECK_THROWS_WITH_AS(j_boolean.get_binary(),
+ "[json.exception.type_error.302] type must be binary, but is boolean",
+ json::type_error&);
+
+ CHECK_THROWS_WITH_AS(j_null_const.get_binary(),
+ "[json.exception.type_error.302] type must be binary, but is null",
+ json::type_error&);
+ CHECK_THROWS_WITH_AS(j_object_const.get_binary(),
+ "[json.exception.type_error.302] type must be binary, but is object",
+ json::type_error&);
+ CHECK_THROWS_WITH_AS(j_array_const.get_binary(),
+ "[json.exception.type_error.302] type must be binary, but is array",
+ json::type_error&);
+ CHECK_THROWS_WITH_AS(j_string_const.get_binary(),
+ "[json.exception.type_error.302] type must be binary, but is string",
+ json::type_error&);
+ CHECK_THROWS_WITH_AS(j_boolean_const.get_binary(),
+ "[json.exception.type_error.302] type must be binary, but is boolean",
+ json::type_error&);
+ }
+ }
+
+#if JSON_USE_IMPLICIT_CONVERSIONS
+ SECTION("get a binary value (implicit)")
+ {
+ json::binary_t const n_reference{{1, 2, 3}};
+ json const j(n_reference);
+
+ SECTION("binary_t")
+ {
+ json::binary_t const b = j;
+ CHECK(*json(b).m_data.m_value.binary == *j.m_data.m_value.binary);
+ }
+ }
+#endif
+
+#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
+ SECTION("get an enum")
+ {
+ enum c_enum { value_1, value_2 }; // NOLINT(cppcoreguidelines-use-enum-class)
+ enum class cpp_enum { value_1, value_2 };
+
+ CHECK(json(value_1).get<c_enum>() == value_1);
+ CHECK(json(cpp_enum::value_1).get<cpp_enum>() == cpp_enum::value_1);
+ }
+#endif
+
+ SECTION("more involved conversions")
+ {
+ SECTION("object-like STL containers")
+ {
+ json const j1 = {{"one", 1}, {"two", 2}, {"three", 3}};
+ json const j2 = {{"one", 1u}, {"two", 2u}, {"three", 3u}};
+ json const j3 = {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}};
+ json const j4 = {{"one", true}, {"two", false}, {"three", true}};
+ json const j5 = {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}};
+
+ SECTION("std::map")
+ {
+ j1.get<std::map<std::string, int>>();
+ j2.get<std::map<std::string, unsigned int>>();
+ j3.get<std::map<std::string, double>>();
+ j4.get<std::map<std::string, bool>>();
+ j5.get<std::map<std::string, std::string>>();
+ }
+
+ SECTION("std::unordered_map")
+ {
+ j1.get<std::unordered_map<std::string, int>>();
+ j2.get<std::unordered_map<std::string, unsigned int>>();
+ j3.get<std::unordered_map<std::string, double>>();
+ j4.get<std::unordered_map<std::string, bool>>();
+ j5.get<std::unordered_map<std::string, std::string>>();
+ // CHECK(m5["one"] == "eins");
+ }
+
+ SECTION("std::multimap")
+ {
+ j1.get<std::multimap<std::string, int>>();
+ j2.get<std::multimap<std::string, unsigned int>>();
+ j3.get<std::multimap<std::string, double>>();
+ j4.get<std::multimap<std::string, bool>>();
+ j5.get<std::multimap<std::string, std::string>>();
+ // CHECK(m5["one"] == "eins");
+ }
+
+ SECTION("std::unordered_multimap")
+ {
+ j1.get<std::unordered_multimap<std::string, int>>();
+ j2.get<std::unordered_multimap<std::string, unsigned int>>();
+ j3.get<std::unordered_multimap<std::string, double>>();
+ j4.get<std::unordered_multimap<std::string, bool>>();
+ j5.get<std::unordered_multimap<std::string, std::string>>();
+ // CHECK(m5["one"] == "eins");
+ }
+
+ SECTION("exception in case of a non-object type")
+ {
+ CHECK_THROWS_WITH_AS(
+ (json().get<std::map<std::string, int>>()),
+ "[json.exception.type_error.302] type must be object, but is null", json::type_error&);
+ }
+ }
+
+ SECTION("array-like STL containers")
+ {
+ json const j1 = {1, 2, 3, 4};
+ json const j2 = {1u, 2u, 3u, 4u};
+ json const j3 = {1.2, 2.3, 3.4, 4.5};
+ json const j4 = {true, false, true};
+ json const j5 = {"one", "two", "three"};
+
+ SECTION("std::list")
+ {
+ j1.get<std::list<int>>();
+ j2.get<std::list<unsigned int>>();
+ j3.get<std::list<double>>();
+ j4.get<std::list<bool>>();
+ j5.get<std::list<std::string>>();
+ }
+
+ SECTION("std::forward_list")
+ {
+ j1.get<std::forward_list<int>>();
+ j2.get<std::forward_list<unsigned int>>();
+ j3.get<std::forward_list<double>>();
+ j4.get<std::forward_list<bool>>();
+ j5.get<std::forward_list<std::string>>();
+ }
+
+ SECTION("std::array")
+ {
+ j1.get<std::array<int, 4>>();
+ j2.get<std::array<unsigned int, 3>>();
+ j3.get<std::array<double, 4>>();
+ j4.get<std::array<bool, 3>>();
+ j5.get<std::array<std::string, 3>>();
+
+ SECTION("std::array is larger than JSON")
+ {
+ std::array<int, 6> arr6 = {{1, 2, 3, 4, 5, 6}};
+ CHECK_THROWS_WITH_AS(j1.get_to(arr6), "[json.exception.out_of_range.401] "
+ "array index 4 is out of range", json::out_of_range&);
+ }
+
+ SECTION("std::array is smaller than JSON")
+ {
+ std::array<int, 2> arr2 = {{8, 9}};
+ j1.get_to(arr2);
+ CHECK(arr2[0] == 1);
+ CHECK(arr2[1] == 2);
+ }
+ }
+
+ SECTION("std::valarray")
+ {
+ j1.get<std::valarray<int>>();
+ j2.get<std::valarray<unsigned int>>();
+ j3.get<std::valarray<double>>();
+ j4.get<std::valarray<bool>>();
+ j5.get<std::valarray<std::string>>();
+ }
+
+ SECTION("std::vector")
+ {
+ j1.get<std::vector<int>>();
+ j2.get<std::vector<unsigned int>>();
+ j3.get<std::vector<double>>();
+ j4.get<std::vector<bool>>();
+ j5.get<std::vector<std::string>>();
+ }
+
+ SECTION("std::deque")
+ {
+ j1.get<std::deque<int>>();
+ j2.get<std::deque<unsigned int>>();
+ j2.get<std::deque<double>>();
+ j4.get<std::deque<bool>>();
+ j5.get<std::deque<std::string>>();
+ }
+
+ SECTION("std::set")
+ {
+ j1.get<std::set<int>>();
+ j2.get<std::set<unsigned int>>();
+ j3.get<std::set<double>>();
+ j4.get<std::set<bool>>();
+ j5.get<std::set<std::string>>();
+ }
+
+ SECTION("std::unordered_set")
+ {
+ j1.get<std::unordered_set<int>>();
+ j2.get<std::unordered_set<unsigned int>>();
+ j3.get<std::unordered_set<double>>();
+ j4.get<std::unordered_set<bool>>();
+ j5.get<std::unordered_set<std::string>>();
+ }
+
+ SECTION("std::map (array of pairs)")
+ {
+ const std::map<int, int> m{{0, 1}, {1, 2}, {2, 3}};
+ json const j6 = m;
+
+ auto m2 = j6.get<std::map<int, int>>();
+ CHECK(m == m2);
+
+ json const j7 = {0, 1, 2, 3};
+ json const j8 = 2;
+ CHECK_THROWS_WITH_AS((j7.get<std::map<int, int>>()),
+ "[json.exception.type_error.302] type must be array, "
+ "but is number", json::type_error&);
+ CHECK_THROWS_WITH_AS((j8.get<std::map<int, int>>()),
+ "[json.exception.type_error.302] type must be array, "
+ "but is number", json::type_error&);
+
+ SECTION("superfluous entries")
+ {
+ json const j9 = {{0, 1, 2}, {1, 2, 3}, {2, 3, 4}};
+ m2 = j9.get<std::map<int, int>>();
+ CHECK(m == m2);
+ }
+ }
+
+ SECTION("std::unordered_map (array of pairs)")
+ {
+ const std::unordered_map<int, int> m{{0, 1}, {1, 2}, {2, 3}};
+ json const j6 = m;
+
+ auto m2 = j6.get<std::unordered_map<int, int>>();
+ CHECK(m == m2);
+
+ json const j7 = {0, 1, 2, 3};
+ json const j8 = 2;
+ CHECK_THROWS_WITH_AS((j7.get<std::unordered_map<int, int>>()),
+ "[json.exception.type_error.302] type must be array, "
+ "but is number", json::type_error&);
+ CHECK_THROWS_WITH_AS((j8.get<std::unordered_map<int, int>>()),
+ "[json.exception.type_error.302] type must be array, "
+ "but is number", json::type_error&);
+
+ SECTION("superfluous entries")
+ {
+ json const j9{{0, 1, 2}, {1, 2, 3}, {2, 3, 4}};
+ m2 = j9.get<std::unordered_map<int, int>>();
+ CHECK(m == m2);
+ }
+ }
+
+ SECTION("exception in case of a non-object type")
+ {
+ // does type really must be an array? or it rather must not be null?
+ // that's what I thought when other test like this one broke
+ CHECK_THROWS_WITH_AS(
+ (json().get<std::list<int>>()),
+ "[json.exception.type_error.302] type must be array, but is null", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ (json().get<std::vector<int>>()),
+ "[json.exception.type_error.302] type must be array, but is null", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ (json().get<std::vector<json>>()),
+ "[json.exception.type_error.302] type must be array, but is null", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ (json().get<std::list<json>>()),
+ "[json.exception.type_error.302] type must be array, but is null", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ (json().get<std::valarray<int>>()),
+ "[json.exception.type_error.302] type must be array, but is null", json::type_error&);
+ CHECK_THROWS_WITH_AS(
+ (json().get<std::map<int, int>>()),
+ "[json.exception.type_error.302] type must be array, but is null", json::type_error&);
+ }
+ }
+ }
+}
+
+enum class cards {kreuz, pik, herz, karo};
+
+// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
+NLOHMANN_JSON_SERIALIZE_ENUM(cards,
+{
+ {cards::kreuz, "kreuz"},
+ {cards::pik, "pik"},
+ {cards::pik, "puk"}, // second entry for cards::puk; will not be used
+ {cards::herz, "herz"},
+ {cards::karo, "karo"}
+})
+
+enum TaskState // NOLINT(cert-int09-c,readability-enum-initial-value,cppcoreguidelines-use-enum-class)
+{
+ TS_STOPPED,
+ TS_RUNNING,
+ TS_COMPLETED,
+ TS_INVALID = -1,
+};
+
+// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
+NLOHMANN_JSON_SERIALIZE_ENUM(TaskState,
+{
+ {TS_INVALID, nullptr},
+ {TS_STOPPED, "stopped"},
+ {TS_RUNNING, "running"},
+ {TS_COMPLETED, "completed"},
+})
+
+TEST_CASE("JSON to enum mapping")
+{
+ SECTION("enum class")
+ {
+ // enum -> json
+ CHECK(json(cards::kreuz) == "kreuz");
+ CHECK(json(cards::pik) == "pik");
+ CHECK(json(cards::herz) == "herz");
+ CHECK(json(cards::karo) == "karo");
+
+ // json -> enum
+ CHECK(cards::kreuz == json("kreuz"));
+ CHECK(cards::pik == json("pik"));
+ CHECK(cards::herz == json("herz"));
+ CHECK(cards::karo == json("karo"));
+
+ // invalid json -> first enum
+ CHECK(cards::kreuz == json("what?").get<cards>());
+ }
+
+ SECTION("traditional enum")
+ {
+ // enum -> json
+ CHECK(json(TS_STOPPED) == "stopped");
+ CHECK(json(TS_RUNNING) == "running");
+ CHECK(json(TS_COMPLETED) == "completed");
+ CHECK(json(TS_INVALID) == json());
+
+ // json -> enum
+ CHECK(TS_STOPPED == json("stopped"));
+ CHECK(TS_RUNNING == json("running"));
+ CHECK(TS_COMPLETED == json("completed"));
+ CHECK(TS_INVALID == json());
+
+ // invalid json -> first enum
+ CHECK(TS_INVALID == json("what?").get<TaskState>());
+ }
+}
+
+#ifdef JSON_HAS_CPP_17
+#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
+TEST_CASE("std::filesystem::path")
+{
+ SECTION("ascii")
+ {
+ json const j_string = "Path";
+ auto p = j_string.template get<nlohmann::detail::std_fs::path>();
+ json const j_path = p;
+
+ CHECK(j_path.template get<std::string>() ==
+ j_string.template get<std::string>());
+ }
+
+ SECTION("utf-8")
+ {
+ json const j_string = "P\xc4\x9b\xc5\xa1ina";
+ auto p = j_string.template get<nlohmann::detail::std_fs::path>();
+ json const j_path = p;
+
+ CHECK(j_path.template get<std::string>() ==
+ j_string.template get<std::string>());
+ }
+}
+#endif
+
+#ifndef JSON_USE_IMPLICIT_CONVERSIONS
+TEST_CASE("std::optional")
+{
+ SECTION("null")
+ {
+ json j_null;
+ std::optional<std::string> opt_null;
+
+ CHECK(json(opt_null) == j_null);
+ CHECK(j_null.get<std::optional<std::string>>() == std::nullopt);
+ }
+
+ SECTION("string")
+ {
+ json j_string = "string";
+ std::optional<std::string> opt_string = "string";
+
+ CHECK(json(opt_string) == j_string);
+ CHECK(std::optional<std::string>(j_string) == opt_string);
+ }
+
+ SECTION("bool")
+ {
+ json j_bool = true;
+ std::optional<bool> opt_bool = true;
+
+ CHECK(json(opt_bool) == j_bool);
+ CHECK(std::optional<bool>(j_bool) == opt_bool);
+ }
+
+ SECTION("number")
+ {
+ json j_number = 1;
+ std::optional<int> opt_int = 1;
+
+ CHECK(json(opt_int) == j_number);
+ CHECK(j_number.get<std::optional<int>>() == opt_int);
+ }
+
+ SECTION("array")
+ {
+ json j_array = {1, 2, nullptr};
+ std::vector<std::optional<int>> opt_array = {{1, 2, std::nullopt}};
+
+ CHECK(json(opt_array) == j_array);
+ CHECK(j_array.get<std::vector<std::optional<int>>>() == opt_array);
+ }
+
+ SECTION("object")
+ {
+ json j_object = {{"one", 1}, {"two", 2}, {"zero", nullptr}};
+ std::map<std::string, std::optional<int>> opt_object {{"one", 1}, {"two", 2}, {"zero", std::nullopt}};
+
+ CHECK(json(opt_object) == j_object);
+ CHECK(std::map<std::string, std::optional<int>>(j_object) == opt_object);
+ }
+}
+#endif
+#endif
+
+#ifdef JSON_HAS_CPP_17
+ #undef JSON_HAS_CPP_17
+#endif
+
+#ifdef JSON_HAS_CPP_14
+ #undef JSON_HAS_CPP_14
+#endif
+DOCTEST_CLANG_SUPPRESS_WARNING_POP
diff --git a/json4cpp/tests/src/unit-custom-base-class.cpp b/json4cpp/tests/src/unit-custom-base-class.cpp
new file mode 100644
index 0000000000..7dab5c5766
--- /dev/null
+++ b/json4cpp/tests/src/unit-custom-base-class.cpp
@@ -0,0 +1,335 @@
+// __ _____ _____ _____
+// __| | __| | | | 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 <set>
+#include <sstream>
+#include <string>
+
+#include "doctest_compatibility.h"
+
+#include <nlohmann/json.hpp>
+
+// Test extending nlohmann::json by using a custom base class.
+// Add some metadata to each node and test the behaviour of copy / move
+template<class MetaDataType>
+class json_metadata
+{
+ public:
+ using metadata_t = MetaDataType;
+ metadata_t& metadata()
+ {
+ return m_metadata;
+ }
+ const metadata_t& metadata() const
+ {
+ return m_metadata;
+ }
+ private:
+ metadata_t m_metadata = {};
+};
+
+template<class T>
+using json_with_metadata =
+ nlohmann::basic_json <
+ std::map,
+ std::vector,
+ std::string,
+ bool,
+ std::int64_t,
+ std::uint64_t,
+ double,
+ std::allocator,
+ nlohmann::adl_serializer,
+ std::vector<std::uint8_t>,
+ json_metadata<T>
+ >;
+
+TEST_CASE("JSON Node Metadata")
+{
+ SECTION("type int")
+ {
+ using json = json_with_metadata<int>;
+ json null;
+ auto obj = json::object();
+ auto array = json::array();
+
+ null.metadata() = 1;
+ obj.metadata() = 2;
+ array.metadata() = 3;
+ auto copy = array;
+
+ CHECK(null.metadata() == 1);
+ CHECK(obj.metadata() == 2);
+ CHECK(array.metadata() == 3);
+ CHECK(copy.metadata() == 3);
+ }
+ SECTION("type vector<int>")
+ {
+ using json = json_with_metadata<std::vector<int>>;
+ json value;
+ value.metadata().emplace_back(1);
+ auto copy = value;
+ value.metadata().emplace_back(2);
+
+ CHECK(copy.metadata().size() == 1);
+ CHECK(copy.metadata().at(0) == 1);
+ CHECK(value.metadata().size() == 2);
+ CHECK(value.metadata().at(0) == 1);
+ CHECK(value.metadata().at(1) == 2);
+ }
+ SECTION("copy ctor")
+ {
+ using json = json_with_metadata<std::vector<int>>;
+ json value;
+ value.metadata().emplace_back(1);
+ value.metadata().emplace_back(2);
+
+ json copy = value;
+
+ CHECK(copy.metadata().size() == 2);
+ CHECK(copy.metadata().at(0) == 1);
+ CHECK(copy.metadata().at(1) == 2);
+ CHECK(value.metadata().size() == 2);
+ CHECK(value.metadata().at(0) == 1);
+ CHECK(value.metadata().at(1) == 2);
+
+ value.metadata().clear();
+ CHECK(copy.metadata().size() == 2);
+ CHECK(value.metadata().size() == 0);
+ }
+ SECTION("move ctor")
+ {
+ using json = json_with_metadata<std::vector<int>>;
+ json value;
+ value.metadata().emplace_back(1);
+ value.metadata().emplace_back(2);
+
+ const json moved = std::move(value);
+
+ CHECK(moved.metadata().size() == 2);
+ CHECK(moved.metadata().at(0) == 1);
+ CHECK(moved.metadata().at(1) == 2);
+ }
+ SECTION("move assign")
+ {
+ using json = json_with_metadata<std::vector<int>>;
+ json value;
+ value.metadata().emplace_back(1);
+ value.metadata().emplace_back(2);
+
+ json moved;
+ moved = std::move(value);
+
+ CHECK(moved.metadata().size() == 2);
+ CHECK(moved.metadata().at(0) == 1);
+ CHECK(moved.metadata().at(1) == 2);
+ }
+ SECTION("copy assign")
+ {
+ using json = json_with_metadata<std::vector<int>>;
+ json value;
+ value.metadata().emplace_back(1);
+ value.metadata().emplace_back(2);
+
+ json copy;
+ copy = value;
+
+ CHECK(copy.metadata().size() == 2);
+ CHECK(copy.metadata().at(0) == 1);
+ CHECK(copy.metadata().at(1) == 2);
+ CHECK(value.metadata().size() == 2);
+ CHECK(value.metadata().at(0) == 1);
+ CHECK(value.metadata().at(1) == 2);
+
+ value.metadata().clear();
+ CHECK(copy.metadata().size() == 2);
+ CHECK(value.metadata().size() == 0);
+ }
+ SECTION("type unique_ptr<int>")
+ {
+ using json = json_with_metadata<std::unique_ptr<int>>;
+ json value;
+ value.metadata().reset(new int(42)); // NOLINT(cppcoreguidelines-owning-memory)
+ auto moved = std::move(value);
+
+ CHECK(moved.metadata() != nullptr);
+ CHECK(*moved.metadata() == 42);
+ }
+ SECTION("type vector<int> in json array")
+ {
+ using json = json_with_metadata<std::vector<int>>;
+ json value;
+ value.metadata().emplace_back(1);
+ value.metadata().emplace_back(2);
+
+ json const array(10, value);
+
+ CHECK(value.metadata().size() == 2);
+ CHECK(value.metadata().at(0) == 1);
+ CHECK(value.metadata().at(1) == 2);
+
+ for (const auto& val : array)
+ {
+ CHECK(val.metadata().size() == 2);
+ CHECK(val.metadata().at(0) == 1);
+ CHECK(val.metadata().at(1) == 2);
+ }
+ }
+}
+
+// Test extending nlohmann::json by using a custom base class.
+// Add a custom member function template iterating over the whole json tree.
+class visitor_adaptor
+{
+ public:
+ template <class Fnc>
+ void visit(const Fnc& fnc) const;
+ private:
+ template <class Ptr, class Fnc>
+ void do_visit(const Ptr& ptr, const Fnc& fnc) const;
+};
+
+using json_with_visitor_t = nlohmann::basic_json <
+ std::map,
+ std::vector,
+ std::string,
+ bool,
+ std::int64_t,
+ std::uint64_t,
+ double,
+ std::allocator,
+ nlohmann::adl_serializer,
+ std::vector<std::uint8_t>,
+ visitor_adaptor
+ >;
+
+template <class Fnc>
+void visitor_adaptor::visit(const Fnc& fnc) const
+{
+ do_visit(json_with_visitor_t::json_pointer{}, fnc);
+}
+
+template <class Ptr, class Fnc>
+void visitor_adaptor::do_visit(const Ptr& ptr, const Fnc& fnc) const
+{
+ using value_t = nlohmann::detail::value_t;
+ const json_with_visitor_t& json = *static_cast<const json_with_visitor_t*>(this); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
+ switch (json.type())
+ {
+ case value_t::object:
+ for (const auto& entry : json.items())
+ {
+ entry.value().do_visit(ptr / entry.key(), fnc);
+ }
+ break;
+ case value_t::array:
+ for (std::size_t i = 0; i < json.size(); ++i)
+ {
+ json.at(i).do_visit(ptr / std::to_string(i), fnc);
+ }
+ break;
+ case value_t::discarded:
+ break;
+ case value_t::null:
+ case value_t::string:
+ case value_t::boolean:
+ case value_t::number_integer:
+ case value_t::number_unsigned:
+ case value_t::number_float:
+ case value_t::binary:
+ default:
+ fnc(ptr, json);
+ }
+}
+
+TEST_CASE("JSON Visit Node")
+{
+ json_with_visitor_t json;
+ json["null"];
+ json["int"] = -1;
+ json["uint"] = 1U;
+ json["float"] = 1.0;
+ json["boolean"] = true;
+ json["string"] = "string";
+ json["array"].push_back(0);
+ json["array"].push_back(1);
+ json["array"].push_back(json);
+
+ std::set<std::string> expected
+ {
+ "/null - null - null",
+ "/int - number_integer - -1",
+ "/uint - number_unsigned - 1",
+ "/float - number_float - 1.0",
+ "/boolean - boolean - true",
+ "/string - string - \"string\"",
+ "/array/0 - number_integer - 0",
+ "/array/1 - number_integer - 1",
+
+ "/array/2/null - null - null",
+ "/array/2/int - number_integer - -1",
+ "/array/2/uint - number_unsigned - 1",
+ "/array/2/float - number_float - 1.0",
+ "/array/2/boolean - boolean - true",
+ "/array/2/string - string - \"string\"",
+ "/array/2/array/0 - number_integer - 0",
+ "/array/2/array/1 - number_integer - 1"
+ };
+
+ json.visit(
+ [&](const json_with_visitor_t::json_pointer & p,
+ const json_with_visitor_t& j)
+ {
+ std::stringstream str;
+ str << p.to_string() << " - " ;
+ using value_t = nlohmann::detail::value_t;
+ switch (j.type())
+ {
+ case value_t::object:
+ str << "object";
+ break;
+ case value_t::array:
+ str << "array";
+ break;
+ case value_t::discarded:
+ str << "discarded";
+ break;
+ case value_t::null:
+ str << "null";
+ break;
+ case value_t::string:
+ str << "string";
+ break;
+ case value_t::boolean:
+ str << "boolean";
+ break;
+ case value_t::number_integer:
+ str << "number_integer";
+ break;
+ case value_t::number_unsigned:
+ str << "number_unsigned";
+ break;
+ case value_t::number_float:
+ str << "number_float";
+ break;
+ case value_t::binary:
+ str << "binary";
+ break;
+ default:
+ str << "error";
+ break;
+ }
+ str << " - " << j.dump();
+ CHECK(json.at(p) == j);
+ INFO(str.str());
+ CHECK(expected.count(str.str()) == 1);
+ expected.erase(str.str());
+ }
+ );
+ CHECK(expected.empty());
+}
diff --git a/json4cpp/tests/src/unit-deserialization.cpp b/json4cpp/tests/src/unit-deserialization.cpp
new file mode 100644
index 0000000000..0ade1ad52c
--- /dev/null
+++ b/json4cpp/tests/src/unit-deserialization.cpp
@@ -0,0 +1,1236 @@
+// __ _____ _____ _____
+// __| | __| | | | 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 <iostream>
+#include <iterator>
+#include <sstream>
+#include <valarray>
+
+#if defined(_WIN32)
+ #define NOMINMAX
+ #include <windows.h> // for GetACP()
+#endif
+
+namespace
+{
+struct SaxEventLogger : public nlohmann::json_sax<json>
+{
+ bool null() override
+ {
+ events.emplace_back("null()");
+ return true;
+ }
+
+ bool boolean(bool val) override
+ {
+ events.emplace_back(val ? "boolean(true)" : "boolean(false)");
+ return true;
+ }
+
+ bool number_integer(json::number_integer_t val) override
+ {
+ events.push_back("number_integer(" + std::to_string(val) + ")");
+ return true;
+ }
+
+ bool number_unsigned(json::number_unsigned_t val) override
+ {
+ events.push_back("number_unsigned(" + std::to_string(val) + ")");
+ return true;
+ }
+
+ bool number_float(json::number_float_t /*val*/, const std::string& s) override
+ {
+ events.push_back("number_float(" + s + ")");
+ return true;
+ }
+
+ bool string(std::string& val) override
+ {
+ events.push_back("string(" + val + ")");
+ return true;
+ }
+
+ bool binary(json::binary_t& val) override
+ {
+ std::string binary_contents = "binary(";
+ std::string comma_space;
+ for (auto b : val)
+ {
+ binary_contents.append(comma_space);
+ binary_contents.append(std::to_string(static_cast<int>(b)));
+ comma_space = ", ";
+ }
+ binary_contents.append(")");
+ events.push_back(binary_contents);
+ return true;
+ }
+
+ bool start_object(std::size_t elements) override
+ {
+ if (elements == (std::numeric_limits<std::size_t>::max)())
+ {
+ events.emplace_back("start_object()");
+ }
+ else
+ {
+ events.push_back("start_object(" + std::to_string(elements) + ")");
+ }
+ return true;
+ }
+
+ bool key(std::string& val) override
+ {
+ events.push_back("key(" + val + ")");
+ return true;
+ }
+
+ bool end_object() override
+ {
+ events.emplace_back("end_object()");
+ return true;
+ }
+
+ bool start_array(std::size_t elements) override
+ {
+ if (elements == (std::numeric_limits<std::size_t>::max)())
+ {
+ events.emplace_back("start_array()");
+ }
+ else
+ {
+ events.push_back("start_array(" + std::to_string(elements) + ")");
+ }
+ return true;
+ }
+
+ bool end_array() override
+ {
+ events.emplace_back("end_array()");
+ return true;
+ }
+
+ bool parse_error(std::size_t position, const std::string& /*last_token*/, const json::exception& /*ex*/) override
+ {
+ events.push_back("parse_error(" + std::to_string(position) + ")");
+ return false;
+ }
+
+ std::vector<std::string> events {}; // NOLINT(readability-redundant-member-init)
+};
+
+struct SaxEventLoggerExitAfterStartObject : public SaxEventLogger
+{
+ bool start_object(std::size_t elements) override
+ {
+ if (elements == (std::numeric_limits<std::size_t>::max)())
+ {
+ events.emplace_back("start_object()");
+ }
+ else
+ {
+ events.push_back("start_object(" + std::to_string(elements) + ")");
+ }
+ return false;
+ }
+};
+
+struct SaxEventLoggerExitAfterKey : public SaxEventLogger
+{
+ bool key(std::string& val) override
+ {
+ events.push_back("key(" + val + ")");
+ return false;
+ }
+};
+
+struct SaxEventLoggerExitAfterStartArray : public SaxEventLogger
+{
+ bool start_array(std::size_t elements) override
+ {
+ if (elements == (std::numeric_limits<std::size_t>::max)())
+ {
+ events.emplace_back("start_array()");
+ }
+ else
+ {
+ events.push_back("start_array(" + std::to_string(elements) + ")");
+ }
+ return false;
+ }
+};
+
+template <typename T>
+class proxy_iterator
+{
+ public:
+ using iterator = typename T::iterator;
+ using value_type = typename std::iterator_traits<iterator>::value_type;
+ using reference = typename std::iterator_traits<iterator>::reference;
+ using pointer = typename std::iterator_traits<iterator>::pointer;
+ using difference_type =
+ typename std::iterator_traits<iterator>::difference_type;
+ using iterator_category = std::input_iterator_tag;
+
+ proxy_iterator() = default;
+ explicit proxy_iterator(iterator& it) : m_it(std::addressof(it)) {}
+
+ proxy_iterator& operator++()
+ {
+ ++*m_it;
+ return *this;
+ }
+
+ proxy_iterator& operator--()
+ {
+ --*m_it;
+ return *this;
+ }
+
+ bool operator==(const proxy_iterator& rhs) const
+ {
+ return (m_it && rhs.m_it) ? (*m_it == *rhs.m_it) : (m_it == rhs.m_it);
+ }
+
+ bool operator!=(const proxy_iterator& rhs) const
+ {
+ return !(*this == rhs);
+ }
+
+ reference operator*() const
+ {
+ return **m_it;
+ }
+
+ private:
+ iterator* m_it = nullptr;
+};
+
+// JSON_HAS_CPP_20
+#if defined(__cpp_char8_t)
+bool check_utf8()
+{
+#if defined(_WIN32)
+ // Runtime check of the active ANSI code page
+ // 65001 == UTF-8
+ return GetACP() == 65001;
+#else
+ return true;
+#endif
+}
+#endif
+} // namespace
+
+TEST_CASE("deserialization")
+{
+ SECTION("successful deserialization")
+ {
+ SECTION("stream")
+ {
+ std::stringstream ss1;
+ std::stringstream ss2;
+ std::stringstream ss3;
+ ss1 << R"(["foo",1,2,3,false,{"one":1}])";
+ ss2 << R"(["foo",1,2,3,false,{"one":1}])";
+ ss3 << R"(["foo",1,2,3,false,{"one":1}])";
+ const json j = json::parse(ss1);
+ CHECK(json::accept(ss2));
+ CHECK(j == json({"foo", 1, 2, 3, false, {{"one", 1}}}));
+
+ SaxEventLogger l;
+ CHECK(json::sax_parse(ss3, &l));
+ CHECK(l.events.size() == 11);
+ CHECK(l.events == std::vector<std::string>(
+ {
+ "start_array()", "string(foo)", "number_unsigned(1)",
+ "number_unsigned(2)", "number_unsigned(3)", "boolean(false)",
+ "start_object()", "key(one)", "number_unsigned(1)",
+ "end_object()", "end_array()"
+ }));
+ }
+
+ SECTION("string literal")
+ {
+ const auto* s = R"(["foo",1,2,3,false,{"one":1}])";
+ const json j = json::parse(s);
+ CHECK(json::accept(s));
+ CHECK(j == json({"foo", 1, 2, 3, false, {{"one", 1}}}));
+
+ SaxEventLogger l;
+ CHECK(json::sax_parse(s, &l));
+ CHECK(l.events.size() == 11);
+ CHECK(l.events == std::vector<std::string>(
+ {
+ "start_array()", "string(foo)", "number_unsigned(1)",
+ "number_unsigned(2)", "number_unsigned(3)", "boolean(false)",
+ "start_object()", "key(one)", "number_unsigned(1)",
+ "end_object()", "end_array()"
+ }));
+ }
+
+ SECTION("string_t")
+ {
+ json::string_t const s = R"(["foo",1,2,3,false,{"one":1}])";
+ const json j = json::parse(s);
+ CHECK(json::accept(s));
+ CHECK(j == json({"foo", 1, 2, 3, false, {{"one", 1}}}));
+
+ SaxEventLogger l;
+ CHECK(json::sax_parse(s, &l));
+ CHECK(l.events.size() == 11);
+ CHECK(l.events == std::vector<std::string>(
+ {
+ "start_array()", "string(foo)", "number_unsigned(1)",
+ "number_unsigned(2)", "number_unsigned(3)", "boolean(false)",
+ "start_object()", "key(one)", "number_unsigned(1)",
+ "end_object()", "end_array()"
+ }));
+ }
+
+ SECTION("operator<<")
+ {
+ std::stringstream ss;
+ ss << R"(["foo",1,2,3,false,{"one":1}])";
+ json j;
+ j << ss;
+ CHECK(j == json({"foo", 1, 2, 3, false, {{"one", 1}}}));
+ }
+
+ SECTION("operator>>")
+ {
+ std::stringstream ss;
+ ss << R"(["foo",1,2,3,false,{"one":1}])";
+ json j;
+ ss >> j;
+ CHECK(j == json({"foo", 1, 2, 3, false, {{"one", 1}}}));
+ }
+
+ SECTION("user-defined string literal")
+ {
+ CHECK("[\"foo\",1,2,3,false,{\"one\":1}]"_json == json({"foo", 1, 2, 3, false, {{"one", 1}}}));
+ }
+ }
+
+ SECTION("unsuccessful deserialization")
+ {
+ SECTION("stream")
+ {
+ std::stringstream ss1;
+ std::stringstream ss3;
+ std::stringstream ss4;
+ std::stringstream ss5;
+ ss1 << R"(["foo",1,2,3,false,{"one":1})";
+ ss3 << R"(["foo",1,2,3,false,{"one":1})";
+ ss4 << R"(["foo",1,2,3,false,{"one":1})";
+ ss5 << R"(["foo",1,2,3,false,{"one":1})";
+
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::parse(ss1), "[json.exception.parse_error.101] parse error at line 1, column 29: syntax error while parsing array - unexpected end of input; expected ']'", json::parse_error&);
+ CHECK(!json::accept(ss3));
+
+ json j_error;
+ CHECK_NOTHROW(j_error = json::parse(ss4, nullptr, false));
+ CHECK(j_error.is_discarded());
+
+ SaxEventLogger l;
+ CHECK(!json::sax_parse(ss5, &l));
+ CHECK(l.events.size() == 11);
+ CHECK(l.events == std::vector<std::string>(
+ {
+ "start_array()", "string(foo)", "number_unsigned(1)",
+ "number_unsigned(2)", "number_unsigned(3)", "boolean(false)",
+ "start_object()", "key(one)", "number_unsigned(1)",
+ "end_object()", "parse_error(29)"
+ }));
+ }
+
+ SECTION("string")
+ {
+ json::string_t const s = R"(["foo",1,2,3,false,{"one":1})";
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::parse(s), "[json.exception.parse_error.101] parse error at line 1, column 29: syntax error while parsing array - unexpected end of input; expected ']'", json::parse_error&);
+ CHECK(!json::accept(s));
+
+ json j_error;
+ CHECK_NOTHROW(j_error = json::parse(s, nullptr, false));
+ CHECK(j_error.is_discarded());
+
+ SaxEventLogger l;
+ CHECK(!json::sax_parse(s, &l));
+ CHECK(l.events.size() == 11);
+ CHECK(l.events == std::vector<std::string>(
+ {
+ "start_array()", "string(foo)", "number_unsigned(1)",
+ "number_unsigned(2)", "number_unsigned(3)", "boolean(false)",
+ "start_object()", "key(one)", "number_unsigned(1)",
+ "end_object()", "parse_error(29)"
+ }));
+
+ const char* string = nullptr;
+ CHECK_THROWS_WITH_AS(_ = json::parse(string), "[json.exception.parse_error.101] parse error: attempting to parse an empty input; check that your input string or stream contains the expected JSON", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::parse(nullptr), "[json.exception.parse_error.101] parse error: attempting to parse an empty input; check that your input string or stream contains the expected JSON", json::parse_error&);
+ }
+
+ SECTION("operator<<")
+ {
+ std::stringstream ss;
+ ss << R"(["foo",1,2,3,false,{"one":1})";
+ json j;
+ CHECK_THROWS_WITH_AS(j << ss, "[json.exception.parse_error.101] parse error at line 1, column 29: syntax error while parsing array - unexpected end of input; expected ']'", json::parse_error&);
+ }
+
+ SECTION("operator>>")
+ {
+ std::stringstream ss;
+ ss << R"(["foo",1,2,3,false,{"one":1})";
+ json j;
+ CHECK_THROWS_WITH_AS(ss >> j, "[json.exception.parse_error.101] parse error at line 1, column 29: syntax error while parsing array - unexpected end of input; expected ']'", json::parse_error&);
+ }
+
+ SECTION("user-defined string literal")
+ {
+ CHECK_THROWS_WITH_AS("[\"foo\",1,2,3,false,{\"one\":1}"_json, "[json.exception.parse_error.101] parse error at line 1, column 29: syntax error while parsing array - unexpected end of input; expected ']'", json::parse_error&);
+ }
+ }
+
+ SECTION("contiguous containers")
+ {
+ SECTION("directly")
+ {
+ SECTION("from std::vector")
+ {
+ std::vector<uint8_t> const v = {'t', 'r', 'u', 'e'};
+ CHECK(json::parse(v) == json(true));
+ CHECK(json::accept(v));
+
+ SaxEventLogger l;
+ CHECK(json::sax_parse(v, &l));
+ CHECK(l.events.size() == 1);
+ CHECK(l.events == std::vector<std::string>({"boolean(true)"}));
+ }
+
+ SECTION("from std::array")
+ {
+ std::array<uint8_t, 5> const v { {'t', 'r', 'u', 'e'} };
+ CHECK(json::parse(v) == json(true));
+ CHECK(json::accept(v));
+
+ SaxEventLogger l;
+ CHECK(json::sax_parse(v, &l));
+ CHECK(l.events.size() == 1);
+ CHECK(l.events == std::vector<std::string>({"boolean(true)"}));
+ }
+
+ SECTION("from array")
+ {
+ uint8_t v[] = {'t', 'r', 'u', 'e'}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
+ CHECK(json::parse(v) == json(true));
+ CHECK(json::accept(v));
+
+ SaxEventLogger l;
+ CHECK(json::sax_parse(v, &l));
+ CHECK(l.events.size() == 1);
+ CHECK(l.events == std::vector<std::string>({"boolean(true)"}));
+ }
+
+ SECTION("from chars")
+ {
+ auto* v = new uint8_t[5]; // NOLINT(cppcoreguidelines-owning-memory)
+ v[0] = 't';
+ v[1] = 'r';
+ v[2] = 'u';
+ v[3] = 'e';
+ v[4] = '\0';
+ CHECK(json::parse(v) == json(true));
+ CHECK(json::accept(v));
+
+ SaxEventLogger l;
+ CHECK(json::sax_parse(v, &l));
+ CHECK(l.events.size() == 1);
+ CHECK(l.events == std::vector<std::string>({"boolean(true)"}));
+
+ delete[] v; // NOLINT(cppcoreguidelines-owning-memory)
+ }
+
+ SECTION("from std::string")
+ {
+ std::string const v = {'t', 'r', 'u', 'e'};
+ CHECK(json::parse(v) == json(true));
+ CHECK(json::accept(v));
+
+ SaxEventLogger l;
+ CHECK(json::sax_parse(v, &l));
+ CHECK(l.events.size() == 1);
+ CHECK(l.events == std::vector<std::string>({"boolean(true)"}));
+ }
+
+ SECTION("from std::initializer_list")
+ {
+ std::initializer_list<uint8_t> const v = {'t', 'r', 'u', 'e'};
+ CHECK(json::parse(v) == json(true));
+ CHECK(json::accept(v));
+
+ SaxEventLogger l;
+ CHECK(json::sax_parse(v, &l));
+ CHECK(l.events.size() == 1);
+ CHECK(l.events == std::vector<std::string>({"boolean(true)"}));
+ }
+
+ SECTION("empty container")
+ {
+ std::vector<uint8_t> const v;
+ json _;
+ CHECK_THROWS_AS(_ = json::parse(v), json::parse_error&);
+ CHECK(!json::accept(v));
+
+ SaxEventLogger l;
+ CHECK(!json::sax_parse(v, &l));
+ CHECK(l.events.size() == 1);
+ CHECK(l.events == std::vector<std::string>({"parse_error(1)"}));
+ }
+ }
+
+ SECTION("via iterator range")
+ {
+ SECTION("from std::vector")
+ {
+ std::vector<uint8_t> v = {'t', 'r', 'u', 'e'};
+ CHECK(json::parse(std::begin(v), std::end(v)) == json(true));
+ CHECK(json::accept(std::begin(v), std::end(v)));
+
+ SaxEventLogger l;
+ CHECK(json::sax_parse(std::begin(v), std::end(v), &l));
+ CHECK(l.events.size() == 1);
+ CHECK(l.events == std::vector<std::string>({"boolean(true)"}));
+
+ }
+
+ SECTION("from std::array")
+ {
+ std::array<uint8_t, 5> v { {'t', 'r', 'u', 'e'} };
+ CHECK(json::parse(std::begin(v), std::end(v)) == json(true));
+ CHECK(json::accept(std::begin(v), std::end(v)));
+
+ SaxEventLogger l;
+ CHECK(json::sax_parse(std::begin(v), std::end(v), &l));
+ CHECK(l.events.size() == 1);
+ CHECK(l.events == std::vector<std::string>({"boolean(true)"}));
+ }
+
+ SECTION("from array")
+ {
+ uint8_t v[] = {'t', 'r', 'u', 'e'}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
+ CHECK(json::parse(std::begin(v), std::end(v)) == json(true));
+ CHECK(json::accept(std::begin(v), std::end(v)));
+
+ SaxEventLogger l;
+ CHECK(json::sax_parse(std::begin(v), std::end(v), &l));
+ CHECK(l.events.size() == 1);
+ CHECK(l.events == std::vector<std::string>({"boolean(true)"}));
+ }
+
+ SECTION("from std::string")
+ {
+ std::string v = {'t', 'r', 'u', 'e'};
+ CHECK(json::parse(std::begin(v), std::end(v)) == json(true));
+ CHECK(json::accept(std::begin(v), std::end(v)));
+
+ SaxEventLogger l;
+ CHECK(json::sax_parse(std::begin(v), std::end(v), &l));
+ CHECK(l.events.size() == 1);
+ CHECK(l.events == std::vector<std::string>({"boolean(true)"}));
+ }
+
+ SECTION("from std::initializer_list")
+ {
+ std::initializer_list<uint8_t> const v = {'t', 'r', 'u', 'e'};
+ CHECK(json::parse(std::begin(v), std::end(v)) == json(true));
+ CHECK(json::accept(std::begin(v), std::end(v)));
+
+ SaxEventLogger l;
+ CHECK(json::sax_parse(std::begin(v), std::end(v), &l));
+ CHECK(l.events.size() == 1);
+ CHECK(l.events == std::vector<std::string>({"boolean(true)"}));
+ }
+
+ SECTION("from std::valarray")
+ {
+ std::valarray<uint8_t> v = {'t', 'r', 'u', 'e'};
+ CHECK(json::parse(std::begin(v), std::end(v)) == json(true));
+ CHECK(json::accept(std::begin(v), std::end(v)));
+
+ SaxEventLogger l;
+ CHECK(json::sax_parse(std::begin(v), std::end(v), &l));
+ CHECK(l.events.size() == 1);
+ CHECK(l.events == std::vector<std::string>({"boolean(true)"}));
+ }
+
+ SECTION("with empty range")
+ {
+ std::vector<uint8_t> v;
+ json _;
+ CHECK_THROWS_AS(_ = json::parse(std::begin(v), std::end(v)), json::parse_error&);
+ CHECK(!json::accept(std::begin(v), std::end(v)));
+
+ SaxEventLogger l;
+ CHECK(!json::sax_parse(std::begin(v), std::end(v), &l));
+ CHECK(l.events.size() == 1);
+ CHECK(l.events == std::vector<std::string>({"parse_error(1)"}));
+ }
+
+ SECTION("iterator_input_adapter advances iterators correctly")
+ {
+ using nlohmann::json;
+ using nlohmann::detail::input_format_t;
+ using nlohmann::detail::json_sax_dom_parser;
+ using proxy = proxy_iterator<std::string>;
+
+ std::string str1 = "[1]";
+ std::string str2 = "[2]";
+ std::string str = str1 + str2;
+
+ auto first = str.begin();
+ auto last = str.end();
+ json j;
+ json_sax_dom_parser<json, nlohmann::detail::string_input_adapter_type> sax(j, true);
+
+ CHECK(json::sax_parse(proxy(first), proxy(last), &sax,
+ input_format_t::json, false));
+ CHECK(j.dump() == str1);
+ CHECK(std::string(first, last) == str2);
+ }
+ }
+
+ // these cases are required for 100% line coverage
+ SECTION("error cases")
+ {
+ SECTION("case 1")
+ {
+ std::array<std::uint8_t, 9> v = {{'\"', 'a', 'a', 'a', 'a', 'a', 'a', '\\', 'u'}};
+ json _;
+ CHECK_THROWS_AS(_ = json::parse(std::begin(v), std::end(v)), json::parse_error&);
+ CHECK(!json::accept(std::begin(v), std::end(v)));
+
+ json j_error;
+ CHECK_NOTHROW(j_error = json::parse(std::begin(v), std::end(v), nullptr, false));
+ CHECK(j_error.is_discarded());
+
+ SaxEventLogger l;
+ CHECK(!json::sax_parse(std::begin(v), std::end(v), &l));
+ CHECK(l.events.size() == 1);
+ CHECK(l.events == std::vector<std::string>({"parse_error(10)"}));
+ }
+
+ SECTION("case 2")
+ {
+ std::array<std::uint8_t, 10> v = {{'\"', 'a', 'a', 'a', 'a', 'a', 'a', '\\', 'u', '1'}};
+ json _;
+ CHECK_THROWS_AS(_ = json::parse(std::begin(v), std::end(v)), json::parse_error&);
+ CHECK(!json::accept(std::begin(v), std::end(v)));
+
+ json j_error;
+ CHECK_NOTHROW(j_error = json::parse(std::begin(v), std::end(v), nullptr, false));
+ CHECK(j_error.is_discarded());
+
+ SaxEventLogger l;
+ CHECK(!json::sax_parse(std::begin(v), std::end(v), &l));
+ CHECK(l.events.size() == 1);
+ CHECK(l.events == std::vector<std::string>({"parse_error(11)"}));
+ }
+
+ SECTION("case 3")
+ {
+ std::array<std::uint8_t, 17> v = {{'\"', 'a', 'a', 'a', 'a', 'a', 'a', '\\', 'u', '1', '1', '1', '1', '1', '1', '1', '1'}};
+ json _;
+ CHECK_THROWS_AS(_ = json::parse(std::begin(v), std::end(v)), json::parse_error&);
+ CHECK(!json::accept(std::begin(v), std::end(v)));
+
+ json j_error;
+ CHECK_NOTHROW(j_error = json::parse(std::begin(v), std::end(v), nullptr, false));
+ CHECK(j_error.is_discarded());
+
+ SaxEventLogger l;
+ CHECK(!json::sax_parse(std::begin(v), std::end(v), &l));
+ CHECK(l.events.size() == 1);
+ CHECK(l.events == std::vector<std::string>({"parse_error(18)"}));
+ }
+
+ SECTION("case 4")
+ {
+ std::array<std::uint8_t, 17> v = {{'\"', 'a', 'a', 'a', 'a', 'a', 'a', 'u', '1', '1', '1', '1', '1', '1', '1', '1', '\\'}};
+ json _;
+ CHECK_THROWS_AS(_ = json::parse(std::begin(v), std::end(v)), json::parse_error&);
+ CHECK(!json::accept(std::begin(v), std::end(v)));
+
+ json j_error;
+ CHECK_NOTHROW(j_error = json::parse(std::begin(v), std::end(v), nullptr, false));
+ CHECK(j_error.is_discarded());
+
+ SaxEventLogger l;
+ CHECK(!json::sax_parse(std::begin(v), std::end(v), &l));
+ CHECK(l.events.size() == 1);
+ CHECK(l.events == std::vector<std::string>({"parse_error(18)"}));
+ }
+
+ SECTION("case 5")
+ {
+ std::array<std::uint8_t, 3> v = {{'\"', 0x7F, 0xC1}};
+ json _;
+ CHECK_THROWS_AS(_ = json::parse(std::begin(v), std::end(v)), json::parse_error&);
+ CHECK(!json::accept(std::begin(v), std::end(v)));
+
+ json j_error;
+ CHECK_NOTHROW(j_error = json::parse(std::begin(v), std::end(v), nullptr, false));
+ CHECK(j_error.is_discarded());
+
+ SaxEventLogger l;
+ CHECK(!json::sax_parse(std::begin(v), std::end(v), &l));
+ CHECK(l.events.size() == 1);
+ CHECK(l.events == std::vector<std::string>({"parse_error(3)"}));
+ }
+
+ SECTION("case 6")
+ {
+ std::array<std::uint8_t, 4> v = {{'\"', 0x7F, 0xDF, 0x7F}};
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::parse(std::begin(v), std::end(v)), "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\x7f\xdf\x7f'", json::parse_error&);
+ CHECK(!json::accept(std::begin(v), std::end(v)));
+
+ json j_error;
+ CHECK_NOTHROW(j_error = json::parse(std::begin(v), std::end(v), nullptr, false));
+ CHECK(j_error.is_discarded());
+
+ SaxEventLogger l;
+ CHECK(!json::sax_parse(std::begin(v), std::end(v), &l));
+ CHECK(l.events.size() == 1);
+ CHECK(l.events == std::vector<std::string>({"parse_error(4)"}));
+ }
+
+ SECTION("case 7")
+ {
+ std::array<std::uint8_t, 4> v = {{'\"', 0x7F, 0xDF, 0xC0}};
+ json _;
+ CHECK_THROWS_AS(_ = json::parse(std::begin(v), std::end(v)), json::parse_error&);
+ CHECK(!json::accept(std::begin(v), std::end(v)));
+
+ json j_error;
+ CHECK_NOTHROW(j_error = json::parse(std::begin(v), std::end(v), nullptr, false));
+ CHECK(j_error.is_discarded());
+
+ SaxEventLogger l;
+ CHECK(!json::sax_parse(std::begin(v), std::end(v), &l));
+ CHECK(l.events.size() == 1);
+ CHECK(l.events == std::vector<std::string>({"parse_error(4)"}));
+ }
+
+ SECTION("case 8")
+ {
+ std::array<std::uint8_t, 4> v = {{'\"', 0x7F, 0xE0, 0x9F}};
+ json _;
+ CHECK_THROWS_AS(_ = json::parse(std::begin(v), std::end(v)), json::parse_error&);
+ CHECK(!json::accept(std::begin(v), std::end(v)));
+
+ json j_error;
+ CHECK_NOTHROW(j_error = json::parse(std::begin(v), std::end(v), nullptr, false));
+ CHECK(j_error.is_discarded());
+
+ SaxEventLogger l;
+ CHECK(!json::sax_parse(std::begin(v), std::end(v), &l));
+ CHECK(l.events.size() == 1);
+ CHECK(l.events == std::vector<std::string>({"parse_error(4)"}));
+ }
+
+ SECTION("case 9")
+ {
+ std::array<std::uint8_t, 4> v = {{'\"', 0x7F, 0xEF, 0xC0}};
+ json _;
+ CHECK_THROWS_AS(_ = json::parse(std::begin(v), std::end(v)), json::parse_error&);
+ CHECK(!json::accept(std::begin(v), std::end(v)));
+
+ json j_error;
+ CHECK_NOTHROW(j_error = json::parse(std::begin(v), std::end(v), nullptr, false));
+ CHECK(j_error.is_discarded());
+
+ SaxEventLogger l;
+ CHECK(!json::sax_parse(std::begin(v), std::end(v), &l));
+ CHECK(l.events.size() == 1);
+ CHECK(l.events == std::vector<std::string>({"parse_error(4)"}));
+ }
+
+ SECTION("case 10")
+ {
+ std::array<std::uint8_t, 4> v = {{'\"', 0x7F, 0xED, 0x7F}};
+ json _;
+ CHECK_THROWS_AS(_ = json::parse(std::begin(v), std::end(v)), json::parse_error&);
+ CHECK(!json::accept(std::begin(v), std::end(v)));
+
+ json j_error;
+ CHECK_NOTHROW(j_error = json::parse(std::begin(v), std::end(v), nullptr, false));
+ CHECK(j_error.is_discarded());
+
+ SaxEventLogger l;
+ CHECK(!json::sax_parse(std::begin(v), std::end(v), &l));
+ CHECK(l.events.size() == 1);
+ CHECK(l.events == std::vector<std::string>({"parse_error(4)"}));
+ }
+
+ SECTION("case 11")
+ {
+ std::array<std::uint8_t, 4> v = {{'\"', 0x7F, 0xF0, 0x8F}};
+ json _;
+ CHECK_THROWS_AS(_ = json::parse(std::begin(v), std::end(v)), json::parse_error&);
+ CHECK(!json::accept(std::begin(v), std::end(v)));
+
+ json j_error;
+ CHECK_NOTHROW(j_error = json::parse(std::begin(v), std::end(v), nullptr, false));
+ CHECK(j_error.is_discarded());
+
+ SaxEventLogger l;
+ CHECK(!json::sax_parse(std::begin(v), std::end(v), &l));
+ CHECK(l.events.size() == 1);
+ CHECK(l.events == std::vector<std::string>({"parse_error(4)"}));
+ }
+
+ SECTION("case 12")
+ {
+ std::array<std::uint8_t, 4> v = {{'\"', 0x7F, 0xF0, 0xC0}};
+ json _;
+ CHECK_THROWS_AS(_ = json::parse(std::begin(v), std::end(v)), json::parse_error&);
+ CHECK(!json::accept(std::begin(v), std::end(v)));
+
+ json j_error;
+ CHECK_NOTHROW(j_error = json::parse(std::begin(v), std::end(v), nullptr, false));
+ CHECK(j_error.is_discarded());
+
+ SaxEventLogger l;
+ CHECK(!json::sax_parse(std::begin(v), std::end(v), &l));
+ CHECK(l.events.size() == 1);
+ CHECK(l.events == std::vector<std::string>({"parse_error(4)"}));
+ }
+
+ SECTION("case 13")
+ {
+ std::array<std::uint8_t, 4> v = {{'\"', 0x7F, 0xF3, 0x7F}};
+ json _;
+ CHECK_THROWS_AS(_ = json::parse(std::begin(v), std::end(v)), json::parse_error&);
+ CHECK(!json::accept(std::begin(v), std::end(v)));
+
+ json j_error;
+ CHECK_NOTHROW(j_error = json::parse(std::begin(v), std::end(v), nullptr, false));
+ CHECK(j_error.is_discarded());
+
+ SaxEventLogger l;
+ CHECK(!json::sax_parse(std::begin(v), std::end(v), &l));
+ CHECK(l.events.size() == 1);
+ CHECK(l.events == std::vector<std::string>({"parse_error(4)"}));
+ }
+
+ SECTION("case 14")
+ {
+ std::array<std::uint8_t, 4> v = {{'\"', 0x7F, 0xF3, 0xC0}};
+ json _;
+ CHECK_THROWS_AS(_ = json::parse(std::begin(v), std::end(v)), json::parse_error&);
+ CHECK(!json::accept(std::begin(v), std::end(v)));
+
+ json j_error;
+ CHECK_NOTHROW(j_error = json::parse(std::begin(v), std::end(v), nullptr, false));
+ CHECK(j_error.is_discarded());
+
+ SaxEventLogger l;
+ CHECK(!json::sax_parse(std::begin(v), std::end(v), &l));
+ CHECK(l.events.size() == 1);
+ CHECK(l.events == std::vector<std::string>({"parse_error(4)"}));
+ }
+
+ SECTION("case 15")
+ {
+ std::array<std::uint8_t, 4> v = {{'\"', 0x7F, 0xF4, 0x7F}};
+ json _;
+ CHECK_THROWS_AS(_ = json::parse(std::begin(v), std::end(v)), json::parse_error&);
+ CHECK(!json::accept(std::begin(v), std::end(v)));
+
+ json j_error;
+ CHECK_NOTHROW(j_error = json::parse(std::begin(v), std::end(v), nullptr, false));
+ CHECK(j_error.is_discarded());
+
+ SaxEventLogger l;
+ CHECK(!json::sax_parse(std::begin(v), std::end(v), &l));
+ CHECK(l.events.size() == 1);
+ CHECK(l.events == std::vector<std::string>({"parse_error(4)"}));
+ }
+
+ SECTION("case 16")
+ {
+ std::array<std::uint8_t, 6> v = {{'{', '\"', '\"', ':', '1', '1'}};
+ json _;
+ CHECK_THROWS_AS(_ = json::parse(std::begin(v), std::end(v)), json::parse_error&);
+ CHECK(!json::accept(std::begin(v), std::end(v)));
+
+ json j_error;
+ CHECK_NOTHROW(j_error = json::parse(std::begin(v), std::end(v), nullptr, false));
+ CHECK(j_error.is_discarded());
+
+ SaxEventLogger l;
+ CHECK(!json::sax_parse(std::begin(v), std::end(v), &l));
+ CHECK(l.events.size() == 4);
+ CHECK(l.events == std::vector<std::string>(
+ {
+ "start_object()", "key()", "number_unsigned(11)",
+ "parse_error(7)"
+ }));
+ }
+ }
+ }
+
+ SECTION("ignoring byte-order marks")
+ {
+ std::string bom = "\xEF\xBB\xBF";
+
+ SECTION("BOM only")
+ {
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::parse(bom), "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - unexpected end of input; expected '[', '{', or a literal", json::parse_error&);
+
+ CHECK_THROWS_WITH_AS(_ = json::parse(std::istringstream(bom)), "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - unexpected end of input; expected '[', '{', or a literal", json::parse_error&);
+
+ SaxEventLogger l;
+ CHECK(!json::sax_parse(bom, &l));
+ CHECK(l.events.size() == 1);
+ CHECK(l.events == std::vector<std::string>(
+ {
+ "parse_error(4)"
+ }));
+ }
+
+ SECTION("BOM and content")
+ {
+ CHECK(json::parse(bom + "1") == 1);
+ CHECK(json::parse(std::istringstream(bom + "1")) == 1);
+
+ SaxEventLogger l1;
+ SaxEventLogger l2;
+ CHECK(json::sax_parse(std::istringstream(bom + "1"), &l1));
+ CHECK(json::sax_parse(bom + "1", &l2));
+ CHECK(l1.events.size() == 1);
+ CHECK(l1.events == std::vector<std::string>(
+ {
+ "number_unsigned(1)"
+ }));
+ CHECK(l2.events.size() == 1);
+ CHECK(l2.events == std::vector<std::string>(
+ {
+ "number_unsigned(1)"
+ }));
+ }
+
+ SECTION("2 byte of BOM")
+ {
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::parse(bom.substr(0, 2)), "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid BOM; must be 0xEF 0xBB 0xBF if given; last read: '\xEF\xBB'", json::parse_error&);
+
+ CHECK_THROWS_WITH_AS(_ = json::parse(std::istringstream(bom.substr(0, 2))), "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid BOM; must be 0xEF 0xBB 0xBF if given; last read: '\xEF\xBB'", json::parse_error&);
+
+ SaxEventLogger l1;
+ SaxEventLogger l2;
+ CHECK(!json::sax_parse(std::istringstream(bom.substr(0, 2)), &l1));
+ CHECK(!json::sax_parse(bom.substr(0, 2), &l2));
+ CHECK(l1.events.size() == 1);
+ CHECK(l1.events == std::vector<std::string>(
+ {
+ "parse_error(3)"
+ }));
+ CHECK(l2.events.size() == 1);
+ CHECK(l2.events == std::vector<std::string>(
+ {
+ "parse_error(3)"
+ }));
+ }
+
+ SECTION("1 byte of BOM")
+ {
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::parse(bom.substr(0, 1)), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid BOM; must be 0xEF 0xBB 0xBF if given; last read: '\xEF'", json::parse_error&);
+
+ CHECK_THROWS_WITH_AS(_ = json::parse(std::istringstream(bom.substr(0, 1))), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid BOM; must be 0xEF 0xBB 0xBF if given; last read: '\xEF'", json::parse_error&);
+
+ SaxEventLogger l1;
+ SaxEventLogger l2;
+ CHECK(!json::sax_parse(std::istringstream(bom.substr(0, 1)), &l1));
+ CHECK(!json::sax_parse(bom.substr(0, 1), &l2));
+ CHECK(l1.events.size() == 1);
+ CHECK(l1.events == std::vector<std::string>(
+ {
+ "parse_error(2)"
+ }));
+ CHECK(l2.events.size() == 1);
+ CHECK(l2.events == std::vector<std::string>(
+ {
+ "parse_error(2)"
+ }));
+ }
+
+ SECTION("variations")
+ {
+ // calculate variations of each byte of the BOM to make sure
+ // that the BOM and only the BOM is skipped
+ for (int i0 = -1; i0 < 2; ++i0)
+ {
+ for (int i1 = -1; i1 < 2; ++i1)
+ {
+ for (int i2 = -1; i2 < 2; ++i2)
+ {
+ // debug output for the variations
+ CAPTURE(i0)
+ CAPTURE(i1)
+ CAPTURE(i2)
+
+ std::string s;
+ s.push_back(static_cast<char>(bom[0] + i0));
+ s.push_back(static_cast<char>(bom[1] + i1));
+ s.push_back(static_cast<char>(bom[2] + i2));
+
+ if (i0 == 0 && i1 == 0 && i2 == 0)
+ {
+ // without any variation, we skip the BOM
+ CHECK(json::parse(s + "null") == json());
+ CHECK(json::parse(std::istringstream(s + "null")) == json());
+
+ SaxEventLogger l;
+ CHECK(json::sax_parse(s + "null", &l));
+ CHECK(l.events.size() == 1);
+ CHECK(l.events == std::vector<std::string>(
+ {
+ "null()"
+ }));
+ }
+ else
+ {
+ // any variation is an error
+ json _;
+ CHECK_THROWS_AS(_ = json::parse(s + "null"), json::parse_error&);
+ CHECK_THROWS_AS(_ = json::parse(std::istringstream(s + "null")), json::parse_error&);
+
+ SaxEventLogger l;
+ CHECK(!json::sax_parse(s + "null", &l));
+ CHECK(l.events.size() == 1);
+
+ if (i0 != 0)
+ {
+ CHECK(l.events == std::vector<std::string>(
+ {
+ "parse_error(1)"
+ }));
+ }
+ else if (i1 != 0)
+ {
+ CHECK(l.events == std::vector<std::string>(
+ {
+ "parse_error(2)"
+ }));
+ }
+ else
+ {
+ CHECK(l.events == std::vector<std::string>(
+ {
+ "parse_error(3)"
+ }));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ SECTION("preserve state after parsing")
+ {
+ std::istringstream s(bom + "123 456");
+ json j;
+ j << s;
+ CHECK(j == 123);
+ j << s;
+ CHECK(j == 456);
+ }
+ }
+
+ SECTION("SAX and early abort")
+ {
+ std::string const s = R"([1, ["string", 43.12], null, {"key1": true, "key2": false}])";
+
+ SaxEventLogger default_logger;
+ SaxEventLoggerExitAfterStartObject exit_after_start_object;
+ SaxEventLoggerExitAfterKey exit_after_key;
+ SaxEventLoggerExitAfterStartArray exit_after_start_array;
+
+ json::sax_parse(s, &default_logger);
+ CHECK(default_logger.events.size() == 14);
+ CHECK(default_logger.events == std::vector<std::string>(
+ {
+ "start_array()", "number_unsigned(1)", "start_array()",
+ "string(string)", "number_float(43.12)", "end_array()", "null()",
+ "start_object()", "key(key1)", "boolean(true)", "key(key2)",
+ "boolean(false)", "end_object()", "end_array()"
+ }));
+
+ json::sax_parse(s, &exit_after_start_object);
+ CHECK(exit_after_start_object.events.size() == 8);
+ CHECK(exit_after_start_object.events == std::vector<std::string>(
+ {
+ "start_array()", "number_unsigned(1)", "start_array()",
+ "string(string)", "number_float(43.12)", "end_array()", "null()",
+ "start_object()"
+ }));
+
+ json::sax_parse(s, &exit_after_key);
+ CHECK(exit_after_key.events.size() == 9);
+ CHECK(exit_after_key.events == std::vector<std::string>(
+ {
+ "start_array()", "number_unsigned(1)", "start_array()",
+ "string(string)", "number_float(43.12)", "end_array()", "null()",
+ "start_object()", "key(key1)"
+ }));
+
+ json::sax_parse(s, &exit_after_start_array);
+ CHECK(exit_after_start_array.events.size() == 1);
+ CHECK(exit_after_start_array.events == std::vector<std::string>(
+ {
+ "start_array()"
+ }));
+ }
+
+ SECTION("JSON Lines")
+ {
+ SECTION("Example file")
+ {
+ std::stringstream ss;
+ ss << R"({"name": "Gilbert", "wins": [["straight", "7♣"], ["one pair", "10♥"]]}
+ {"name": "Alexa", "wins": [["two pair", "4♠"], ["two pair", "9♠"]]}
+ {"name": "May", "wins": []}
+ {"name": "Deloise", "wins": [["three of a kind", "5♣"]]}
+)";
+
+ std::string line;
+ int object_count = 0;
+ while (std::getline(ss, line))
+ {
+ ++object_count;
+ CHECK(json::accept(line));
+ }
+
+ CHECK(object_count == 4);
+ }
+
+ SECTION("Example file without trailing newline")
+ {
+ std::stringstream ss;
+ ss << R"({"name": "Gilbert", "wins": [["straight", "7♣"], ["one pair", "10♥"]]}
+ {"name": "Alexa", "wins": [["two pair", "4♠"], ["two pair", "9♠"]]}
+ {"name": "May", "wins": []}
+ {"name": "Deloise", "wins": [["three of a kind", "5♣"]]})";
+
+ std::string line;
+ int object_count = 0;
+ while (std::getline(ss, line))
+ {
+ ++object_count;
+ CHECK(json::accept(line));
+ }
+
+ CHECK(object_count == 4);
+ }
+ }
+
+ // build with C++20
+ // JSON_HAS_CPP_20
+#if defined(__cpp_char8_t)
+ SECTION("Using _json with char8_t literals #4945")
+ {
+ // Regular narrow string literal
+ const auto j1 = R"({"key": "value", "num": 42})"_json;
+ CHECK(j1["key"] == "value");
+ CHECK(j1["num"] == 42);
+
+ // UTF-8 prefixed literal (C++20 and later);
+ // MSVC may not set /utf-8, so we need to check
+ if (check_utf8())
+ {
+ const auto j2 = u8R"({"emoji": "😀", "msg": "hello"})"_json;
+ CHECK(j2["emoji"] == "😀");
+ CHECK(j2["msg"] == "hello");
+ }
+
+ const auto j3 = u8R"({"key": "value", "num": 42})"_json;
+ CHECK(j3["key"] == "value");
+ CHECK(j3["num"] == 42);
+ }
+#endif
+}
+
+// select the types to test - char8_t is only available since C++20 if and only
+// if __cpp_char8_t is defined.
+#define TYPE_LIST(...) __VA_ARGS__
+#if defined(__cpp_char8_t) && (__cpp_char8_t >= 201811L)
+ #define ASCII_TYPES TYPE_LIST(char, wchar_t, char16_t, char32_t, char8_t)
+#else
+ #define ASCII_TYPES TYPE_LIST(char, wchar_t, char16_t, char32_t)
+#endif
+
+TEST_CASE_TEMPLATE("deserialization of different character types (ASCII)", T, ASCII_TYPES) // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
+{
+ std::vector<T> const v = {'t', 'r', 'u', 'e'};
+ CHECK(json::parse(v) == json(true));
+ CHECK(json::accept(v));
+
+ SaxEventLogger l;
+ CHECK(json::sax_parse(v, &l));
+ CHECK(l.events.size() == 1);
+ CHECK(l.events == std::vector<std::string>({"boolean(true)"}));
+}
+
+TEST_CASE_TEMPLATE("deserialization of different character types (UTF-8)", T, char, unsigned char, std::uint8_t) // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
+{
+ // a star emoji
+ std::vector<T> const v = {'"', static_cast<T>(0xe2u), static_cast<T>(0xadu), static_cast<T>(0x90u), static_cast<T>(0xefu), static_cast<T>(0xb8u), static_cast<T>(0x8fu), '"'};
+ CHECK(json::parse(v).dump(-1, ' ', true) == "\"\\u2b50\\ufe0f\"");
+ CHECK(json::accept(v));
+
+ SaxEventLogger l;
+ CHECK(json::sax_parse(v, &l));
+ CHECK(l.events.size() == 1);
+}
+
+TEST_CASE_TEMPLATE("deserialization of different character types (UTF-16)", T, char16_t) // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
+{
+ // a star emoji
+ std::vector<T> const v = {static_cast<T>('"'), static_cast<T>(0x2b50), static_cast<T>(0xfe0f), static_cast<T>('"')};
+ CHECK(json::parse(v).dump(-1, ' ', true) == "\"\\u2b50\\ufe0f\"");
+ CHECK(json::accept(v));
+
+ SaxEventLogger l;
+ CHECK(json::sax_parse(v, &l));
+ CHECK(l.events.size() == 1);
+}
+
+TEST_CASE_TEMPLATE("deserialization of different character types (UTF-32)", T, char32_t) // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
+{
+ // a star emoji
+ std::vector<T> const v = {static_cast<T>('"'), static_cast<T>(0x2b50), static_cast<T>(0xfe0f), static_cast<T>('"')};
+ CHECK(json::parse(v).dump(-1, ' ', true) == "\"\\u2b50\\ufe0f\"");
+ CHECK(json::accept(v));
+
+ SaxEventLogger l;
+ CHECK(json::sax_parse(v, &l));
+ CHECK(l.events.size() == 1);
+}
diff --git a/json4cpp/tests/src/unit-diagnostic-positions-only.cpp b/json4cpp/tests/src/unit-diagnostic-positions-only.cpp
new file mode 100644
index 0000000000..735376514a
--- /dev/null
+++ b/json4cpp/tests/src/unit-diagnostic-positions-only.cpp
@@ -0,0 +1,44 @@
+// __ _____ _____ _____
+// __| | __| | | | 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"
+
+#ifdef JSON_DIAGNOSTICS
+ #undef JSON_DIAGNOSTICS
+#endif
+
+#define JSON_DIAGNOSTICS 0
+#define JSON_DIAGNOSTIC_POSITIONS 1
+#include <nlohmann/json.hpp>
+
+using json = nlohmann::json;
+
+TEST_CASE("Better diagnostics with positions only")
+{
+ SECTION("invalid type")
+ {
+ const std::string json_invalid_string = R"(
+ {
+ "address": {
+ "street": "Fake Street",
+ "housenumber": "1"
+ }
+ }
+ )";
+ json j = json::parse(json_invalid_string);
+ CHECK_THROWS_WITH_AS(j.at("address").at("housenumber").get<int>(),
+ "[json.exception.type_error.302] (bytes 108-111) type must be number, but is string", json::type_error);
+ }
+
+ SECTION("invalid type without positions")
+ {
+ const json j = "foo";
+ CHECK_THROWS_WITH_AS(j.get<int>(),
+ "[json.exception.type_error.302] type must be number, but is string", json::type_error);
+ }
+}
diff --git a/json4cpp/tests/src/unit-diagnostic-positions.cpp b/json4cpp/tests/src/unit-diagnostic-positions.cpp
new file mode 100644
index 0000000000..e6e9752df0
--- /dev/null
+++ b/json4cpp/tests/src/unit-diagnostic-positions.cpp
@@ -0,0 +1,40 @@
+// __ _____ _____ _____
+// __| | __| | | | 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_DIAGNOSTICS 1
+#define JSON_DIAGNOSTIC_POSITIONS 1
+#include <nlohmann/json.hpp>
+
+using json = nlohmann::json;
+
+TEST_CASE("Better diagnostics with positions")
+{
+ SECTION("invalid type")
+ {
+ const std::string json_invalid_string = R"(
+ {
+ "address": {
+ "street": "Fake Street",
+ "housenumber": "1"
+ }
+ }
+ )";
+ json j = json::parse(json_invalid_string);
+ CHECK_THROWS_WITH_AS(j.at("address").at("housenumber").get<int>(),
+ "[json.exception.type_error.302] (/address/housenumber) (bytes 108-111) type must be number, but is string", json::type_error);
+ }
+
+ SECTION("invalid type without positions")
+ {
+ const json j = "foo";
+ CHECK_THROWS_WITH_AS(j.get<int>(),
+ "[json.exception.type_error.302] type must be number, but is string", json::type_error);
+ }
+}
diff --git a/json4cpp/tests/src/unit-diagnostics.cpp b/json4cpp/tests/src/unit-diagnostics.cpp
new file mode 100644
index 0000000000..c5feafe442
--- /dev/null
+++ b/json4cpp/tests/src/unit-diagnostics.cpp
@@ -0,0 +1,265 @@
+// __ _____ _____ _____
+// __| | __| | | | 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"
+
+#ifdef JSON_DIAGNOSTICS
+ #undef JSON_DIAGNOSTICS
+#endif
+
+#define JSON_DIAGNOSTICS 1
+
+#include <nlohmann/json.hpp>
+using nlohmann::json;
+
+TEST_CASE("Better diagnostics")
+{
+ SECTION("empty JSON Pointer")
+ {
+ json const j = 1;
+ std::string s;
+ CHECK_THROWS_WITH_AS(s = j.get<std::string>(), "[json.exception.type_error.302] type must be string, but is number", json::type_error);
+ }
+
+ SECTION("invalid type")
+ {
+ json j;
+ j["a"]["b"]["c"] = 1;
+ std::string s;
+ CHECK_THROWS_WITH_AS(s = j["a"]["b"]["c"].get<std::string>(), "[json.exception.type_error.302] (/a/b/c) type must be string, but is number", json::type_error);
+ }
+
+ SECTION("missing key")
+ {
+ json j;
+ j["object"]["object"] = true;
+ CHECK_THROWS_WITH_AS(j["object"].at("not_found"), "[json.exception.out_of_range.403] (/object) key 'not_found' not found", json::out_of_range);
+ }
+
+ SECTION("array index out of range")
+ {
+ json j;
+ j["array"][4] = true;
+ CHECK_THROWS_WITH_AS(j["array"].at(5), "[json.exception.out_of_range.401] (/array) array index 5 is out of range", json::out_of_range);
+ }
+
+ SECTION("array index at wrong type")
+ {
+ json j;
+ j["array"][4] = true;
+ CHECK_THROWS_WITH_AS(j["array"][4][5], "[json.exception.type_error.305] (/array/4) cannot use operator[] with a numeric argument with boolean", json::type_error);
+ }
+
+ SECTION("wrong iterator")
+ {
+ json j;
+ j["array"] = json::array();
+ CHECK_THROWS_WITH_AS(j["array"].erase(j.begin()), "[json.exception.invalid_iterator.202] (/array) iterator does not fit current value", json::invalid_iterator);
+ }
+
+ SECTION("JSON Pointer escaping")
+ {
+ json j;
+ j["a/b"]["m~n"] = 1;
+ std::string s;
+ CHECK_THROWS_WITH_AS(s = j["a/b"]["m~n"].get<std::string>(), "[json.exception.type_error.302] (/a~1b/m~0n) type must be string, but is number", json::type_error);
+ }
+
+ SECTION("Parse error")
+ {
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::parse(""), "[json.exception.parse_error.101] parse error at line 1, column 1: attempting to parse an empty input; check that your input string or stream contains the expected JSON", json::parse_error);
+ }
+
+ SECTION("Wrong type in update()")
+ {
+ json j = {{"foo", "bar"}};
+ json k = {{"bla", 1}};
+
+ CHECK_THROWS_WITH_AS(j.update(k["bla"].begin(), k["bla"].end()), "[json.exception.type_error.312] (/bla) cannot use update() with number", json::type_error);
+ CHECK_THROWS_WITH_AS(j.update(k["bla"]), "[json.exception.type_error.312] (/bla) cannot use update() with number", json::type_error);
+ }
+}
+
+TEST_CASE("Regression tests for extended diagnostics")
+{
+ SECTION("Regression test for https://github.com/nlohmann/json/pull/2562#pullrequestreview-574858448")
+ {
+ CHECK_THROWS_WITH_AS(json({"0", "0"})[1].get<int>(), "[json.exception.type_error.302] (/1) type must be number, but is string", json::type_error);
+ CHECK_THROWS_WITH_AS(json({"0", "1"})[1].get<int>(), "[json.exception.type_error.302] (/1) type must be number, but is string", json::type_error);
+ }
+
+ SECTION("Regression test for https://github.com/nlohmann/json/pull/2562/files/380a613f2b5d32425021129cd1f371ddcfd54ddf#r563259793")
+ {
+ json j;
+ j["/foo"] = {1, 2, 3};
+ CHECK_THROWS_WITH_AS(j.unflatten(), "[json.exception.type_error.315] (/~1foo) values in object must be primitive", json::type_error);
+ }
+
+ SECTION("Regression test for issue #2838 - Assertion failure when inserting into arrays with JSON_DIAGNOSTICS set")
+ {
+ // void push_back(basic_json&& val)
+ {
+ json j_arr = json::array();
+ j_arr.push_back(json::object());
+ j_arr.push_back(json::object());
+ j_arr.push_back(json::object());
+ j_arr.push_back(json::object());
+ json j_obj = json::object();
+ j_obj["key"] = j_arr;
+ }
+
+ // void push_back(const basic_json& val)
+ {
+ json j_arr = json::array();
+ auto object = json::object();
+ j_arr.push_back(object);
+ j_arr.push_back(object);
+ j_arr.push_back(object);
+ j_arr.push_back(object);
+ json j_obj = json::object();
+ j_obj["key"] = j_arr;
+ }
+
+ // reference emplace_back(Args&& ... args)
+ {
+ json j_arr = json::array();
+ j_arr.emplace_back(json::object());
+ j_arr.emplace_back(json::object());
+ j_arr.emplace_back(json::object());
+ j_arr.emplace_back(json::object());
+ json j_obj = json::object();
+ j_obj["key"] = j_arr;
+ }
+
+ // iterator insert(const_iterator pos, const basic_json& val)
+ {
+ json j_arr = json::array();
+ j_arr.insert(j_arr.begin(), json::object());
+ j_arr.insert(j_arr.begin(), json::object());
+ j_arr.insert(j_arr.begin(), json::object());
+ j_arr.insert(j_arr.begin(), json::object());
+ json j_obj = json::object();
+ j_obj["key"] = j_arr;
+ }
+
+ // iterator insert(const_iterator pos, size_type cnt, const basic_json& val)
+ {
+ json j_arr = json::array();
+ j_arr.insert(j_arr.begin(), 2, json::object());
+ json j_obj = json::object();
+ j_obj["key"] = j_arr;
+ }
+
+ // iterator insert(const_iterator pos, const_iterator first, const_iterator last)
+ {
+ json j_arr = json::array();
+ json j_objects = {json::object(), json::object()};
+ j_arr.insert(j_arr.begin(), j_objects.begin(), j_objects.end());
+ json j_obj = json::object();
+ j_obj["key"] = j_arr;
+ }
+ }
+
+ SECTION("Regression test for issue #2962 - JSON_DIAGNOSTICS assertion for ordered_json")
+ {
+ nlohmann::ordered_json j;
+ nlohmann::ordered_json j2;
+ const std::string value;
+ j["first"] = value;
+ j["second"] = value;
+ j2["something"] = j;
+ }
+
+ SECTION("Regression test for issue #3007 - Parent pointers properly set when using update()")
+ {
+ // void update(const_reference j)
+ {
+ json j = json::object();
+
+ {
+ json j2 = json::object();
+ j2["one"] = 1;
+
+ j.update(j2);
+ }
+
+ // Must call operator[] on const element, otherwise m_parent gets updated.
+ auto const& constJ = j;
+ CHECK_THROWS_WITH_AS(constJ["one"].at(0), "[json.exception.type_error.304] (/one) cannot use at() with number", json::type_error);
+ }
+
+ // void update(const_iterator first, const_iterator last)
+ {
+ json j = json::object();
+
+ {
+ json j2 = json::object();
+ j2["one"] = 1;
+
+ j.update(j2.begin(), j2.end());
+ }
+
+ // Must call operator[] on const element, otherwise m_parent gets updated.
+ auto const& constJ = j;
+ CHECK_THROWS_WITH_AS(constJ["one"].at(0), "[json.exception.type_error.304] (/one) cannot use at() with number", json::type_error);
+ }
+
+ // Code from #3007 triggering unwanted assertion without fix to update().
+ {
+ json root = json::array();
+ json lower = json::object();
+
+ {
+ json lowest = json::object();
+ lowest["one"] = 1;
+
+ lower.update(lowest);
+ }
+
+ root.push_back(lower);
+ }
+ }
+
+ SECTION("Regression test for issue #3032 - Yet another assertion failure when inserting into arrays with JSON_DIAGNOSTICS set")
+ {
+ // reference operator[](size_type idx)
+ {
+ json j_arr = json::array();
+ j_arr[0] = 0;
+ j_arr[1] = 1;
+ j_arr[2] = 2;
+ j_arr[3] = 3;
+ j_arr[4] = 4;
+ j_arr[5] = 5;
+ j_arr[6] = 6;
+ j_arr[7] = 7;
+ json const j_arr_copy = j_arr;
+ }
+ }
+
+ SECTION("Regression test for issue #3915 - JSON_DIAGNOSTICS trigger assertion")
+ {
+ json j = json::object();
+ j["root"] = "root_str";
+
+ json jj = json::object();
+ jj["child"] = json::object();
+
+ // If do not push anything in object, then no assert will be produced
+ jj["child"]["prop1"] = "prop1_value";
+
+ // Push all properties of child in parent
+ j.insert(jj.at("child").begin(), jj.at("child").end());
+
+ // Here assert is generated when construct new json
+ const json k(j);
+
+ CHECK(k.dump() == "{\"prop1\":\"prop1_value\",\"root\":\"root_str\"}");
+ }
+}
diff --git a/json4cpp/tests/src/unit-disabled_exceptions.cpp b/json4cpp/tests/src/unit-disabled_exceptions.cpp
new file mode 100644
index 0000000000..e4532e2349
--- /dev/null
+++ b/json4cpp/tests/src/unit-disabled_exceptions.cpp
@@ -0,0 +1,51 @@
+// __ _____ _____ _____
+// __| | __| | | | 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"
+
+// disable -Wnoexcept as exceptions are switched off for this test suite
+DOCTEST_GCC_SUPPRESS_WARNING_PUSH
+DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept")
+
+#include <nlohmann/json.hpp>
+using json = nlohmann::json;
+
+/////////////////////////////////////////////////////////////////////
+// for #2824
+/////////////////////////////////////////////////////////////////////
+
+class sax_no_exception : public nlohmann::detail::json_sax_dom_parser<json, nlohmann::detail::string_input_adapter_type>
+{
+ public:
+ explicit sax_no_exception(json& j) : nlohmann::detail::json_sax_dom_parser<json, nlohmann::detail::string_input_adapter_type>(j, false) {}
+
+ static bool parse_error(std::size_t /*position*/, const std::string& /*last_token*/, const json::exception& ex)
+ {
+ error_string = new std::string(ex.what()); // NOLINT(cppcoreguidelines-owning-memory)
+ return false;
+ }
+
+ static std::string* error_string;
+};
+
+std::string* sax_no_exception::error_string = nullptr;
+
+TEST_CASE("Tests with disabled exceptions")
+{
+ SECTION("issue #2824 - encoding of json::exception::what()")
+ {
+ json j;
+ sax_no_exception sax(j);
+
+ CHECK (!json::sax_parse("xyz", &sax));
+ CHECK(*sax_no_exception::error_string == "[json.exception.parse_error.101] parse error at line 1, column 1: syntax error while parsing value - invalid literal; last read: 'x'");
+ delete sax_no_exception::error_string; // NOLINT(cppcoreguidelines-owning-memory)
+ }
+}
+
+DOCTEST_GCC_SUPPRESS_WARNING_POP
diff --git a/json4cpp/tests/src/unit-element_access1.cpp b/json4cpp/tests/src/unit-element_access1.cpp
new file mode 100644
index 0000000000..eccefb3ce7
--- /dev/null
+++ b/json4cpp/tests/src/unit-element_access1.cpp
@@ -0,0 +1,880 @@
+// __ _____ _____ _____
+// __| | __| | | | 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;
+
+TEST_CASE("element access 1")
+{
+ SECTION("array")
+ {
+ json j = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
+ const json j_const = j; // NOLINT(performance-unnecessary-copy-initialization)
+
+ SECTION("access specified element with bounds checking")
+ {
+ SECTION("access within bounds")
+ {
+ CHECK(j.at(0) == json(1));
+ CHECK(j.at(1) == json(1u));
+ CHECK(j.at(2) == json(true));
+ CHECK(j.at(3) == json(nullptr));
+ CHECK(j.at(4) == json("string"));
+ CHECK(j.at(5) == json(42.23));
+ CHECK(j.at(6) == json::object());
+ CHECK(j.at(7) == json({1, 2, 3}));
+
+ CHECK(j_const.at(0) == json(1));
+ CHECK(j_const.at(1) == json(1u));
+ CHECK(j_const.at(2) == json(true));
+ CHECK(j_const.at(3) == json(nullptr));
+ CHECK(j_const.at(4) == json("string"));
+ CHECK(j_const.at(5) == json(42.23));
+ CHECK(j_const.at(6) == json::object());
+ CHECK(j_const.at(7) == json({1, 2, 3}));
+ }
+
+ SECTION("access outside bounds")
+ {
+ CHECK_THROWS_WITH_AS(j.at(8),
+ "[json.exception.out_of_range.401] array index 8 is out of range", json::out_of_range&);
+ CHECK_THROWS_WITH_AS(j_const.at(8),
+ "[json.exception.out_of_range.401] array index 8 is out of range", json::out_of_range&);
+ }
+
+ SECTION("access on non-array type")
+ {
+ SECTION("null")
+ {
+ json j_nonarray(json::value_t::null);
+ const json j_nonarray_const(j_nonarray); // NOLINT(performance-unnecessary-copy-initialization)
+
+ CHECK_THROWS_WITH_AS(j_nonarray.at(0), "[json.exception.type_error.304] cannot use at() with null", json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonarray_const.at(0), "[json.exception.type_error.304] cannot use at() with null", json::type_error&);
+ }
+
+ SECTION("boolean")
+ {
+ json j_nonarray(json::value_t::boolean);
+ const json j_nonarray_const(j_nonarray); // NOLINT(performance-unnecessary-copy-initialization)
+
+ CHECK_THROWS_WITH_AS(j_nonarray.at(0), "[json.exception.type_error.304] cannot use at() with boolean", json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonarray_const.at(0), "[json.exception.type_error.304] cannot use at() with boolean", json::type_error&);
+ }
+
+ SECTION("string")
+ {
+ json j_nonarray(json::value_t::string);
+ const json j_nonarray_const(j_nonarray); // NOLINT(performance-unnecessary-copy-initialization)
+
+ CHECK_THROWS_WITH_AS(j_nonarray.at(0), "[json.exception.type_error.304] cannot use at() with string", json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonarray_const.at(0), "[json.exception.type_error.304] cannot use at() with string", json::type_error&);
+ }
+
+ SECTION("object")
+ {
+ json j_nonarray(json::value_t::object);
+ const json j_nonarray_const(j_nonarray); // NOLINT(performance-unnecessary-copy-initialization)
+
+ CHECK_THROWS_WITH_AS(j_nonarray.at(0), "[json.exception.type_error.304] cannot use at() with object", json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonarray_const.at(0), "[json.exception.type_error.304] cannot use at() with object", json::type_error&);
+ }
+
+ SECTION("number (integer)")
+ {
+ json j_nonarray(json::value_t::number_integer);
+ const json j_nonarray_const(j_nonarray); // NOLINT(performance-unnecessary-copy-initialization)
+
+ CHECK_THROWS_WITH_AS(j_nonarray.at(0), "[json.exception.type_error.304] cannot use at() with number", json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonarray_const.at(0), "[json.exception.type_error.304] cannot use at() with number", json::type_error&);
+ }
+
+ SECTION("number (unsigned)")
+ {
+ json j_nonarray(json::value_t::number_unsigned);
+ const json j_nonarray_const(j_nonarray); // NOLINT(performance-unnecessary-copy-initialization)
+
+ CHECK_THROWS_WITH_AS(j_nonarray.at(0), "[json.exception.type_error.304] cannot use at() with number", json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonarray_const.at(0), "[json.exception.type_error.304] cannot use at() with number", json::type_error&);
+ }
+
+ SECTION("number (floating-point)")
+ {
+ json j_nonarray(json::value_t::number_float);
+ const json j_nonarray_const(j_nonarray); // NOLINT(performance-unnecessary-copy-initialization)
+
+ CHECK_THROWS_WITH_AS(j_nonarray.at(0), "[json.exception.type_error.304] cannot use at() with number", json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonarray_const.at(0), "[json.exception.type_error.304] cannot use at() with number", json::type_error&);
+ }
+ }
+ }
+
+ SECTION("front and back")
+ {
+ CHECK(j.front() == json(1));
+ CHECK(j_const.front() == json(1));
+ CHECK(j.back() == json({1, 2, 3}));
+ CHECK(j_const.back() == json({1, 2, 3}));
+ }
+
+ SECTION("access specified element")
+ {
+ SECTION("access within bounds")
+ {
+ CHECK(j[0] == json(1));
+ CHECK(j[1] == json(1u));
+ CHECK(j[2] == json(true));
+ CHECK(j[3] == json(nullptr));
+ CHECK(j[4] == json("string"));
+ CHECK(j[5] == json(42.23));
+ CHECK(j[6] == json::object());
+ CHECK(j[7] == json({1, 2, 3}));
+
+ CHECK(j_const[0] == json(1));
+ CHECK(j_const[1] == json(1u));
+ CHECK(j_const[2] == json(true));
+ CHECK(j_const[3] == json(nullptr));
+ CHECK(j_const[4] == json("string"));
+ CHECK(j_const[5] == json(42.23));
+ CHECK(j_const[6] == json::object());
+ CHECK(j_const[7] == json({1, 2, 3}));
+ }
+
+ SECTION("access on non-array type")
+ {
+ SECTION("null")
+ {
+ SECTION("standard tests")
+ {
+ json j_nonarray(json::value_t::null);
+ const json j_nonarray_const(j_nonarray); // NOLINT(performance-unnecessary-copy-initialization)
+ CHECK_NOTHROW(j_nonarray[0]);
+ CHECK_THROWS_WITH_AS(j_nonarray_const[0], "[json.exception.type_error.305] cannot use operator[] with a numeric argument with null", json::type_error&);
+ }
+
+ SECTION("implicit transformation to properly filled array")
+ {
+ json j_nonarray;
+ j_nonarray[3] = 42;
+ CHECK(j_nonarray == json({nullptr, nullptr, nullptr, 42}));
+ }
+ }
+
+ SECTION("boolean")
+ {
+ json j_nonarray(json::value_t::boolean);
+ const json j_nonarray_const(j_nonarray); // NOLINT(performance-unnecessary-copy-initialization)
+ CHECK_THROWS_WITH_AS(j_nonarray[0], "[json.exception.type_error.305] cannot use operator[] with a numeric argument with boolean", json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonarray_const[0], "[json.exception.type_error.305] cannot use operator[] with a numeric argument with boolean", json::type_error&);
+ }
+
+ SECTION("string")
+ {
+ json j_nonarray(json::value_t::string);
+ const json j_nonarray_const(j_nonarray); // NOLINT(performance-unnecessary-copy-initialization)
+ CHECK_THROWS_WITH_AS(j_nonarray[0], "[json.exception.type_error.305] cannot use operator[] with a numeric argument with string", json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonarray_const[0], "[json.exception.type_error.305] cannot use operator[] with a numeric argument with string", json::type_error&);
+ }
+
+ SECTION("object")
+ {
+ json j_nonarray(json::value_t::object);
+ const json j_nonarray_const(j_nonarray); // NOLINT(performance-unnecessary-copy-initialization)
+ CHECK_THROWS_WITH_AS(j_nonarray[0], "[json.exception.type_error.305] cannot use operator[] with a numeric argument with object", json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonarray_const[0], "[json.exception.type_error.305] cannot use operator[] with a numeric argument with object", json::type_error&);
+ }
+
+ SECTION("number (integer)")
+ {
+ json j_nonarray(json::value_t::number_integer);
+ const json j_nonarray_const(j_nonarray); // NOLINT(performance-unnecessary-copy-initialization)
+ CHECK_THROWS_WITH_AS(j_nonarray[0], "[json.exception.type_error.305] cannot use operator[] with a numeric argument with number", json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonarray_const[0], "[json.exception.type_error.305] cannot use operator[] with a numeric argument with number", json::type_error&);
+ }
+
+ SECTION("number (unsigned)")
+ {
+ json j_nonarray(json::value_t::number_unsigned);
+ const json j_nonarray_const(j_nonarray); // NOLINT(performance-unnecessary-copy-initialization)
+ CHECK_THROWS_WITH_AS(j_nonarray[0], "[json.exception.type_error.305] cannot use operator[] with a numeric argument with number", json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonarray_const[0], "[json.exception.type_error.305] cannot use operator[] with a numeric argument with number", json::type_error&);
+ }
+
+ SECTION("number (floating-point)")
+ {
+ json j_nonarray(json::value_t::number_float);
+ const json j_nonarray_const(j_nonarray); // NOLINT(performance-unnecessary-copy-initialization)
+ CHECK_THROWS_WITH_AS(j_nonarray[0], "[json.exception.type_error.305] cannot use operator[] with a numeric argument with number", json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonarray_const[0], "[json.exception.type_error.305] cannot use operator[] with a numeric argument with number", json::type_error&);
+ }
+ }
+ }
+
+ SECTION("remove specified element")
+ {
+ SECTION("remove element by index")
+ {
+ {
+ json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
+ jarray.erase(0);
+ CHECK(jarray == json({1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}));
+ }
+ {
+ json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
+ jarray.erase(1);
+ CHECK(jarray == json({1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}));
+ }
+ {
+ json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
+ jarray.erase(2);
+ CHECK(jarray == json({1, 1u, nullptr, "string", 42.23, json::object(), {1, 2, 3}}));
+ }
+ {
+ json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
+ jarray.erase(3);
+ CHECK(jarray == json({1, 1u, true, "string", 42.23, json::object(), {1, 2, 3}}));
+ }
+ {
+ json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
+ jarray.erase(4);
+ CHECK(jarray == json({1, 1u, true, nullptr, 42.23, json::object(), {1, 2, 3}}));
+ }
+ {
+ json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
+ jarray.erase(5);
+ CHECK(jarray == json({1, 1u, true, nullptr, "string", json::object(), {1, 2, 3}}));
+ }
+ {
+ json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
+ jarray.erase(6);
+ CHECK(jarray == json({1, 1u, true, nullptr, "string", 42.23, {1, 2, 3}}));
+ }
+ {
+ json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
+ jarray.erase(7);
+ CHECK(jarray == json({1, 1u, true, nullptr, "string", 42.23, json::object()}));
+ }
+ {
+ json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
+ CHECK_THROWS_WITH_AS(jarray.erase(8), "[json.exception.out_of_range.401] array index 8 is out of range", json::out_of_range&);
+ }
+ }
+
+ SECTION("remove element by iterator")
+ {
+ SECTION("erase(begin())")
+ {
+ {
+ json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
+ json::iterator const it2 = jarray.erase(jarray.begin());
+ CHECK(jarray == json({1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}));
+ CHECK(*it2 == json(1u));
+ }
+ {
+ json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
+ json::const_iterator const it2 = jarray.erase(jarray.cbegin());
+ CHECK(jarray == json({1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}));
+ CHECK(*it2 == json(1u));
+ }
+ }
+
+ SECTION("erase(begin(), end())")
+ {
+ {
+ json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
+ const json::iterator it2 = jarray.erase(jarray.begin(), jarray.end());
+ CHECK(jarray == json::array());
+ CHECK(it2 == jarray.end());
+ }
+ {
+ json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
+ const json::const_iterator it2 = jarray.erase(jarray.cbegin(), jarray.cend());
+ CHECK(jarray == json::array());
+ CHECK(it2 == jarray.cend());
+ }
+ }
+
+ SECTION("erase(begin(), begin())")
+ {
+ {
+ json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
+ json::iterator const it2 = jarray.erase(jarray.begin(), jarray.begin());
+ CHECK(jarray == json({1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}));
+ CHECK(*it2 == json(1));
+ }
+ {
+ json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
+ json::const_iterator const it2 = jarray.erase(jarray.cbegin(), jarray.cbegin());
+ CHECK(jarray == json({1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}));
+ CHECK(*it2 == json(1));
+ }
+ }
+
+ SECTION("erase at offset")
+ {
+ {
+ json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
+ json::iterator const it = jarray.begin() + 4;
+ json::iterator const it2 = jarray.erase(it);
+ CHECK(jarray == json({1, 1u, true, nullptr, 42.23, json::object(), {1, 2, 3}}));
+ CHECK(*it2 == json(42.23));
+ }
+ {
+ json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
+ json::const_iterator const it = jarray.cbegin() + 4;
+ json::const_iterator const it2 = jarray.erase(it);
+ CHECK(jarray == json({1, 1u, true, nullptr, 42.23, json::object(), {1, 2, 3}}));
+ CHECK(*it2 == json(42.23));
+ }
+ }
+
+ SECTION("erase subrange")
+ {
+ {
+ json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
+ json::iterator const it2 = jarray.erase(jarray.begin() + 3, jarray.begin() + 6);
+ CHECK(jarray == json({1, 1u, true, json::object(), {1, 2, 3}}));
+ CHECK(*it2 == json::object());
+ }
+ {
+ json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
+ json::const_iterator const it2 = jarray.erase(jarray.cbegin() + 3, jarray.cbegin() + 6);
+ CHECK(jarray == json({1, 1u, true, json::object(), {1, 2, 3}}));
+ CHECK(*it2 == json::object());
+ }
+ }
+
+ SECTION("different arrays")
+ {
+ {
+ json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
+ json jarray2 = {"foo", "bar"};
+
+ CHECK_THROWS_WITH_AS(jarray.erase(jarray2.begin()),
+ "[json.exception.invalid_iterator.202] iterator does not fit current value", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(jarray.erase(jarray.begin(), jarray2.end()),
+ "[json.exception.invalid_iterator.203] iterators do not fit current value", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(jarray.erase(jarray2.begin(), jarray.end()),
+ "[json.exception.invalid_iterator.203] iterators do not fit current value", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(jarray.erase(jarray2.begin(), jarray2.end()),
+ "[json.exception.invalid_iterator.203] iterators do not fit current value", json::invalid_iterator&);
+ }
+ {
+ json jarray = {1, 1u, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}};
+ json const jarray2 = {"foo", "bar"};
+
+ CHECK_THROWS_WITH_AS(jarray.erase(jarray2.cbegin()),
+ "[json.exception.invalid_iterator.202] iterator does not fit current value", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(jarray.erase(jarray.cbegin(), jarray2.cend()),
+ "[json.exception.invalid_iterator.203] iterators do not fit current value", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(jarray.erase(jarray2.cbegin(), jarray.cend()),
+ "[json.exception.invalid_iterator.203] iterators do not fit current value", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(jarray.erase(jarray2.cbegin(), jarray2.cend()),
+ "[json.exception.invalid_iterator.203] iterators do not fit current value", json::invalid_iterator&);
+ }
+ }
+ }
+
+ SECTION("remove element by index in non-array type")
+ {
+ SECTION("null")
+ {
+ json j_nonobject(json::value_t::null);
+ CHECK_THROWS_WITH_AS(j_nonobject.erase(0), "[json.exception.type_error.307] cannot use erase() with null", json::type_error&);
+ }
+
+ SECTION("boolean")
+ {
+ json j_nonobject(json::value_t::boolean);
+ CHECK_THROWS_WITH_AS(j_nonobject.erase(0), "[json.exception.type_error.307] cannot use erase() with boolean", json::type_error&);
+ }
+
+ SECTION("string")
+ {
+ json j_nonobject(json::value_t::string);
+ CHECK_THROWS_WITH_AS(j_nonobject.erase(0), "[json.exception.type_error.307] cannot use erase() with string", json::type_error&);
+ }
+
+ SECTION("object")
+ {
+ json j_nonobject(json::value_t::object);
+ CHECK_THROWS_WITH_AS(j_nonobject.erase(0), "[json.exception.type_error.307] cannot use erase() with object", json::type_error&);
+ }
+
+ SECTION("number (integer)")
+ {
+ json j_nonobject(json::value_t::number_integer);
+ CHECK_THROWS_WITH_AS(j_nonobject.erase(0), "[json.exception.type_error.307] cannot use erase() with number", json::type_error&);
+ }
+
+ SECTION("number (unsigned)")
+ {
+ json j_nonobject(json::value_t::number_unsigned);
+ CHECK_THROWS_WITH_AS(j_nonobject.erase(0), "[json.exception.type_error.307] cannot use erase() with number", json::type_error&);
+ }
+
+ SECTION("number (floating-point)")
+ {
+ json j_nonobject(json::value_t::number_float);
+ CHECK_THROWS_WITH_AS(j_nonobject.erase(0), "[json.exception.type_error.307] cannot use erase() with number", json::type_error&);
+ }
+ }
+ }
+ }
+
+ SECTION("other values")
+ {
+ SECTION("front and back")
+ {
+ SECTION("null")
+ {
+ {
+ json j;
+ CHECK_THROWS_WITH_AS(j.front(), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(j.back(), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ }
+ {
+ const json j{};
+ CHECK_THROWS_WITH_AS(j.front(), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(j.back(), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ }
+ }
+
+ SECTION("string")
+ {
+ {
+ json j = "foo";
+ CHECK(j.front() == j);
+ CHECK(j.back() == j);
+ }
+ {
+ const json j = "bar";
+ CHECK(j.front() == j);
+ CHECK(j.back() == j);
+ }
+ }
+
+ SECTION("number (boolean)")
+ {
+ {
+ json j = false;
+ CHECK(j.front() == j);
+ CHECK(j.back() == j);
+ }
+ {
+ const json j = true;
+ CHECK(j.front() == j);
+ CHECK(j.back() == j);
+ }
+ }
+
+ SECTION("number (integer)")
+ {
+ {
+ json j = 17;
+ CHECK(j.front() == j);
+ CHECK(j.back() == j);
+ }
+ {
+ const json j = 17;
+ CHECK(j.front() == j);
+ CHECK(j.back() == j);
+ }
+ }
+
+ SECTION("number (unsigned)")
+ {
+ {
+ json j = 17u;
+ CHECK(j.front() == j);
+ CHECK(j.back() == j);
+ }
+ {
+ const json j = 17u;
+ CHECK(j.front() == j);
+ CHECK(j.back() == j);
+ }
+ }
+
+ SECTION("number (floating point)")
+ {
+ {
+ json j = 23.42;
+ CHECK(j.front() == j);
+ CHECK(j.back() == j);
+ }
+ {
+ const json j = 23.42;
+ CHECK(j.front() == j);
+ CHECK(j.back() == j);
+ }
+ }
+ }
+
+ SECTION("erase with one valid iterator")
+ {
+ SECTION("null")
+ {
+ {
+ json j;
+ CHECK_THROWS_WITH_AS(j.erase(j.begin()), "[json.exception.type_error.307] cannot use erase() with null", json::type_error&);
+ }
+ {
+ json j;
+ CHECK_THROWS_WITH_AS(j.erase(j.begin()),
+ "[json.exception.type_error.307] cannot use erase() with null", json::type_error&);
+ }
+ }
+
+ SECTION("string")
+ {
+ {
+ json j = "foo";
+ const json::iterator it = j.erase(j.begin());
+ CHECK(j.type() == json::value_t::null);
+ CHECK(it == j.end());
+ }
+ {
+ json j = "bar";
+ const json::const_iterator it = j.erase(j.cbegin());
+ CHECK(j.type() == json::value_t::null);
+ CHECK(it == j.end());
+ }
+ }
+
+ SECTION("number (boolean)")
+ {
+ {
+ json j = false;
+ const json::iterator it = j.erase(j.begin());
+ CHECK(j.type() == json::value_t::null);
+ CHECK(it == j.end());
+ }
+ {
+ json j = true;
+ const json::const_iterator it = j.erase(j.cbegin());
+ CHECK(j.type() == json::value_t::null);
+ CHECK(it == j.end());
+ }
+ }
+
+ SECTION("number (integer)")
+ {
+ {
+ json j = 17;
+ const json::iterator it = j.erase(j.begin());
+ CHECK(j.type() == json::value_t::null);
+ CHECK(it == j.end());
+ }
+ {
+ json j = 17;
+ const json::const_iterator it = j.erase(j.cbegin());
+ CHECK(j.type() == json::value_t::null);
+ CHECK(it == j.end());
+ }
+ }
+
+ SECTION("number (unsigned)")
+ {
+ {
+ json j = 17u;
+ const json::iterator it = j.erase(j.begin());
+ CHECK(j.type() == json::value_t::null);
+ CHECK(it == j.end());
+ }
+ {
+ json j = 17u;
+ const json::const_iterator it = j.erase(j.cbegin());
+ CHECK(j.type() == json::value_t::null);
+ CHECK(it == j.end());
+ }
+ }
+
+ SECTION("number (floating point)")
+ {
+ {
+ json j = 23.42;
+ const json::iterator it = j.erase(j.begin());
+ CHECK(j.type() == json::value_t::null);
+ CHECK(it == j.end());
+ }
+ {
+ json j = 23.42;
+ const json::const_iterator it = j.erase(j.cbegin());
+ CHECK(j.type() == json::value_t::null);
+ CHECK(it == j.end());
+ }
+ }
+
+ SECTION("binary")
+ {
+ {
+ json j = json::binary({1, 2, 3});
+ const json::iterator it = j.erase(j.begin());
+ CHECK(j.type() == json::value_t::null);
+ CHECK(it == j.end());
+ }
+ {
+ json j = json::binary({1, 2, 3});
+ const json::const_iterator it = j.erase(j.cbegin());
+ CHECK(j.type() == json::value_t::null);
+ CHECK(it == j.end());
+ }
+ }
+ }
+
+ SECTION("erase with one invalid iterator")
+ {
+ SECTION("string")
+ {
+ {
+ json j = "foo";
+ CHECK_THROWS_WITH_AS(j.erase(j.end()), "[json.exception.invalid_iterator.205] iterator out of range", json::invalid_iterator&);
+ }
+ {
+ json j = "bar";
+ CHECK_THROWS_WITH_AS(j.erase(j.cend()), "[json.exception.invalid_iterator.205] iterator out of range", json::invalid_iterator&);
+ }
+ }
+
+ SECTION("number (boolean)")
+ {
+ {
+ json j = false;
+ CHECK_THROWS_WITH_AS(j.erase(j.end()), "[json.exception.invalid_iterator.205] iterator out of range", json::invalid_iterator&);
+ }
+ {
+ json j = true;
+ CHECK_THROWS_WITH_AS(j.erase(j.cend()), "[json.exception.invalid_iterator.205] iterator out of range", json::invalid_iterator&);
+ }
+ }
+
+ SECTION("number (integer)")
+ {
+ {
+ json j = 17;
+ CHECK_THROWS_WITH_AS(j.erase(j.end()), "[json.exception.invalid_iterator.205] iterator out of range", json::invalid_iterator&);
+ }
+ {
+ json j = 17;
+ CHECK_THROWS_WITH_AS(j.erase(j.cend()), "[json.exception.invalid_iterator.205] iterator out of range", json::invalid_iterator&);
+ }
+ }
+
+ SECTION("number (unsigned)")
+ {
+ {
+ json j = 17u;
+ CHECK_THROWS_WITH_AS(j.erase(j.end()), "[json.exception.invalid_iterator.205] iterator out of range", json::invalid_iterator&);
+ }
+ {
+ json j = 17u;
+ CHECK_THROWS_WITH_AS(j.erase(j.cend()), "[json.exception.invalid_iterator.205] iterator out of range", json::invalid_iterator&);
+ }
+ }
+
+ SECTION("number (floating point)")
+ {
+ {
+ json j = 23.42;
+ CHECK_THROWS_WITH_AS(j.erase(j.end()), "[json.exception.invalid_iterator.205] iterator out of range", json::invalid_iterator&);
+ }
+ {
+ json j = 23.42;
+ CHECK_THROWS_WITH_AS(j.erase(j.cend()), "[json.exception.invalid_iterator.205] iterator out of range", json::invalid_iterator&);
+ }
+ }
+ }
+
+ SECTION("erase with two valid iterators")
+ {
+ SECTION("null")
+ {
+ {
+ json j;
+ CHECK_THROWS_WITH_AS(j.erase(j.begin(), j.end()), "[json.exception.type_error.307] cannot use erase() with null", json::type_error&);
+ }
+ {
+ json j;
+ CHECK_THROWS_WITH_AS(j.erase(j.cbegin(), j.cend()), "[json.exception.type_error.307] cannot use erase() with null", json::type_error&);
+ }
+ }
+
+ SECTION("string")
+ {
+ {
+ json j = "foo";
+ const json::iterator it = j.erase(j.begin(), j.end());
+ CHECK(j.type() == json::value_t::null);
+ CHECK(it == j.end());
+ }
+ {
+ json j = "bar";
+ const json::const_iterator it = j.erase(j.cbegin(), j.cend());
+ CHECK(j.type() == json::value_t::null);
+ CHECK(it == j.end());
+ }
+ }
+
+ SECTION("number (boolean)")
+ {
+ {
+ json j = false;
+ const json::iterator it = j.erase(j.begin(), j.end());
+ CHECK(j.type() == json::value_t::null);
+ CHECK(it == j.end());
+ }
+ {
+ json j = true;
+ const json::const_iterator it = j.erase(j.cbegin(), j.cend());
+ CHECK(j.type() == json::value_t::null);
+ CHECK(it == j.end());
+ }
+ }
+
+ SECTION("number (integer)")
+ {
+ {
+ json j = 17;
+ const json::iterator it = j.erase(j.begin(), j.end());
+ CHECK(j.type() == json::value_t::null);
+ CHECK(it == j.end());
+ }
+ {
+ json j = 17;
+ const json::const_iterator it = j.erase(j.cbegin(), j.cend());
+ CHECK(j.type() == json::value_t::null);
+ CHECK(it == j.end());
+ }
+ }
+
+ SECTION("number (unsigned)")
+ {
+ {
+ json j = 17u;
+ const json::iterator it = j.erase(j.begin(), j.end());
+ CHECK(j.type() == json::value_t::null);
+ CHECK(it == j.end());
+ }
+ {
+ json j = 17u;
+ const json::const_iterator it = j.erase(j.cbegin(), j.cend());
+ CHECK(j.type() == json::value_t::null);
+ CHECK(it == j.end());
+ }
+ }
+
+ SECTION("number (floating point)")
+ {
+ {
+ json j = 23.42;
+ const json::iterator it = j.erase(j.begin(), j.end());
+ CHECK(j.type() == json::value_t::null);
+ CHECK(it == j.end());
+ }
+ {
+ json j = 23.42;
+ const json::const_iterator it = j.erase(j.cbegin(), j.cend());
+ CHECK(j.type() == json::value_t::null);
+ CHECK(it == j.end());
+ }
+ }
+
+ SECTION("binary")
+ {
+ {
+ json j = json::binary({1, 2, 3});
+ const json::iterator it = j.erase(j.begin(), j.end());
+ CHECK(j.type() == json::value_t::null);
+ CHECK(it == j.end());
+ }
+ {
+ json j = json::binary({1, 2, 3});
+ const json::const_iterator it = j.erase(j.cbegin(), j.cend());
+ CHECK(j.type() == json::value_t::null);
+ CHECK(it == j.end());
+ }
+ }
+ }
+
+ SECTION("erase with two invalid iterators")
+ {
+ SECTION("string")
+ {
+ {
+ json j = "foo";
+ CHECK_THROWS_WITH_AS(j.erase(j.end(), j.end()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(j.erase(j.begin(), j.begin()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ }
+ {
+ json j = "bar";
+ CHECK_THROWS_WITH_AS(j.erase(j.cend(), j.cend()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(j.erase(j.cbegin(), j.cbegin()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ }
+ }
+
+ SECTION("number (boolean)")
+ {
+ {
+ json j = false;
+ CHECK_THROWS_WITH_AS(j.erase(j.end(), j.end()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(j.erase(j.begin(), j.begin()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ }
+ {
+ json j = true;
+ CHECK_THROWS_WITH_AS(j.erase(j.cend(), j.cend()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(j.erase(j.cbegin(), j.cbegin()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ }
+ }
+
+ SECTION("number (integer)")
+ {
+ {
+ json j = 17;
+ CHECK_THROWS_WITH_AS(j.erase(j.end(), j.end()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(j.erase(j.begin(), j.begin()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ }
+ {
+ json j = 17;
+ CHECK_THROWS_WITH_AS(j.erase(j.cend(), j.cend()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(j.erase(j.cbegin(), j.cbegin()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ }
+ }
+
+ SECTION("number (unsigned)")
+ {
+ {
+ json j = 17u;
+ CHECK_THROWS_WITH_AS(j.erase(j.end(), j.end()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(j.erase(j.begin(), j.begin()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ }
+ {
+ json j = 17u;
+ CHECK_THROWS_WITH_AS(j.erase(j.cend(), j.cend()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(j.erase(j.cbegin(), j.cbegin()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ }
+ }
+
+ SECTION("number (floating point)")
+ {
+ {
+ json j = 23.42;
+ CHECK_THROWS_WITH_AS(j.erase(j.end(), j.end()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(j.erase(j.begin(), j.begin()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ }
+ {
+ json j = 23.42;
+ CHECK_THROWS_WITH_AS(j.erase(j.cend(), j.cend()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(j.erase(j.cbegin(), j.cbegin()), "[json.exception.invalid_iterator.204] iterators out of range", json::invalid_iterator&);
+ }
+ }
+ }
+ }
+}
diff --git a/json4cpp/tests/src/unit-element_access2.cpp b/json4cpp/tests/src/unit-element_access2.cpp
new file mode 100644
index 0000000000..c4e25bf70f
--- /dev/null
+++ b/json4cpp/tests/src/unit-element_access2.cpp
@@ -0,0 +1,1844 @@
+// __ _____ _____ _____
+// __| | __| | | | 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>
+#ifdef JSON_TEST_NO_GLOBAL_UDLS
+ using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
+#endif
+
+// build test with C++14
+// JSON_HAS_CPP_14
+
+TEST_CASE_TEMPLATE("element access 2", Json, nlohmann::json, nlohmann::ordered_json) // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
+{
+ SECTION("object")
+ {
+ Json j = {{"integer", 1}, {"unsigned", 1u}, {"floating", 42.23}, {"null", nullptr}, {"string", "hello world"}, {"boolean", true}, {"object", Json::object()}, {"array", {1, 2, 3}}};
+ const Json j_const = j;
+
+ SECTION("access specified element with bounds checking")
+ {
+ SECTION("access within bounds")
+ {
+ CHECK(j.at("integer") == Json(1));
+ CHECK(j.at("unsigned") == Json(1u));
+ CHECK(j.at("boolean") == Json(true));
+ CHECK(j.at("null") == Json(nullptr));
+ CHECK(j.at("string") == Json("hello world"));
+ CHECK(j.at("floating") == Json(42.23));
+ CHECK(j.at("object") == Json::object());
+ CHECK(j.at("array") == Json({1, 2, 3}));
+
+ CHECK(j_const.at("integer") == Json(1));
+ CHECK(j_const.at("unsigned") == Json(1u));
+ CHECK(j_const.at("boolean") == Json(true));
+ CHECK(j_const.at("null") == Json(nullptr));
+ CHECK(j_const.at("string") == Json("hello world"));
+ CHECK(j_const.at("floating") == Json(42.23));
+ CHECK(j_const.at("object") == Json::object());
+ CHECK(j_const.at("array") == Json({1, 2, 3}));
+
+#ifdef JSON_HAS_CPP_17
+ CHECK(j.at(std::string_view("integer")) == Json(1));
+ CHECK(j.at(std::string_view("unsigned")) == Json(1u));
+ CHECK(j.at(std::string_view("boolean")) == Json(true));
+ CHECK(j.at(std::string_view("null")) == Json(nullptr));
+ CHECK(j.at(std::string_view("string")) == Json("hello world"));
+ CHECK(j.at(std::string_view("floating")) == Json(42.23));
+ CHECK(j.at(std::string_view("object")) == Json::object());
+ CHECK(j.at(std::string_view("array")) == Json({1, 2, 3}));
+
+ CHECK(j_const.at(std::string_view("integer")) == Json(1));
+ CHECK(j_const.at(std::string_view("unsigned")) == Json(1u));
+ CHECK(j_const.at(std::string_view("boolean")) == Json(true));
+ CHECK(j_const.at(std::string_view("null")) == Json(nullptr));
+ CHECK(j_const.at(std::string_view("string")) == Json("hello world"));
+ CHECK(j_const.at(std::string_view("floating")) == Json(42.23));
+ CHECK(j_const.at(std::string_view("object")) == Json::object());
+ CHECK(j_const.at(std::string_view("array")) == Json({1, 2, 3}));
+#endif
+ }
+
+ SECTION("access outside bounds")
+ {
+ CHECK_THROWS_WITH_AS(j.at("foo"), "[json.exception.out_of_range.403] key 'foo' not found", typename Json::out_of_range&);
+ CHECK_THROWS_WITH_AS(j_const.at("foo"), "[json.exception.out_of_range.403] key 'foo' not found", typename Json::out_of_range&);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK_THROWS_WITH_AS(j.at(std::string_view("foo")), "[json.exception.out_of_range.403] key 'foo' not found", typename Json::out_of_range&);
+ CHECK_THROWS_WITH_AS(j_const.at(std::string_view("foo")), "[json.exception.out_of_range.403] key 'foo' not found", typename Json::out_of_range&);
+#endif
+ }
+
+ SECTION("access on non-object type")
+ {
+ SECTION("null")
+ {
+ Json j_nonobject(Json::value_t::null);
+ const Json j_nonobject_const(j_nonobject); // NOLINT(performance-unnecessary-copy-initialization)
+ CHECK_THROWS_WITH_AS(j_nonobject.at("foo"), "[json.exception.type_error.304] cannot use at() with null", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.at("foo"), "[json.exception.type_error.304] cannot use at() with null", typename Json::type_error&);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK_THROWS_WITH_AS(j_nonobject.at(std::string_view(std::string_view("foo"))), "[json.exception.type_error.304] cannot use at() with null", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.at(std::string_view(std::string_view("foo"))), "[json.exception.type_error.304] cannot use at() with null", typename Json::type_error&);
+#endif
+ }
+
+ SECTION("boolean")
+ {
+ Json j_nonobject(Json::value_t::boolean);
+ const Json j_nonobject_const(j_nonobject); // NOLINT(performance-unnecessary-copy-initialization)
+ CHECK_THROWS_WITH_AS(j_nonobject.at("foo"), "[json.exception.type_error.304] cannot use at() with boolean", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.at("foo"), "[json.exception.type_error.304] cannot use at() with boolean", typename Json::type_error&);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK_THROWS_WITH_AS(j_nonobject.at(std::string_view("foo")), "[json.exception.type_error.304] cannot use at() with boolean", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.at(std::string_view("foo")), "[json.exception.type_error.304] cannot use at() with boolean", typename Json::type_error&);
+#endif
+ }
+
+ SECTION("string")
+ {
+ Json j_nonobject(Json::value_t::string);
+ const Json j_nonobject_const(j_nonobject); // NOLINT(performance-unnecessary-copy-initialization)
+ CHECK_THROWS_WITH_AS(j_nonobject.at("foo"), "[json.exception.type_error.304] cannot use at() with string", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.at("foo"), "[json.exception.type_error.304] cannot use at() with string", typename Json::type_error&);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK_THROWS_WITH_AS(j_nonobject.at(std::string_view("foo")), "[json.exception.type_error.304] cannot use at() with string", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.at(std::string_view("foo")), "[json.exception.type_error.304] cannot use at() with string", typename Json::type_error&);
+#endif
+ }
+
+ SECTION("array")
+ {
+ Json j_nonobject(Json::value_t::array);
+ const Json j_nonobject_const(j_nonobject); // NOLINT(performance-unnecessary-copy-initialization)
+ CHECK_THROWS_WITH_AS(j_nonobject.at("foo"), "[json.exception.type_error.304] cannot use at() with array", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.at("foo"), "[json.exception.type_error.304] cannot use at() with array", typename Json::type_error&);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK_THROWS_WITH_AS(j_nonobject.at(std::string_view("foo")), "[json.exception.type_error.304] cannot use at() with array", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.at(std::string_view("foo")), "[json.exception.type_error.304] cannot use at() with array", typename Json::type_error&);
+#endif
+ }
+
+ SECTION("number (integer)")
+ {
+ Json j_nonobject(Json::value_t::number_integer);
+ const Json j_nonobject_const(j_nonobject); // NOLINT(performance-unnecessary-copy-initialization)
+ CHECK_THROWS_WITH_AS(j_nonobject.at("foo"), "[json.exception.type_error.304] cannot use at() with number", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.at("foo"), "[json.exception.type_error.304] cannot use at() with number", typename Json::type_error&);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK_THROWS_WITH_AS(j_nonobject.at(std::string_view("foo")), "[json.exception.type_error.304] cannot use at() with number", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.at(std::string_view("foo")), "[json.exception.type_error.304] cannot use at() with number", typename Json::type_error&);
+#endif
+ }
+
+ SECTION("number (unsigned)")
+ {
+ Json j_nonobject(Json::value_t::number_unsigned);
+ const Json j_nonobject_const(j_nonobject); // NOLINT(performance-unnecessary-copy-initialization)
+ CHECK_THROWS_WITH_AS(j_nonobject.at("foo"), "[json.exception.type_error.304] cannot use at() with number", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.at("foo"), "[json.exception.type_error.304] cannot use at() with number", typename Json::type_error&);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK_THROWS_WITH_AS(j_nonobject.at(std::string_view("foo")), "[json.exception.type_error.304] cannot use at() with number", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.at(std::string_view("foo")), "[json.exception.type_error.304] cannot use at() with number", typename Json::type_error&);
+#endif
+ }
+
+ SECTION("number (floating-point)")
+ {
+ Json j_nonobject(Json::value_t::number_float);
+ const Json j_nonobject_const(j_nonobject); // NOLINT(performance-unnecessary-copy-initialization)
+ CHECK_THROWS_WITH_AS(j_nonobject.at("foo"), "[json.exception.type_error.304] cannot use at() with number", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.at("foo"), "[json.exception.type_error.304] cannot use at() with number", typename Json::type_error&);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK_THROWS_WITH_AS(j_nonobject.at(std::string_view("foo")), "[json.exception.type_error.304] cannot use at() with number", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.at(std::string_view("foo")), "[json.exception.type_error.304] cannot use at() with number", typename Json::type_error&);
+#endif
+ }
+ }
+ }
+
+ SECTION("access specified element with default value")
+ {
+ SECTION("given a key")
+ {
+ SECTION("access existing value")
+ {
+ CHECK(j.value("integer", 2) == 1);
+ CHECK(j.value("integer", 1.0) == Approx(1));
+ CHECK(j.value("unsigned", 2) == 1u);
+ CHECK(j.value("unsigned", 1.0) == Approx(1u));
+ CHECK(j.value("null", Json(1)) == Json());
+ CHECK(j.value("boolean", false) == true);
+ CHECK(j.value("string", "bar") == "hello world");
+ CHECK(j.value("string", std::string("bar")) == "hello world");
+ CHECK(j.value("floating", 12.34) == Approx(42.23));
+ CHECK(j.value("floating", 12) == 42);
+ CHECK(j.value("object", Json({{"foo", "bar"}})) == Json::object());
+ CHECK(j.value("array", Json({10, 100})) == Json({1, 2, 3}));
+
+ CHECK(j_const.value("integer", 2) == 1);
+ CHECK(j_const.value("integer", 1.0) == Approx(1));
+ CHECK(j_const.value("unsigned", 2) == 1u);
+ CHECK(j_const.value("unsigned", 1.0) == Approx(1u));
+ CHECK(j_const.value("boolean", false) == true);
+ CHECK(j_const.value("string", "bar") == "hello world");
+ CHECK(j_const.value("string", std::string("bar")) == "hello world");
+ CHECK(j_const.value("floating", 12.34) == Approx(42.23));
+ CHECK(j_const.value("floating", 12) == 42);
+ CHECK(j_const.value("object", Json({{"foo", "bar"}})) == Json::object());
+ CHECK(j_const.value("array", Json({10, 100})) == Json({1, 2, 3}));
+
+#ifdef JSON_HAS_CPP_17
+ CHECK(j.value(std::string_view("integer"), 2) == 1);
+ CHECK(j.value(std::string_view("integer"), 1.0) == Approx(1));
+ CHECK(j.value(std::string_view("unsigned"), 2) == 1u);
+ CHECK(j.value(std::string_view("unsigned"), 1.0) == Approx(1u));
+ CHECK(j.value(std::string_view("null"), Json(1)) == Json());
+ CHECK(j.value(std::string_view("boolean"), false) == true);
+ CHECK(j.value(std::string_view("string"), "bar") == "hello world");
+ CHECK(j.value(std::string_view("string"), std::string("bar")) == "hello world");
+ CHECK(j.value(std::string_view("floating"), 12.34) == Approx(42.23));
+ CHECK(j.value(std::string_view("floating"), 12) == 42);
+ CHECK(j.value(std::string_view("object"), Json({{"foo", "bar"}})) == Json::object());
+ CHECK(j.value(std::string_view("array"), Json({10, 100})) == Json({1, 2, 3}));
+
+ CHECK(j_const.value(std::string_view("integer"), 2) == 1);
+ CHECK(j_const.value(std::string_view("integer"), 1.0) == Approx(1));
+ CHECK(j_const.value(std::string_view("unsigned"), 2) == 1u);
+ CHECK(j_const.value(std::string_view("unsigned"), 1.0) == Approx(1u));
+ CHECK(j_const.value(std::string_view("boolean"), false) == true);
+ CHECK(j_const.value(std::string_view("string"), "bar") == "hello world");
+ CHECK(j_const.value(std::string_view("string"), std::string("bar")) == "hello world");
+ CHECK(j_const.value(std::string_view("floating"), 12.34) == Approx(42.23));
+ CHECK(j_const.value(std::string_view("floating"), 12) == 42);
+ CHECK(j_const.value(std::string_view("object"), Json({{"foo", "bar"}})) == Json::object());
+ CHECK(j_const.value(std::string_view("array"), Json({10, 100})) == Json({1, 2, 3}));
+#endif
+ }
+
+ SECTION("access non-existing value")
+ {
+ CHECK(j.value("_", 2) == 2);
+ CHECK(j.value("_", 2u) == 2u);
+ CHECK(j.value("_", false) == false);
+ CHECK(j.value("_", "bar") == "bar");
+ CHECK(j.value("_", 12.34) == Approx(12.34));
+ CHECK(j.value("_", Json({{"foo", "bar"}})) == Json({{"foo", "bar"}}));
+ CHECK(j.value("_", Json({10, 100})) == Json({10, 100}));
+
+ CHECK(j_const.value("_", 2) == 2);
+ CHECK(j_const.value("_", 2u) == 2u);
+ CHECK(j_const.value("_", false) == false);
+ CHECK(j_const.value("_", "bar") == "bar");
+ CHECK(j_const.value("_", 12.34) == Approx(12.34));
+ CHECK(j_const.value("_", Json({{"foo", "bar"}})) == Json({{"foo", "bar"}}));
+ CHECK(j_const.value("_", Json({10, 100})) == Json({10, 100}));
+
+#ifdef JSON_HAS_CPP_17
+ CHECK(j.value(std::string_view("_"), 2) == 2);
+ CHECK(j.value(std::string_view("_"), 2u) == 2u);
+ CHECK(j.value(std::string_view("_"), false) == false);
+ CHECK(j.value(std::string_view("_"), "bar") == "bar");
+ CHECK(j.value(std::string_view("_"), 12.34) == Approx(12.34));
+ CHECK(j.value(std::string_view("_"), Json({{"foo", "bar"}})) == Json({{"foo", "bar"}}));
+ CHECK(j.value(std::string_view("_"), Json({10, 100})) == Json({10, 100}));
+
+ CHECK(j_const.value(std::string_view("_"), 2) == 2);
+ CHECK(j_const.value(std::string_view("_"), 2u) == 2u);
+ CHECK(j_const.value(std::string_view("_"), false) == false);
+ CHECK(j_const.value(std::string_view("_"), "bar") == "bar");
+ CHECK(j_const.value(std::string_view("_"), 12.34) == Approx(12.34));
+ CHECK(j_const.value(std::string_view("_"), Json({{"foo", "bar"}})) == Json({{"foo", "bar"}}));
+ CHECK(j_const.value(std::string_view("_"), Json({10, 100})) == Json({10, 100}));
+#endif
+ }
+
+ SECTION("access on non-object type")
+ {
+ SECTION("null")
+ {
+ Json j_nonobject(Json::value_t::null);
+ const Json j_nonobject_const(Json::value_t::null);
+ CHECK_THROWS_WITH_AS(j_nonobject.value("foo", 1), "[json.exception.type_error.306] cannot use value() with null", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.value("foo", 1), "[json.exception.type_error.306] cannot use value() with null", typename Json::type_error&);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK_THROWS_WITH_AS(j_nonobject.value(std::string_view("foo"), 1), "[json.exception.type_error.306] cannot use value() with null", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.value(std::string_view("foo"), 1), "[json.exception.type_error.306] cannot use value() with null", typename Json::type_error&);
+#endif
+ }
+
+ SECTION("boolean")
+ {
+ Json j_nonobject(Json::value_t::boolean);
+ const Json j_nonobject_const(Json::value_t::boolean);
+ CHECK_THROWS_WITH_AS(j_nonobject.value("foo", 1), "[json.exception.type_error.306] cannot use value() with boolean", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.value("foo", 1), "[json.exception.type_error.306] cannot use value() with boolean", typename Json::type_error&);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK_THROWS_WITH_AS(j_nonobject.value(std::string_view("foo"), 1), "[json.exception.type_error.306] cannot use value() with boolean", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.value(std::string_view("foo"), 1), "[json.exception.type_error.306] cannot use value() with boolean", typename Json::type_error&);
+#endif
+ }
+
+ SECTION("string")
+ {
+ Json j_nonobject(Json::value_t::string);
+ const Json j_nonobject_const(Json::value_t::string);
+ CHECK_THROWS_WITH_AS(j_nonobject.value("foo", 1), "[json.exception.type_error.306] cannot use value() with string", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.value("foo", 1), "[json.exception.type_error.306] cannot use value() with string", typename Json::type_error&);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK_THROWS_WITH_AS(j_nonobject.value(std::string_view("foo"), 1), "[json.exception.type_error.306] cannot use value() with string", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.value(std::string_view("foo"), 1), "[json.exception.type_error.306] cannot use value() with string", typename Json::type_error&);
+#endif
+ }
+
+ SECTION("array")
+ {
+ Json j_nonobject(Json::value_t::array);
+ const Json j_nonobject_const(Json::value_t::array);
+ CHECK_THROWS_WITH_AS(j_nonobject.value("foo", 1), "[json.exception.type_error.306] cannot use value() with array", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.value("foo", 1), "[json.exception.type_error.306] cannot use value() with array", typename Json::type_error&);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK_THROWS_WITH_AS(j_nonobject.value(std::string_view("foo"), 1), "[json.exception.type_error.306] cannot use value() with array", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.value(std::string_view("foo"), 1), "[json.exception.type_error.306] cannot use value() with array", typename Json::type_error&);
+#endif
+ }
+
+ SECTION("number (integer)")
+ {
+ Json j_nonobject(Json::value_t::number_integer);
+ const Json j_nonobject_const(Json::value_t::number_integer);
+ CHECK_THROWS_WITH_AS(j_nonobject.value("foo", 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.value("foo", 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK_THROWS_WITH_AS(j_nonobject.value(std::string_view("foo"), 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.value(std::string_view("foo"), 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
+#endif
+ }
+
+ SECTION("number (unsigned)")
+ {
+ Json j_nonobject(Json::value_t::number_unsigned);
+ const Json j_nonobject_const(Json::value_t::number_unsigned);
+ CHECK_THROWS_WITH_AS(j_nonobject.value("foo", 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.value("foo", 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK_THROWS_WITH_AS(j_nonobject.value(std::string_view("foo"), 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.value(std::string_view("foo"), 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
+#endif
+ }
+
+ SECTION("number (floating-point)")
+ {
+ Json j_nonobject(Json::value_t::number_float);
+ const Json j_nonobject_const(Json::value_t::number_float);
+ CHECK_THROWS_WITH_AS(j_nonobject.value("foo", 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.value("foo", 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK_THROWS_WITH_AS(j_nonobject.value(std::string_view("foo"), 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.value(std::string_view("foo"), 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
+#endif
+ }
+ }
+ }
+
+ SECTION("given a JSON pointer")
+ {
+ SECTION("access existing value")
+ {
+ CHECK(j.value("/integer"_json_pointer, 2) == 1);
+ CHECK(j.value("/integer"_json_pointer, 1.0) == Approx(1));
+ CHECK(j.value("/unsigned"_json_pointer, 2) == 1u);
+ CHECK(j.value("/unsigned"_json_pointer, 1.0) == Approx(1u));
+ CHECK(j.value("/null"_json_pointer, Json(1)) == Json());
+ CHECK(j.value("/boolean"_json_pointer, false) == true);
+ CHECK(j.value("/string"_json_pointer, "bar") == "hello world");
+ CHECK(j.value("/string"_json_pointer, std::string("bar")) == "hello world");
+ CHECK(j.value("/floating"_json_pointer, 12.34) == Approx(42.23));
+ CHECK(j.value("/floating"_json_pointer, 12) == 42);
+ CHECK(j.value("/object"_json_pointer, Json({{"foo", "bar"}})) == Json::object());
+ CHECK(j.value("/array"_json_pointer, Json({10, 100})) == Json({1, 2, 3}));
+
+ CHECK(j_const.value("/integer"_json_pointer, 2) == 1);
+ CHECK(j_const.value("/integer"_json_pointer, 1.0) == Approx(1));
+ CHECK(j_const.value("/unsigned"_json_pointer, 2) == 1u);
+ CHECK(j_const.value("/unsigned"_json_pointer, 1.0) == Approx(1u));
+ CHECK(j_const.value("/boolean"_json_pointer, false) == true);
+ CHECK(j_const.value("/string"_json_pointer, "bar") == "hello world");
+ CHECK(j_const.value("/string"_json_pointer, std::string("bar")) == "hello world");
+ CHECK(j_const.value("/floating"_json_pointer, 12.34) == Approx(42.23));
+ CHECK(j_const.value("/floating"_json_pointer, 12) == 42);
+ CHECK(j_const.value("/object"_json_pointer, Json({{"foo", "bar"}})) == Json::object());
+ CHECK(j_const.value("/array"_json_pointer, Json({10, 100})) == Json({1, 2, 3}));
+ }
+
+ SECTION("access on non-object type")
+ {
+ SECTION("null")
+ {
+ Json j_nonobject(Json::value_t::null);
+ const Json j_nonobject_const(Json::value_t::null);
+ CHECK_THROWS_WITH_AS(j_nonobject.value("/foo"_json_pointer, 1), "[json.exception.type_error.306] cannot use value() with null", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.value("/foo"_json_pointer, 1), "[json.exception.type_error.306] cannot use value() with null", typename Json::type_error&);
+ }
+
+ SECTION("boolean")
+ {
+ Json j_nonobject(Json::value_t::boolean);
+ const Json j_nonobject_const(Json::value_t::boolean);
+ CHECK_THROWS_WITH_AS(j_nonobject.value("/foo"_json_pointer, 1), "[json.exception.type_error.306] cannot use value() with boolean", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.value("/foo"_json_pointer, 1), "[json.exception.type_error.306] cannot use value() with boolean", typename Json::type_error&);
+ }
+
+ SECTION("string")
+ {
+ Json j_nonobject(Json::value_t::string);
+ const Json j_nonobject_const(Json::value_t::string);
+ CHECK_THROWS_WITH_AS(j_nonobject.value("/foo"_json_pointer, 1), "[json.exception.type_error.306] cannot use value() with string", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.value("/foo"_json_pointer, 1), "[json.exception.type_error.306] cannot use value() with string", typename Json::type_error&);
+ }
+
+ SECTION("array")
+ {
+ Json j_nonobject(Json::value_t::array);
+ const Json j_nonobject_const(Json::value_t::array);
+ CHECK_THROWS_WITH_AS(j_nonobject.value("/foo"_json_pointer, 1), "[json.exception.type_error.306] cannot use value() with array", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.value("/foo"_json_pointer, 1), "[json.exception.type_error.306] cannot use value() with array", typename Json::type_error&);
+ }
+
+ SECTION("number (integer)")
+ {
+ Json j_nonobject(Json::value_t::number_integer);
+ const Json j_nonobject_const(Json::value_t::number_integer);
+ CHECK_THROWS_WITH_AS(j_nonobject.value("/foo"_json_pointer, 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.value("/foo"_json_pointer, 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
+ }
+
+ SECTION("number (unsigned)")
+ {
+ Json j_nonobject(Json::value_t::number_unsigned);
+ const Json j_nonobject_const(Json::value_t::number_unsigned);
+ CHECK_THROWS_WITH_AS(j_nonobject.value("/foo"_json_pointer, 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.value("/foo"_json_pointer, 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
+ }
+
+ SECTION("number (floating-point)")
+ {
+ Json j_nonobject(Json::value_t::number_float);
+ const Json j_nonobject_const(Json::value_t::number_float);
+ CHECK_THROWS_WITH_AS(j_nonobject.value("/foo"_json_pointer, 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject_const.value("/foo"_json_pointer, 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
+ }
+ }
+ }
+ }
+
+ SECTION("non-const operator[]")
+ {
+ {
+ Json j_null;
+ CHECK(j_null.is_null());
+ j_null["key"] = 1;
+ CHECK(j_null.is_object());
+ CHECK(j_null.size() == 1);
+ j_null["key"] = 2;
+ CHECK(j_null.size() == 1);
+ }
+#ifdef JSON_HAS_CPP_17
+ {
+ std::string_view const key = "key";
+ Json j_null;
+ CHECK(j_null.is_null());
+ j_null[key] = 1;
+ CHECK(j_null.is_object());
+ CHECK(j_null.size() == 1);
+ j_null[key] = 2;
+ CHECK(j_null.size() == 1);
+ }
+#endif
+ }
+
+ SECTION("front and back")
+ {
+ if (std::is_same<Json, nlohmann::ordered_json>::value)
+ {
+ // "integer" is the first key
+ CHECK(j.front() == Json(1));
+ CHECK(j_const.front() == Json(1));
+ // "array" is last key
+ CHECK(j.back() == Json({1, 2, 3}));
+ CHECK(j_const.back() == Json({1, 2, 3}));
+ }
+ else
+ {
+ // "array" is the smallest key
+ CHECK(j.front() == Json({1, 2, 3}));
+ CHECK(j_const.front() == Json({1, 2, 3}));
+ // "unsigned" is the largest key
+ CHECK(j.back() == Json(1u));
+ CHECK(j_const.back() == Json(1u));
+ }
+ }
+
+ SECTION("access specified element")
+ {
+ SECTION("access within bounds")
+ {
+ CHECK(j["integer"] == Json(1));
+ CHECK(j[typename Json::object_t::key_type("integer")] == j["integer"]);
+
+ CHECK(j["unsigned"] == Json(1u));
+ CHECK(j[typename Json::object_t::key_type("unsigned")] == j["unsigned"]);
+
+ CHECK(j["boolean"] == Json(true));
+ CHECK(j[typename Json::object_t::key_type("boolean")] == j["boolean"]);
+
+ CHECK(j["null"] == Json(nullptr));
+ CHECK(j[typename Json::object_t::key_type("null")] == j["null"]);
+
+ CHECK(j["string"] == Json("hello world"));
+ CHECK(j[typename Json::object_t::key_type("string")] == j["string"]);
+
+ CHECK(j["floating"] == Json(42.23));
+ CHECK(j[typename Json::object_t::key_type("floating")] == j["floating"]);
+
+ CHECK(j["object"] == Json::object());
+ CHECK(j[typename Json::object_t::key_type("object")] == j["object"]);
+
+ CHECK(j["array"] == Json({1, 2, 3}));
+ CHECK(j[typename Json::object_t::key_type("array")] == j["array"]);
+
+ CHECK(j_const["integer"] == Json(1));
+ CHECK(j_const[typename Json::object_t::key_type("integer")] == j["integer"]);
+
+ CHECK(j_const["boolean"] == Json(true));
+ CHECK(j_const[typename Json::object_t::key_type("boolean")] == j["boolean"]);
+
+ CHECK(j_const["null"] == Json(nullptr));
+ CHECK(j_const[typename Json::object_t::key_type("null")] == j["null"]);
+
+ CHECK(j_const["string"] == Json("hello world"));
+ CHECK(j_const[typename Json::object_t::key_type("string")] == j["string"]);
+
+ CHECK(j_const["floating"] == Json(42.23));
+ CHECK(j_const[typename Json::object_t::key_type("floating")] == j["floating"]);
+
+ CHECK(j_const["object"] == Json::object());
+ CHECK(j_const[typename Json::object_t::key_type("object")] == j["object"]);
+
+ CHECK(j_const["array"] == Json({1, 2, 3}));
+ CHECK(j_const[typename Json::object_t::key_type("array")] == j["array"]);
+ }
+
+#ifdef JSON_HAS_CPP_17
+ SECTION("access within bounds (string_view)")
+ {
+ CHECK(j["integer"] == Json(1));
+ CHECK(j[std::string_view("integer")] == j["integer"]);
+
+ CHECK(j["unsigned"] == Json(1u));
+ CHECK(j[std::string_view("unsigned")] == j["unsigned"]);
+
+ CHECK(j["boolean"] == Json(true));
+ CHECK(j[std::string_view("boolean")] == j["boolean"]);
+
+ CHECK(j["null"] == Json(nullptr));
+ CHECK(j[std::string_view("null")] == j["null"]);
+
+ CHECK(j["string"] == Json("hello world"));
+ CHECK(j[std::string_view("string")] == j["string"]);
+
+ CHECK(j["floating"] == Json(42.23));
+ CHECK(j[std::string_view("floating")] == j["floating"]);
+
+ CHECK(j["object"] == Json::object());
+ CHECK(j[std::string_view("object")] == j["object"]);
+
+ CHECK(j["array"] == Json({1, 2, 3}));
+ CHECK(j[std::string_view("array")] == j["array"]);
+
+ CHECK(j_const["integer"] == Json(1));
+ CHECK(j_const[std::string_view("integer")] == j["integer"]);
+
+ CHECK(j_const["boolean"] == Json(true));
+ CHECK(j_const[std::string_view("boolean")] == j["boolean"]);
+
+ CHECK(j_const["null"] == Json(nullptr));
+ CHECK(j_const[std::string_view("null")] == j["null"]);
+
+ CHECK(j_const["string"] == Json("hello world"));
+ CHECK(j_const[std::string_view("string")] == j["string"]);
+
+ CHECK(j_const["floating"] == Json(42.23));
+ CHECK(j_const[std::string_view("floating")] == j["floating"]);
+
+ CHECK(j_const["object"] == Json::object());
+ CHECK(j_const[std::string_view("object")] == j["object"]);
+
+ CHECK(j_const["array"] == Json({1, 2, 3}));
+ CHECK(j_const[std::string_view("array")] == j["array"]);
+ }
+#endif
+
+ SECTION("access on non-object type")
+ {
+ SECTION("null")
+ {
+ Json j_nonobject(Json::value_t::null);
+ Json j_nonobject2(Json::value_t::null);
+ const Json j_const_nonobject(j_nonobject);
+
+ CHECK_NOTHROW(j_nonobject["foo"]);
+ CHECK_NOTHROW(j_nonobject2[typename Json::object_t::key_type("foo")]);
+ CHECK_THROWS_WITH_AS(j_const_nonobject["foo"], "[json.exception.type_error.305] cannot use operator[] with a string argument with null", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_const_nonobject[typename Json::object_t::key_type("foo")], "[json.exception.type_error.305] cannot use operator[] with a string argument with null", typename Json::type_error&);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK_NOTHROW(j_nonobject2[std::string_view("foo")]);
+ CHECK_THROWS_WITH_AS(j_const_nonobject[std::string_view("foo")], "[json.exception.type_error.305] cannot use operator[] with a string argument with null", typename Json::type_error&);
+#endif
+ }
+
+ SECTION("boolean")
+ {
+ Json j_nonobject(Json::value_t::boolean);
+ const Json j_const_nonobject(j_nonobject);
+ CHECK_THROWS_WITH_AS(j_nonobject["foo"],
+ "[json.exception.type_error.305] cannot use operator[] with a string argument with boolean", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject[typename Json::object_t::key_type("foo")],
+ "[json.exception.type_error.305] cannot use operator[] with a string argument with boolean", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_const_nonobject["foo"],
+ "[json.exception.type_error.305] cannot use operator[] with a string argument with boolean", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_const_nonobject[typename Json::object_t::key_type("foo")],
+ "[json.exception.type_error.305] cannot use operator[] with a string argument with boolean", typename Json::type_error&);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK_THROWS_WITH_AS(j_nonobject[std::string_view("foo")], "[json.exception.type_error.305] cannot use operator[] with a string argument with boolean", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_const_nonobject[std::string_view("foo")], "[json.exception.type_error.305] cannot use operator[] with a string argument with boolean", typename Json::type_error&);
+#endif
+ }
+
+ SECTION("string")
+ {
+ Json j_nonobject(Json::value_t::string);
+ const Json j_const_nonobject(j_nonobject);
+ CHECK_THROWS_WITH_AS(j_nonobject["foo"],
+ "[json.exception.type_error.305] cannot use operator[] with a string argument with string", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject[typename Json::object_t::key_type("foo")],
+ "[json.exception.type_error.305] cannot use operator[] with a string argument with string", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_const_nonobject["foo"],
+ "[json.exception.type_error.305] cannot use operator[] with a string argument with string", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_const_nonobject[typename Json::object_t::key_type("foo")],
+ "[json.exception.type_error.305] cannot use operator[] with a string argument with string", typename Json::type_error&);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK_THROWS_WITH_AS(j_nonobject[std::string_view("foo")], "[json.exception.type_error.305] cannot use operator[] with a string argument with string", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_const_nonobject[std::string_view("foo")], "[json.exception.type_error.305] cannot use operator[] with a string argument with string", typename Json::type_error&);
+#endif
+ }
+
+ SECTION("array")
+ {
+ Json j_nonobject(Json::value_t::array);
+ const Json j_const_nonobject(j_nonobject);
+ CHECK_THROWS_WITH_AS(j_nonobject["foo"],
+ "[json.exception.type_error.305] cannot use operator[] with a string argument with array", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject[typename Json::object_t::key_type("foo")], "[json.exception.type_error.305] cannot use operator[] with a string argument with array", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_const_nonobject["foo"],
+ "[json.exception.type_error.305] cannot use operator[] with a string argument with array", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_const_nonobject[typename Json::object_t::key_type("foo")],
+ "[json.exception.type_error.305] cannot use operator[] with a string argument with array", typename Json::type_error&);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK_THROWS_WITH_AS(j_nonobject[std::string_view("foo")], "[json.exception.type_error.305] cannot use operator[] with a string argument with array", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_const_nonobject[std::string_view("foo")], "[json.exception.type_error.305] cannot use operator[] with a string argument with array", typename Json::type_error&);
+#endif
+ }
+
+ SECTION("number (integer)")
+ {
+ Json j_nonobject(Json::value_t::number_integer);
+ const Json j_const_nonobject(j_nonobject);
+ CHECK_THROWS_WITH_AS(j_nonobject["foo"],
+ "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject[typename Json::object_t::key_type("foo")],
+ "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_const_nonobject["foo"],
+ "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_const_nonobject[typename Json::object_t::key_type("foo")],
+ "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK_THROWS_WITH_AS(j_nonobject[std::string_view("foo")], "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_const_nonobject[std::string_view("foo")], "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
+#endif
+ }
+
+ SECTION("number (unsigned)")
+ {
+ Json j_nonobject(Json::value_t::number_unsigned);
+ const Json j_const_nonobject(j_nonobject);
+ CHECK_THROWS_WITH_AS(j_nonobject["foo"],
+ "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject[typename Json::object_t::key_type("foo")],
+ "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_const_nonobject["foo"],
+ "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_const_nonobject[typename Json::object_t::key_type("foo")],
+ "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK_THROWS_WITH_AS(j_nonobject[std::string_view("foo")], "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_const_nonobject[std::string_view("foo")], "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
+#endif
+ }
+
+ SECTION("number (floating-point)")
+ {
+ Json j_nonobject(Json::value_t::number_float);
+ const Json j_const_nonobject(j_nonobject);
+ CHECK_THROWS_WITH_AS(j_nonobject["foo"],
+ "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonobject[typename Json::object_t::key_type("foo")],
+ "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_const_nonobject["foo"],
+ "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_const_nonobject[typename Json::object_t::key_type("foo")],
+ "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK_THROWS_WITH_AS(j_nonobject[std::string_view("foo")], "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(j_const_nonobject[std::string_view("foo")], "[json.exception.type_error.305] cannot use operator[] with a string argument with number", typename Json::type_error&);
+#endif
+ }
+ }
+ }
+
+ SECTION("remove specified element")
+ {
+ SECTION("remove element by key")
+ {
+ CHECK(j.find("integer") != j.end());
+ CHECK(j.erase("integer") == 1);
+ CHECK(j.find("integer") == j.end());
+ CHECK(j.erase("integer") == 0);
+
+ CHECK(j.find("unsigned") != j.end());
+ CHECK(j.erase("unsigned") == 1);
+ CHECK(j.find("unsigned") == j.end());
+ CHECK(j.erase("unsigned") == 0);
+
+ CHECK(j.find("boolean") != j.end());
+ CHECK(j.erase("boolean") == 1);
+ CHECK(j.find("boolean") == j.end());
+ CHECK(j.erase("boolean") == 0);
+
+ CHECK(j.find("null") != j.end());
+ CHECK(j.erase("null") == 1);
+ CHECK(j.find("null") == j.end());
+ CHECK(j.erase("null") == 0);
+
+ CHECK(j.find("string") != j.end());
+ CHECK(j.erase("string") == 1);
+ CHECK(j.find("string") == j.end());
+ CHECK(j.erase("string") == 0);
+
+ CHECK(j.find("floating") != j.end());
+ CHECK(j.erase("floating") == 1);
+ CHECK(j.find("floating") == j.end());
+ CHECK(j.erase("floating") == 0);
+
+ CHECK(j.find("object") != j.end());
+ CHECK(j.erase("object") == 1);
+ CHECK(j.find("object") == j.end());
+ CHECK(j.erase("object") == 0);
+
+ CHECK(j.find("array") != j.end());
+ CHECK(j.erase("array") == 1);
+ CHECK(j.find("array") == j.end());
+ CHECK(j.erase("array") == 0);
+ }
+
+#ifdef JSON_HAS_CPP_17
+ SECTION("remove element by key (string_view)")
+ {
+ CHECK(j.find(std::string_view("integer")) != j.end());
+ CHECK(j.erase(std::string_view("integer")) == 1);
+ CHECK(j.find(std::string_view("integer")) == j.end());
+ CHECK(j.erase(std::string_view("integer")) == 0);
+
+ CHECK(j.find(std::string_view("unsigned")) != j.end());
+ CHECK(j.erase(std::string_view("unsigned")) == 1);
+ CHECK(j.find(std::string_view("unsigned")) == j.end());
+ CHECK(j.erase(std::string_view("unsigned")) == 0);
+
+ CHECK(j.find(std::string_view("boolean")) != j.end());
+ CHECK(j.erase(std::string_view("boolean")) == 1);
+ CHECK(j.find(std::string_view("boolean")) == j.end());
+ CHECK(j.erase(std::string_view("boolean")) == 0);
+
+ CHECK(j.find(std::string_view("null")) != j.end());
+ CHECK(j.erase(std::string_view("null")) == 1);
+ CHECK(j.find(std::string_view("null")) == j.end());
+ CHECK(j.erase(std::string_view("null")) == 0);
+
+ CHECK(j.find(std::string_view("string")) != j.end());
+ CHECK(j.erase(std::string_view("string")) == 1);
+ CHECK(j.find(std::string_view("string")) == j.end());
+ CHECK(j.erase(std::string_view("string")) == 0);
+
+ CHECK(j.find(std::string_view("floating")) != j.end());
+ CHECK(j.erase(std::string_view("floating")) == 1);
+ CHECK(j.find(std::string_view("floating")) == j.end());
+ CHECK(j.erase(std::string_view("floating")) == 0);
+
+ CHECK(j.find(std::string_view("object")) != j.end());
+ CHECK(j.erase(std::string_view("object")) == 1);
+ CHECK(j.find(std::string_view("object")) == j.end());
+ CHECK(j.erase(std::string_view("object")) == 0);
+
+ CHECK(j.find(std::string_view("array")) != j.end());
+ CHECK(j.erase(std::string_view("array")) == 1);
+ CHECK(j.find(std::string_view("array")) == j.end());
+ CHECK(j.erase(std::string_view("array")) == 0);
+ }
+#endif
+
+ SECTION("remove element by iterator")
+ {
+ SECTION("erase(begin())")
+ {
+ {
+ Json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
+ typename Json::iterator const it2 = jobject.erase(jobject.begin());
+ CHECK(jobject == Json({{"b", 1}, {"c", 17u}}));
+ CHECK(*it2 == Json(1));
+ }
+ {
+ Json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
+ typename Json::const_iterator const it2 = jobject.erase(jobject.cbegin());
+ CHECK(jobject == Json({{"b", 1}, {"c", 17u}}));
+ CHECK(*it2 == Json(1));
+ }
+ }
+
+ SECTION("erase(begin(), end())")
+ {
+ {
+ Json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
+ const typename Json::iterator it2 = jobject.erase(jobject.begin(), jobject.end());
+ CHECK(jobject == Json::object());
+ CHECK(it2 == jobject.end());
+ }
+ {
+ Json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
+ const typename Json::const_iterator it2 = jobject.erase(jobject.cbegin(), jobject.cend());
+ CHECK(jobject == Json::object());
+ CHECK(it2 == jobject.cend());
+ }
+ }
+
+ SECTION("erase(begin(), begin())")
+ {
+ {
+ Json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
+ typename Json::iterator const it2 = jobject.erase(jobject.begin(), jobject.begin());
+ CHECK(jobject == Json({{"a", "a"}, {"b", 1}, {"c", 17u}}));
+ CHECK(*it2 == Json("a"));
+ }
+ {
+ Json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
+ typename Json::const_iterator const it2 = jobject.erase(jobject.cbegin(), jobject.cbegin());
+ CHECK(jobject == Json({{"a", "a"}, {"b", 1}, {"c", 17u}}));
+ CHECK(*it2 == Json("a"));
+ }
+ }
+
+ SECTION("erase at offset")
+ {
+ {
+ Json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
+ typename Json::iterator const it = jobject.find("b");
+ typename Json::iterator const it2 = jobject.erase(it);
+ CHECK(jobject == Json({{"a", "a"}, {"c", 17u}}));
+ CHECK(*it2 == Json(17));
+ }
+ {
+ Json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}};
+ typename Json::const_iterator const it = jobject.find("b");
+ typename Json::const_iterator const it2 = jobject.erase(it);
+ CHECK(jobject == Json({{"a", "a"}, {"c", 17u}}));
+ CHECK(*it2 == Json(17));
+ }
+ }
+
+ SECTION("erase subrange")
+ {
+ {
+ Json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}, {"d", false}, {"e", true}};
+ typename Json::iterator const it2 = jobject.erase(jobject.find("b"), jobject.find("e"));
+ CHECK(jobject == Json({{"a", "a"}, {"e", true}}));
+ CHECK(*it2 == Json(true));
+ }
+ {
+ Json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}, {"d", false}, {"e", true}};
+ typename Json::const_iterator const it2 = jobject.erase(jobject.find("b"), jobject.find("e"));
+ CHECK(jobject == Json({{"a", "a"}, {"e", true}}));
+ CHECK(*it2 == Json(true));
+ }
+ }
+
+ SECTION("different objects")
+ {
+ {
+ Json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}, {"d", false}, {"e", true}};
+ Json jobject2 = {{"a", "a"}, {"b", 1}, {"c", 17u}};
+ CHECK_THROWS_WITH_AS(jobject.erase(jobject2.begin()),
+ "[json.exception.invalid_iterator.202] iterator does not fit current value", typename Json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(jobject.erase(jobject.begin(), jobject2.end()),
+ "[json.exception.invalid_iterator.203] iterators do not fit current value", typename Json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(jobject.erase(jobject2.begin(), jobject.end()),
+ "[json.exception.invalid_iterator.203] iterators do not fit current value", typename Json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(jobject.erase(jobject2.begin(), jobject2.end()),
+ "[json.exception.invalid_iterator.203] iterators do not fit current value", typename Json::invalid_iterator&);
+ }
+ {
+ Json jobject = {{"a", "a"}, {"b", 1}, {"c", 17u}, {"d", false}, {"e", true}};
+ Json jobject2 = {{"a", "a"}, {"b", 1}, {"c", 17u}};
+ CHECK_THROWS_WITH_AS(jobject.erase(jobject2.cbegin()),
+ "[json.exception.invalid_iterator.202] iterator does not fit current value", typename Json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(jobject.erase(jobject.cbegin(), jobject2.cend()),
+ "[json.exception.invalid_iterator.203] iterators do not fit current value", typename Json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(jobject.erase(jobject2.cbegin(), jobject.cend()),
+ "[json.exception.invalid_iterator.203] iterators do not fit current value", typename Json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(jobject.erase(jobject2.cbegin(), jobject2.cend()),
+ "[json.exception.invalid_iterator.203] iterators do not fit current value", typename Json::invalid_iterator&);
+ }
+ }
+ }
+
+ SECTION("remove element by key in non-object type")
+ {
+ SECTION("null")
+ {
+ Json j_nonobject(Json::value_t::null);
+ CHECK_THROWS_WITH_AS(j_nonobject.erase("foo"), "[json.exception.type_error.307] cannot use erase() with null", typename Json::type_error&);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK_THROWS_WITH_AS(j_nonobject.erase(std::string_view("foo")), "[json.exception.type_error.307] cannot use erase() with null", typename Json::type_error&);
+#endif
+ }
+
+ SECTION("boolean")
+ {
+ Json j_nonobject(Json::value_t::boolean);
+ CHECK_THROWS_WITH_AS(j_nonobject.erase("foo"), "[json.exception.type_error.307] cannot use erase() with boolean", typename Json::type_error&);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK_THROWS_WITH_AS(j_nonobject.erase(std::string_view("foo")), "[json.exception.type_error.307] cannot use erase() with boolean", typename Json::type_error&);
+#endif
+ }
+
+ SECTION("string")
+ {
+ Json j_nonobject(Json::value_t::string);
+ CHECK_THROWS_WITH_AS(j_nonobject.erase("foo"), "[json.exception.type_error.307] cannot use erase() with string", typename Json::type_error&);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK_THROWS_WITH_AS(j_nonobject.erase(std::string_view("foo")), "[json.exception.type_error.307] cannot use erase() with string", typename Json::type_error&);
+#endif
+ }
+
+ SECTION("array")
+ {
+ Json j_nonobject(Json::value_t::array);
+ CHECK_THROWS_WITH_AS(j_nonobject.erase("foo"), "[json.exception.type_error.307] cannot use erase() with array", typename Json::type_error&);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK_THROWS_WITH_AS(j_nonobject.erase(std::string_view("foo")), "[json.exception.type_error.307] cannot use erase() with array", typename Json::type_error&);
+#endif
+ }
+
+ SECTION("number (integer)")
+ {
+ Json j_nonobject(Json::value_t::number_integer);
+ CHECK_THROWS_WITH_AS(j_nonobject.erase("foo"), "[json.exception.type_error.307] cannot use erase() with number", typename Json::type_error&);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK_THROWS_WITH_AS(j_nonobject.erase(std::string_view("foo")), "[json.exception.type_error.307] cannot use erase() with number", typename Json::type_error&);
+#endif
+ }
+
+ SECTION("number (floating-point)")
+ {
+ Json j_nonobject(Json::value_t::number_float);
+ CHECK_THROWS_WITH_AS(j_nonobject.erase("foo"), "[json.exception.type_error.307] cannot use erase() with number", typename Json::type_error&);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK_THROWS_WITH_AS(j_nonobject.erase(std::string_view("foo")), "[json.exception.type_error.307] cannot use erase() with number", typename Json::type_error&);
+#endif
+ }
+ }
+ }
+
+ SECTION("find an element in an object")
+ {
+ SECTION("existing element")
+ {
+ for (const auto* key :
+ {"integer", "unsigned", "floating", "null", "string", "boolean", "object", "array"
+ })
+ {
+ CHECK(j.find(key) != j.end());
+ CHECK(*j.find(key) == j.at(key));
+ CHECK(j_const.find(key) != j_const.end());
+ CHECK(*j_const.find(key) == j_const.at(key));
+ }
+#ifdef JSON_HAS_CPP_17
+ for (const std::string_view key :
+ {"integer", "unsigned", "floating", "null", "string", "boolean", "object", "array"
+ })
+ {
+ CHECK(j.find(key) != j.end());
+ CHECK(*j.find(key) == j.at(key));
+ CHECK(j_const.find(key) != j_const.end());
+ CHECK(*j_const.find(key) == j_const.at(key));
+ }
+#endif
+ }
+
+ SECTION("nonexisting element")
+ {
+ CHECK(j.find("foo") == j.end());
+ CHECK(j_const.find("foo") == j_const.end());
+
+#ifdef JSON_HAS_CPP_17
+ CHECK(j.find(std::string_view("foo")) == j.end());
+ CHECK(j_const.find(std::string_view("foo")) == j_const.end());
+#endif
+ }
+
+ SECTION("all types")
+ {
+ SECTION("null")
+ {
+ Json j_nonarray(Json::value_t::null);
+ const Json j_nonarray_const(j_nonarray);
+
+ CHECK(j_nonarray.find("foo") == j_nonarray.end());
+ CHECK(j_nonarray_const.find("foo") == j_nonarray_const.end());
+
+#ifdef JSON_HAS_CPP_17
+ CHECK(j_nonarray.find(std::string_view("foo")) == j_nonarray.end());
+ CHECK(j_nonarray_const.find(std::string_view("foo")) == j_nonarray_const.end());
+#endif
+ }
+
+ SECTION("string")
+ {
+ Json j_nonarray(Json::value_t::string);
+ const Json j_nonarray_const(j_nonarray);
+
+ CHECK(j_nonarray.find("foo") == j_nonarray.end());
+ CHECK(j_nonarray_const.find("foo") == j_nonarray_const.end());
+
+#ifdef JSON_HAS_CPP_17
+ CHECK(j_nonarray.find(std::string_view("foo")) == j_nonarray.end());
+ CHECK(j_nonarray_const.find(std::string_view("foo")) == j_nonarray_const.end());
+#endif
+ }
+
+ SECTION("object")
+ {
+ Json j_nonarray(Json::value_t::object);
+ const Json j_nonarray_const(j_nonarray);
+
+ CHECK(j_nonarray.find("foo") == j_nonarray.end());
+ CHECK(j_nonarray_const.find("foo") == j_nonarray_const.end());
+
+#ifdef JSON_HAS_CPP_17
+ CHECK(j_nonarray.find(std::string_view("foo")) == j_nonarray.end());
+ CHECK(j_nonarray_const.find(std::string_view("foo")) == j_nonarray_const.end());
+#endif
+ }
+
+ SECTION("array")
+ {
+ Json j_nonarray(Json::value_t::array);
+ const Json j_nonarray_const(j_nonarray);
+
+ CHECK(j_nonarray.find("foo") == j_nonarray.end());
+ CHECK(j_nonarray_const.find("foo") == j_nonarray_const.end());
+
+#ifdef JSON_HAS_CPP_17
+ CHECK(j_nonarray.find(std::string_view("foo")) == j_nonarray.end());
+ CHECK(j_nonarray_const.find(std::string_view("foo")) == j_nonarray_const.end());
+#endif
+ }
+
+ SECTION("boolean")
+ {
+ Json j_nonarray(Json::value_t::boolean);
+ const Json j_nonarray_const(j_nonarray);
+
+ CHECK(j_nonarray.find("foo") == j_nonarray.end());
+ CHECK(j_nonarray_const.find("foo") == j_nonarray_const.end());
+
+#ifdef JSON_HAS_CPP_17
+ CHECK(j_nonarray.find(std::string_view("foo")) == j_nonarray.end());
+ CHECK(j_nonarray_const.find(std::string_view("foo")) == j_nonarray_const.end());
+#endif
+ }
+
+ SECTION("number (integer)")
+ {
+ Json j_nonarray(Json::value_t::number_integer);
+ const Json j_nonarray_const(j_nonarray);
+
+ CHECK(j_nonarray.find("foo") == j_nonarray.end());
+ CHECK(j_nonarray_const.find("foo") == j_nonarray_const.end());
+
+#ifdef JSON_HAS_CPP_17
+ CHECK(j_nonarray.find(std::string_view("foo")) == j_nonarray.end());
+ CHECK(j_nonarray_const.find(std::string_view("foo")) == j_nonarray_const.end());
+#endif
+ }
+
+ SECTION("number (unsigned)")
+ {
+ Json j_nonarray(Json::value_t::number_unsigned);
+ const Json j_nonarray_const(j_nonarray);
+
+ CHECK(j_nonarray.find("foo") == j_nonarray.end());
+ CHECK(j_nonarray_const.find("foo") == j_nonarray_const.end());
+
+#ifdef JSON_HAS_CPP_17
+ CHECK(j_nonarray.find(std::string_view("foo")) == j_nonarray.end());
+ CHECK(j_nonarray_const.find(std::string_view("foo")) == j_nonarray_const.end());
+#endif
+ }
+
+ SECTION("number (floating-point)")
+ {
+ Json j_nonarray(Json::value_t::number_float);
+ const Json j_nonarray_const(j_nonarray);
+
+ CHECK(j_nonarray.find("foo") == j_nonarray.end());
+ CHECK(j_nonarray_const.find("foo") == j_nonarray_const.end());
+
+#ifdef JSON_HAS_CPP_17
+ CHECK(j_nonarray.find(std::string_view("foo")) == j_nonarray.end());
+ CHECK(j_nonarray_const.find(std::string_view("foo")) == j_nonarray_const.end());
+#endif
+ }
+ }
+ }
+
+ SECTION("count keys in an object")
+ {
+ SECTION("existing element")
+ {
+ for (const auto* key :
+ {"integer", "unsigned", "floating", "null", "string", "boolean", "object", "array"
+ })
+ {
+ CHECK(j.count(key) == 1);
+ CHECK(j_const.count(key) == 1);
+ }
+#ifdef JSON_HAS_CPP_17
+ for (const std::string_view key :
+ {"integer", "unsigned", "floating", "null", "string", "boolean", "object", "array"
+ })
+ {
+ CHECK(j.count(key) == 1);
+ CHECK(j_const.count(key) == 1);
+ }
+#endif
+ }
+
+ SECTION("nonexisting element")
+ {
+ CHECK(j.count("foo") == 0);
+ CHECK(j_const.count("foo") == 0);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK(j.count(std::string_view("foo")) == 0);
+ CHECK(j_const.count(std::string_view("foo")) == 0);
+#endif
+ }
+
+ SECTION("all types")
+ {
+ SECTION("null")
+ {
+ Json j_nonobject(Json::value_t::null);
+ const Json j_nonobject_const(Json::value_t::null);
+
+ CHECK(j_nonobject.count("foo") == 0);
+ CHECK(j_nonobject_const.count("foo") == 0);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK(j.count(std::string_view("foo")) == 0);
+ CHECK(j_const.count(std::string_view("foo")) == 0);
+#endif
+ }
+
+ SECTION("string")
+ {
+ Json j_nonobject(Json::value_t::string);
+ const Json j_nonobject_const(Json::value_t::string);
+
+ CHECK(j_nonobject.count("foo") == 0);
+ CHECK(j_nonobject_const.count("foo") == 0);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK(j.count(std::string_view("foo")) == 0);
+ CHECK(j_const.count(std::string_view("foo")) == 0);
+#endif
+ }
+
+ SECTION("object")
+ {
+ Json j_nonobject(Json::value_t::object);
+ const Json j_nonobject_const(Json::value_t::object);
+
+ CHECK(j_nonobject.count("foo") == 0);
+ CHECK(j_nonobject_const.count("foo") == 0);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK(j.count(std::string_view("foo")) == 0);
+ CHECK(j_const.count(std::string_view("foo")) == 0);
+#endif
+ }
+
+ SECTION("array")
+ {
+ Json j_nonobject(Json::value_t::array);
+ const Json j_nonobject_const(Json::value_t::array);
+
+ CHECK(j_nonobject.count("foo") == 0);
+ CHECK(j_nonobject_const.count("foo") == 0);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK(j.count(std::string_view("foo")) == 0);
+ CHECK(j_const.count(std::string_view("foo")) == 0);
+#endif
+ }
+
+ SECTION("boolean")
+ {
+ Json j_nonobject(Json::value_t::boolean);
+ const Json j_nonobject_const(Json::value_t::boolean);
+
+ CHECK(j_nonobject.count("foo") == 0);
+ CHECK(j_nonobject_const.count("foo") == 0);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK(j.count(std::string_view("foo")) == 0);
+ CHECK(j_const.count(std::string_view("foo")) == 0);
+#endif
+ }
+
+ SECTION("number (integer)")
+ {
+ Json j_nonobject(Json::value_t::number_integer);
+ const Json j_nonobject_const(Json::value_t::number_integer);
+
+ CHECK(j_nonobject.count("foo") == 0);
+ CHECK(j_nonobject_const.count("foo") == 0);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK(j.count(std::string_view("foo")) == 0);
+ CHECK(j_const.count(std::string_view("foo")) == 0);
+#endif
+ }
+
+ SECTION("number (unsigned)")
+ {
+ Json j_nonobject(Json::value_t::number_unsigned);
+ const Json j_nonobject_const(Json::value_t::number_unsigned);
+
+ CHECK(j_nonobject.count("foo") == 0);
+ CHECK(j_nonobject_const.count("foo") == 0);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK(j.count(std::string_view("foo")) == 0);
+ CHECK(j_const.count(std::string_view("foo")) == 0);
+#endif
+ }
+
+ SECTION("number (floating-point)")
+ {
+ Json j_nonobject(Json::value_t::number_float);
+ const Json j_nonobject_const(Json::value_t::number_float);
+
+ CHECK(j_nonobject.count("foo") == 0);
+ CHECK(j_nonobject_const.count("foo") == 0);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK(j.count(std::string_view("foo")) == 0);
+ CHECK(j_const.count(std::string_view("foo")) == 0);
+#endif
+ }
+ }
+ }
+
+ SECTION("check existence of key in an object")
+ {
+ SECTION("existing element")
+ {
+ for (const auto* key :
+ {"integer", "unsigned", "floating", "null", "string", "boolean", "object", "array"
+ })
+ {
+ CHECK(j.contains(key) == true);
+ CHECK(j_const.contains(key) == true);
+ }
+
+#ifdef JSON_HAS_CPP_17
+ for (const std::string_view key :
+ {"integer", "unsigned", "floating", "null", "string", "boolean", "object", "array"
+ })
+ {
+ CHECK(j.contains(key) == true);
+ CHECK(j_const.contains(key) == true);
+ }
+#endif
+ }
+
+ SECTION("nonexisting element")
+ {
+ CHECK(j.contains("foo") == false);
+ CHECK(j_const.contains("foo") == false);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK(j.contains(std::string_view("foo")) == false);
+ CHECK(j_const.contains(std::string_view("foo")) == false);
+#endif
+ }
+
+ SECTION("all types")
+ {
+ SECTION("null")
+ {
+ Json j_nonobject(Json::value_t::null);
+ const Json j_nonobject_const(Json::value_t::null);
+
+ CHECK(j_nonobject.contains("foo") == false);
+ CHECK(j_nonobject_const.contains("foo") == false);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK(j_nonobject.contains(std::string_view("foo")) == false);
+ CHECK(j_nonobject_const.contains(std::string_view("foo")) == false);
+#endif
+ }
+
+ SECTION("string")
+ {
+ Json j_nonobject(Json::value_t::string);
+ const Json j_nonobject_const(Json::value_t::string);
+
+ CHECK(j_nonobject.contains("foo") == false);
+ CHECK(j_nonobject_const.contains("foo") == false);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK(j_nonobject.contains(std::string_view("foo")) == false);
+ CHECK(j_nonobject_const.contains(std::string_view("foo")) == false);
+#endif
+ }
+
+ SECTION("object")
+ {
+ Json j_nonobject(Json::value_t::object);
+ const Json j_nonobject_const(Json::value_t::object);
+
+ CHECK(j_nonobject.contains("foo") == false);
+ CHECK(j_nonobject_const.contains("foo") == false);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK(j_nonobject.contains(std::string_view("foo")) == false);
+ CHECK(j_nonobject_const.contains(std::string_view("foo")) == false);
+#endif
+ }
+
+ SECTION("array")
+ {
+ Json j_nonobject(Json::value_t::array);
+ const Json j_nonobject_const(Json::value_t::array);
+
+ CHECK(j_nonobject.contains("foo") == false);
+ CHECK(j_nonobject_const.contains("foo") == false);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK(j_nonobject.contains(std::string_view("foo")) == false);
+ CHECK(j_nonobject_const.contains(std::string_view("foo")) == false);
+#endif
+ }
+
+ SECTION("boolean")
+ {
+ Json j_nonobject(Json::value_t::boolean);
+ const Json j_nonobject_const(Json::value_t::boolean);
+
+ CHECK(j_nonobject.contains("foo") == false);
+ CHECK(j_nonobject_const.contains("foo") == false);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK(j_nonobject.contains(std::string_view("foo")) == false);
+ CHECK(j_nonobject_const.contains(std::string_view("foo")) == false);
+#endif
+ }
+
+ SECTION("number (integer)")
+ {
+ Json j_nonobject(Json::value_t::number_integer);
+ const Json j_nonobject_const(Json::value_t::number_integer);
+
+ CHECK(j_nonobject.contains("foo") == false);
+ CHECK(j_nonobject_const.contains("foo") == false);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK(j_nonobject.contains(std::string_view("foo")) == false);
+ CHECK(j_nonobject_const.contains(std::string_view("foo")) == false);
+#endif
+ }
+
+ SECTION("number (unsigned)")
+ {
+ Json j_nonobject(Json::value_t::number_unsigned);
+ const Json j_nonobject_const(Json::value_t::number_unsigned);
+
+ CHECK(j_nonobject.contains("foo") == false);
+ CHECK(j_nonobject_const.contains("foo") == false);
+
+#ifdef JSON_HAS_CPP_17
+ CHECK(j_nonobject.contains(std::string_view("foo")) == false);
+ CHECK(j_nonobject_const.contains(std::string_view("foo")) == false);
+#endif
+ }
+
+ SECTION("number (floating-point)")
+ {
+ Json j_nonobject(Json::value_t::number_float);
+ const Json j_nonobject_const(Json::value_t::number_float);
+ CHECK(j_nonobject.contains("foo") == false);
+ CHECK(j_nonobject_const.contains("foo") == false);
+#ifdef JSON_HAS_CPP_17
+ CHECK(j_nonobject.contains(std::string_view("foo")) == false);
+ CHECK(j_nonobject_const.contains(std::string_view("foo")) == false);
+#endif
+ }
+ }
+ }
+ }
+}
+
+#if !defined(JSON_NOEXCEPTION)
+TEST_CASE_TEMPLATE("element access 2 (throwing tests)", Json, nlohmann::json, nlohmann::ordered_json) // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
+{
+ SECTION("object")
+ {
+ Json j = {{"integer", 1}, {"unsigned", 1u}, {"floating", 42.23}, {"null", nullptr}, {"string", "hello world"}, {"boolean", true}, {"object", Json::object()}, {"array", {1, 2, 3}}};
+ const Json j_const = {{"integer", 1}, {"unsigned", 1u}, {"floating", 42.23}, {"null", nullptr}, {"string", "hello world"}, {"boolean", true}, {"object", Json::object()}, {"array", {1, 2, 3}}};
+
+ SECTION("access specified element with default value")
+ {
+ SECTION("given a JSON pointer")
+ {
+ SECTION("access non-existing value")
+ {
+ CHECK(j.value("/not/existing"_json_pointer, 2) == 2);
+ CHECK(j.value("/not/existing"_json_pointer, 2u) == 2u);
+ CHECK(j.value("/not/existing"_json_pointer, false) == false);
+ CHECK(j.value("/not/existing"_json_pointer, "bar") == "bar");
+ CHECK(j.value("/not/existing"_json_pointer, 12.34) == Approx(12.34));
+ CHECK(j.value("/not/existing"_json_pointer, Json({{"foo", "bar"}})) == Json({{"foo", "bar"}}));
+ CHECK(j.value("/not/existing"_json_pointer, Json({10, 100})) == Json({10, 100}));
+
+ CHECK(j_const.value("/not/existing"_json_pointer, 2) == 2);
+ CHECK(j_const.value("/not/existing"_json_pointer, 2u) == 2u);
+ CHECK(j_const.value("/not/existing"_json_pointer, false) == false);
+ CHECK(j_const.value("/not/existing"_json_pointer, "bar") == "bar");
+ CHECK(j_const.value("/not/existing"_json_pointer, 12.34) == Approx(12.34));
+ CHECK(j_const.value("/not/existing"_json_pointer, Json({{"foo", "bar"}})) == Json({{"foo", "bar"}}));
+ CHECK(j_const.value("/not/existing"_json_pointer, Json({10, 100})) == Json({10, 100}));
+ }
+ }
+ }
+ }
+}
+#endif
+
+// TODO(falbrechtskirchinger) merge with the other test case; clean up
+TEST_CASE_TEMPLATE("element access 2 (additional value() tests)", Json, nlohmann::json, nlohmann::ordered_json) // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
+{
+ using string_t = typename Json::string_t;
+ using number_integer_t = typename Json::number_integer_t;
+
+ // test assumes string_t and object_t::key_type are the same
+ REQUIRE(std::is_same<string_t, typename Json::object_t::key_type>::value);
+
+ Json j
+ {
+ {"foo", "bar"},
+ {"baz", 42}
+ };
+
+ const char* cpstr = "default";
+ const char castr[] = "default"; // NOLINT(hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-avoid-c-arrays)
+ string_t const str = "default";
+
+ number_integer_t integer = 69;
+ std::size_t size = 69;
+
+ SECTION("deduced ValueType")
+ {
+ SECTION("literal key")
+ {
+ CHECK(j.value("foo", "default") == "bar");
+ CHECK(j.value("foo", cpstr) == "bar");
+ CHECK(j.value("foo", castr) == "bar");
+ CHECK(j.value("foo", str) == "bar");
+ // this test is in fact different from the one below,
+ // because of 0 considering const char * overloads
+ // whereas any other number does not
+ CHECK(j.value("baz", 0) == 42);
+ CHECK(j.value("baz", 47) == 42);
+ CHECK(j.value("baz", integer) == 42);
+ CHECK(j.value("baz", size) == 42);
+
+ CHECK(j.value("bar", "default") == "default");
+ CHECK(j.value("bar", 0) == 0);
+ CHECK(j.value("bar", 47) == 47);
+ CHECK(j.value("bar", integer) == integer);
+ CHECK(j.value("bar", size) == size);
+
+ CHECK_THROWS_WITH_AS(Json().value("foo", "default"), "[json.exception.type_error.306] cannot use value() with null", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(Json().value("foo", str), "[json.exception.type_error.306] cannot use value() with null", typename Json::type_error&);
+ }
+
+ SECTION("const char * key")
+ {
+ const char* key = "foo";
+ const char* key2 = "baz";
+ const char* key_notfound = "bar";
+
+ CHECK(j.value(key, "default") == "bar");
+ CHECK(j.value(key, cpstr) == "bar");
+ CHECK(j.value(key, castr) == "bar");
+ CHECK(j.value(key, str) == "bar");
+ CHECK(j.value(key2, 0) == 42);
+ CHECK(j.value(key2, 47) == 42);
+ CHECK(j.value(key2, integer) == 42);
+ CHECK(j.value(key2, size) == 42);
+
+ CHECK(j.value(key_notfound, "default") == "default");
+ CHECK(j.value(key_notfound, 0) == 0);
+ CHECK(j.value(key_notfound, 47) == 47);
+ CHECK(j.value(key_notfound, integer) == integer);
+ CHECK(j.value(key_notfound, size) == size);
+
+ CHECK_THROWS_WITH_AS(Json().value(key, "default"), "[json.exception.type_error.306] cannot use value() with null", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(Json().value(key, str), "[json.exception.type_error.306] cannot use value() with null", typename Json::type_error&);
+ }
+
+ SECTION("const char(&)[] key")
+ {
+ const char key[] = "foo"; // NOLINT(misc-const-correctness,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-avoid-c-arrays)
+ const char key2[] = "baz"; // NOLINT(misc-const-correctness,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-avoid-c-arrays)
+ const char key_notfound[] = "bar"; // NOLINT(misc-const-correctness,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-avoid-c-arrays)
+
+ CHECK(j.value(key, "default") == "bar");
+ CHECK(j.value(key, cpstr) == "bar");
+ CHECK(j.value(key, castr) == "bar");
+ CHECK(j.value(key, str) == "bar");
+ CHECK(j.value(key2, 0) == 42);
+ CHECK(j.value(key2, 47) == 42);
+ CHECK(j.value(key2, integer) == 42);
+ CHECK(j.value(key2, size) == 42);
+
+ CHECK(j.value(key_notfound, "default") == "default");
+ CHECK(j.value(key_notfound, 0) == 0);
+ CHECK(j.value(key_notfound, 47) == 47);
+ CHECK(j.value(key_notfound, integer) == integer);
+ CHECK(j.value(key_notfound, size) == size);
+
+ CHECK_THROWS_WITH_AS(Json().value(key, "default"), "[json.exception.type_error.306] cannot use value() with null", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(Json().value(key, str), "[json.exception.type_error.306] cannot use value() with null", typename Json::type_error&);
+ }
+
+ SECTION("string_t/object_t::key_type key")
+ {
+ string_t const key = "foo";
+ string_t const key2 = "baz";
+ string_t const key_notfound = "bar";
+
+ CHECK(j.value(key, "default") == "bar");
+ CHECK(j.value(key, cpstr) == "bar");
+ CHECK(j.value(key, castr) == "bar");
+ CHECK(j.value(key, str) == "bar");
+ CHECK(j.value(key2, 0) == 42);
+ CHECK(j.value(key2, 47) == 42);
+ CHECK(j.value(key2, integer) == 42);
+ CHECK(j.value(key2, size) == 42);
+
+ CHECK(j.value(key_notfound, "default") == "default");
+ CHECK(j.value(key_notfound, 0) == 0);
+ CHECK(j.value(key_notfound, 47) == 47);
+ CHECK(j.value(key_notfound, integer) == integer);
+ CHECK(j.value(key_notfound, size) == size);
+
+ CHECK_THROWS_WITH_AS(Json().value(key, "default"), "[json.exception.type_error.306] cannot use value() with null", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(Json().value(key, str), "[json.exception.type_error.306] cannot use value() with null", typename Json::type_error&);
+ }
+
+#ifdef JSON_HAS_CPP_17
+ SECTION("std::string_view key")
+ {
+ std::string_view const key = "foo";
+ std::string_view const key2 = "baz";
+ std::string_view const key_notfound = "bar";
+
+ CHECK(j.value(key, "default") == "bar");
+ CHECK(j.value(key, cpstr) == "bar");
+ CHECK(j.value(key, castr) == "bar");
+ CHECK(j.value(key, str) == "bar");
+ CHECK(j.value(key2, 0) == 42);
+ CHECK(j.value(key2, 47) == 42);
+ CHECK(j.value(key2, integer) == 42);
+ CHECK(j.value(key2, size) == 42);
+
+ CHECK(j.value(key_notfound, "default") == "default");
+ CHECK(j.value(key_notfound, 0) == 0);
+ CHECK(j.value(key_notfound, 47) == 47);
+ CHECK(j.value(key_notfound, integer) == integer);
+ CHECK(j.value(key_notfound, size) == size);
+
+ CHECK_THROWS_WITH_AS(Json().value(key, "default"), "[json.exception.type_error.306] cannot use value() with null", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(Json().value(key, str), "[json.exception.type_error.306] cannot use value() with null", typename Json::type_error&);
+ }
+#endif
+ }
+
+ SECTION("explicit ValueType")
+ {
+ SECTION("literal key")
+ {
+ CHECK(j.template value<string_t>("foo", "default") == "bar");
+ CHECK(j.template value<string_t>("foo", cpstr) == "bar");
+ CHECK(j.template value<string_t>("foo", castr) == "bar");
+ CHECK(j.template value<string_t>("foo", str) == "bar");
+ CHECK(j.template value<number_integer_t>("baz", 0) == 42);
+ CHECK(j.template value<number_integer_t>("baz", 47) == 42);
+ CHECK(j.template value<number_integer_t>("baz", integer) == 42);
+ CHECK(j.template value<std::size_t>("baz", 0) == 42);
+ CHECK(j.template value<std::size_t>("baz", 47) == 42);
+ CHECK(j.template value<std::size_t>("baz", size) == 42);
+
+ CHECK(j.template value<string_t>("bar", "default") == "default");
+ CHECK(j.template value<number_integer_t>("bar", 0) == 0);
+ CHECK(j.template value<number_integer_t>("bar", 47) == 47);
+ CHECK(j.template value<number_integer_t>("bar", integer) == integer);
+ CHECK(j.template value<std::size_t>("bar", 0) == 0);
+ CHECK(j.template value<std::size_t>("bar", 47) == 47);
+ CHECK(j.template value<std::size_t>("bar", size) == size);
+
+ CHECK_THROWS_WITH_AS(Json().template value<string_t>("foo", "default"), "[json.exception.type_error.306] cannot use value() with null", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(Json().template value<string_t>("foo", str), "[json.exception.type_error.306] cannot use value() with null", typename Json::type_error&);
+ }
+
+ SECTION("const char * key")
+ {
+ const char* key = "foo";
+ const char* key2 = "baz";
+ const char* key_notfound = "bar";
+
+ CHECK(j.template value<string_t>(key, "default") == "bar");
+ CHECK(j.template value<string_t>(key, cpstr) == "bar");
+ CHECK(j.template value<string_t>(key, castr) == "bar");
+ CHECK(j.template value<string_t>(key, str) == "bar");
+ CHECK(j.template value<number_integer_t>(key2, 0) == 42);
+ CHECK(j.template value<number_integer_t>(key2, 47) == 42);
+ CHECK(j.template value<number_integer_t>(key2, integer) == 42);
+ CHECK(j.template value<std::size_t>(key2, 0) == 42);
+ CHECK(j.template value<std::size_t>(key2, 47) == 42);
+ CHECK(j.template value<std::size_t>(key2, size) == 42);
+
+ CHECK(j.template value<string_t>(key_notfound, "default") == "default");
+ CHECK(j.template value<number_integer_t>(key_notfound, 0) == 0);
+ CHECK(j.template value<number_integer_t>(key_notfound, 47) == 47);
+ CHECK(j.template value<number_integer_t>(key_notfound, integer) == integer);
+ CHECK(j.template value<std::size_t>(key_notfound, 0) == 0);
+ CHECK(j.template value<std::size_t>(key_notfound, 47) == 47);
+ CHECK(j.template value<std::size_t>(key_notfound, size) == size);
+
+ CHECK_THROWS_WITH_AS(Json().template value<string_t>(key, "default"), "[json.exception.type_error.306] cannot use value() with null", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(Json().template value<string_t>(key, str), "[json.exception.type_error.306] cannot use value() with null", typename Json::type_error&);
+ }
+
+ SECTION("const char(&)[] key")
+ {
+ const char key[] = "foo"; // NOLINT(misc-const-correctness,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-avoid-c-arrays)
+ const char key2[] = "baz"; // NOLINT(misc-const-correctness,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-avoid-c-arrays)
+ const char key_notfound[] = "bar"; // NOLINT(misc-const-correctness,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-avoid-c-arrays)
+
+ CHECK(j.template value<string_t>(key, "default") == "bar");
+ CHECK(j.template value<string_t>(key, cpstr) == "bar");
+ CHECK(j.template value<string_t>(key, castr) == "bar");
+ CHECK(j.template value<string_t>(key, str) == "bar");
+ CHECK(j.template value<number_integer_t>(key2, 0) == 42);
+ CHECK(j.template value<number_integer_t>(key2, 47) == 42);
+ CHECK(j.template value<number_integer_t>(key2, integer) == 42);
+ CHECK(j.template value<std::size_t>(key2, 0) == 42);
+ CHECK(j.template value<std::size_t>(key2, 47) == 42);
+ CHECK(j.template value<std::size_t>(key2, size) == 42);
+
+ CHECK(j.template value<string_t>(key_notfound, "default") == "default");
+ CHECK(j.template value<number_integer_t>(key_notfound, 0) == 0);
+ CHECK(j.template value<number_integer_t>(key_notfound, 47) == 47);
+ CHECK(j.template value<number_integer_t>(key_notfound, integer) == integer);
+ CHECK(j.template value<std::size_t>(key_notfound, 0) == 0);
+ CHECK(j.template value<std::size_t>(key_notfound, 47) == 47);
+ CHECK(j.template value<std::size_t>(key_notfound, size) == size);
+
+ CHECK_THROWS_WITH_AS(Json().template value<string_t>(key, "default"), "[json.exception.type_error.306] cannot use value() with null", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(Json().template value<string_t>(key, str), "[json.exception.type_error.306] cannot use value() with null", typename Json::type_error&);
+ }
+
+ SECTION("string_t/object_t::key_type key")
+ {
+ string_t const key = "foo";
+ string_t const key2 = "baz";
+ string_t const key_notfound = "bar";
+
+ CHECK(j.template value<string_t>(key, "default") == "bar");
+ CHECK(j.template value<string_t>(key, cpstr) == "bar");
+ CHECK(j.template value<string_t>(key, castr) == "bar");
+ CHECK(j.template value<string_t>(key, str) == "bar");
+ CHECK(j.template value<number_integer_t>(key2, 0) == 42);
+ CHECK(j.template value<number_integer_t>(key2, 47) == 42);
+ CHECK(j.template value<std::size_t>(key2, 0) == 42);
+ CHECK(j.template value<std::size_t>(key2, 47) == 42);
+
+ CHECK(j.template value<string_t>(key_notfound, "default") == "default");
+ CHECK(j.template value<number_integer_t>(key_notfound, 0) == 0);
+ CHECK(j.template value<number_integer_t>(key_notfound, 47) == 47);
+ CHECK(j.template value<std::size_t>(key_notfound, 0) == 0);
+ CHECK(j.template value<std::size_t>(key_notfound, 47) == 47);
+
+ CHECK_THROWS_WITH_AS(Json().template value<string_t>(key, "default"), "[json.exception.type_error.306] cannot use value() with null", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(Json().template value<string_t>(key, str), "[json.exception.type_error.306] cannot use value() with null", typename Json::type_error&);
+ }
+
+#ifdef JSON_HAS_CPP_17
+ SECTION("std::string_view key")
+ {
+ std::string_view const key = "foo";
+ std::string_view const key2 = "baz";
+ std::string_view const key_notfound = "bar";
+
+ CHECK(j.template value<string_t>(key, "default") == "bar");
+ CHECK(j.template value<string_t>(key, cpstr) == "bar");
+ CHECK(j.template value<string_t>(key, castr) == "bar");
+ CHECK(j.template value<string_t>(key, str) == "bar");
+ CHECK(j.template value<number_integer_t>(key2, 0) == 42);
+ CHECK(j.template value<number_integer_t>(key2, 47) == 42);
+ CHECK(j.template value<number_integer_t>(key2, integer) == 42);
+ CHECK(j.template value<std::size_t>(key2, 0) == 42);
+ CHECK(j.template value<std::size_t>(key2, 47) == 42);
+ CHECK(j.template value<std::size_t>(key2, size) == 42);
+
+ CHECK(j.template value<string_t>(key_notfound, "default") == "default");
+ CHECK(j.template value<number_integer_t>(key_notfound, 0) == 0);
+ CHECK(j.template value<number_integer_t>(key_notfound, 47) == 47);
+ CHECK(j.template value<number_integer_t>(key_notfound, integer) == integer);
+ CHECK(j.template value<std::size_t>(key_notfound, 0) == 0);
+ CHECK(j.template value<std::size_t>(key_notfound, 47) == 47);
+ CHECK(j.template value<std::size_t>(key_notfound, size) == size);
+
+ CHECK(j.template value<std::string_view>(key, "default") == "bar");
+ CHECK(j.template value<std::string_view>(key, cpstr) == "bar");
+ CHECK(j.template value<std::string_view>(key, castr) == "bar");
+ CHECK(j.template value<std::string_view>(key, str) == "bar");
+
+ CHECK(j.template value<std::string_view>(key_notfound, "default") == "default");
+
+ CHECK_THROWS_WITH_AS(Json().template value<string_t>(key, "default"), "[json.exception.type_error.306] cannot use value() with null", typename Json::type_error&);
+ CHECK_THROWS_WITH_AS(Json().template value<string_t>(key, str), "[json.exception.type_error.306] cannot use value() with null", typename Json::type_error&);
+ }
+#endif
+ }
+}
+
+#ifdef JSON_HAS_CPP_17
+TEST_CASE("operator[] with user-defined std::string_view-convertible types")
+{
+ using json = nlohmann::json;
+
+ class TestClass
+ {
+ std::string key_data_ = "foo";
+
+ public:
+ operator std::string_view() const
+ {
+ return key_data_;
+ }
+ };
+
+ struct TestStruct
+ {
+ operator std::string_view() const
+ {
+ return "bar";
+ }
+ };
+
+ json j = {{"foo", "from_class"}, {"bar", "from_struct"}};
+ TestClass foo_obj;
+ TestStruct bar_obj;
+
+ SECTION("read access")
+ {
+ CHECK(j[foo_obj] == "from_class");
+ CHECK(j[TestClass{}] == "from_class");
+ CHECK(j[bar_obj] == "from_struct");
+ CHECK(j[TestStruct{}] == "from_struct");
+ }
+
+ SECTION("write access")
+ {
+ j[TestClass{}] = "updated_class";
+ j[TestStruct{}] = "updated_struct";
+ CHECK(j["foo"] == "updated_class");
+ CHECK(j["bar"] == "updated_struct");
+
+ SECTION("direct std::string_view access")
+ {
+ CHECK(j[std::string_view{"foo"}] == "updated_class");
+ CHECK(j[std::string_view{"bar"}] == "updated_struct");
+ }
+ }
+}
+#endif
diff --git a/json4cpp/tests/src/unit-hash.cpp b/json4cpp/tests/src/unit-hash.cpp
new file mode 100644
index 0000000000..c161efa6e9
--- /dev/null
+++ b/json4cpp/tests/src/unit-hash.cpp
@@ -0,0 +1,113 @@
+// __ _____ _____ _____
+// __| | __| | | | 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 json = nlohmann::json;
+using ordered_json = nlohmann::ordered_json;
+
+#include <set>
+
+TEST_CASE("hash<nlohmann::json>")
+{
+ // Collect hashes for different JSON values and make sure that they are distinct
+ // We cannot compare against fixed values, because the implementation of
+ // std::hash may differ between compilers.
+
+ std::set<std::size_t> hashes;
+
+ // null
+ hashes.insert(std::hash<json> {}(json(nullptr)));
+
+ // boolean
+ hashes.insert(std::hash<json> {}(json(true)));
+ hashes.insert(std::hash<json> {}(json(false)));
+
+ // string
+ hashes.insert(std::hash<json> {}(json("")));
+ hashes.insert(std::hash<json> {}(json("foo")));
+
+ // number
+ hashes.insert(std::hash<json> {}(json(0)));
+ hashes.insert(std::hash<json> {}(json(static_cast<unsigned>(0))));
+
+ hashes.insert(std::hash<json> {}(json(-1)));
+ hashes.insert(std::hash<json> {}(json(0.0)));
+ hashes.insert(std::hash<json> {}(json(42.23)));
+
+ // array
+ hashes.insert(std::hash<json> {}(json::array()));
+ hashes.insert(std::hash<json> {}(json::array({1, 2, 3})));
+
+ // object
+ hashes.insert(std::hash<json> {}(json::object()));
+ hashes.insert(std::hash<json> {}(json::object({{"foo", "bar"}})));
+
+ // binary
+ hashes.insert(std::hash<json> {}(json::binary({})));
+ hashes.insert(std::hash<json> {}(json::binary({}, 0)));
+ hashes.insert(std::hash<json> {}(json::binary({}, 42)));
+ hashes.insert(std::hash<json> {}(json::binary({1, 2, 3})));
+ hashes.insert(std::hash<json> {}(json::binary({1, 2, 3}, 0)));
+ hashes.insert(std::hash<json> {}(json::binary({1, 2, 3}, 42)));
+
+ // discarded
+ hashes.insert(std::hash<json> {}(json(json::value_t::discarded)));
+
+ CHECK(hashes.size() == 21);
+}
+
+TEST_CASE("hash<nlohmann::ordered_json>")
+{
+ // Collect hashes for different JSON values and make sure that they are distinct
+ // We cannot compare against fixed values, because the implementation of
+ // std::hash may differ between compilers.
+
+ std::set<std::size_t> hashes;
+
+ // null
+ hashes.insert(std::hash<ordered_json> {}(ordered_json(nullptr)));
+
+ // boolean
+ hashes.insert(std::hash<ordered_json> {}(ordered_json(true)));
+ hashes.insert(std::hash<ordered_json> {}(ordered_json(false)));
+
+ // string
+ hashes.insert(std::hash<ordered_json> {}(ordered_json("")));
+ hashes.insert(std::hash<ordered_json> {}(ordered_json("foo")));
+
+ // number
+ hashes.insert(std::hash<ordered_json> {}(ordered_json(0)));
+ hashes.insert(std::hash<ordered_json> {}(ordered_json(static_cast<unsigned>(0))));
+
+ hashes.insert(std::hash<ordered_json> {}(ordered_json(-1)));
+ hashes.insert(std::hash<ordered_json> {}(ordered_json(0.0)));
+ hashes.insert(std::hash<ordered_json> {}(ordered_json(42.23)));
+
+ // array
+ hashes.insert(std::hash<ordered_json> {}(ordered_json::array()));
+ hashes.insert(std::hash<ordered_json> {}(ordered_json::array({1, 2, 3})));
+
+ // object
+ hashes.insert(std::hash<ordered_json> {}(ordered_json::object()));
+ hashes.insert(std::hash<ordered_json> {}(ordered_json::object({{"foo", "bar"}})));
+
+ // binary
+ hashes.insert(std::hash<ordered_json> {}(ordered_json::binary({})));
+ hashes.insert(std::hash<ordered_json> {}(ordered_json::binary({}, 0)));
+ hashes.insert(std::hash<ordered_json> {}(ordered_json::binary({}, 42)));
+ hashes.insert(std::hash<ordered_json> {}(ordered_json::binary({1, 2, 3})));
+ hashes.insert(std::hash<ordered_json> {}(ordered_json::binary({1, 2, 3}, 0)));
+ hashes.insert(std::hash<ordered_json> {}(ordered_json::binary({1, 2, 3}, 42)));
+
+ // discarded
+ hashes.insert(std::hash<ordered_json> {}(ordered_json(ordered_json::value_t::discarded)));
+
+ CHECK(hashes.size() == 21);
+}
diff --git a/json4cpp/tests/src/unit-inspection.cpp b/json4cpp/tests/src/unit-inspection.cpp
new file mode 100644
index 0000000000..c70913d5b1
--- /dev/null
+++ b/json4cpp/tests/src/unit-inspection.cpp
@@ -0,0 +1,459 @@
+// __ _____ _____ _____
+// __| | __| | | | 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;
+
+#include <fstream>
+#include <sstream>
+#include "make_test_data_available.hpp"
+
+TEST_CASE("object inspection")
+{
+ SECTION("convenience type checker")
+ {
+ SECTION("object")
+ {
+ json const j {{"foo", 1}, {"bar", false}};
+ CHECK(!j.is_null());
+ CHECK(!j.is_boolean());
+ CHECK(!j.is_number());
+ CHECK(!j.is_number_integer());
+ CHECK(!j.is_number_unsigned());
+ CHECK(!j.is_number_float());
+ CHECK(!j.is_binary());
+ CHECK(j.is_object());
+ CHECK(!j.is_array());
+ CHECK(!j.is_string());
+ CHECK(!j.is_discarded());
+ CHECK(!j.is_primitive());
+ CHECK(j.is_structured());
+ }
+
+ SECTION("array")
+ {
+ json const j {"foo", 1, 1u, 42.23, false};
+ CHECK(!j.is_null());
+ CHECK(!j.is_boolean());
+ CHECK(!j.is_number());
+ CHECK(!j.is_number_integer());
+ CHECK(!j.is_number_unsigned());
+ CHECK(!j.is_number_float());
+ CHECK(!j.is_binary());
+ CHECK(!j.is_object());
+ CHECK(j.is_array());
+ CHECK(!j.is_string());
+ CHECK(!j.is_discarded());
+ CHECK(!j.is_primitive());
+ CHECK(j.is_structured());
+ }
+
+ SECTION("null")
+ {
+ json const j(nullptr);
+ CHECK(j.is_null());
+ CHECK(!j.is_boolean());
+ CHECK(!j.is_number());
+ CHECK(!j.is_number_integer());
+ CHECK(!j.is_number_unsigned());
+ CHECK(!j.is_number_float());
+ CHECK(!j.is_binary());
+ CHECK(!j.is_object());
+ CHECK(!j.is_array());
+ CHECK(!j.is_string());
+ CHECK(!j.is_discarded());
+ CHECK(j.is_primitive());
+ CHECK(!j.is_structured());
+ }
+
+ SECTION("boolean")
+ {
+ json const j(true);
+ CHECK(!j.is_null());
+ CHECK(j.is_boolean());
+ CHECK(!j.is_number());
+ CHECK(!j.is_number_integer());
+ CHECK(!j.is_number_unsigned());
+ CHECK(!j.is_number_float());
+ CHECK(!j.is_binary());
+ CHECK(!j.is_object());
+ CHECK(!j.is_array());
+ CHECK(!j.is_string());
+ CHECK(!j.is_discarded());
+ CHECK(j.is_primitive());
+ CHECK(!j.is_structured());
+ }
+
+ SECTION("string")
+ {
+ json const j("Hello world");
+ CHECK(!j.is_null());
+ CHECK(!j.is_boolean());
+ CHECK(!j.is_number());
+ CHECK(!j.is_number_integer());
+ CHECK(!j.is_number_unsigned());
+ CHECK(!j.is_number_float());
+ CHECK(!j.is_binary());
+ CHECK(!j.is_object());
+ CHECK(!j.is_array());
+ CHECK(j.is_string());
+ CHECK(!j.is_discarded());
+ CHECK(j.is_primitive());
+ CHECK(!j.is_structured());
+ }
+
+ SECTION("number (integer)")
+ {
+ json const j(42);
+ CHECK(!j.is_null());
+ CHECK(!j.is_boolean());
+ CHECK(j.is_number());
+ CHECK(j.is_number_integer());
+ CHECK(!j.is_number_unsigned());
+ CHECK(!j.is_number_float());
+ CHECK(!j.is_binary());
+ CHECK(!j.is_object());
+ CHECK(!j.is_array());
+ CHECK(!j.is_string());
+ CHECK(!j.is_discarded());
+ CHECK(j.is_primitive());
+ CHECK(!j.is_structured());
+ }
+
+ SECTION("number (unsigned)")
+ {
+ json const j(42u);
+ CHECK(!j.is_null());
+ CHECK(!j.is_boolean());
+ CHECK(j.is_number());
+ CHECK(j.is_number_integer());
+ CHECK(j.is_number_unsigned());
+ CHECK(!j.is_number_float());
+ CHECK(!j.is_binary());
+ CHECK(!j.is_object());
+ CHECK(!j.is_array());
+ CHECK(!j.is_string());
+ CHECK(!j.is_discarded());
+ CHECK(j.is_primitive());
+ CHECK(!j.is_structured());
+ }
+
+ SECTION("number (floating-point)")
+ {
+ json const j(42.23);
+ CHECK(!j.is_null());
+ CHECK(!j.is_boolean());
+ CHECK(j.is_number());
+ CHECK(!j.is_number_integer());
+ CHECK(!j.is_number_unsigned());
+ CHECK(j.is_number_float());
+ CHECK(!j.is_binary());
+ CHECK(!j.is_object());
+ CHECK(!j.is_array());
+ CHECK(!j.is_string());
+ CHECK(!j.is_discarded());
+ CHECK(j.is_primitive());
+ CHECK(!j.is_structured());
+ }
+
+ SECTION("binary")
+ {
+ json const j(json::value_t::binary);
+ CHECK(!j.is_null());
+ CHECK(!j.is_boolean());
+ CHECK(!j.is_number());
+ CHECK(!j.is_number_integer());
+ CHECK(!j.is_number_unsigned());
+ CHECK(!j.is_number_float());
+ CHECK(j.is_binary());
+ CHECK(!j.is_object());
+ CHECK(!j.is_array());
+ CHECK(!j.is_string());
+ CHECK(!j.is_discarded());
+ CHECK(j.is_primitive());
+ CHECK(!j.is_structured());
+ }
+
+ SECTION("discarded")
+ {
+ json const j(json::value_t::discarded);
+ CHECK(!j.is_null());
+ CHECK(!j.is_boolean());
+ CHECK(!j.is_number());
+ CHECK(!j.is_number_integer());
+ CHECK(!j.is_number_unsigned());
+ CHECK(!j.is_number_float());
+ CHECK(!j.is_binary());
+ CHECK(!j.is_object());
+ CHECK(!j.is_array());
+ CHECK(!j.is_string());
+ CHECK(j.is_discarded());
+ CHECK(!j.is_primitive());
+ CHECK(!j.is_structured());
+ }
+ }
+
+ SECTION("serialization")
+ {
+ json const j {{"object", json::object()}, {"array", {1, 2, 3, 4}}, {"number", 42}, {"boolean", false}, {"null", nullptr}, {"string", "Hello world"} };
+
+ SECTION("no indent / indent=-1")
+ {
+ CHECK(j.dump() ==
+ "{\"array\":[1,2,3,4],\"boolean\":false,\"null\":null,\"number\":42,\"object\":{},\"string\":\"Hello world\"}");
+
+ CHECK(j.dump() == j.dump(-1));
+ }
+
+ SECTION("indent=0")
+ {
+ CHECK(j.dump(0) ==
+ "{\n\"array\": [\n1,\n2,\n3,\n4\n],\n\"boolean\": false,\n\"null\": null,\n\"number\": 42,\n\"object\": {},\n\"string\": \"Hello world\"\n}");
+ }
+
+ SECTION("indent=1, space='\t'")
+ {
+ CHECK(j.dump(1, '\t') ==
+ "{\n\t\"array\": [\n\t\t1,\n\t\t2,\n\t\t3,\n\t\t4\n\t],\n\t\"boolean\": false,\n\t\"null\": null,\n\t\"number\": 42,\n\t\"object\": {},\n\t\"string\": \"Hello world\"\n}");
+ }
+
+ SECTION("indent=4")
+ {
+ CHECK(j.dump(4) ==
+ "{\n \"array\": [\n 1,\n 2,\n 3,\n 4\n ],\n \"boolean\": false,\n \"null\": null,\n \"number\": 42,\n \"object\": {},\n \"string\": \"Hello world\"\n}");
+ }
+
+ SECTION("indent=x")
+ {
+ CHECK(j.dump().size() == 94);
+ CHECK(j.dump(1).size() == 127);
+ CHECK(j.dump(2).size() == 142);
+ CHECK(j.dump(512).size() == 7792);
+
+ // important test, because it yields a resize of the indent_string
+ // inside the dump() function
+ CHECK(j.dump(1024).size() == 15472);
+
+ const auto binary = json::binary({1, 2, 3}, 128);
+ CHECK(binary.dump(1024).size() == 2086);
+ }
+
+ SECTION("dump and floating-point numbers")
+ {
+ auto s = json(42.23).dump();
+ CHECK(s.find("42.23") != std::string::npos);
+ }
+
+ SECTION("dump and small floating-point numbers")
+ {
+ auto s = json(1.23456e-78).dump();
+ CHECK(s.find("1.23456e-78") != std::string::npos);
+ }
+
+ SECTION("dump and non-ASCII characters")
+ {
+ CHECK(json("ä").dump() == "\"ä\"");
+ CHECK(json("Ö").dump() == "\"Ö\"");
+ CHECK(json("❤️").dump() == "\"❤️\"");
+ }
+
+ SECTION("dump with ensure_ascii and non-ASCII characters")
+ {
+ CHECK(json("ä").dump(-1, ' ', true) == "\"\\u00e4\"");
+ CHECK(json("Ö").dump(-1, ' ', true) == "\"\\u00d6\"");
+ CHECK(json("❤️").dump(-1, ' ', true) == "\"\\u2764\\ufe0f\"");
+ }
+
+ SECTION("full Unicode escaping to ASCII")
+ {
+ SECTION("parsing yields the same JSON value")
+ {
+ std::ifstream f_escaped(TEST_DATA_DIRECTORY "/json_nlohmann_tests/all_unicode_ascii.json");
+ std::ifstream f_unescaped(TEST_DATA_DIRECTORY "/json_nlohmann_tests/all_unicode.json");
+
+ const json j1 = json::parse(f_escaped);
+ const json j2 = json::parse(f_unescaped);
+ CHECK(j1 == j2);
+ }
+
+ SECTION("dumping yields the same JSON text")
+ {
+ std::ifstream f_escaped(TEST_DATA_DIRECTORY "/json_nlohmann_tests/all_unicode_ascii.json");
+ std::ifstream f_unescaped(TEST_DATA_DIRECTORY "/json_nlohmann_tests/all_unicode.json");
+
+ json const value = json::parse(f_unescaped);
+ const std::string text = value.dump(4, ' ', true);
+
+ const std::string expected((std::istreambuf_iterator<char>(f_escaped)),
+ std::istreambuf_iterator<char>());
+ CHECK(text == expected);
+ }
+ }
+
+ SECTION("serialization of discarded element")
+ {
+ json const j_discarded(json::value_t::discarded);
+ CHECK(j_discarded.dump() == "<discarded>");
+ }
+
+ SECTION("check that precision is reset after serialization")
+ {
+ // create stringstream and set precision
+ std::stringstream ss;
+ ss.precision(3);
+ ss << 3.141592653589793 << std::fixed;
+ CHECK(ss.str() == "3.14");
+
+ // reset stringstream
+ ss.str(std::string());
+
+ // use stringstream for JSON serialization
+ json const j_number = 3.14159265358979;
+ ss << j_number;
+
+ // check that precision has been overridden during serialization
+ CHECK(ss.str() == "3.14159265358979");
+
+ // check that precision has been restored
+ CHECK(ss.precision() == 3);
+ }
+ }
+
+ SECTION("round trips")
+ {
+ for (const auto& s :
+ {"3.141592653589793", "1000000000000000010E5"
+ })
+ {
+ json const j1 = json::parse(s);
+ const std::string s1 = j1.dump();
+ json const j2 = json::parse(s1);
+ std::string s2 = j2.dump();
+ CHECK(s1 == s2);
+ }
+ }
+
+ SECTION("return the type of the object (explicit)")
+ {
+ SECTION("null")
+ {
+ json const j = nullptr;
+ CHECK(j.type() == json::value_t::null);
+ }
+
+ SECTION("object")
+ {
+ json const j = {{"foo", "bar"}};
+ CHECK(j.type() == json::value_t::object);
+ }
+
+ SECTION("array")
+ {
+ json const j = {1, 2, 3, 4};
+ CHECK(j.type() == json::value_t::array);
+ }
+
+ SECTION("boolean")
+ {
+ json const j = true;
+ CHECK(j.type() == json::value_t::boolean);
+ }
+
+ SECTION("string")
+ {
+ json const j = "Hello world";
+ CHECK(j.type() == json::value_t::string);
+ }
+
+ SECTION("number (integer)")
+ {
+ json const j = 23;
+ CHECK(j.type() == json::value_t::number_integer);
+ }
+
+ SECTION("number (unsigned)")
+ {
+ json const j = 23u;
+ CHECK(j.type() == json::value_t::number_unsigned);
+ }
+
+ SECTION("number (floating-point)")
+ {
+ json const j = 42.23;
+ CHECK(j.type() == json::value_t::number_float);
+ }
+ }
+
+ SECTION("return the type of the object (implicit)")
+ {
+ SECTION("null")
+ {
+ json const j = nullptr;
+ const json::value_t t = j;
+ CHECK(t == j.type());
+ }
+
+ SECTION("object")
+ {
+ json const j = {{"foo", "bar"}};
+ const json::value_t t = j;
+ CHECK(t == j.type());
+ }
+
+ SECTION("array")
+ {
+ json const j = {1, 2, 3, 4};
+ const json::value_t t = j;
+ CHECK(t == j.type());
+ }
+
+ SECTION("boolean")
+ {
+ json const j = true;
+ const json::value_t t = j;
+ CHECK(t == j.type());
+ }
+
+ SECTION("string")
+ {
+ json const j = "Hello world";
+ const json::value_t t = j;
+ CHECK(t == j.type());
+ }
+
+ SECTION("number (integer)")
+ {
+ json const j = 23;
+ const json::value_t t = j;
+ CHECK(t == j.type());
+ }
+
+ SECTION("number (unsigned)")
+ {
+ json const j = 23u;
+ const json::value_t t = j;
+ CHECK(t == j.type());
+ }
+
+ SECTION("number (floating-point)")
+ {
+ json const j = 42.23;
+ const json::value_t t = j;
+ CHECK(t == j.type());
+ }
+
+ SECTION("binary")
+ {
+ json const j = json::binary({});
+ const json::value_t t = j;
+ CHECK(t == j.type());
+ }
+ }
+}
diff --git a/json4cpp/tests/src/unit-items.cpp b/json4cpp/tests/src/unit-items.cpp
new file mode 100644
index 0000000000..fa89484471
--- /dev/null
+++ b/json4cpp/tests/src/unit-items.cpp
@@ -0,0 +1,1433 @@
+// __ _____ _____ _____
+// __| | __| | | | 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;
+
+// This test suite uses range for loops where values are copied. This is inefficient in usual code, but required to achieve 100% coverage.
+DOCTEST_GCC_SUPPRESS_WARNING_PUSH
+#if DOCTEST_GCC >= DOCTEST_COMPILER(11, 0, 0)
+ DOCTEST_GCC_SUPPRESS_WARNING("-Wrange-loop-construct")
+#endif
+DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wrange-loop-construct")
+
+TEST_CASE("iterator_wrapper")
+{
+ SECTION("object")
+ {
+ SECTION("value")
+ {
+ json j = { {"A", 1}, {"B", 2} };
+ int counter = 1;
+
+ for (auto i : json::iterator_wrapper(j)) // NOLINT(performance-for-range-copy)
+ {
+ switch (counter++)
+ {
+ case 1:
+ {
+ CHECK(i.key() == "A");
+ CHECK(i.value() == json(1));
+ break;
+ }
+
+ case 2:
+ {
+ CHECK(i.key() == "B");
+ CHECK(i.value() == json(2));
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ CHECK(counter == 3);
+ }
+
+ SECTION("reference")
+ {
+ json j = { {"A", 1}, {"B", 2} };
+ int counter = 1;
+
+ for (auto& i : json::iterator_wrapper(j)) // NOLINT(readability-qualified-auto)
+ {
+ switch (counter++)
+ {
+ case 1:
+ {
+ CHECK(i.key() == "A");
+ CHECK(i.value() == json(1));
+
+ // change the value
+ i.value() = json(11);
+ CHECK(i.value() == json(11));
+ break;
+ }
+
+ case 2:
+ {
+ CHECK(i.key() == "B");
+ CHECK(i.value() == json(2));
+
+ // change the value
+ i.value() = json(22);
+ CHECK(i.value() == json(22));
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ CHECK(counter == 3);
+
+ // check if values where changed
+ CHECK(j == json({ {"A", 11}, {"B", 22} }));
+ }
+
+ SECTION("const value")
+ {
+ json j = { {"A", 1}, {"B", 2} };
+ int counter = 1;
+
+ for (const auto i : json::iterator_wrapper(j)) // NOLINT(performance-for-range-copy)
+ {
+ switch (counter++)
+ {
+ case 1:
+ {
+ CHECK(i.key() == "A");
+ CHECK(i.value() == json(1));
+ break;
+ }
+
+ case 2:
+ {
+ CHECK(i.key() == "B");
+ CHECK(i.value() == json(2));
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ CHECK(counter == 3);
+ }
+
+ SECTION("const reference")
+ {
+ json j = { {"A", 1}, {"B", 2} };
+ int counter = 1;
+
+ for (const auto& i : json::iterator_wrapper(j))
+ {
+ switch (counter++)
+ {
+ case 1:
+ {
+ CHECK(i.key() == "A");
+ CHECK(i.value() == json(1));
+ break;
+ }
+
+ case 2:
+ {
+ CHECK(i.key() == "B");
+ CHECK(i.value() == json(2));
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ CHECK(counter == 3);
+ }
+ }
+
+ SECTION("const object")
+ {
+ SECTION("value")
+ {
+ const json j = { {"A", 1}, {"B", 2} };
+ int counter = 1;
+
+ for (auto i : json::iterator_wrapper(j)) // NOLINT(performance-for-range-copy)
+ {
+ switch (counter++)
+ {
+ case 1:
+ {
+ CHECK(i.key() == "A");
+ CHECK(i.value() == json(1));
+ break;
+ }
+
+ case 2:
+ {
+ CHECK(i.key() == "B");
+ CHECK(i.value() == json(2));
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ CHECK(counter == 3);
+ }
+
+ SECTION("reference")
+ {
+ const json j = { {"A", 1}, {"B", 2} };
+ int counter = 1;
+
+ for (auto& i : json::iterator_wrapper(j)) // NOLINT(readability-qualified-auto)
+ {
+ switch (counter++)
+ {
+ case 1:
+ {
+ CHECK(i.key() == "A");
+ CHECK(i.value() == json(1));
+ break;
+ }
+
+ case 2:
+ {
+ CHECK(i.key() == "B");
+ CHECK(i.value() == json(2));
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ CHECK(counter == 3);
+ }
+
+ SECTION("const value")
+ {
+ const json j = { {"A", 1}, {"B", 2} };
+ int counter = 1;
+
+ for (const auto i : json::iterator_wrapper(j)) // NOLINT(performance-for-range-copy)
+ {
+ switch (counter++)
+ {
+ case 1:
+ {
+ CHECK(i.key() == "A");
+ CHECK(i.value() == json(1));
+ break;
+ }
+
+ case 2:
+ {
+ CHECK(i.key() == "B");
+ CHECK(i.value() == json(2));
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ CHECK(counter == 3);
+ }
+
+ SECTION("const reference")
+ {
+ const json j = { {"A", 1}, {"B", 2} };
+ int counter = 1;
+
+ for (const auto& i : json::iterator_wrapper(j))
+ {
+ switch (counter++)
+ {
+ case 1:
+ {
+ CHECK(i.key() == "A");
+ CHECK(i.value() == json(1));
+ break;
+ }
+
+ case 2:
+ {
+ CHECK(i.key() == "B");
+ CHECK(i.value() == json(2));
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ CHECK(counter == 3);
+ }
+ }
+
+ SECTION("array")
+ {
+ SECTION("value")
+ {
+ json j = { "A", "B" };
+ int counter = 1;
+
+ for (auto i : json::iterator_wrapper(j)) // NOLINT(performance-for-range-copy)
+ {
+ switch (counter++)
+ {
+ case 1:
+ {
+ CHECK(i.key() == "0");
+ CHECK(i.value() == "A");
+ break;
+ }
+
+ case 2:
+ {
+ CHECK(i.key() == "1");
+ CHECK(i.value() == "B");
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ CHECK(counter == 3);
+ }
+
+ SECTION("reference")
+ {
+ json j = { "A", "B" };
+ int counter = 1;
+
+ for (auto& i : json::iterator_wrapper(j)) // NOLINT(readability-qualified-auto)
+ {
+ switch (counter++)
+ {
+ case 1:
+ {
+ CHECK(i.key() == "0");
+ CHECK(i.value() == "A");
+
+ // change the value
+ i.value() = "AA";
+ CHECK(i.value() == "AA");
+ break;
+ }
+
+ case 2:
+ {
+ CHECK(i.key() == "1");
+ CHECK(i.value() == "B");
+
+ // change the value
+ i.value() = "BB";
+ CHECK(i.value() == "BB");
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ CHECK(counter == 3);
+
+ // check if values where changed
+ CHECK(j == json({ "AA", "BB" }));
+ }
+
+ SECTION("const value")
+ {
+ json j = { "A", "B" };
+ int counter = 1;
+
+ for (const auto i : json::iterator_wrapper(j)) // NOLINT(performance-for-range-copy)
+ {
+ switch (counter++)
+ {
+ case 1:
+ {
+ CHECK(i.key() == "0");
+ CHECK(i.value() == "A");
+ break;
+ }
+
+ case 2:
+ {
+ CHECK(i.key() == "1");
+ CHECK(i.value() == "B");
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ CHECK(counter == 3);
+ }
+
+ SECTION("const reference")
+ {
+ json j = { "A", "B" };
+ int counter = 1;
+
+ for (const auto& i : json::iterator_wrapper(j))
+ {
+ switch (counter++)
+ {
+ case 1:
+ {
+ CHECK(i.key() == "0");
+ CHECK(i.value() == "A");
+ break;
+ }
+
+ case 2:
+ {
+ CHECK(i.key() == "1");
+ CHECK(i.value() == "B");
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ CHECK(counter == 3);
+ }
+ }
+
+ SECTION("const array")
+ {
+ SECTION("value")
+ {
+ const json j = { "A", "B" };
+ int counter = 1;
+
+ for (auto i : json::iterator_wrapper(j)) // NOLINT(performance-for-range-copy)
+ {
+ switch (counter++)
+ {
+ case 1:
+ {
+ CHECK(i.key() == "0");
+ CHECK(i.value() == "A");
+ break;
+ }
+
+ case 2:
+ {
+ CHECK(i.key() == "1");
+ CHECK(i.value() == "B");
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ CHECK(counter == 3);
+ }
+
+ SECTION("reference")
+ {
+ const json j = { "A", "B" };
+ int counter = 1;
+
+ for (auto& i : json::iterator_wrapper(j)) // NOLINT(readability-qualified-auto)
+ {
+ switch (counter++)
+ {
+ case 1:
+ {
+ CHECK(i.key() == "0");
+ CHECK(i.value() == "A");
+ break;
+ }
+
+ case 2:
+ {
+ CHECK(i.key() == "1");
+ CHECK(i.value() == "B");
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ CHECK(counter == 3);
+ }
+
+ SECTION("const value")
+ {
+ const json j = { "A", "B" };
+ int counter = 1;
+
+ for (const auto i : json::iterator_wrapper(j)) // NOLINT(performance-for-range-copy)
+ {
+ switch (counter++)
+ {
+ case 1:
+ {
+ CHECK(i.key() == "0");
+ CHECK(i.value() == "A");
+ break;
+ }
+
+ case 2:
+ {
+ CHECK(i.key() == "1");
+ CHECK(i.value() == "B");
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ CHECK(counter == 3);
+ }
+
+ SECTION("const reference")
+ {
+ const json j = { "A", "B" };
+ int counter = 1;
+
+ for (const auto& i : json::iterator_wrapper(j))
+ {
+ switch (counter++)
+ {
+ case 1:
+ {
+ CHECK(i.key() == "0");
+ CHECK(i.value() == "A");
+ break;
+ }
+
+ case 2:
+ {
+ CHECK(i.key() == "1");
+ CHECK(i.value() == "B");
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ CHECK(counter == 3);
+ }
+ }
+
+ SECTION("primitive")
+ {
+ SECTION("value")
+ {
+ json j = 1;
+ int counter = 1;
+
+ for (auto i : json::iterator_wrapper(j)) // NOLINT(performance-for-range-copy)
+ {
+ ++counter;
+ CHECK(i.key() == "");
+ CHECK(i.value() == json(1));
+ }
+
+ CHECK(counter == 2);
+ }
+
+ SECTION("reference")
+ {
+ json j = 1;
+ int counter = 1;
+
+ for (auto& i : json::iterator_wrapper(j)) // NOLINT(readability-qualified-auto)
+ {
+ ++counter;
+ CHECK(i.key() == "");
+ CHECK(i.value() == json(1));
+
+ // change value
+ i.value() = json(2);
+ }
+
+ CHECK(counter == 2);
+
+ // check if value has changed
+ CHECK(j == json(2));
+ }
+
+ SECTION("const value")
+ {
+ json j = 1;
+ int counter = 1;
+
+ for (const auto i : json::iterator_wrapper(j)) // NOLINT(performance-for-range-copy)
+ {
+ ++counter;
+ CHECK(i.key() == "");
+ CHECK(i.value() == json(1));
+ }
+
+ CHECK(counter == 2);
+ }
+
+ SECTION("const reference")
+ {
+ json j = 1;
+ int counter = 1;
+
+ for (const auto& i : json::iterator_wrapper(j))
+ {
+ ++counter;
+ CHECK(i.key() == "");
+ CHECK(i.value() == json(1));
+ }
+
+ CHECK(counter == 2);
+ }
+ }
+
+ SECTION("const primitive")
+ {
+ SECTION("value")
+ {
+ const json j = 1;
+ int counter = 1;
+
+ for (auto i : json::iterator_wrapper(j)) // NOLINT(performance-for-range-copy)
+ {
+ ++counter;
+ CHECK(i.key() == "");
+ CHECK(i.value() == json(1));
+ }
+
+ CHECK(counter == 2);
+ }
+
+ SECTION("reference")
+ {
+ const json j = 1;
+ int counter = 1;
+
+ for (auto& i : json::iterator_wrapper(j)) // NOLINT(readability-qualified-auto)
+ {
+ ++counter;
+ CHECK(i.key() == "");
+ CHECK(i.value() == json(1));
+ }
+
+ CHECK(counter == 2);
+ }
+
+ SECTION("const value")
+ {
+ const json j = 1;
+ int counter = 1;
+
+ for (const auto i : json::iterator_wrapper(j)) // NOLINT(performance-for-range-copy)
+ {
+ ++counter;
+ CHECK(i.key() == "");
+ CHECK(i.value() == json(1));
+ }
+
+ CHECK(counter == 2);
+ }
+
+ SECTION("const reference")
+ {
+ const json j = 1;
+ int counter = 1;
+
+ for (const auto& i : json::iterator_wrapper(j))
+ {
+ ++counter;
+ CHECK(i.key() == "");
+ CHECK(i.value() == json(1));
+ }
+
+ CHECK(counter == 2);
+ }
+ }
+}
+
+TEST_CASE("items()")
+{
+ SECTION("object")
+ {
+ SECTION("value")
+ {
+ json j = { {"A", 1}, {"B", 2} };
+ int counter = 1;
+
+ for (auto i : j.items()) // NOLINT(performance-for-range-copy)
+ {
+ switch (counter++)
+ {
+ case 1:
+ {
+ CHECK(i.key() == "A");
+ CHECK(i.value() == json(1));
+ break;
+ }
+
+ case 2:
+ {
+ CHECK(i.key() == "B");
+ CHECK(i.value() == json(2));
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ CHECK(counter == 3);
+ }
+
+ SECTION("reference")
+ {
+ json j = { {"A", 1}, {"B", 2} };
+ int counter = 1;
+
+ for (auto& i : j.items()) // NOLINT(readability-qualified-auto)
+ {
+ switch (counter++)
+ {
+ case 1:
+ {
+ CHECK(i.key() == "A");
+ CHECK(i.value() == json(1));
+
+ // change the value
+ i.value() = json(11);
+ CHECK(i.value() == json(11));
+ break;
+ }
+
+ case 2:
+ {
+ CHECK(i.key() == "B");
+ CHECK(i.value() == json(2));
+
+ // change the value
+ i.value() = json(22);
+ CHECK(i.value() == json(22));
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ CHECK(counter == 3);
+
+ // check if values where changed
+ CHECK(j == json({ {"A", 11}, {"B", 22} }));
+ }
+
+ SECTION("const value")
+ {
+ json j = { {"A", 1}, {"B", 2} };
+ int counter = 1;
+
+ for (const auto i : j.items()) // NOLINT(performance-for-range-copy)
+ {
+ switch (counter++)
+ {
+ case 1:
+ {
+ CHECK(i.key() == "A");
+ CHECK(i.value() == json(1));
+ break;
+ }
+
+ case 2:
+ {
+ CHECK(i.key() == "B");
+ CHECK(i.value() == json(2));
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ CHECK(counter == 3);
+ }
+
+ SECTION("const reference")
+ {
+ json j = { {"A", 1}, {"B", 2} };
+ int counter = 1;
+
+ for (const auto& i : j.items())
+ {
+ switch (counter++)
+ {
+ case 1:
+ {
+ CHECK(i.key() == "A");
+ CHECK(i.value() == json(1));
+ break;
+ }
+
+ case 2:
+ {
+ CHECK(i.key() == "B");
+ CHECK(i.value() == json(2));
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ CHECK(counter == 3);
+ }
+
+#ifdef JSON_HAS_CPP_17
+ SECTION("structured bindings")
+ {
+ json j = { {"A", 1}, {"B", 2} };
+
+ std::map<std::string, int> m;
+
+ for (auto const&[key, value] : j.items())
+ {
+ m.emplace(key, value);
+ }
+
+ CHECK(j.get<decltype(m)>() == m);
+ }
+#endif
+ }
+
+ SECTION("const object")
+ {
+ SECTION("value")
+ {
+ const json j = { {"A", 1}, {"B", 2} };
+ int counter = 1;
+
+ for (auto i : j.items()) // NOLINT(performance-for-range-copy)
+ {
+ switch (counter++)
+ {
+ case 1:
+ {
+ CHECK(i.key() == "A");
+ CHECK(i.value() == json(1));
+ break;
+ }
+
+ case 2:
+ {
+ CHECK(i.key() == "B");
+ CHECK(i.value() == json(2));
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ CHECK(counter == 3);
+ }
+
+ SECTION("reference")
+ {
+ const json j = { {"A", 1}, {"B", 2} };
+ int counter = 1;
+
+ for (auto& i : j.items()) // NOLINT(readability-qualified-auto)
+ {
+ switch (counter++)
+ {
+ case 1:
+ {
+ CHECK(i.key() == "A");
+ CHECK(i.value() == json(1));
+ break;
+ }
+
+ case 2:
+ {
+ CHECK(i.key() == "B");
+ CHECK(i.value() == json(2));
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ CHECK(counter == 3);
+ }
+
+ SECTION("const value")
+ {
+ const json j = { {"A", 1}, {"B", 2} };
+ int counter = 1;
+
+ for (const auto i : j.items()) // NOLINT(performance-for-range-copy)
+ {
+ switch (counter++)
+ {
+ case 1:
+ {
+ CHECK(i.key() == "A");
+ CHECK(i.value() == json(1));
+ break;
+ }
+
+ case 2:
+ {
+ CHECK(i.key() == "B");
+ CHECK(i.value() == json(2));
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ CHECK(counter == 3);
+ }
+
+ SECTION("const reference")
+ {
+ const json j = { {"A", 1}, {"B", 2} };
+ int counter = 1;
+
+ for (const auto& i : j.items())
+ {
+ switch (counter++)
+ {
+ case 1:
+ {
+ CHECK(i.key() == "A");
+ CHECK(i.value() == json(1));
+ break;
+ }
+
+ case 2:
+ {
+ CHECK(i.key() == "B");
+ CHECK(i.value() == json(2));
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ CHECK(counter == 3);
+ }
+ }
+
+ SECTION("array")
+ {
+ SECTION("value")
+ {
+ json j = { "A", "B" };
+ int counter = 1;
+
+ for (auto i : j.items()) // NOLINT(performance-for-range-copy)
+ {
+ switch (counter++)
+ {
+ case 1:
+ {
+ CHECK(i.key() == "0");
+ CHECK(i.value() == "A");
+ break;
+ }
+
+ case 2:
+ {
+ CHECK(i.key() == "1");
+ CHECK(i.value() == "B");
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ CHECK(counter == 3);
+ }
+
+ SECTION("reference")
+ {
+ json j = { "A", "B" };
+ int counter = 1;
+
+ for (auto& i : j.items()) // NOLINT(readability-qualified-auto)
+ {
+ switch (counter++)
+ {
+ case 1:
+ {
+ CHECK(i.key() == "0");
+ CHECK(i.value() == "A");
+
+ // change the value
+ i.value() = "AA";
+ CHECK(i.value() == "AA");
+ break;
+ }
+
+ case 2:
+ {
+ CHECK(i.key() == "1");
+ CHECK(i.value() == "B");
+
+ // change the value
+ i.value() = "BB";
+ CHECK(i.value() == "BB");
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ CHECK(counter == 3);
+
+ // check if values where changed
+ CHECK(j == json({ "AA", "BB" }));
+ }
+
+ SECTION("const value")
+ {
+ json j = { "A", "B" };
+ int counter = 1;
+
+ for (const auto i : j.items()) // NOLINT(performance-for-range-copy)
+ {
+ switch (counter++)
+ {
+ case 1:
+ {
+ CHECK(i.key() == "0");
+ CHECK(i.value() == "A");
+ break;
+ }
+
+ case 2:
+ {
+ CHECK(i.key() == "1");
+ CHECK(i.value() == "B");
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ CHECK(counter == 3);
+ }
+
+ SECTION("const reference")
+ {
+ json j = { "A", "B" };
+ int counter = 1;
+
+ for (const auto& i : j.items())
+ {
+ switch (counter++)
+ {
+ case 1:
+ {
+ CHECK(i.key() == "0");
+ CHECK(i.value() == "A");
+ break;
+ }
+
+ case 2:
+ {
+ CHECK(i.key() == "1");
+ CHECK(i.value() == "B");
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ CHECK(counter == 3);
+ }
+ }
+
+ SECTION("const array")
+ {
+ SECTION("value")
+ {
+ const json j = { "A", "B" };
+ int counter = 1;
+
+ for (auto i : j.items()) // NOLINT(performance-for-range-copy)
+ {
+ switch (counter++)
+ {
+ case 1:
+ {
+ CHECK(i.key() == "0");
+ CHECK(i.value() == "A");
+ break;
+ }
+
+ case 2:
+ {
+ CHECK(i.key() == "1");
+ CHECK(i.value() == "B");
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ CHECK(counter == 3);
+ }
+
+ SECTION("reference")
+ {
+ const json j = { "A", "B" };
+ int counter = 1;
+
+ for (auto& i : j.items()) // NOLINT(readability-qualified-auto)
+ {
+ switch (counter++)
+ {
+ case 1:
+ {
+ CHECK(i.key() == "0");
+ CHECK(i.value() == "A");
+ break;
+ }
+
+ case 2:
+ {
+ CHECK(i.key() == "1");
+ CHECK(i.value() == "B");
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ CHECK(counter == 3);
+ }
+
+ SECTION("const value")
+ {
+ const json j = { "A", "B" };
+ int counter = 1;
+
+ for (const auto i : j.items()) // NOLINT(performance-for-range-copy)
+ {
+ switch (counter++)
+ {
+ case 1:
+ {
+ CHECK(i.key() == "0");
+ CHECK(i.value() == "A");
+ break;
+ }
+
+ case 2:
+ {
+ CHECK(i.key() == "1");
+ CHECK(i.value() == "B");
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ CHECK(counter == 3);
+ }
+
+ SECTION("const reference")
+ {
+ const json j = { "A", "B" };
+ int counter = 1;
+
+ for (const auto& i : j.items())
+ {
+ switch (counter++)
+ {
+ case 1:
+ {
+ CHECK(i.key() == "0");
+ CHECK(i.value() == "A");
+ break;
+ }
+
+ case 2:
+ {
+ CHECK(i.key() == "1");
+ CHECK(i.value() == "B");
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ CHECK(counter == 3);
+ }
+ }
+
+ SECTION("primitive")
+ {
+ SECTION("value")
+ {
+ json j = 1;
+ int counter = 1;
+
+ for (auto i : j.items()) // NOLINT(performance-for-range-copy)
+ {
+ ++counter;
+ CHECK(i.key() == "");
+ CHECK(i.value() == json(1));
+ }
+
+ CHECK(counter == 2);
+ }
+
+ SECTION("reference")
+ {
+ json j = 1;
+ int counter = 1;
+
+ for (auto& i : j.items()) // NOLINT(readability-qualified-auto)
+ {
+ ++counter;
+ CHECK(i.key() == "");
+ CHECK(i.value() == json(1));
+
+ // change value
+ i.value() = json(2);
+ }
+
+ CHECK(counter == 2);
+
+ // check if value has changed
+ CHECK(j == json(2));
+ }
+
+ SECTION("const value")
+ {
+ json j = 1;
+ int counter = 1;
+
+ for (const auto i : j.items()) // NOLINT(performance-for-range-copy)
+ {
+ ++counter;
+ CHECK(i.key() == "");
+ CHECK(i.value() == json(1));
+ }
+
+ CHECK(counter == 2);
+ }
+
+ SECTION("const reference")
+ {
+ json j = 1;
+ int counter = 1;
+
+ for (const auto& i : j.items())
+ {
+ ++counter;
+ CHECK(i.key() == "");
+ CHECK(i.value() == json(1));
+ }
+
+ CHECK(counter == 2);
+ }
+ }
+
+ SECTION("const primitive")
+ {
+ SECTION("value")
+ {
+ const json j = 1;
+ int counter = 1;
+
+ for (auto i : j.items()) // NOLINT(performance-for-range-copy)
+ {
+ ++counter;
+ CHECK(i.key() == "");
+ CHECK(i.value() == json(1));
+ }
+
+ CHECK(counter == 2);
+ }
+
+ SECTION("reference")
+ {
+ const json j = 1;
+ int counter = 1;
+
+ for (auto& i : j.items()) // NOLINT(readability-qualified-auto)
+ {
+ ++counter;
+ CHECK(i.key() == "");
+ CHECK(i.value() == json(1));
+ }
+
+ CHECK(counter == 2);
+ }
+
+ SECTION("const value")
+ {
+ const json j = 1;
+ int counter = 1;
+
+ for (const auto i : j.items()) // NOLINT(performance-for-range-copy)
+ {
+ ++counter;
+ CHECK(i.key() == "");
+ CHECK(i.value() == json(1));
+ }
+
+ CHECK(counter == 2);
+ }
+
+ SECTION("const reference")
+ {
+ const json j = 1;
+ int counter = 1;
+
+ for (const auto& i : j.items())
+ {
+ ++counter;
+ CHECK(i.key() == "");
+ CHECK(i.value() == json(1));
+ }
+
+ CHECK(counter == 2);
+ }
+ }
+}
+
+DOCTEST_GCC_SUPPRESS_WARNING_POP
+DOCTEST_CLANG_SUPPRESS_WARNING_POP
diff --git a/json4cpp/tests/src/unit-iterators1.cpp b/json4cpp/tests/src/unit-iterators1.cpp
new file mode 100644
index 0000000000..b7b0aa9eb5
--- /dev/null
+++ b/json4cpp/tests/src/unit-iterators1.cpp
@@ -0,0 +1,1630 @@
+// __ _____ _____ _____
+// __| | __| | | | 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;
+
+TEST_CASE("iterators 1")
+{
+ SECTION("basic behavior")
+ {
+ SECTION("uninitialized")
+ {
+ json::iterator it; // NOLINT(misc-const-correctness)
+ CHECK(it.m_object == nullptr);
+
+ json::const_iterator cit; // NOLINT(misc-const-correctness)
+ CHECK(cit.m_object == nullptr);
+ }
+
+ SECTION("boolean")
+ {
+ json j = true;
+ json j_const(j);
+
+ SECTION("json + begin/end")
+ {
+ json::iterator it = j.begin();
+ CHECK(it != j.end());
+ CHECK(*it == j);
+
+ it++;
+ CHECK(it != j.begin());
+ CHECK(it == j.end());
+
+ it--;
+ CHECK(it == j.begin());
+ CHECK(it != j.end());
+ CHECK(*it == j);
+
+ ++it;
+ CHECK(it != j.begin());
+ CHECK(it == j.end());
+
+ --it;
+ CHECK(it == j.begin());
+ CHECK(it != j.end());
+ CHECK(*it == j);
+ }
+
+ SECTION("const json + begin/end")
+ {
+ json::const_iterator it = j_const.begin();
+ CHECK(it != j_const.end());
+ CHECK(*it == j_const);
+
+ it++;
+ CHECK(it != j_const.begin());
+ CHECK(it == j_const.end());
+
+ it--;
+ CHECK(it == j_const.begin());
+ CHECK(it != j_const.end());
+ CHECK(*it == j_const);
+
+ ++it;
+ CHECK(it != j_const.begin());
+ CHECK(it == j_const.end());
+
+ --it;
+ CHECK(it == j_const.begin());
+ CHECK(it != j_const.end());
+ CHECK(*it == j_const);
+ }
+
+ SECTION("json + cbegin/cend")
+ {
+ json::const_iterator it = j.cbegin();
+ CHECK(it != j.cend());
+ CHECK(*it == j);
+
+ it++;
+ CHECK(it != j.cbegin());
+ CHECK(it == j.cend());
+
+ it--;
+ CHECK(it == j.cbegin());
+ CHECK(it != j.cend());
+ CHECK(*it == j);
+
+ ++it;
+ CHECK(it != j.cbegin());
+ CHECK(it == j.cend());
+
+ --it;
+ CHECK(it == j.cbegin());
+ CHECK(it != j.cend());
+ CHECK(*it == j);
+ }
+
+ SECTION("const json + cbegin/cend")
+ {
+ json::const_iterator it = j_const.cbegin();
+ CHECK(it != j_const.cend());
+ CHECK(*it == j_const);
+
+ it++;
+ CHECK(it != j_const.cbegin());
+ CHECK(it == j_const.cend());
+
+ it--;
+ CHECK(it == j_const.cbegin());
+ CHECK(it != j_const.cend());
+ CHECK(*it == j_const);
+
+ ++it;
+ CHECK(it != j_const.cbegin());
+ CHECK(it == j_const.cend());
+
+ --it;
+ CHECK(it == j_const.cbegin());
+ CHECK(it != j_const.cend());
+ CHECK(*it == j_const);
+ }
+
+ SECTION("json + rbegin/rend")
+ {
+ json::reverse_iterator it = j.rbegin();
+ CHECK(it != j.rend());
+ CHECK(*it == j);
+
+ it++;
+ CHECK(it != j.rbegin());
+ CHECK(it == j.rend());
+
+ it--;
+ CHECK(it == j.rbegin());
+ CHECK(it != j.rend());
+ CHECK(*it == j);
+
+ ++it;
+ CHECK(it != j.rbegin());
+ CHECK(it == j.rend());
+
+ --it;
+ CHECK(it == j.rbegin());
+ CHECK(it != j.rend());
+ CHECK(*it == j);
+ }
+
+ SECTION("json + crbegin/crend")
+ {
+ json::const_reverse_iterator it = j.crbegin();
+ CHECK(it != j.crend());
+ CHECK(*it == j);
+
+ it++;
+ CHECK(it != j.crbegin());
+ CHECK(it == j.crend());
+
+ it--;
+ CHECK(it == j.crbegin());
+ CHECK(it != j.crend());
+ CHECK(*it == j);
+
+ ++it;
+ CHECK(it != j.crbegin());
+ CHECK(it == j.crend());
+
+ --it;
+ CHECK(it == j.crbegin());
+ CHECK(it != j.crend());
+ CHECK(*it == j);
+ }
+
+ SECTION("const json + crbegin/crend")
+ {
+ json::const_reverse_iterator it = j_const.crbegin();
+ CHECK(it != j_const.crend());
+ CHECK(*it == j_const);
+
+ it++;
+ CHECK(it != j_const.crbegin());
+ CHECK(it == j_const.crend());
+
+ it--;
+ CHECK(it == j_const.crbegin());
+ CHECK(it != j_const.crend());
+ CHECK(*it == j_const);
+
+ ++it;
+ CHECK(it != j_const.crbegin());
+ CHECK(it == j_const.crend());
+
+ --it;
+ CHECK(it == j_const.crbegin());
+ CHECK(it != j_const.crend());
+ CHECK(*it == j_const);
+ }
+
+ SECTION("additional tests")
+ {
+ SECTION("!(begin != begin)")
+ {
+ CHECK(!(j.begin() != j.begin()));
+ }
+
+ SECTION("!(end != end)")
+ {
+ CHECK(!(j.end() != j.end()));
+ }
+
+ SECTION("begin < end")
+ {
+ CHECK(j.begin() < j.end());
+ }
+
+ SECTION("begin <= end")
+ {
+ CHECK(j.begin() <= j.end());
+ }
+
+ SECTION("end > begin")
+ {
+ CHECK(j.end() > j.begin());
+ }
+
+ SECTION("end >= begin")
+ {
+ CHECK(j.end() >= j.begin());
+ }
+
+ SECTION("end == end")
+ {
+ CHECK(j.end() == j.end());
+ }
+
+ SECTION("end <= end")
+ {
+ CHECK(j.end() <= j.end());
+ }
+
+ SECTION("begin == begin")
+ {
+ CHECK(j.begin() == j.begin());
+ }
+
+ SECTION("begin <= begin")
+ {
+ CHECK(j.begin() <= j.begin());
+ }
+
+ SECTION("begin >= begin")
+ {
+ CHECK(j.begin() >= j.begin());
+ }
+
+ SECTION("!(begin == end)")
+ {
+ CHECK(!(j.begin() == j.end()));
+ }
+
+ SECTION("begin != end")
+ {
+ CHECK(j.begin() != j.end());
+ }
+
+ SECTION("begin+1 == end")
+ {
+ CHECK(j.begin() + 1 == j.end());
+ }
+
+ SECTION("begin == end-1")
+ {
+ CHECK(j.begin() == j.end() - 1);
+ }
+
+ SECTION("begin != end+1")
+ {
+ CHECK(j.begin() != j.end() + 1);
+ }
+
+ SECTION("end != end+1")
+ {
+ CHECK(j.end() != j.end() + 1);
+ }
+
+ SECTION("begin+1 != begin+2")
+ {
+ CHECK(j.begin() + 1 != j.begin() + 2);
+ }
+
+ SECTION("begin+1 < begin+2")
+ {
+ CHECK(j.begin() + 1 < j.begin() + 2);
+ }
+
+ SECTION("begin+1 <= begin+2")
+ {
+ CHECK(j.begin() + 1 <= j.begin() + 2);
+ }
+
+ SECTION("end+1 != end+2")
+ {
+ CHECK(j.end() + 1 != j.end() + 2);
+ }
+ }
+
+ SECTION("key/value")
+ {
+ auto it = j.begin();
+ auto cit = j_const.cbegin();
+ CHECK_THROWS_WITH_AS(it.key(), "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators", json::invalid_iterator&);
+ CHECK(it.value() == json(true));
+ CHECK_THROWS_WITH_AS(cit.key(), "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators", json::invalid_iterator&);
+ CHECK(cit.value() == json(true));
+
+ auto rit = j.rend();
+ auto crit = j.crend();
+ CHECK_THROWS_WITH_AS(rit.key(), "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(rit.value(), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(crit.key(), "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(crit.value(), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ }
+ }
+
+ SECTION("string")
+ {
+ json j = "hello world";
+ json j_const(j);
+
+ SECTION("json + begin/end")
+ {
+ json::iterator it = j.begin();
+ CHECK(it != j.end());
+ CHECK(*it == j);
+
+ it++;
+ CHECK(it != j.begin());
+ CHECK(it == j.end());
+
+ it--;
+ CHECK(it == j.begin());
+ CHECK(it != j.end());
+ CHECK(*it == j);
+
+ ++it;
+ CHECK(it != j.begin());
+ CHECK(it == j.end());
+
+ --it;
+ CHECK(it == j.begin());
+ CHECK(it != j.end());
+ CHECK(*it == j);
+ }
+
+ SECTION("const json + begin/end")
+ {
+ json::const_iterator it = j_const.begin();
+ CHECK(it != j_const.end());
+ CHECK(*it == j_const);
+
+ it++;
+ CHECK(it != j_const.begin());
+ CHECK(it == j_const.end());
+
+ it--;
+ CHECK(it == j_const.begin());
+ CHECK(it != j_const.end());
+ CHECK(*it == j_const);
+
+ ++it;
+ CHECK(it != j_const.begin());
+ CHECK(it == j_const.end());
+
+ --it;
+ CHECK(it == j_const.begin());
+ CHECK(it != j_const.end());
+ CHECK(*it == j_const);
+ }
+
+ SECTION("json + cbegin/cend")
+ {
+ json::const_iterator it = j.cbegin();
+ CHECK(it != j.cend());
+ CHECK(*it == j);
+
+ it++;
+ CHECK(it != j.cbegin());
+ CHECK(it == j.cend());
+
+ it--;
+ CHECK(it == j.cbegin());
+ CHECK(it != j.cend());
+ CHECK(*it == j);
+
+ ++it;
+ CHECK(it != j.cbegin());
+ CHECK(it == j.cend());
+
+ --it;
+ CHECK(it == j.cbegin());
+ CHECK(it != j.cend());
+ CHECK(*it == j);
+ }
+
+ SECTION("const json + cbegin/cend")
+ {
+ json::const_iterator it = j_const.cbegin();
+ CHECK(it != j_const.cend());
+ CHECK(*it == j_const);
+
+ it++;
+ CHECK(it != j_const.cbegin());
+ CHECK(it == j_const.cend());
+
+ it--;
+ CHECK(it == j_const.cbegin());
+ CHECK(it != j_const.cend());
+ CHECK(*it == j_const);
+
+ ++it;
+ CHECK(it != j_const.cbegin());
+ CHECK(it == j_const.cend());
+
+ --it;
+ CHECK(it == j_const.cbegin());
+ CHECK(it != j_const.cend());
+ CHECK(*it == j_const);
+ }
+
+ SECTION("json + rbegin/rend")
+ {
+ json::reverse_iterator it = j.rbegin();
+ CHECK(it != j.rend());
+ CHECK(*it == j);
+
+ it++;
+ CHECK(it != j.rbegin());
+ CHECK(it == j.rend());
+
+ it--;
+ CHECK(it == j.rbegin());
+ CHECK(it != j.rend());
+ CHECK(*it == j);
+
+ ++it;
+ CHECK(it != j.rbegin());
+ CHECK(it == j.rend());
+
+ --it;
+ CHECK(it == j.rbegin());
+ CHECK(it != j.rend());
+ CHECK(*it == j);
+ }
+
+ SECTION("json + crbegin/crend")
+ {
+ json::const_reverse_iterator it = j.crbegin();
+ CHECK(it != j.crend());
+ CHECK(*it == j);
+
+ it++;
+ CHECK(it != j.crbegin());
+ CHECK(it == j.crend());
+
+ it--;
+ CHECK(it == j.crbegin());
+ CHECK(it != j.crend());
+ CHECK(*it == j);
+
+ ++it;
+ CHECK(it != j.crbegin());
+ CHECK(it == j.crend());
+
+ --it;
+ CHECK(it == j.crbegin());
+ CHECK(it != j.crend());
+ CHECK(*it == j);
+ }
+
+ SECTION("const json + crbegin/crend")
+ {
+ json::const_reverse_iterator it = j_const.crbegin();
+ CHECK(it != j_const.crend());
+ CHECK(*it == j_const);
+
+ it++;
+ CHECK(it != j_const.crbegin());
+ CHECK(it == j_const.crend());
+
+ it--;
+ CHECK(it == j_const.crbegin());
+ CHECK(it != j_const.crend());
+ CHECK(*it == j_const);
+
+ ++it;
+ CHECK(it != j_const.crbegin());
+ CHECK(it == j_const.crend());
+
+ --it;
+ CHECK(it == j_const.crbegin());
+ CHECK(it != j_const.crend());
+ CHECK(*it == j_const);
+ }
+
+ SECTION("key/value")
+ {
+ auto it = j.begin();
+ auto cit = j_const.cbegin();
+ CHECK_THROWS_WITH_AS(it.key(), "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators", json::invalid_iterator&);
+ CHECK(it.value() == json("hello world"));
+ CHECK_THROWS_WITH_AS(cit.key(), "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators", json::invalid_iterator&);
+ CHECK(cit.value() == json("hello world"));
+
+ auto rit = j.rend();
+ auto crit = j.crend();
+ CHECK_THROWS_WITH_AS(rit.key(), "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(rit.value(), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(crit.key(), "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(crit.value(), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ }
+ }
+
+ SECTION("array")
+ {
+ json j = {1, 2, 3};
+ json j_const(j);
+
+ SECTION("json + begin/end")
+ {
+ json::iterator it_begin = j.begin();
+ json::iterator it_end = j.end();
+
+ auto it = it_begin;
+ CHECK(it != it_end);
+ CHECK(*it == j[0]);
+
+ it++;
+ CHECK(it != it_begin);
+ CHECK(it != it_end);
+ CHECK(*it == j[1]);
+
+ ++it;
+ CHECK(it != it_begin);
+ CHECK(it != it_end);
+ CHECK(*it == j[2]);
+
+ ++it;
+ CHECK(it != it_begin);
+ CHECK(it == it_end);
+ }
+
+ SECTION("const json + begin/end")
+ {
+ json::const_iterator it_begin = j_const.begin();
+ json::const_iterator it_end = j_const.end();
+
+ auto it = it_begin;
+ CHECK(it != it_end);
+ CHECK(*it == j_const[0]);
+
+ it++;
+ CHECK(it != it_begin);
+ CHECK(it != it_end);
+ CHECK(*it == j_const[1]);
+
+ ++it;
+ CHECK(it != it_begin);
+ CHECK(it != it_end);
+ CHECK(*it == j_const[2]);
+
+ ++it;
+ CHECK(it != it_begin);
+ CHECK(it == it_end);
+ }
+
+ SECTION("json + cbegin/cend")
+ {
+ json::const_iterator it_begin = j.cbegin();
+ json::const_iterator it_end = j.cend();
+
+ auto it = it_begin;
+ CHECK(it != it_end);
+ CHECK(*it == j[0]);
+
+ it++;
+ CHECK(it != it_begin);
+ CHECK(it != it_end);
+ CHECK(*it == j[1]);
+
+ ++it;
+ CHECK(it != it_begin);
+ CHECK(it != it_end);
+ CHECK(*it == j[2]);
+
+ ++it;
+ CHECK(it != it_begin);
+ CHECK(it == it_end);
+ }
+
+ SECTION("const json + cbegin/cend")
+ {
+ json::const_iterator it_begin = j_const.cbegin();
+ json::const_iterator it_end = j_const.cend();
+
+ auto it = it_begin;
+ CHECK(it != it_end);
+ CHECK(*it == j[0]);
+
+ it++;
+ CHECK(it != it_begin);
+ CHECK(it != it_end);
+ CHECK(*it == j[1]);
+
+ ++it;
+ CHECK(it != it_begin);
+ CHECK(it != it_end);
+ CHECK(*it == j[2]);
+
+ ++it;
+ CHECK(it != it_begin);
+ CHECK(it == it_end);
+ }
+
+ SECTION("json + rbegin/rend")
+ {
+ json::reverse_iterator it_begin = j.rbegin();
+ json::reverse_iterator it_end = j.rend();
+
+ auto it = it_begin;
+ CHECK(it != it_end);
+ CHECK(*it == j[2]);
+
+ it++;
+ CHECK(it != it_begin);
+ CHECK(it != it_end);
+ CHECK(*it == j[1]);
+
+ ++it;
+ CHECK(it != it_begin);
+ CHECK(it != it_end);
+ CHECK(*it == j[0]);
+
+ ++it;
+ CHECK(it != it_begin);
+ CHECK(it == it_end);
+ }
+
+ SECTION("json + crbegin/crend")
+ {
+ json::const_reverse_iterator it_begin = j.crbegin();
+ json::const_reverse_iterator it_end = j.crend();
+
+ auto it = it_begin;
+ CHECK(it != it_end);
+ CHECK(*it == j[2]);
+
+ it++;
+ CHECK(it != it_begin);
+ CHECK(it != it_end);
+ CHECK(*it == j[1]);
+
+ ++it;
+ CHECK(it != it_begin);
+ CHECK(it != it_end);
+ CHECK(*it == j[0]);
+
+ ++it;
+ CHECK(it != it_begin);
+ CHECK(it == it_end);
+ }
+
+ SECTION("const json + crbegin/crend")
+ {
+ json::const_reverse_iterator it_begin = j_const.crbegin();
+ json::const_reverse_iterator it_end = j_const.crend();
+
+ auto it = it_begin;
+ CHECK(it != it_end);
+ CHECK(*it == j[2]);
+
+ it++;
+ CHECK(it != it_begin);
+ CHECK(it != it_end);
+ CHECK(*it == j[1]);
+
+ ++it;
+ CHECK(it != it_begin);
+ CHECK(it != it_end);
+ CHECK(*it == j[0]);
+
+ ++it;
+ CHECK(it != it_begin);
+ CHECK(it == it_end);
+ }
+
+ SECTION("key/value")
+ {
+ auto it = j.begin();
+ auto cit = j_const.cbegin();
+ CHECK_THROWS_WITH_AS(it.key(), "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators", json::invalid_iterator&);
+ CHECK(it.value() == json(1));
+ CHECK_THROWS_WITH_AS(cit.key(), "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators", json::invalid_iterator&);
+ CHECK(cit.value() == json(1));
+ }
+ }
+
+ SECTION("object")
+ {
+ json j = {{"A", 1}, {"B", 2}, {"C", 3}};
+ json j_const(j);
+
+ SECTION("json + begin/end")
+ {
+ json::iterator it_begin = j.begin();
+ json::iterator it_end = j.end();
+
+ auto it = it_begin;
+ CHECK(it != it_end);
+ CHECK(*it == j["A"]);
+
+ it++;
+ CHECK(it != it_begin);
+ CHECK(it != it_end);
+ CHECK(*it == j["B"]);
+
+ ++it;
+ CHECK(it != it_begin);
+ CHECK(it != it_end);
+ CHECK(*it == j["C"]);
+
+ ++it;
+ CHECK(it != it_begin);
+ CHECK(it == it_end);
+ }
+
+ SECTION("const json + begin/end")
+ {
+ json::const_iterator it_begin = j_const.begin();
+ json::const_iterator it_end = j_const.end();
+
+ auto it = it_begin;
+ CHECK(it != it_end);
+ CHECK(*it == j_const["A"]);
+
+ it++;
+ CHECK(it != it_begin);
+ CHECK(it != it_end);
+ CHECK(*it == j_const["B"]);
+
+ ++it;
+ CHECK(it != it_begin);
+ CHECK(it != it_end);
+ CHECK(*it == j_const["C"]);
+
+ ++it;
+ CHECK(it != it_begin);
+ CHECK(it == it_end);
+ }
+
+ SECTION("json + cbegin/cend")
+ {
+ json::const_iterator it_begin = j.cbegin();
+ json::const_iterator it_end = j.cend();
+
+ auto it = it_begin;
+ CHECK(it != it_end);
+ CHECK(*it == j["A"]);
+
+ it++;
+ CHECK(it != it_begin);
+ CHECK(it != it_end);
+ CHECK(*it == j["B"]);
+
+ ++it;
+ CHECK(it != it_begin);
+ CHECK(it != it_end);
+ CHECK(*it == j["C"]);
+
+ ++it;
+ CHECK(it != it_begin);
+ CHECK(it == it_end);
+ }
+
+ SECTION("const json + cbegin/cend")
+ {
+ json::const_iterator it_begin = j_const.cbegin();
+ json::const_iterator it_end = j_const.cend();
+
+ auto it = it_begin;
+ CHECK(it != it_end);
+ CHECK(*it == j_const["A"]);
+
+ it++;
+ CHECK(it != it_begin);
+ CHECK(it != it_end);
+ CHECK(*it == j_const["B"]);
+
+ ++it;
+ CHECK(it != it_begin);
+ CHECK(it != it_end);
+ CHECK(*it == j_const["C"]);
+
+ ++it;
+ CHECK(it != it_begin);
+ CHECK(it == it_end);
+ }
+
+ SECTION("json + rbegin/rend")
+ {
+ json::reverse_iterator it_begin = j.rbegin();
+ json::reverse_iterator it_end = j.rend();
+
+ auto it = it_begin;
+ CHECK(it != it_end);
+ CHECK(*it == j["C"]);
+
+ it++;
+ CHECK(it != it_begin);
+ CHECK(it != it_end);
+ CHECK(*it == j["B"]);
+
+ ++it;
+ CHECK(it != it_begin);
+ CHECK(it != it_end);
+ CHECK(*it == j["A"]);
+
+ ++it;
+ CHECK(it != it_begin);
+ CHECK(it == it_end);
+ }
+
+ SECTION("json + crbegin/crend")
+ {
+ json::const_reverse_iterator it_begin = j.crbegin();
+ json::const_reverse_iterator it_end = j.crend();
+
+ auto it = it_begin;
+ CHECK(it != it_end);
+ CHECK(*it == j["C"]);
+
+ it++;
+ CHECK(it != it_begin);
+ CHECK(it != it_end);
+ CHECK(*it == j["B"]);
+
+ ++it;
+ CHECK(it != it_begin);
+ CHECK(it != it_end);
+ CHECK(*it == j["A"]);
+
+ ++it;
+ CHECK(it != it_begin);
+ CHECK(it == it_end);
+ }
+
+ SECTION("const json + crbegin/crend")
+ {
+ json::const_reverse_iterator it_begin = j_const.crbegin();
+ json::const_reverse_iterator it_end = j_const.crend();
+
+ auto it = it_begin;
+ CHECK(it != it_end);
+ CHECK(*it == j["C"]);
+
+ it++;
+ CHECK(it != it_begin);
+ CHECK(it != it_end);
+ CHECK(*it == j["B"]);
+
+ ++it;
+ CHECK(it != it_begin);
+ CHECK(it != it_end);
+ CHECK(*it == j["A"]);
+
+ ++it;
+ CHECK(it != it_begin);
+ CHECK(it == it_end);
+ }
+
+ SECTION("key/value")
+ {
+ auto it = j.begin();
+ auto cit = j_const.cbegin();
+ CHECK(it.key() == "A");
+ CHECK(it.value() == json(1));
+ CHECK(cit.key() == "A");
+ CHECK(cit.value() == json(1));
+ }
+ }
+
+ SECTION("number (integer)")
+ {
+ json j = 23;
+ json j_const(j);
+
+ SECTION("json + begin/end")
+ {
+ json::iterator it = j.begin();
+ CHECK(it != j.end());
+ CHECK(*it == j);
+
+ it++;
+ CHECK(it != j.begin());
+ CHECK(it == j.end());
+
+ it--;
+ CHECK(it == j.begin());
+ CHECK(it != j.end());
+ CHECK(*it == j);
+
+ ++it;
+ CHECK(it != j.begin());
+ CHECK(it == j.end());
+
+ --it;
+ CHECK(it == j.begin());
+ CHECK(it != j.end());
+ CHECK(*it == j);
+ }
+
+ SECTION("const json + begin/end")
+ {
+ json::const_iterator it = j_const.begin();
+ CHECK(it != j_const.end());
+ CHECK(*it == j_const);
+
+ it++;
+ CHECK(it != j_const.begin());
+ CHECK(it == j_const.end());
+
+ it--;
+ CHECK(it == j_const.begin());
+ CHECK(it != j_const.end());
+ CHECK(*it == j_const);
+
+ ++it;
+ CHECK(it != j_const.begin());
+ CHECK(it == j_const.end());
+
+ --it;
+ CHECK(it == j_const.begin());
+ CHECK(it != j_const.end());
+ CHECK(*it == j_const);
+ }
+
+ SECTION("json + cbegin/cend")
+ {
+ json::const_iterator it = j.cbegin();
+ CHECK(it != j.cend());
+ CHECK(*it == j);
+
+ it++;
+ CHECK(it != j.cbegin());
+ CHECK(it == j.cend());
+
+ it--;
+ CHECK(it == j.cbegin());
+ CHECK(it != j.cend());
+ CHECK(*it == j);
+
+ ++it;
+ CHECK(it != j.cbegin());
+ CHECK(it == j.cend());
+
+ --it;
+ CHECK(it == j.cbegin());
+ CHECK(it != j.cend());
+ CHECK(*it == j);
+ }
+
+ SECTION("const json + cbegin/cend")
+ {
+ json::const_iterator it = j_const.cbegin();
+ CHECK(it != j_const.cend());
+ CHECK(*it == j_const);
+
+ it++;
+ CHECK(it != j_const.cbegin());
+ CHECK(it == j_const.cend());
+
+ it--;
+ CHECK(it == j_const.cbegin());
+ CHECK(it != j_const.cend());
+ CHECK(*it == j_const);
+
+ ++it;
+ CHECK(it != j_const.cbegin());
+ CHECK(it == j_const.cend());
+
+ --it;
+ CHECK(it == j_const.cbegin());
+ CHECK(it != j_const.cend());
+ CHECK(*it == j_const);
+ }
+
+ SECTION("json + rbegin/rend")
+ {
+ json::reverse_iterator it = j.rbegin();
+ CHECK(it != j.rend());
+ CHECK(*it == j);
+
+ it++;
+ CHECK(it != j.rbegin());
+ CHECK(it == j.rend());
+
+ it--;
+ CHECK(it == j.rbegin());
+ CHECK(it != j.rend());
+ CHECK(*it == j);
+
+ ++it;
+ CHECK(it != j.rbegin());
+ CHECK(it == j.rend());
+
+ --it;
+ CHECK(it == j.rbegin());
+ CHECK(it != j.rend());
+ CHECK(*it == j);
+ }
+
+ SECTION("json + crbegin/crend")
+ {
+ json::const_reverse_iterator it = j.crbegin();
+ CHECK(it != j.crend());
+ CHECK(*it == j);
+
+ it++;
+ CHECK(it != j.crbegin());
+ CHECK(it == j.crend());
+
+ it--;
+ CHECK(it == j.crbegin());
+ CHECK(it != j.crend());
+ CHECK(*it == j);
+
+ ++it;
+ CHECK(it != j.crbegin());
+ CHECK(it == j.crend());
+
+ --it;
+ CHECK(it == j.crbegin());
+ CHECK(it != j.crend());
+ CHECK(*it == j);
+ }
+
+ SECTION("const json + crbegin/crend")
+ {
+ json::const_reverse_iterator it = j_const.crbegin();
+ CHECK(it != j_const.crend());
+ CHECK(*it == j_const);
+
+ it++;
+ CHECK(it != j_const.crbegin());
+ CHECK(it == j_const.crend());
+
+ it--;
+ CHECK(it == j_const.crbegin());
+ CHECK(it != j_const.crend());
+ CHECK(*it == j_const);
+
+ ++it;
+ CHECK(it != j_const.crbegin());
+ CHECK(it == j_const.crend());
+
+ --it;
+ CHECK(it == j_const.crbegin());
+ CHECK(it != j_const.crend());
+ CHECK(*it == j_const);
+ }
+
+ SECTION("key/value")
+ {
+ auto it = j.begin();
+ auto cit = j_const.cbegin();
+ CHECK_THROWS_WITH_AS(it.key(), "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators", json::invalid_iterator&);
+ CHECK(it.value() == json(23));
+ CHECK_THROWS_WITH_AS(cit.key(), "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators", json::invalid_iterator&);
+ CHECK(cit.value() == json(23));
+
+ auto rit = j.rend();
+ auto crit = j.crend();
+ CHECK_THROWS_WITH_AS(rit.key(), "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(rit.value(), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(crit.key(), "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(crit.value(), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ }
+ }
+
+ SECTION("number (unsigned)")
+ {
+ json j = 23u;
+ json j_const(j);
+
+ SECTION("json + begin/end")
+ {
+ json::iterator it = j.begin();
+ CHECK(it != j.end());
+ CHECK(*it == j);
+
+ it++;
+ CHECK(it != j.begin());
+ CHECK(it == j.end());
+
+ it--;
+ CHECK(it == j.begin());
+ CHECK(it != j.end());
+ CHECK(*it == j);
+
+ ++it;
+ CHECK(it != j.begin());
+ CHECK(it == j.end());
+
+ --it;
+ CHECK(it == j.begin());
+ CHECK(it != j.end());
+ CHECK(*it == j);
+ }
+
+ SECTION("const json + begin/end")
+ {
+ json::const_iterator it = j_const.begin();
+ CHECK(it != j_const.end());
+ CHECK(*it == j_const);
+
+ it++;
+ CHECK(it != j_const.begin());
+ CHECK(it == j_const.end());
+
+ it--;
+ CHECK(it == j_const.begin());
+ CHECK(it != j_const.end());
+ CHECK(*it == j_const);
+
+ ++it;
+ CHECK(it != j_const.begin());
+ CHECK(it == j_const.end());
+
+ --it;
+ CHECK(it == j_const.begin());
+ CHECK(it != j_const.end());
+ CHECK(*it == j_const);
+ }
+
+ SECTION("json + cbegin/cend")
+ {
+ json::const_iterator it = j.cbegin();
+ CHECK(it != j.cend());
+ CHECK(*it == j);
+
+ it++;
+ CHECK(it != j.cbegin());
+ CHECK(it == j.cend());
+
+ it--;
+ CHECK(it == j.cbegin());
+ CHECK(it != j.cend());
+ CHECK(*it == j);
+
+ ++it;
+ CHECK(it != j.cbegin());
+ CHECK(it == j.cend());
+
+ --it;
+ CHECK(it == j.cbegin());
+ CHECK(it != j.cend());
+ CHECK(*it == j);
+ }
+
+ SECTION("const json + cbegin/cend")
+ {
+ json::const_iterator it = j_const.cbegin();
+ CHECK(it != j_const.cend());
+ CHECK(*it == j_const);
+
+ it++;
+ CHECK(it != j_const.cbegin());
+ CHECK(it == j_const.cend());
+
+ it--;
+ CHECK(it == j_const.cbegin());
+ CHECK(it != j_const.cend());
+ CHECK(*it == j_const);
+
+ ++it;
+ CHECK(it != j_const.cbegin());
+ CHECK(it == j_const.cend());
+
+ --it;
+ CHECK(it == j_const.cbegin());
+ CHECK(it != j_const.cend());
+ CHECK(*it == j_const);
+ }
+
+ SECTION("json + rbegin/rend")
+ {
+ json::reverse_iterator it = j.rbegin();
+ CHECK(it != j.rend());
+ CHECK(*it == j);
+
+ it++;
+ CHECK(it != j.rbegin());
+ CHECK(it == j.rend());
+
+ it--;
+ CHECK(it == j.rbegin());
+ CHECK(it != j.rend());
+ CHECK(*it == j);
+
+ ++it;
+ CHECK(it != j.rbegin());
+ CHECK(it == j.rend());
+
+ --it;
+ CHECK(it == j.rbegin());
+ CHECK(it != j.rend());
+ CHECK(*it == j);
+ }
+
+ SECTION("json + crbegin/crend")
+ {
+ json::const_reverse_iterator it = j.crbegin();
+ CHECK(it != j.crend());
+ CHECK(*it == j);
+
+ it++;
+ CHECK(it != j.crbegin());
+ CHECK(it == j.crend());
+
+ it--;
+ CHECK(it == j.crbegin());
+ CHECK(it != j.crend());
+ CHECK(*it == j);
+
+ ++it;
+ CHECK(it != j.crbegin());
+ CHECK(it == j.crend());
+
+ --it;
+ CHECK(it == j.crbegin());
+ CHECK(it != j.crend());
+ CHECK(*it == j);
+ }
+
+ SECTION("const json + crbegin/crend")
+ {
+ json::const_reverse_iterator it = j_const.crbegin();
+ CHECK(it != j_const.crend());
+ CHECK(*it == j_const);
+
+ it++;
+ CHECK(it != j_const.crbegin());
+ CHECK(it == j_const.crend());
+
+ it--;
+ CHECK(it == j_const.crbegin());
+ CHECK(it != j_const.crend());
+ CHECK(*it == j_const);
+
+ ++it;
+ CHECK(it != j_const.crbegin());
+ CHECK(it == j_const.crend());
+
+ --it;
+ CHECK(it == j_const.crbegin());
+ CHECK(it != j_const.crend());
+ CHECK(*it == j_const);
+ }
+
+ SECTION("key/value")
+ {
+ auto it = j.begin();
+ auto cit = j_const.cbegin();
+ CHECK_THROWS_WITH_AS(it.key(), "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators", json::invalid_iterator&);
+ CHECK(it.value() == json(23));
+ CHECK_THROWS_WITH_AS(cit.key(), "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators", json::invalid_iterator&);
+ CHECK(cit.value() == json(23));
+
+ auto rit = j.rend();
+ auto crit = j.crend();
+ CHECK_THROWS_WITH_AS(rit.key(), "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(rit.value(), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(crit.key(), "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(crit.value(), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ }
+ }
+
+ SECTION("number (float)")
+ {
+ json j = 23.42;
+ json j_const(j);
+
+ SECTION("json + begin/end")
+ {
+ json::iterator it = j.begin();
+ CHECK(it != j.end());
+ CHECK(*it == j);
+
+ it++;
+ CHECK(it != j.begin());
+ CHECK(it == j.end());
+
+ it--;
+ CHECK(it == j.begin());
+ CHECK(it != j.end());
+ CHECK(*it == j);
+
+ ++it;
+ CHECK(it != j.begin());
+ CHECK(it == j.end());
+
+ --it;
+ CHECK(it == j.begin());
+ CHECK(it != j.end());
+ CHECK(*it == j);
+ }
+
+ SECTION("const json + begin/end")
+ {
+ json::const_iterator it = j_const.begin();
+ CHECK(it != j_const.end());
+ CHECK(*it == j_const);
+
+ it++;
+ CHECK(it != j_const.begin());
+ CHECK(it == j_const.end());
+
+ it--;
+ CHECK(it == j_const.begin());
+ CHECK(it != j_const.end());
+ CHECK(*it == j_const);
+
+ ++it;
+ CHECK(it != j_const.begin());
+ CHECK(it == j_const.end());
+
+ --it;
+ CHECK(it == j_const.begin());
+ CHECK(it != j_const.end());
+ CHECK(*it == j_const);
+ }
+
+ SECTION("json + cbegin/cend")
+ {
+ json::const_iterator it = j.cbegin();
+ CHECK(it != j.cend());
+ CHECK(*it == j);
+
+ it++;
+ CHECK(it != j.cbegin());
+ CHECK(it == j.cend());
+
+ it--;
+ CHECK(it == j.cbegin());
+ CHECK(it != j.cend());
+ CHECK(*it == j);
+
+ ++it;
+ CHECK(it != j.cbegin());
+ CHECK(it == j.cend());
+
+ --it;
+ CHECK(it == j.cbegin());
+ CHECK(it != j.cend());
+ CHECK(*it == j);
+ }
+
+ SECTION("const json + cbegin/cend")
+ {
+ json::const_iterator it = j_const.cbegin();
+ CHECK(it != j_const.cend());
+ CHECK(*it == j_const);
+
+ it++;
+ CHECK(it != j_const.cbegin());
+ CHECK(it == j_const.cend());
+
+ it--;
+ CHECK(it == j_const.cbegin());
+ CHECK(it != j_const.cend());
+ CHECK(*it == j_const);
+
+ ++it;
+ CHECK(it != j_const.cbegin());
+ CHECK(it == j_const.cend());
+
+ --it;
+ CHECK(it == j_const.cbegin());
+ CHECK(it != j_const.cend());
+ CHECK(*it == j_const);
+ }
+
+ SECTION("json + rbegin/rend")
+ {
+ json::reverse_iterator it = j.rbegin();
+ CHECK(it != j.rend());
+ CHECK(*it == j);
+
+ it++;
+ CHECK(it != j.rbegin());
+ CHECK(it == j.rend());
+
+ it--;
+ CHECK(it == j.rbegin());
+ CHECK(it != j.rend());
+ CHECK(*it == j);
+
+ ++it;
+ CHECK(it != j.rbegin());
+ CHECK(it == j.rend());
+
+ --it;
+ CHECK(it == j.rbegin());
+ CHECK(it != j.rend());
+ CHECK(*it == j);
+ }
+
+ SECTION("json + crbegin/crend")
+ {
+ json::const_reverse_iterator it = j.crbegin();
+ CHECK(it != j.crend());
+ CHECK(*it == j);
+
+ it++;
+ CHECK(it != j.crbegin());
+ CHECK(it == j.crend());
+
+ it--;
+ CHECK(it == j.crbegin());
+ CHECK(it != j.crend());
+ CHECK(*it == j);
+
+ ++it;
+ CHECK(it != j.crbegin());
+ CHECK(it == j.crend());
+
+ --it;
+ CHECK(it == j.crbegin());
+ CHECK(it != j.crend());
+ CHECK(*it == j);
+ }
+
+ SECTION("const json + crbegin/crend")
+ {
+ json::const_reverse_iterator it = j_const.crbegin();
+ CHECK(it != j_const.crend());
+ CHECK(*it == j_const);
+
+ it++;
+ CHECK(it != j_const.crbegin());
+ CHECK(it == j_const.crend());
+
+ it--;
+ CHECK(it == j_const.crbegin());
+ CHECK(it != j_const.crend());
+ CHECK(*it == j_const);
+
+ ++it;
+ CHECK(it != j_const.crbegin());
+ CHECK(it == j_const.crend());
+
+ --it;
+ CHECK(it == j_const.crbegin());
+ CHECK(it != j_const.crend());
+ CHECK(*it == j_const);
+ }
+
+ SECTION("key/value")
+ {
+ auto it = j.begin();
+ auto cit = j_const.cbegin();
+ CHECK_THROWS_WITH_AS(it.key(), "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators", json::invalid_iterator&);
+ CHECK(it.value() == json(23.42));
+ CHECK_THROWS_WITH_AS(cit.key(), "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators", json::invalid_iterator&);
+ CHECK(cit.value() == json(23.42));
+
+ auto rit = j.rend();
+ auto crit = j.crend();
+ CHECK_THROWS_WITH_AS(rit.key(), "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(rit.value(), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(crit.key(), "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(crit.value(), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ }
+ }
+
+ SECTION("null")
+ {
+ json j = nullptr;
+ json j_const(j);
+
+ SECTION("json + begin/end")
+ {
+ const json::iterator it = j.begin();
+ CHECK(it == j.end());
+ }
+
+ SECTION("const json + begin/end")
+ {
+ const json::const_iterator it_begin = j_const.begin();
+ json::const_iterator it_end = j_const.end();
+ CHECK(it_begin == it_end);
+ }
+
+ SECTION("json + cbegin/cend")
+ {
+ const json::const_iterator it_begin = j.cbegin();
+ json::const_iterator it_end = j.cend();
+ CHECK(it_begin == it_end);
+ }
+
+ SECTION("const json + cbegin/cend")
+ {
+ const json::const_iterator it_begin = j_const.cbegin();
+ json::const_iterator it_end = j_const.cend();
+ CHECK(it_begin == it_end);
+ }
+
+ SECTION("json + rbegin/rend")
+ {
+ const json::reverse_iterator it = j.rbegin();
+ CHECK(it == j.rend());
+ }
+
+ SECTION("json + crbegin/crend")
+ {
+ const json::const_reverse_iterator it = j.crbegin();
+ CHECK(it == j.crend());
+ }
+
+ SECTION("const json + crbegin/crend")
+ {
+ const json::const_reverse_iterator it = j_const.crbegin();
+ CHECK(it == j_const.crend());
+ }
+
+ SECTION("key/value")
+ {
+ auto it = j.begin();
+ auto cit = j_const.cbegin();
+ CHECK_THROWS_WITH_AS(it.key(), "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it.value(), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(cit.key(), "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(cit.value(), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+
+ auto rit = j.rend();
+ auto crit = j.crend();
+ CHECK_THROWS_WITH_AS(rit.key(), "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(rit.value(), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(crit.key(), "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(crit.value(), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ }
+ }
+ }
+
+ SECTION("conversion from iterator to const iterator")
+ {
+ SECTION("boolean")
+ {
+ json j = true;
+ json::const_iterator it = j.begin();
+ CHECK(it == j.cbegin());
+ it = j.begin();
+ CHECK(it == j.cbegin());
+ }
+ SECTION("string")
+ {
+ json j = "hello world";
+ json::const_iterator it = j.begin();
+ CHECK(it == j.cbegin());
+ it = j.begin();
+ CHECK(it == j.cbegin());
+ }
+ SECTION("array")
+ {
+ json j = {1, 2, 3};
+ json::const_iterator it = j.begin();
+ CHECK(it == j.cbegin());
+ it = j.begin();
+ CHECK(it == j.cbegin());
+ }
+ SECTION("object")
+ {
+ json j = {{"A", 1}, {"B", 2}, {"C", 3}};
+ json::const_iterator it = j.begin();
+ CHECK(it == j.cbegin());
+ it = j.begin();
+ CHECK(it == j.cbegin());
+ }
+ SECTION("number (integer)")
+ {
+ json j = 23;
+ json::const_iterator it = j.begin();
+ CHECK(it == j.cbegin());
+ it = j.begin();
+ CHECK(it == j.cbegin());
+ }
+ SECTION("number (unsigned)")
+ {
+ json j = 23u;
+ json::const_iterator it = j.begin();
+ CHECK(it == j.cbegin());
+ it = j.begin();
+ CHECK(it == j.cbegin());
+ }
+ SECTION("number (float)")
+ {
+ json j = 23.42;
+ json::const_iterator it = j.begin();
+ CHECK(it == j.cbegin());
+ it = j.begin();
+ CHECK(it == j.cbegin());
+ }
+ SECTION("null")
+ {
+ json j = nullptr;
+ json::const_iterator it = j.begin();
+ CHECK(it == j.cbegin());
+ it = j.begin();
+ CHECK(it == j.cbegin());
+ }
+ }
+}
diff --git a/json4cpp/tests/src/unit-iterators2.cpp b/json4cpp/tests/src/unit-iterators2.cpp
new file mode 100644
index 0000000000..ee9d15c6f2
--- /dev/null
+++ b/json4cpp/tests/src/unit-iterators2.cpp
@@ -0,0 +1,972 @@
+// __ _____ _____ _____
+// __| | __| | | | 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
+
+// cmake/test.cmake selects the C++ standard versions with which to build a
+// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
+// When using macros that are only defined for particular versions of the standard
+// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
+// version macro in a comment close by, like this:
+// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
+
+#include "doctest_compatibility.h"
+
+#include <nlohmann/json.hpp>
+using nlohmann::json;
+
+#if JSON_HAS_RANGES
+ #include <algorithm>
+ #include <ranges>
+#endif
+
+TEST_CASE("iterators 2")
+{
+ SECTION("iterator comparisons")
+ {
+ json j_values = {nullptr, true, 42, 42u, 23.23, {{"one", 1}, {"two", 2}}, {1, 2, 3, 4, 5}, "Hello, world"};
+
+ for (json& j : j_values)
+ {
+ auto it1 = j.begin();
+ auto it2 = j.begin();
+ auto it3 = j.begin();
+ ++it2;
+ ++it3;
+ ++it3;
+ auto it1_c = j.cbegin();
+ auto it2_c = j.cbegin();
+ auto it3_c = j.cbegin();
+ ++it2_c;
+ ++it3_c;
+ ++it3_c;
+
+ // comparison: equal
+ {
+ CHECK(it1 == it1);
+ CHECK(!(it1 == it2));
+ CHECK(!(it1 == it3));
+ CHECK(!(it2 == it3));
+ CHECK(it1_c == it1_c);
+ CHECK(!(it1_c == it2_c));
+ CHECK(!(it1_c == it3_c));
+ CHECK(!(it2_c == it3_c));
+ }
+
+ // comparison: not equal
+ {
+ // check definition
+ CHECK( (it1 != it1) == !(it1 == it1) );
+ CHECK( (it1 != it2) == !(it1 == it2) );
+ CHECK( (it1 != it3) == !(it1 == it3) );
+ CHECK( (it2 != it3) == !(it2 == it3) );
+ CHECK( (it1_c != it1_c) == !(it1_c == it1_c) );
+ CHECK( (it1_c != it2_c) == !(it1_c == it2_c) );
+ CHECK( (it1_c != it3_c) == !(it1_c == it3_c) );
+ CHECK( (it2_c != it3_c) == !(it2_c == it3_c) );
+ }
+
+ // comparison: smaller
+ {
+ if (j.type() == json::value_t::object)
+ {
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(it1 < it1, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1 < it2, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it2 < it3, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1 < it3, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c < it1_c, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c < it2_c, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it2_c < it3_c, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c < it3_c, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+#else
+ CHECK_THROWS_WITH_AS(it1 < it1, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1 < it2, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it2 < it3, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1 < it3, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c < it1_c, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c < it2_c, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it2_c < it3_c, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c < it3_c, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+#endif
+ }
+ else
+ {
+ CHECK(!(it1 < it1));
+ CHECK(it1 < it2);
+ CHECK(it1 < it3);
+ CHECK(it2 < it3);
+ CHECK(!(it1_c < it1_c));
+ CHECK(it1_c < it2_c);
+ CHECK(it1_c < it3_c);
+ CHECK(it2_c < it3_c);
+ }
+ }
+
+ // comparison: less than or equal
+ {
+ if (j.type() == json::value_t::object)
+ {
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(it1 <= it1, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1 <= it2, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it2 <= it3, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1 <= it3, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c <= it1_c, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c <= it2_c, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it2_c <= it3_c, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c <= it3_c, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+#else
+ CHECK_THROWS_WITH_AS(it1 <= it1, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1 <= it2, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it2 <= it3, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1 <= it3, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c <= it1_c, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c <= it2_c, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it2_c <= it3_c, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c <= it3_c, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+#endif
+ }
+ else
+ {
+ // check definition
+ CHECK( (it1 <= it1) == !(it1 < it1) );
+ CHECK( (it1 <= it2) == !(it2 < it1) );
+ CHECK( (it1 <= it3) == !(it3 < it1) );
+ CHECK( (it2 <= it3) == !(it3 < it2) );
+ CHECK( (it1_c <= it1_c) == !(it1_c < it1_c) );
+ CHECK( (it1_c <= it2_c) == !(it2_c < it1_c) );
+ CHECK( (it1_c <= it3_c) == !(it3_c < it1_c) );
+ CHECK( (it2_c <= it3_c) == !(it3_c < it2_c) );
+ }
+ }
+
+ // comparison: greater than
+ {
+ if (j.type() == json::value_t::object)
+ {
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(it1 > it1, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1 > it2, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it2 > it3, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1 > it3, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c > it1_c, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c > it2_c, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it2_c > it3_c, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c > it3_c, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+#else
+ CHECK_THROWS_WITH_AS(it1 > it1, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1 > it2, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it2 > it3, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1 > it3, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c > it1_c, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c > it2_c, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it2_c > it3_c, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c > it3_c, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+#endif
+ }
+ else
+ {
+ // check definition
+ CHECK( (it1 > it1) == (it1 < it1) );
+ CHECK( (it1 > it2) == (it2 < it1) );
+ CHECK( (it1 > it3) == (it3 < it1) );
+ CHECK( (it2 > it3) == (it3 < it2) );
+ CHECK( (it1_c > it1_c) == (it1_c < it1_c) );
+ CHECK( (it1_c > it2_c) == (it2_c < it1_c) );
+ CHECK( (it1_c > it3_c) == (it3_c < it1_c) );
+ CHECK( (it2_c > it3_c) == (it3_c < it2_c) );
+ }
+ }
+
+ // comparison: greater than or equal
+ {
+ if (j.type() == json::value_t::object)
+ {
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(it1 >= it1, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1 >= it2, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it2 >= it3, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1 >= it3, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c >= it1_c, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c >= it2_c, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it2_c >= it3_c, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c >= it3_c, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+#else
+ CHECK_THROWS_WITH_AS(it1 >= it1, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1 >= it2, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it2 >= it3, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1 >= it3, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c >= it1_c, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c >= it2_c, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it2_c >= it3_c, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c >= it3_c, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+#endif
+ }
+ else
+ {
+ // check definition
+ CHECK( (it1 >= it1) == !(it1 < it1) );
+ CHECK( (it1 >= it2) == !(it1 < it2) );
+ CHECK( (it1 >= it3) == !(it1 < it3) );
+ CHECK( (it2 >= it3) == !(it2 < it3) );
+ CHECK( (it1_c >= it1_c) == !(it1_c < it1_c) );
+ CHECK( (it1_c >= it2_c) == !(it1_c < it2_c) );
+ CHECK( (it1_c >= it3_c) == !(it1_c < it3_c) );
+ CHECK( (it2_c >= it3_c) == !(it2_c < it3_c) );
+ }
+ }
+ }
+
+ // check exceptions if different objects are compared
+ for (auto j : j_values)
+ {
+ for (auto k : j_values)
+ {
+ if (j != k)
+ {
+#if JSON_DIAGNOSTICS
+ // the output differs in each loop, so we cannot fix a string for the expected exception
+#else
+ CHECK_THROWS_WITH_AS(j.begin() == k.begin(), "[json.exception.invalid_iterator.212] cannot compare iterators of different containers", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(j.cbegin() == k.cbegin(), "[json.exception.invalid_iterator.212] cannot compare iterators of different containers", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(j.begin() < k.begin(), "[json.exception.invalid_iterator.212] cannot compare iterators of different containers", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(j.cbegin() < k.cbegin(), "[json.exception.invalid_iterator.212] cannot compare iterators of different containers", json::invalid_iterator&);
+#endif
+ }
+ }
+ }
+ }
+
+ SECTION("iterator arithmetic")
+ {
+ json j_object = {{"one", 1}, {"two", 2}, {"three", 3}};
+ json j_array = {1, 2, 3, 4, 5, 6};
+ json j_null = nullptr;
+ json j_value = 42;
+
+ SECTION("addition and subtraction")
+ {
+ SECTION("object")
+ {
+ {
+ auto it = j_object.begin();
+ CHECK_THROWS_WITH_AS(it += 1, "[json.exception.invalid_iterator.209] cannot use offsets with object iterators", json::invalid_iterator&);
+ }
+ {
+ auto it = j_object.cbegin();
+ CHECK_THROWS_WITH_AS(it += 1, "[json.exception.invalid_iterator.209] cannot use offsets with object iterators", json::invalid_iterator&);
+ }
+ {
+ auto it = j_object.begin();
+ CHECK_THROWS_WITH_AS(it + 1, "[json.exception.invalid_iterator.209] cannot use offsets with object iterators", json::invalid_iterator&);
+ }
+ {
+ auto it = j_object.cbegin();
+ CHECK_THROWS_WITH_AS(it + 1, "[json.exception.invalid_iterator.209] cannot use offsets with object iterators", json::invalid_iterator&);
+ }
+ {
+ auto it = j_object.begin();
+ CHECK_THROWS_WITH_AS(1 + it, "[json.exception.invalid_iterator.209] cannot use offsets with object iterators", json::invalid_iterator&);
+ }
+ {
+ auto it = j_object.cbegin();
+ CHECK_THROWS_WITH_AS(1 + it, "[json.exception.invalid_iterator.209] cannot use offsets with object iterators", json::invalid_iterator&);
+ }
+ {
+ auto it = j_object.begin();
+ CHECK_THROWS_WITH_AS(it -= 1, "[json.exception.invalid_iterator.209] cannot use offsets with object iterators", json::invalid_iterator&);
+ }
+ {
+ auto it = j_object.cbegin();
+ CHECK_THROWS_WITH_AS(it -= 1, "[json.exception.invalid_iterator.209] cannot use offsets with object iterators", json::invalid_iterator&);
+ }
+ {
+ auto it = j_object.begin();
+ CHECK_THROWS_WITH_AS(it - 1, "[json.exception.invalid_iterator.209] cannot use offsets with object iterators", json::invalid_iterator&);
+ }
+ {
+ auto it = j_object.cbegin();
+ CHECK_THROWS_WITH_AS(it - 1, "[json.exception.invalid_iterator.209] cannot use offsets with object iterators", json::invalid_iterator&);
+ }
+ {
+ auto it = j_object.begin();
+ CHECK_THROWS_WITH_AS(it - it, "[json.exception.invalid_iterator.209] cannot use offsets with object iterators", json::invalid_iterator&);
+ }
+ {
+ auto it = j_object.cbegin();
+ CHECK_THROWS_WITH_AS(it - it, "[json.exception.invalid_iterator.209] cannot use offsets with object iterators", json::invalid_iterator&);
+ }
+ }
+
+ SECTION("array")
+ {
+ {
+ auto it = j_array.begin();
+ it += 3;
+ CHECK((j_array.begin() + 3) == it);
+ CHECK((3 + j_array.begin()) == it);
+ CHECK((it - 3) == j_array.begin());
+ CHECK((it - j_array.begin()) == 3);
+ CHECK(*it == json(4));
+ it -= 2;
+ CHECK(*it == json(2));
+ }
+ {
+ auto it = j_array.cbegin();
+ it += 3;
+ CHECK((j_array.cbegin() + 3) == it);
+ CHECK((3 + j_array.cbegin()) == it);
+ CHECK((it - 3) == j_array.cbegin());
+ CHECK((it - j_array.cbegin()) == 3);
+ CHECK(*it == json(4));
+ it -= 2;
+ CHECK(*it == json(2));
+ }
+ }
+
+ SECTION("null")
+ {
+ {
+ auto it = j_null.begin();
+ it += 3;
+ CHECK((j_null.begin() + 3) == it);
+ CHECK((3 + j_null.begin()) == it);
+ CHECK((it - 3) == j_null.begin());
+ CHECK((it - j_null.begin()) == 3);
+ CHECK(it != j_null.end());
+ it -= 3;
+ CHECK(it == j_null.end());
+ }
+ {
+ auto it = j_null.cbegin();
+ it += 3;
+ CHECK((j_null.cbegin() + 3) == it);
+ CHECK((3 + j_null.cbegin()) == it);
+ CHECK((it - 3) == j_null.cbegin());
+ CHECK((it - j_null.cbegin()) == 3);
+ CHECK(it != j_null.cend());
+ it -= 3;
+ CHECK(it == j_null.cend());
+ }
+ }
+
+ SECTION("value")
+ {
+ {
+ auto it = j_value.begin();
+ it += 3;
+ CHECK((j_value.begin() + 3) == it);
+ CHECK((3 + j_value.begin()) == it);
+ CHECK((it - 3) == j_value.begin());
+ CHECK((it - j_value.begin()) == 3);
+ CHECK(it != j_value.end());
+ it -= 3;
+ CHECK(*it == json(42));
+ }
+ {
+ auto it = j_value.cbegin();
+ it += 3;
+ CHECK((j_value.cbegin() + 3) == it);
+ CHECK((3 + j_value.cbegin()) == it);
+ CHECK((it - 3) == j_value.cbegin());
+ CHECK((it - j_value.cbegin()) == 3);
+ CHECK(it != j_value.cend());
+ it -= 3;
+ CHECK(*it == json(42));
+ }
+ }
+ }
+
+ SECTION("subscript operator")
+ {
+ SECTION("object")
+ {
+ {
+ auto it = j_object.begin();
+ CHECK_THROWS_WITH_AS(it[0], "[json.exception.invalid_iterator.208] cannot use operator[] for object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it[1], "[json.exception.invalid_iterator.208] cannot use operator[] for object iterators", json::invalid_iterator&);
+ }
+ {
+ auto it = j_object.cbegin();
+ CHECK_THROWS_WITH_AS(it[0], "[json.exception.invalid_iterator.208] cannot use operator[] for object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it[1], "[json.exception.invalid_iterator.208] cannot use operator[] for object iterators", json::invalid_iterator&);
+ }
+ }
+
+ SECTION("array")
+ {
+ {
+ auto it = j_array.begin();
+ CHECK(it[0] == json(1));
+ CHECK(it[1] == json(2));
+ CHECK(it[2] == json(3));
+ CHECK(it[3] == json(4));
+ CHECK(it[4] == json(5));
+ CHECK(it[5] == json(6));
+ }
+ {
+ auto it = j_array.cbegin();
+ CHECK(it[0] == json(1));
+ CHECK(it[1] == json(2));
+ CHECK(it[2] == json(3));
+ CHECK(it[3] == json(4));
+ CHECK(it[4] == json(5));
+ CHECK(it[5] == json(6));
+ }
+ }
+
+ SECTION("null")
+ {
+ {
+ auto it = j_null.begin();
+ CHECK_THROWS_WITH_AS(it[0], "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it[1], "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ }
+ {
+ auto it = j_null.cbegin();
+ CHECK_THROWS_WITH_AS(it[0], "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it[1], "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ }
+ }
+
+ SECTION("value")
+ {
+ {
+ auto it = j_value.begin();
+ CHECK(it[0] == json(42));
+ CHECK_THROWS_WITH_AS(it[1], "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ }
+ {
+ auto it = j_value.cbegin();
+ CHECK(it[0] == json(42));
+ CHECK_THROWS_WITH_AS(it[1], "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ }
+ }
+ }
+ }
+
+ SECTION("reverse iterator comparisons")
+ {
+ json j_values = {nullptr, true, 42, 42u, 23.23, {{"one", 1}, {"two", 2}}, {1, 2, 3, 4, 5}, "Hello, world"};
+
+ for (json& j : j_values)
+ {
+ auto it1 = j.rbegin();
+ auto it2 = j.rbegin();
+ auto it3 = j.rbegin();
+ ++it2;
+ ++it3;
+ ++it3;
+ auto it1_c = j.crbegin();
+ auto it2_c = j.crbegin();
+ auto it3_c = j.crbegin();
+ ++it2_c;
+ ++it3_c;
+ ++it3_c;
+
+ // comparison: equal
+ {
+ CHECK(it1 == it1);
+ CHECK(!(it1 == it2));
+ CHECK(!(it1 == it3));
+ CHECK(!(it2 == it3));
+ CHECK(it1_c == it1_c);
+ CHECK(!(it1_c == it2_c));
+ CHECK(!(it1_c == it3_c));
+ CHECK(!(it2_c == it3_c));
+ }
+
+ // comparison: not equal
+ {
+ // check definition
+ CHECK( (it1 != it1) == !(it1 == it1) );
+ CHECK( (it1 != it2) == !(it1 == it2) );
+ CHECK( (it1 != it3) == !(it1 == it3) );
+ CHECK( (it2 != it3) == !(it2 == it3) );
+ CHECK( (it1_c != it1_c) == !(it1_c == it1_c) );
+ CHECK( (it1_c != it2_c) == !(it1_c == it2_c) );
+ CHECK( (it1_c != it3_c) == !(it1_c == it3_c) );
+ CHECK( (it2_c != it3_c) == !(it2_c == it3_c) );
+ }
+
+ // comparison: smaller
+ {
+ if (j.type() == json::value_t::object)
+ {
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(it1 < it1, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1 < it2, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it2 < it3, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1 < it3, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c < it1_c, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c < it2_c, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it2_c < it3_c, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c < it3_c, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+#else
+ CHECK_THROWS_WITH_AS(it1 < it1, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1 < it2, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it2 < it3, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1 < it3, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c < it1_c, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c < it2_c, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it2_c < it3_c, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c < it3_c, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+#endif
+ }
+ else
+ {
+ CHECK(!(it1 < it1));
+ CHECK(it1 < it2);
+ CHECK(it1 < it3);
+ CHECK(it2 < it3);
+ CHECK(!(it1_c < it1_c));
+ CHECK(it1_c < it2_c);
+ CHECK(it1_c < it3_c);
+ CHECK(it2_c < it3_c);
+ }
+ }
+
+ // comparison: less than or equal
+ {
+ if (j.type() == json::value_t::object)
+ {
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(it1 <= it1, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1 <= it2, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it2 <= it3, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1 <= it3, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c <= it1_c, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c <= it2_c, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it2_c <= it3_c, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c <= it3_c, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+#else
+ CHECK_THROWS_WITH_AS(it1 <= it1, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1 <= it2, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it2 <= it3, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1 <= it3, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c <= it1_c, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c <= it2_c, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it2_c <= it3_c, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c <= it3_c, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+#endif
+ }
+ else
+ {
+ // check definition
+ CHECK( (it1 <= it1) == !(it1 < it1) );
+ CHECK( (it1 <= it2) == !(it2 < it1) );
+ CHECK( (it1 <= it3) == !(it3 < it1) );
+ CHECK( (it2 <= it3) == !(it3 < it2) );
+ CHECK( (it1_c <= it1_c) == !(it1_c < it1_c) );
+ CHECK( (it1_c <= it2_c) == !(it2_c < it1_c) );
+ CHECK( (it1_c <= it3_c) == !(it3_c < it1_c) );
+ CHECK( (it2_c <= it3_c) == !(it3_c < it2_c) );
+ }
+ }
+
+ // comparison: greater than
+ {
+ if (j.type() == json::value_t::object)
+ {
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(it1 > it1, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1 > it2, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it2 > it3, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1 > it3, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c > it1_c, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c > it2_c, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it2_c > it3_c, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c > it3_c, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+#else
+ CHECK_THROWS_WITH_AS(it1 > it1, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1 > it2, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it2 > it3, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1 > it3, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c > it1_c, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c > it2_c, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it2_c > it3_c, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c > it3_c, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+#endif
+ }
+ else
+ {
+ // check definition
+ CHECK( (it1 > it1) == (it1 < it1) );
+ CHECK( (it1 > it2) == (it2 < it1) );
+ CHECK( (it1 > it3) == (it3 < it1) );
+ CHECK( (it2 > it3) == (it3 < it2) );
+ CHECK( (it1_c > it1_c) == (it1_c < it1_c) );
+ CHECK( (it1_c > it2_c) == (it2_c < it1_c) );
+ CHECK( (it1_c > it3_c) == (it3_c < it1_c) );
+ CHECK( (it2_c > it3_c) == (it3_c < it2_c) );
+ }
+ }
+
+ // comparison: greater than or equal
+ {
+ if (j.type() == json::value_t::object)
+ {
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(it1 >= it1, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1 >= it2, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it2 >= it3, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1 >= it3, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c >= it1_c, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c >= it2_c, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it2_c >= it3_c, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c >= it3_c, "[json.exception.invalid_iterator.213] (/5) cannot compare order of object iterators", json::invalid_iterator&);
+#else
+ CHECK_THROWS_WITH_AS(it1 >= it1, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1 >= it2, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it2 >= it3, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1 >= it3, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c >= it1_c, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c >= it2_c, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it2_c >= it3_c, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it1_c >= it3_c, "[json.exception.invalid_iterator.213] cannot compare order of object iterators", json::invalid_iterator&);
+#endif
+ }
+ else
+ {
+ // check definition
+ CHECK( (it1 >= it1) == !(it1 < it1) );
+ CHECK( (it1 >= it2) == !(it1 < it2) );
+ CHECK( (it1 >= it3) == !(it1 < it3) );
+ CHECK( (it2 >= it3) == !(it2 < it3) );
+ CHECK( (it1_c >= it1_c) == !(it1_c < it1_c) );
+ CHECK( (it1_c >= it2_c) == !(it1_c < it2_c) );
+ CHECK( (it1_c >= it3_c) == !(it1_c < it3_c) );
+ CHECK( (it2_c >= it3_c) == !(it2_c < it3_c) );
+ }
+ }
+ }
+
+ // check exceptions if different objects are compared
+ for (auto j : j_values)
+ {
+ for (auto k : j_values)
+ {
+ if (j != k)
+ {
+#if JSON_DIAGNOSTICS
+ // the output differs in each loop, so we cannot fix a string for the expected exception
+#else
+ CHECK_THROWS_WITH_AS(j.rbegin() == k.rbegin(), "[json.exception.invalid_iterator.212] cannot compare iterators of different containers", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(j.crbegin() == k.crbegin(), "[json.exception.invalid_iterator.212] cannot compare iterators of different containers", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(j.rbegin() < k.rbegin(), "[json.exception.invalid_iterator.212] cannot compare iterators of different containers", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(j.crbegin() < k.crbegin(), "[json.exception.invalid_iterator.212] cannot compare iterators of different containers", json::invalid_iterator&);
+#endif
+ }
+ }
+ }
+ }
+
+ SECTION("reverse iterator arithmetic")
+ {
+ json j_object = {{"one", 1}, {"two", 2}, {"three", 3}};
+ json j_array = {1, 2, 3, 4, 5, 6};
+ json j_null = nullptr;
+ json j_value = 42;
+
+ SECTION("addition and subtraction")
+ {
+ SECTION("object")
+ {
+ {
+ auto it = j_object.rbegin();
+ CHECK_THROWS_WITH_AS(it += 1, "[json.exception.invalid_iterator.209] cannot use offsets with object iterators", json::invalid_iterator&);
+ }
+ {
+ auto it = j_object.crbegin();
+ CHECK_THROWS_WITH_AS(it += 1, "[json.exception.invalid_iterator.209] cannot use offsets with object iterators", json::invalid_iterator&);
+ }
+ {
+ auto it = j_object.rbegin();
+ CHECK_THROWS_WITH_AS(it + 1, "[json.exception.invalid_iterator.209] cannot use offsets with object iterators", json::invalid_iterator&);
+ }
+ {
+ auto it = j_object.crbegin();
+ CHECK_THROWS_WITH_AS(it + 1, "[json.exception.invalid_iterator.209] cannot use offsets with object iterators", json::invalid_iterator&);
+ }
+ {
+ auto it = j_object.rbegin();
+ CHECK_THROWS_WITH_AS(1 + it, "[json.exception.invalid_iterator.209] cannot use offsets with object iterators", json::invalid_iterator&);
+ }
+ {
+ auto it = j_object.crbegin();
+ CHECK_THROWS_WITH_AS(1 + it, "[json.exception.invalid_iterator.209] cannot use offsets with object iterators", json::invalid_iterator&);
+ }
+ {
+ auto it = j_object.rbegin();
+ CHECK_THROWS_WITH_AS(it -= 1, "[json.exception.invalid_iterator.209] cannot use offsets with object iterators", json::invalid_iterator&);
+ }
+ {
+ auto it = j_object.crbegin();
+ CHECK_THROWS_WITH_AS(it -= 1, "[json.exception.invalid_iterator.209] cannot use offsets with object iterators", json::invalid_iterator&);
+ }
+ {
+ auto it = j_object.rbegin();
+ CHECK_THROWS_WITH_AS(it - 1, "[json.exception.invalid_iterator.209] cannot use offsets with object iterators", json::invalid_iterator&);
+ }
+ {
+ auto it = j_object.crbegin();
+ CHECK_THROWS_WITH_AS(it - 1, "[json.exception.invalid_iterator.209] cannot use offsets with object iterators", json::invalid_iterator&);
+ }
+ {
+ auto it = j_object.rbegin();
+ CHECK_THROWS_WITH_AS(it - it, "[json.exception.invalid_iterator.209] cannot use offsets with object iterators", json::invalid_iterator&);
+ }
+ {
+ auto it = j_object.crbegin();
+ CHECK_THROWS_WITH_AS(it - it, "[json.exception.invalid_iterator.209] cannot use offsets with object iterators", json::invalid_iterator&);
+ }
+ }
+
+ SECTION("array")
+ {
+ {
+ auto it = j_array.rbegin();
+ it += 3;
+ CHECK((j_array.rbegin() + 3) == it);
+ CHECK(json::reverse_iterator(3 + j_array.rbegin()) == it);
+ CHECK((it - 3) == j_array.rbegin());
+ CHECK((it - j_array.rbegin()) == 3);
+ CHECK(*it == json(3));
+ it -= 2;
+ CHECK(*it == json(5));
+ }
+ {
+ auto it = j_array.crbegin();
+ it += 3;
+ CHECK((j_array.crbegin() + 3) == it);
+ CHECK(json::const_reverse_iterator(3 + j_array.crbegin()) == it);
+ CHECK((it - 3) == j_array.crbegin());
+ CHECK((it - j_array.crbegin()) == 3);
+ CHECK(*it == json(3));
+ it -= 2;
+ CHECK(*it == json(5));
+ }
+ }
+
+ SECTION("null")
+ {
+ {
+ auto it = j_null.rbegin();
+ it += 3;
+ CHECK((j_null.rbegin() + 3) == it);
+ CHECK(json::reverse_iterator(3 + j_null.rbegin()) == it);
+ CHECK((it - 3) == j_null.rbegin());
+ CHECK((it - j_null.rbegin()) == 3);
+ CHECK(it != j_null.rend());
+ it -= 3;
+ CHECK(it == j_null.rend());
+ }
+ {
+ auto it = j_null.crbegin();
+ it += 3;
+ CHECK((j_null.crbegin() + 3) == it);
+ CHECK(json::const_reverse_iterator(3 + j_null.crbegin()) == it);
+ CHECK((it - 3) == j_null.crbegin());
+ CHECK((it - j_null.crbegin()) == 3);
+ CHECK(it != j_null.crend());
+ it -= 3;
+ CHECK(it == j_null.crend());
+ }
+ }
+
+ SECTION("value")
+ {
+ {
+ auto it = j_value.rbegin();
+ it += 3;
+ CHECK((j_value.rbegin() + 3) == it);
+ CHECK(json::reverse_iterator(3 + j_value.rbegin()) == it);
+ CHECK((it - 3) == j_value.rbegin());
+ CHECK((it - j_value.rbegin()) == 3);
+ CHECK(it != j_value.rend());
+ it -= 3;
+ CHECK(*it == json(42));
+ }
+ {
+ auto it = j_value.crbegin();
+ it += 3;
+ CHECK((j_value.crbegin() + 3) == it);
+ CHECK(json::const_reverse_iterator(3 + j_value.crbegin()) == it);
+ CHECK((it - 3) == j_value.crbegin());
+ CHECK((it - j_value.crbegin()) == 3);
+ CHECK(it != j_value.crend());
+ it -= 3;
+ CHECK(*it == json(42));
+ }
+ }
+ }
+
+ SECTION("subscript operator")
+ {
+ SECTION("object")
+ {
+ {
+ auto it = j_object.rbegin();
+ CHECK_THROWS_WITH_AS(it[0], "[json.exception.invalid_iterator.209] cannot use offsets with object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it[1], "[json.exception.invalid_iterator.209] cannot use offsets with object iterators", json::invalid_iterator&);
+ }
+ {
+ auto it = j_object.crbegin();
+ CHECK_THROWS_WITH_AS(it[0], "[json.exception.invalid_iterator.209] cannot use offsets with object iterators", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it[1], "[json.exception.invalid_iterator.209] cannot use offsets with object iterators", json::invalid_iterator&);
+ }
+ }
+
+ SECTION("array")
+ {
+ {
+ auto it = j_array.rbegin();
+ CHECK(it[0] == json(6));
+ CHECK(it[1] == json(5));
+ CHECK(it[2] == json(4));
+ CHECK(it[3] == json(3));
+ CHECK(it[4] == json(2));
+ CHECK(it[5] == json(1));
+ }
+ {
+ auto it = j_array.crbegin();
+ CHECK(it[0] == json(6));
+ CHECK(it[1] == json(5));
+ CHECK(it[2] == json(4));
+ CHECK(it[3] == json(3));
+ CHECK(it[4] == json(2));
+ CHECK(it[5] == json(1));
+ }
+ }
+
+ SECTION("null")
+ {
+ {
+ auto it = j_null.rbegin();
+ CHECK_THROWS_WITH_AS(it[0], "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it[1], "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ }
+ {
+ auto it = j_null.crbegin();
+ CHECK_THROWS_WITH_AS(it[0], "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(it[1], "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ }
+ }
+
+ SECTION("value")
+ {
+ {
+ auto it = j_value.rbegin();
+ CHECK(it[0] == json(42));
+ CHECK_THROWS_WITH_AS(it[1], "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ }
+ {
+ auto it = j_value.crbegin();
+ CHECK(it[0] == json(42));
+ CHECK_THROWS_WITH_AS(it[1], "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
+ }
+ }
+ }
+ }
+
+#if JSON_HAS_RANGES
+ // JSON_HAS_CPP_20 (do not remove; see note at top of file)
+ SECTION("ranges")
+ {
+ SECTION("concepts")
+ {
+ using nlohmann::detail::iteration_proxy_value;
+ CHECK(std::bidirectional_iterator<json::iterator>);
+ CHECK(std::input_iterator<iteration_proxy_value<json::iterator>>);
+
+ CHECK(std::is_same<json::iterator, std::ranges::iterator_t<json>>::value);
+ CHECK(std::ranges::bidirectional_range<json>);
+
+ using nlohmann::detail::iteration_proxy;
+ using items_type = decltype(std::declval<json&>().items());
+ CHECK(std::is_same<items_type, iteration_proxy<json::iterator>>::value);
+ CHECK(std::is_same<iteration_proxy_value<json::iterator>, std::ranges::iterator_t<items_type>>::value);
+ CHECK(std::ranges::input_range<items_type>);
+ }
+
+ // libstdc++ algorithms don't work with Clang 15 (04/2022)
+#if !DOCTEST_CLANG || (DOCTEST_CLANG && defined(__GLIBCXX__))
+ SECTION("algorithms")
+ {
+ SECTION("copy")
+ {
+ json j{"foo", "bar"};
+ auto j_copied = json::array();
+
+ std::ranges::copy(j, std::back_inserter(j_copied));
+
+ CHECK(j == j_copied);
+ }
+
+ SECTION("find_if")
+ {
+ json j{1, 3, 2, 4};
+ auto j_even = json::array();
+
+#if JSON_USE_IMPLICIT_CONVERSIONS
+ auto it = std::ranges::find_if(j, [](int v) noexcept
+ {
+ return (v % 2) == 0;
+ });
+#else
+ auto it = std::ranges::find_if(j, [](const json & j) noexcept
+ {
+ int v;
+ j.get_to(v);
+ return (v % 2) == 0;
+ });
+#endif
+
+ CHECK(*it == 2);
+ }
+ }
+#endif
+
+ // libstdc++ views don't work with Clang 15 (04/2022)
+ // libc++ hides limited ranges implementation behind guard macro
+#if !(DOCTEST_CLANG && (defined(__GLIBCXX__) || defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES)))
+ SECTION("views")
+ {
+ SECTION("reverse")
+ {
+ json j{1, 2, 3, 4, 5};
+ json j_expected{5, 4, 3, 2, 1};
+
+ auto reversed = j | std::views::reverse;
+ CHECK(std::ranges::equal(reversed, j_expected));
+ }
+
+ SECTION("transform")
+ {
+ json j
+ {
+ { "a_key", "a_value"},
+ { "b_key", "b_value"},
+ { "c_key", "c_value"},
+ };
+ json j_expected{"a_key", "b_key", "c_key"};
+
+ // NOLINTNEXTLINE(fuchsia-trailing-return)
+ auto transformed = j.items() | std::views::transform([](const auto & item) -> std::string_view
+ {
+ return item.key();
+ });
+ auto j_transformed = json::array();
+ std::ranges::copy(transformed, std::back_inserter(j_transformed));
+
+ CHECK(j_transformed == j_expected);
+ }
+ }
+#endif
+ }
+#endif
+}
diff --git a/json4cpp/tests/src/unit-iterators3.cpp b/json4cpp/tests/src/unit-iterators3.cpp
new file mode 100644
index 0000000000..f9b86ade39
--- /dev/null
+++ b/json4cpp/tests/src/unit-iterators3.cpp
@@ -0,0 +1,35 @@
+// __ _____ _____ _____
+// __| | __| | | | 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 <string>
+#include <unordered_map>
+#include <vector>
+
+#include <nlohmann/json.hpp>
+using nlohmann::json;
+
+#if (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1)
+ #define JSON_HAS_CPP_14
+#endif
+
+#ifdef JSON_HAS_CPP_14
+TEST_CASE_TEMPLATE("checking forward-iterators", T, // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
+ std::vector<int>, std::string, nlohmann::json)
+{
+ auto it1 = typename T::iterator{};
+ auto it2 = typename T::iterator{};
+ CHECK(it1 == it2);
+ CHECK(it1 <= it2);
+ CHECK(it1 >= it2);
+ CHECK_FALSE(it1 != it2);
+ CHECK_FALSE(it1 < it2);
+ CHECK_FALSE(it1 > it2);
+}
+#endif
diff --git a/json4cpp/tests/src/unit-json_patch.cpp b/json4cpp/tests/src/unit-json_patch.cpp
new file mode 100644
index 0000000000..fa375b2271
--- /dev/null
+++ b/json4cpp/tests/src/unit-json_patch.cpp
@@ -0,0 +1,1336 @@
+// __ _____ _____ _____
+// __| | __| | | | 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 <fstream>
+#include "make_test_data_available.hpp"
+
+TEST_CASE("JSON patch")
+{
+ SECTION("examples from RFC 6902")
+ {
+ SECTION("4. Operations")
+ {
+ // the ordering of members in JSON objects is not significant:
+ const json op1 = R"({ "op": "add", "path": "/a/b/c", "value": "foo" })"_json;
+ const json op2 = R"({ "path": "/a/b/c", "op": "add", "value": "foo" })"_json;
+ const json op3 = R"({ "value": "foo", "path": "/a/b/c", "op": "add" })"_json;
+
+ // check if the operation objects are equivalent
+ CHECK(op1 == op2);
+ CHECK(op1 == op3);
+ }
+
+ SECTION("4.1 add")
+ {
+ json const patch1 = R"([{ "op": "add", "path": "/a/b", "value": [ "foo", "bar" ] }])"_json;
+
+ // However, the object itself or an array containing it does need
+ // to exist, and it remains an error for that not to be the case.
+ // For example, an "add" with a target location of "/a/b" starting
+ // with this document
+ json const doc1 = R"({ "a": { "foo": 1 } })"_json;
+
+ // is not an error, because "a" exists, and "b" will be added to
+ // its value.
+ CHECK_NOTHROW(doc1.patch(patch1));
+ auto doc1_ans = R"(
+ {
+ "a": {
+ "foo": 1,
+ "b": [ "foo", "bar" ]
+ }
+ }
+ )"_json;
+ CHECK(doc1.patch(patch1) == doc1_ans);
+
+ // It is an error in this document:
+ json const doc2 = R"({ "q": { "bar": 2 } })"_json;
+
+ // because "a" does not exist.
+#if JSON_DIAGNOSTIC_POSITIONS
+ CHECK_THROWS_WITH_AS(doc2.patch(patch1), "[json.exception.out_of_range.403] (bytes 0-21) key 'a' not found", json::out_of_range&);
+#else
+ CHECK_THROWS_WITH_AS(doc2.patch(patch1), "[json.exception.out_of_range.403] key 'a' not found", json::out_of_range&);
+#endif
+
+ json const doc3 = R"({ "a": {} })"_json;
+ json const patch2 = R"([{ "op": "add", "path": "/a/b/c", "value": 1 }])"_json;
+
+ // should cause an error because "b" does not exist in doc3
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(doc3.patch(patch2), "[json.exception.out_of_range.403] (/a) key 'b' not found", json::out_of_range&);
+#elif JSON_DIAGNOSTIC_POSITIONS
+ CHECK_THROWS_WITH_AS(doc3.patch(patch2), "[json.exception.out_of_range.403] (bytes 7-9) key 'b' not found", json::out_of_range&);
+#else
+ CHECK_THROWS_WITH_AS(doc3.patch(patch2), "[json.exception.out_of_range.403] key 'b' not found", json::out_of_range&);
+#endif
+ }
+
+ SECTION("4.2 remove")
+ {
+ // If removing an element from an array, any elements above the
+ // specified index are shifted one position to the left.
+ json const doc = {1, 2, 3, 4};
+ json const patch = {{{"op", "remove"}, {"path", "/1"}}};
+ CHECK(doc.patch(patch) == json({1, 3, 4}));
+ }
+
+ SECTION("A.1. Adding an Object Member")
+ {
+ // An example target JSON document:
+ json const doc = R"(
+ { "foo": "bar"}
+ )"_json;
+
+ // A JSON Patch document:
+ json const patch = R"(
+ [
+ { "op": "add", "path": "/baz", "value": "qux" }
+ ]
+ )"_json;
+
+ // The resulting JSON document:
+ json expected = R"(
+ {
+ "baz": "qux",
+ "foo": "bar"
+ }
+ )"_json;
+
+ // check if patched value is as expected
+ CHECK(doc.patch(patch) == expected);
+
+ // check roundtrip
+ CHECK(doc.patch(json::diff(doc, expected)) == expected);
+ }
+
+ SECTION("A.2. Adding an Array Element")
+ {
+ // An example target JSON document:
+ json const doc = R"(
+ { "foo": [ "bar", "baz" ] }
+ )"_json;
+
+ // A JSON Patch document:
+ json const patch = R"(
+ [
+ { "op": "add", "path": "/foo/1", "value": "qux" }
+ ]
+ )"_json;
+
+ // The resulting JSON document:
+ json expected = R"(
+ { "foo": [ "bar", "qux", "baz" ] }
+ )"_json;
+
+ // check if patched value is as expected
+ CHECK(doc.patch(patch) == expected);
+
+ // check roundtrip
+ CHECK(doc.patch(json::diff(doc, expected)) == expected);
+ }
+
+ SECTION("A.3. Removing an Object Member")
+ {
+ // An example target JSON document:
+ json const doc = R"(
+ {
+ "baz": "qux",
+ "foo": "bar"
+ }
+ )"_json;
+
+ // A JSON Patch document:
+ json const patch = R"(
+ [
+ { "op": "remove", "path": "/baz" }
+ ]
+ )"_json;
+
+ // The resulting JSON document:
+ json expected = R"(
+ { "foo": "bar" }
+ )"_json;
+
+ // check if patched value is as expected
+ CHECK(doc.patch(patch) == expected);
+
+ // check roundtrip
+ CHECK(doc.patch(json::diff(doc, expected)) == expected);
+ }
+
+ SECTION("A.4. Removing an Array Element")
+ {
+ // An example target JSON document:
+ json const doc = R"(
+ { "foo": [ "bar", "qux", "baz" ] }
+ )"_json;
+
+ // A JSON Patch document:
+ json const patch = R"(
+ [
+ { "op": "remove", "path": "/foo/1" }
+ ]
+ )"_json;
+
+ // The resulting JSON document:
+ json expected = R"(
+ { "foo": [ "bar", "baz" ] }
+ )"_json;
+
+ // check if patched value is as expected
+ CHECK(doc.patch(patch) == expected);
+
+ // check roundtrip
+ CHECK(doc.patch(json::diff(doc, expected)) == expected);
+ }
+
+ SECTION("A.5. Replacing a Value")
+ {
+ // An example target JSON document:
+ json const doc = R"(
+ {
+ "baz": "qux",
+ "foo": "bar"
+ }
+ )"_json;
+
+ // A JSON Patch document:
+ json const patch = R"(
+ [
+ { "op": "replace", "path": "/baz", "value": "boo" }
+ ]
+ )"_json;
+
+ json expected = R"(
+ {
+ "baz": "boo",
+ "foo": "bar"
+ }
+ )"_json;
+
+ // check if patched value is as expected
+ CHECK(doc.patch(patch) == expected);
+
+ // check roundtrip
+ CHECK(doc.patch(json::diff(doc, expected)) == expected);
+ }
+
+ SECTION("A.6. Moving a Value")
+ {
+ // An example target JSON document:
+ json const doc = R"(
+ {
+ "foo": {
+ "bar": "baz",
+ "waldo": "fred"
+ },
+ "qux": {
+ "corge": "grault"
+ }
+ }
+ )"_json;
+
+ // A JSON Patch document:
+ json const patch = R"(
+ [
+ { "op": "move", "from": "/foo/waldo", "path": "/qux/thud" }
+ ]
+ )"_json;
+
+ // The resulting JSON document:
+ json expected = R"(
+ {
+ "foo": {
+ "bar": "baz"
+ },
+ "qux": {
+ "corge": "grault",
+ "thud": "fred"
+ }
+ }
+ )"_json;
+
+ // check if patched value is as expected
+ CHECK(doc.patch(patch) == expected);
+
+ // check roundtrip
+ CHECK(doc.patch(json::diff(doc, expected)) == expected);
+ }
+
+ SECTION("A.7. Moving a Value")
+ {
+ // An example target JSON document:
+ json const doc = R"(
+ { "foo": [ "all", "grass", "cows", "eat" ] }
+ )"_json;
+
+ // A JSON Patch document:
+ json const patch = R"(
+ [
+ { "op": "move", "from": "/foo/1", "path": "/foo/3" }
+ ]
+ )"_json;
+
+ // The resulting JSON document:
+ json expected = R"(
+ { "foo": [ "all", "cows", "eat", "grass" ] }
+ )"_json;
+
+ // check if patched value is as expected
+ CHECK(doc.patch(patch) == expected);
+
+ // check roundtrip
+ CHECK(doc.patch(json::diff(doc, expected)) == expected);
+ }
+
+ SECTION("A.8. Testing a Value: Success")
+ {
+ // An example target JSON document:
+ json doc = R"(
+ {
+ "baz": "qux",
+ "foo": [ "a", 2, "c" ]
+ }
+ )"_json;
+
+ // A JSON Patch document that will result in successful evaluation:
+ json const patch = R"(
+ [
+ { "op": "test", "path": "/baz", "value": "qux" },
+ { "op": "test", "path": "/foo/1", "value": 2 }
+ ]
+ )"_json;
+
+ // check if evaluation does not throw
+ CHECK_NOTHROW(doc.patch(patch));
+ // check if patched document is unchanged
+ CHECK(doc.patch(patch) == doc);
+ }
+
+ SECTION("A.9. Testing a Value: Error")
+ {
+ // An example target JSON document:
+ json const doc = R"(
+ { "baz": "qux" }
+ )"_json;
+
+ // A JSON Patch document that will result in an error condition:
+ json patch = R"(
+ [
+ { "op": "test", "path": "/baz", "value": "bar" }
+ ]
+ )"_json;
+
+ // check that evaluation throws
+ CHECK_THROWS_AS(doc.patch(patch), json::other_error&);
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] (/0) unsuccessful: " + patch[0].dump());
+#elif JSON_DIAGNOSTIC_POSITIONS
+ CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] (bytes 47-95) unsuccessful: " + patch[0].dump());
+#else
+ CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] unsuccessful: " + patch[0].dump());
+#endif
+ }
+
+ SECTION("A.10. Adding a Nested Member Object")
+ {
+ // An example target JSON document:
+ json const doc = R"(
+ { "foo": "bar" }
+ )"_json;
+
+ // A JSON Patch document:
+ json const patch = R"(
+ [
+ { "op": "add", "path": "/child", "value": { "grandchild": { } } }
+ ]
+ )"_json;
+
+ // The resulting JSON document:
+ json expected = R"(
+ {
+ "foo": "bar",
+ "child": {
+ "grandchild": {
+ }
+ }
+ }
+ )"_json;
+
+ // check if patched value is as expected
+ CHECK(doc.patch(patch) == expected);
+
+ // check roundtrip
+ CHECK(doc.patch(json::diff(doc, expected)) == expected);
+ }
+
+ SECTION("A.11. Ignoring Unrecognized Elements")
+ {
+ // An example target JSON document:
+ json const doc = R"(
+ { "foo": "bar" }
+ )"_json;
+
+ // A JSON Patch document:
+ json const patch = R"(
+ [
+ { "op": "add", "path": "/baz", "value": "qux", "xyz": 123 }
+ ]
+ )"_json;
+
+ json expected = R"(
+ {
+ "foo": "bar",
+ "baz": "qux"
+ }
+ )"_json;
+
+ // check if patched value is as expected
+ CHECK(doc.patch(patch) == expected);
+
+ // check roundtrip
+ CHECK(doc.patch(json::diff(doc, expected)) == expected);
+ }
+
+ SECTION("A.12. Adding to a Nonexistent Target")
+ {
+ // An example target JSON document:
+ json const doc = R"(
+ { "foo": "bar" }
+ )"_json;
+
+ // A JSON Patch document:
+ json const patch = R"(
+ [
+ { "op": "add", "path": "/baz/bat", "value": "qux" }
+ ]
+ )"_json;
+
+ // This JSON Patch document, applied to the target JSON document
+ // above, would result in an error (therefore, it would not be
+ // applied), because the "add" operation's target location that
+ // references neither the root of the document, nor a member of
+ // an existing object, nor a member of an existing array.
+#if JSON_DIAGNOSTIC_POSITIONS
+ CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.403] (bytes 21-37) key 'baz' not found", json::out_of_range&);
+#else
+ CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.403] key 'baz' not found", json::out_of_range&);
+#endif
+ }
+
+ // A.13. Invalid JSON Patch Document
+ // not applicable
+
+ SECTION("A.14. Escape Ordering")
+ {
+ // An example target JSON document:
+ json const doc = R"(
+ {
+ "/": 9,
+ "~1": 10
+ }
+ )"_json;
+
+ // A JSON Patch document:
+ json const patch = R"(
+ [
+ {"op": "test", "path": "/~01", "value": 10}
+ ]
+ )"_json;
+
+ json expected = R"(
+ {
+ "/": 9,
+ "~1": 10
+ }
+ )"_json;
+
+ // check if patched value is as expected
+ CHECK(doc.patch(patch) == expected);
+
+ // check roundtrip
+ CHECK(doc.patch(json::diff(doc, expected)) == expected);
+ }
+
+ SECTION("A.15. Comparing Strings and Numbers")
+ {
+ // An example target JSON document:
+ json const doc = R"(
+ {
+ "/": 9,
+ "~1": 10
+ }
+ )"_json;
+
+ // A JSON Patch document that will result in an error condition:
+ json patch = R"(
+ [
+ {"op": "test", "path": "/~01", "value": "10"}
+ ]
+ )"_json;
+
+ // check that evaluation throws
+ CHECK_THROWS_AS(doc.patch(patch), json::other_error&);
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] (/0) unsuccessful: " + patch[0].dump());
+#elif JSON_DIAGNOSTIC_POSITIONS
+ CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] (bytes 47-92) unsuccessful: " + patch[0].dump());
+#else
+ CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] unsuccessful: " + patch[0].dump());
+#endif
+ }
+
+ SECTION("A.16. Adding an Array Value")
+ {
+ // An example target JSON document:
+ json const doc = R"(
+ { "foo": ["bar"] }
+ )"_json;
+
+ // A JSON Patch document:
+ json const patch = R"(
+ [
+ { "op": "add", "path": "/foo/-", "value": ["abc", "def"] }
+ ]
+ )"_json;
+
+ // The resulting JSON document:
+ json expected = R"(
+ { "foo": ["bar", ["abc", "def"]] }
+ )"_json;
+
+ // check if patched value is as expected
+ CHECK(doc.patch(patch) == expected);
+
+ // check roundtrip
+ CHECK(doc.patch(json::diff(doc, expected)) == expected);
+ }
+ }
+
+ SECTION("own examples")
+ {
+ SECTION("add")
+ {
+ SECTION("add to the root element")
+ {
+ // If the path is the root of the target document - the
+ // specified value becomes the entire content of the target
+ // document.
+
+ // An example target JSON document:
+ json const doc = 17;
+
+ // A JSON Patch document:
+ json const patch = R"(
+ [
+ { "op": "add", "path": "", "value": [1,2,3] }
+ ]
+ )"_json;
+
+ // The resulting JSON document:
+ json expected = {1, 2, 3};
+
+ // check if patched value is as expected
+ CHECK(doc.patch(patch) == expected);
+
+ // check roundtrip
+ CHECK(doc.patch(json::diff(doc, expected)) == expected);
+ }
+
+ SECTION("add to end of the array")
+ {
+ // The specified index MUST NOT be greater than the number of
+ // elements in the array. The example below uses and index of
+ // exactly the number of elements in the array which is legal.
+
+ // An example target JSON document:
+ json const doc = {0, 1, 2};
+
+ // A JSON Patch document:
+ json const patch = R"(
+ [
+ { "op": "add", "path": "/3", "value": 3 }
+ ]
+ )"_json;
+
+ // The resulting JSON document:
+ json expected = {0, 1, 2, 3};
+
+ // check if patched value is as expected
+ CHECK(doc.patch(patch) == expected);
+
+ // check roundtrip
+ CHECK(doc.patch(json::diff(doc, expected)) == expected);
+ }
+ }
+
+ SECTION("copy")
+ {
+ // An example target JSON document:
+ json const doc = R"(
+ {
+ "foo": {
+ "bar": "baz",
+ "waldo": "fred"
+ },
+ "qux": {
+ "corge": "grault"
+ }
+ }
+ )"_json;
+
+ // A JSON Patch document:
+ json const patch = R"(
+ [
+ { "op": "copy", "from": "/foo/waldo", "path": "/qux/thud" }
+ ]
+ )"_json;
+
+ // The resulting JSON document:
+ json expected = R"(
+ {
+ "foo": {
+ "bar": "baz",
+ "waldo": "fred"
+ },
+ "qux": {
+ "corge": "grault",
+ "thud": "fred"
+ }
+ }
+ )"_json;
+
+ // check if patched value is as expected
+ CHECK(doc.patch(patch) == expected);
+
+ // check roundtrip
+ CHECK(doc.patch(json::diff(doc, expected)) == expected);
+ }
+
+ SECTION("replace")
+ {
+ json const j = "string";
+ json const patch = {{{"op", "replace"}, {"path", ""}, {"value", 1}}};
+ CHECK(j.patch(patch) == json(1));
+ }
+
+ SECTION("documentation GIF")
+ {
+ {
+ // a JSON patch
+ json const p1 = R"(
+ [{"op": "add", "path": "/GB", "value": "London"}]
+ )"_json;
+
+ // a JSON value
+ json const source = R"(
+ {"D": "Berlin", "F": "Paris"}
+ )"_json;
+
+ // apply the patch
+ const json target = source.patch(p1);
+ // target = { "D": "Berlin", "F": "Paris", "GB": "London" }
+ CHECK(target == R"({ "D": "Berlin", "F": "Paris", "GB": "London" })"_json);
+
+ // create a diff from two JSONs
+ const json p2 = json::diff(target, source); // NOLINT(readability-suspicious-call-argument)
+ // p2 = [{"op": "delete", "path": "/GB"}]
+ CHECK(p2 == R"([{"op":"remove","path":"/GB"}])"_json);
+ }
+ {
+ // a JSON value
+ json j = {"good", "bad", "ugly"};
+
+ // a JSON pointer
+ auto ptr = json::json_pointer("/2");
+
+ // use to access elements
+ j[ptr] = {{"it", "cattivo"}};
+ CHECK(j == R"(["good","bad",{"it":"cattivo"}])"_json);
+
+ // use user-defined string literal
+ j["/2/en"_json_pointer] = "ugly";
+ CHECK(j == R"(["good","bad",{"en":"ugly","it":"cattivo"}])"_json);
+
+ const json flat = j.flatten();
+ CHECK(flat == R"({"/0":"good","/1":"bad","/2/en":"ugly","/2/it":"cattivo"})"_json);
+ }
+ }
+ }
+
+ SECTION("errors")
+ {
+ SECTION("unknown operation")
+ {
+ SECTION("not an array")
+ {
+ json const j;
+ json const patch = {{"op", "add"}, {"path", ""}, {"value", 1}};
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.104] parse error: JSON patch must be an array of objects", json::parse_error&);
+ }
+
+ SECTION("not an array of objects")
+ {
+ json const j;
+ json const patch = {"op", "add", "path", "", "value", 1};
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.104] parse error: (/0) JSON patch must be an array of objects", json::parse_error&);
+#else
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.104] parse error: JSON patch must be an array of objects", json::parse_error&);
+#endif
+ }
+
+ SECTION("missing 'op'")
+ {
+ json const j;
+ json const patch = {{{"foo", "bar"}}};
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation must have member 'op'", json::parse_error&);
+#else
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation must have member 'op'", json::parse_error&);
+#endif
+ }
+
+ SECTION("non-string 'op'")
+ {
+ json const j;
+ json const patch = {{{"op", 1}}};
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation must have string member 'op'", json::parse_error&);
+#else
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation must have string member 'op'", json::parse_error&);
+#endif
+ }
+
+ SECTION("invalid operation")
+ {
+ json const j;
+ json const patch = {{{"op", "foo"}, {"path", ""}}};
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation value 'foo' is invalid", json::parse_error&);
+#else
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation value 'foo' is invalid", json::parse_error&);
+#endif
+ }
+ }
+
+ SECTION("add")
+ {
+ SECTION("missing 'path'")
+ {
+ json const j;
+ json const patch = {{{"op", "add"}}};
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'add' must have member 'path'", json::parse_error&);
+#else
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'add' must have member 'path'", json::parse_error&);
+#endif
+ }
+
+ SECTION("non-string 'path'")
+ {
+ json const j;
+ json const patch = {{{"op", "add"}, {"path", 1}}};
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'add' must have string member 'path'", json::parse_error&);
+#else
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'add' must have string member 'path'", json::parse_error&);
+#endif
+ }
+
+ SECTION("missing 'value'")
+ {
+ json const j;
+ json const patch = {{{"op", "add"}, {"path", ""}}};
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'add' must have member 'value'", json::parse_error&);
+#else
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'add' must have member 'value'", json::parse_error&);
+#endif
+ }
+
+ SECTION("invalid array index")
+ {
+ json const j = {1, 2};
+ json const patch = {{{"op", "add"}, {"path", "/4"}, {"value", 4}}};
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.out_of_range.401] array index 4 is out of range", json::out_of_range&);
+ }
+ }
+
+ SECTION("remove")
+ {
+ SECTION("missing 'path'")
+ {
+ json const j;
+ json const patch = {{{"op", "remove"}}};
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'remove' must have member 'path'", json::parse_error&);
+#else
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'remove' must have member 'path'", json::parse_error&);
+#endif
+ }
+
+ SECTION("non-string 'path'")
+ {
+ json const j;
+ json const patch = {{{"op", "remove"}, {"path", 1}}};
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'remove' must have string member 'path'", json::parse_error&);
+#else
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'remove' must have string member 'path'", json::parse_error&);
+#endif
+ }
+
+ SECTION("nonexisting target location (array)")
+ {
+ json const j = {1, 2, 3};
+ json const patch = {{{"op", "remove"}, {"path", "/17"}}};
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.out_of_range.401] array index 17 is out of range", json::out_of_range&);
+ }
+
+ SECTION("nonexisting target location (object)")
+ {
+ json const j = {{"foo", 1}, {"bar", 2}};
+ json const patch = {{{"op", "remove"}, {"path", "/baz"}}};
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.out_of_range.403] key 'baz' not found", json::out_of_range&);
+ }
+
+ SECTION("root element as target location")
+ {
+ json const j = "string";
+ json const patch = {{{"op", "remove"}, {"path", ""}}};
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.out_of_range.405] JSON pointer has no parent", json::out_of_range&);
+ }
+ }
+
+ SECTION("replace")
+ {
+ SECTION("missing 'path'")
+ {
+ json const j;
+ json const patch = {{{"op", "replace"}}};
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'replace' must have member 'path'", json::parse_error&);
+#else
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'replace' must have member 'path'", json::parse_error&);
+#endif
+ }
+
+ SECTION("non-string 'path'")
+ {
+ json const j;
+ json const patch = {{{"op", "replace"}, {"path", 1}}};
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'replace' must have string member 'path'", json::parse_error&);
+#else
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'replace' must have string member 'path'", json::parse_error&);
+#endif
+ }
+
+ SECTION("missing 'value'")
+ {
+ json const j;
+ json const patch = {{{"op", "replace"}, {"path", ""}}};
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'replace' must have member 'value'", json::parse_error&);
+#else
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'replace' must have member 'value'", json::parse_error&);
+#endif
+ }
+
+ SECTION("nonexisting target location (array)")
+ {
+ json const j = {1, 2, 3};
+ json const patch = {{{"op", "replace"}, {"path", "/17"}, {"value", 19}}};
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.out_of_range.401] array index 17 is out of range", json::out_of_range&);
+ }
+
+ SECTION("nonexisting target location (object)")
+ {
+ json const j = {{"foo", 1}, {"bar", 2}};
+ json const patch = {{{"op", "replace"}, {"path", "/baz"}, {"value", 3}}};
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.out_of_range.403] key 'baz' not found", json::out_of_range&);
+ }
+ }
+
+ SECTION("move")
+ {
+ SECTION("missing 'path'")
+ {
+ json const j;
+ json const patch = {{{"op", "move"}}};
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'move' must have member 'path'", json::parse_error&);
+#else
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'move' must have member 'path'", json::parse_error&);
+#endif
+ }
+
+ SECTION("non-string 'path'")
+ {
+ json const j;
+ json const patch = {{{"op", "move"}, {"path", 1}}};
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'move' must have string member 'path'", json::parse_error&);
+#else
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'move' must have string member 'path'", json::parse_error&);
+#endif
+ }
+
+ SECTION("missing 'from'")
+ {
+ json const j;
+ json const patch = {{{"op", "move"}, {"path", ""}}};
+ CHECK_THROWS_AS(j.patch(patch), json::parse_error&);
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'move' must have member 'from'", json::parse_error&);
+#else
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'move' must have member 'from'", json::parse_error&);
+#endif
+ }
+
+ SECTION("non-string 'from'")
+ {
+ json const j;
+ json const patch = {{{"op", "move"}, {"path", ""}, {"from", 1}}};
+ CHECK_THROWS_AS(j.patch(patch), json::parse_error&);
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'move' must have string member 'from'", json::parse_error&);
+#else
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'move' must have string member 'from'", json::parse_error&);
+#endif
+ }
+
+ SECTION("nonexisting from location (array)")
+ {
+ json const j = {1, 2, 3};
+ json const patch = {{{"op", "move"}, {"path", "/0"}, {"from", "/5"}}};
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.out_of_range.401] array index 5 is out of range", json::out_of_range&);
+ }
+
+ SECTION("nonexisting from location (object)")
+ {
+ json const j = {{"foo", 1}, {"bar", 2}};
+ json const patch = {{{"op", "move"}, {"path", "/baz"}, {"from", "/baz"}}};
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.out_of_range.403] key 'baz' not found", json::out_of_range&);
+ }
+ }
+
+ SECTION("copy")
+ {
+ SECTION("missing 'path'")
+ {
+ json const j;
+ json const patch = {{{"op", "copy"}}};
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'copy' must have member 'path'", json::parse_error&);
+#else
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'copy' must have member 'path'", json::parse_error&);
+#endif
+ }
+
+ SECTION("non-string 'path'")
+ {
+ json const j;
+ json const patch = {{{"op", "copy"}, {"path", 1}}};
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'copy' must have string member 'path'", json::parse_error&);
+#else
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'copy' must have string member 'path'", json::parse_error&);
+#endif
+ }
+
+ SECTION("missing 'from'")
+ {
+ json const j;
+ json const patch = {{{"op", "copy"}, {"path", ""}}};
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'copy' must have member 'from'", json::parse_error&);
+#else
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'copy' must have member 'from'", json::parse_error&);
+#endif
+ }
+
+ SECTION("non-string 'from'")
+ {
+ json const j;
+ json const patch = {{{"op", "copy"}, {"path", ""}, {"from", 1}}};
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'copy' must have string member 'from'", json::parse_error&);
+#else
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'copy' must have string member 'from'", json::parse_error&);
+#endif
+ }
+
+ SECTION("nonexisting from location (array)")
+ {
+ json const j = {1, 2, 3};
+ json const patch = {{{"op", "copy"}, {"path", "/0"}, {"from", "/5"}}};
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.out_of_range.401] array index 5 is out of range", json::out_of_range&);
+ }
+
+ SECTION("nonexisting from location (object)")
+ {
+ json const j = {{"foo", 1}, {"bar", 2}};
+ json const patch = {{{"op", "copy"}, {"path", "/fob"}, {"from", "/baz"}}};
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.out_of_range.403] key 'baz' not found", json::out_of_range&);
+ }
+ }
+
+ SECTION("test")
+ {
+ SECTION("missing 'path'")
+ {
+ json const j;
+ json const patch = {{{"op", "test"}}};
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'test' must have member 'path'", json::parse_error&);
+#else
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'test' must have member 'path'", json::parse_error&);
+#endif
+ }
+
+ SECTION("non-string 'path'")
+ {
+ json const j;
+ json const patch = {{{"op", "test"}, {"path", 1}}};
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'test' must have string member 'path'", json::parse_error&);
+#else
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'test' must have string member 'path'", json::parse_error&);
+#endif
+ }
+
+ SECTION("missing 'value'")
+ {
+ json const j;
+ json const patch = {{{"op", "test"}, {"path", ""}}};
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'test' must have member 'value'", json::parse_error&);
+#else
+ CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'test' must have member 'value'", json::parse_error&);
+#endif
+ }
+ }
+ }
+
+ SECTION("Examples from jsonpatch.com")
+ {
+ SECTION("Simple Example")
+ {
+ // The original document
+ json const doc = R"(
+ {
+ "baz": "qux",
+ "foo": "bar"
+ }
+ )"_json;
+
+ // The patch
+ json const patch = R"(
+ [
+ { "op": "replace", "path": "/baz", "value": "boo" },
+ { "op": "add", "path": "/hello", "value": ["world"] },
+ { "op": "remove", "path": "/foo"}
+ ]
+ )"_json;
+
+ // The result
+ json result = R"(
+ {
+ "baz": "boo",
+ "hello": ["world"]
+ }
+ )"_json;
+
+ // check if patched value is as expected
+ CHECK(doc.patch(patch) == result);
+
+ // check roundtrip
+ CHECK(doc.patch(json::diff(doc, result)) == result);
+ }
+
+ SECTION("Operations")
+ {
+ // The original document
+ json const doc = R"(
+ {
+ "biscuits": [
+ {"name":"Digestive"},
+ {"name": "Choco Liebniz"}
+ ]
+ }
+ )"_json;
+
+ SECTION("add")
+ {
+ // The patch
+ json const patch = R"(
+ [
+ {"op": "add", "path": "/biscuits/1", "value": {"name": "Ginger Nut"}}
+ ]
+ )"_json;
+
+ // The result
+ json result = R"(
+ {
+ "biscuits": [
+ {"name": "Digestive"},
+ {"name": "Ginger Nut"},
+ {"name": "Choco Liebniz"}
+ ]
+ }
+ )"_json;
+
+ // check if patched value is as expected
+ CHECK(doc.patch(patch) == result);
+
+ // check roundtrip
+ CHECK(doc.patch(json::diff(doc, result)) == result);
+ }
+
+ SECTION("remove")
+ {
+ // The patch
+ json const patch = R"(
+ [
+ {"op": "remove", "path": "/biscuits"}
+ ]
+ )"_json;
+
+ // The result
+ json result = R"(
+ {}
+ )"_json;
+
+ // check if patched value is as expected
+ CHECK(doc.patch(patch) == result);
+
+ // check roundtrip
+ CHECK(doc.patch(json::diff(doc, result)) == result);
+ }
+
+ SECTION("replace")
+ {
+ // The patch
+ json const patch = R"(
+ [
+ {"op": "replace", "path": "/biscuits/0/name", "value": "Chocolate Digestive"}
+ ]
+ )"_json;
+
+ // The result
+ json result = R"(
+ {
+ "biscuits": [
+ {"name": "Chocolate Digestive"},
+ {"name": "Choco Liebniz"}
+ ]
+ }
+ )"_json;
+
+ // check if patched value is as expected
+ CHECK(doc.patch(patch) == result);
+
+ // check roundtrip
+ CHECK(doc.patch(json::diff(doc, result)) == result);
+ }
+
+ SECTION("copy")
+ {
+ // The patch
+ json const patch = R"(
+ [
+ {"op": "copy", "from": "/biscuits/0", "path": "/best_biscuit"}
+ ]
+ )"_json;
+
+ // The result
+ json result = R"(
+ {
+ "biscuits": [
+ {"name": "Digestive"},
+ {"name": "Choco Liebniz"}
+ ],
+ "best_biscuit": {
+ "name": "Digestive"
+ }
+ }
+ )"_json;
+
+ // check if patched value is as expected
+ CHECK(doc.patch(patch) == result);
+
+ // check roundtrip
+ CHECK(doc.patch(json::diff(doc, result)) == result);
+ }
+
+ SECTION("move")
+ {
+ // The patch
+ json const patch = R"(
+ [
+ {"op": "move", "from": "/biscuits", "path": "/cookies"}
+ ]
+ )"_json;
+
+ // The result
+ json result = R"(
+ {
+ "cookies": [
+ {"name": "Digestive"},
+ {"name": "Choco Liebniz"}
+ ]
+ }
+ )"_json;
+
+ // check if patched value is as expected
+ CHECK(doc.patch(patch) == result);
+
+ // check roundtrip
+ CHECK(doc.patch(json::diff(doc, result)) == result);
+ }
+
+ SECTION("test")
+ {
+ // The patch
+ json patch = R"(
+ [
+ {"op": "test", "path": "/best_biscuit/name", "value": "Choco Liebniz"}
+ ]
+ )"_json;
+
+ // the test will fail
+ CHECK_THROWS_AS(doc.patch(patch), json::other_error&);
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] (/0) unsuccessful: " + patch[0].dump());
+#elif JSON_DIAGNOSTIC_POSITIONS
+ CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] (bytes 47-117) unsuccessful: " + patch[0].dump());
+#else
+ CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] unsuccessful: " + patch[0].dump());
+#endif
+ }
+ }
+ }
+
+ SECTION("Examples from bruth.github.io/jsonpatch-js")
+ {
+ SECTION("add")
+ {
+ CHECK(R"( {} )"_json.patch(
+ R"( [{"op": "add", "path": "/foo", "value": "bar"}] )"_json
+ ) == R"( {"foo": "bar"} )"_json);
+
+ CHECK(R"( {"foo": [1, 3]} )"_json.patch(
+ R"( [{"op": "add", "path": "/foo", "value": "bar"}] )"_json
+ ) == R"( {"foo": "bar"} )"_json);
+
+ CHECK(R"( {"foo": [{}]} )"_json.patch(
+ R"( [{"op": "add", "path": "/foo/0/bar", "value": "baz"}] )"_json
+ ) == R"( {"foo": [{"bar": "baz"}]} )"_json);
+ }
+
+ SECTION("remove")
+ {
+ CHECK(R"( {"foo": "bar"} )"_json.patch(
+ R"( [{"op": "remove", "path": "/foo"}] )"_json
+ ) == R"( {} )"_json);
+
+ CHECK(R"( {"foo": [1, 2, 3]} )"_json.patch(
+ R"( [{"op": "remove", "path": "/foo/1"}] )"_json
+ ) == R"( {"foo": [1, 3]} )"_json);
+
+ CHECK(R"( {"foo": [{"bar": "baz"}]} )"_json.patch(
+ R"( [{"op": "remove", "path": "/foo/0/bar"}] )"_json
+ ) == R"( {"foo": [{}]} )"_json);
+ }
+
+ SECTION("replace")
+ {
+ CHECK(R"( {"foo": "bar"} )"_json.patch(
+ R"( [{"op": "replace", "path": "/foo", "value": 1}] )"_json
+ ) == R"( {"foo": 1} )"_json);
+
+ CHECK(R"( {"foo": [1, 2, 3]} )"_json.patch(
+ R"( [{"op": "replace", "path": "/foo/1", "value": 4}] )"_json
+ ) == R"( {"foo": [1, 4, 3]} )"_json);
+
+ CHECK(R"( {"foo": [{"bar": "baz"}]} )"_json.patch(
+ R"( [{"op": "replace", "path": "/foo/0/bar", "value": 1}] )"_json
+ ) == R"( {"foo": [{"bar": 1}]} )"_json);
+ }
+
+ SECTION("move")
+ {
+ CHECK(R"( {"foo": [1, 2, 3]} )"_json.patch(
+ R"( [{"op": "move", "from": "/foo", "path": "/bar"}] )"_json
+ ) == R"( {"bar": [1, 2, 3]} )"_json);
+ }
+
+ SECTION("copy")
+ {
+ CHECK(R"( {"foo": [1, 2, 3]} )"_json.patch(
+ R"( [{"op": "copy", "from": "/foo/1", "path": "/bar"}] )"_json
+ ) == R"( {"foo": [1, 2, 3], "bar": 2} )"_json);
+ }
+
+ SECTION("copy")
+ {
+ CHECK_NOTHROW(R"( {"foo": "bar"} )"_json.patch(
+ R"( [{"op": "test", "path": "/foo", "value": "bar"}] )"_json));
+ }
+ }
+
+ SECTION("Tests from github.com/json-patch/json-patch-tests")
+ {
+ for (const auto* filename :
+ {
+ TEST_DATA_DIRECTORY "/json-patch-tests/spec_tests.json",
+ TEST_DATA_DIRECTORY "/json-patch-tests/tests.json"
+ })
+ {
+ CAPTURE(filename)
+ std::ifstream f(filename);
+ json const suite = json::parse(f);
+
+ for (const auto& test : suite)
+ {
+ INFO_WITH_TEMP(test.value("comment", ""));
+
+ // skip tests marked as disabled
+ if (test.value("disabled", false))
+ {
+ continue;
+ }
+
+ const auto& doc = test["doc"];
+ const auto& patch = test["patch"];
+
+ if (test.count("error") == 0) // NOLINT(readability-container-contains)
+ {
+ // if an expected value is given, use it; use doc otherwise
+ const auto& expected = test.value("expected", doc);
+ CHECK(doc.patch(patch) == expected);
+ }
+ else
+ {
+ CHECK_THROWS(doc.patch(patch));
+ }
+ }
+ }
+ }
+}
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
+}
diff --git a/json4cpp/tests/src/unit-large_json.cpp b/json4cpp/tests/src/unit-large_json.cpp
new file mode 100644
index 0000000000..98d16e3369
--- /dev/null
+++ b/json4cpp/tests/src/unit-large_json.cpp
@@ -0,0 +1,29 @@
+// __ _____ _____ _____
+// __| | __| | | | 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;
+
+#include <algorithm>
+
+TEST_CASE("tests on very large JSONs")
+{
+ SECTION("issue #1419 - Segmentation fault (stack overflow) due to unbounded recursion")
+ {
+ const auto depth = 5000000;
+
+ std::string s(static_cast<std::size_t>(2 * depth), '[');
+ std::fill(s.begin() + depth, s.end(), ']');
+
+ json _;
+ CHECK_NOTHROW(_ = nlohmann::json::parse(s));
+ }
+}
+
diff --git a/json4cpp/tests/src/unit-locale-cpp.cpp b/json4cpp/tests/src/unit-locale-cpp.cpp
new file mode 100644
index 0000000000..0019d3b923
--- /dev/null
+++ b/json4cpp/tests/src/unit-locale-cpp.cpp
@@ -0,0 +1,166 @@
+// __ _____ _____ _____
+// __| | __| | | | 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;
+
+#include <clocale>
+
+struct ParserImpl final: public nlohmann::json_sax<json>
+{
+ bool null() override
+ {
+ return true;
+ }
+ bool boolean(bool /*val*/) override
+ {
+ return true;
+ }
+ bool number_integer(json::number_integer_t /*val*/) override
+ {
+ return true;
+ }
+ bool number_unsigned(json::number_unsigned_t /*val*/) override
+ {
+ return true;
+ }
+ bool number_float(json::number_float_t /*val*/, const json::string_t& s) override
+ {
+ float_string_copy = s;
+ return true;
+ }
+ bool string(json::string_t& /*val*/) override
+ {
+ return true;
+ }
+ bool binary(json::binary_t& /*val*/) override
+ {
+ return true;
+ }
+ bool start_object(std::size_t /*val*/) override
+ {
+ return true;
+ }
+ bool key(json::string_t& /*val*/) override
+ {
+ return true;
+ }
+ bool end_object() override
+ {
+ return true;
+ }
+ bool start_array(std::size_t /*val*/) override
+ {
+ return true;
+ }
+ bool end_array() override
+ {
+ return true;
+ }
+ bool parse_error(std::size_t /*val*/, const std::string& /*val*/, const nlohmann::detail::exception& /*val*/) override
+ {
+ return false;
+ }
+
+ ~ParserImpl() override;
+
+ ParserImpl()
+ : float_string_copy("not set")
+ {}
+
+ ParserImpl(const ParserImpl& other)
+ : float_string_copy(other.float_string_copy)
+ {}
+
+ ParserImpl(ParserImpl&& other) noexcept
+ : float_string_copy(std::move(other.float_string_copy))
+ {}
+
+ ParserImpl& operator=(const ParserImpl& other)
+ {
+ if (this != &other)
+ {
+ float_string_copy = other.float_string_copy;
+ }
+ return *this;
+ }
+
+ ParserImpl& operator=(ParserImpl&& other) noexcept
+ {
+ if (this != &other)
+ {
+ float_string_copy = std::move(other.float_string_copy);
+ }
+ return *this;
+ }
+
+ json::string_t float_string_copy;
+};
+
+ParserImpl::~ParserImpl() = default;
+
+TEST_CASE("locale-dependent test (LC_NUMERIC=C)")
+{
+ WARN_MESSAGE(std::setlocale(LC_NUMERIC, "C") != nullptr, "could not set locale");
+
+ SECTION("check if locale is properly set")
+ {
+ std::array<char, 6> buffer = {};
+ CHECK(std::snprintf(buffer.data(), buffer.size(), "%.2f", 12.34) == 5); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
+ CHECK(std::string(buffer.data()) == "12.34");
+ }
+
+ SECTION("parsing")
+ {
+ CHECK(json::parse("12.34").dump() == "12.34");
+ }
+
+ SECTION("SAX parsing")
+ {
+ ParserImpl sax {};
+ json::sax_parse( "12.34", &sax );
+ CHECK(sax.float_string_copy == "12.34");
+ }
+}
+
+TEST_CASE("locale-dependent test (LC_NUMERIC=de_DE)")
+{
+ if (std::setlocale(LC_NUMERIC, "de_DE") != nullptr)
+ {
+ SECTION("check if locale is properly set")
+ {
+ std::array<char, 6> buffer = {};
+ CHECK(std::snprintf(buffer.data(), buffer.size(), "%.2f", 12.34) == 5); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
+ const auto snprintf_result = std::string(buffer.data());
+ if (snprintf_result != "12,34")
+ {
+ CAPTURE(snprintf_result)
+ MESSAGE("To test if number parsing is locale-independent, we set the locale to de_DE. However, on this system, the decimal separator doesn't change to `,` potentially due to a known musl issue (https://github.com/nlohmann/json/issues/4767).");
+ }
+ }
+
+ SECTION("parsing")
+ {
+ CHECK(json::parse("12.34").dump() == "12.34");
+ }
+
+ SECTION("SAX parsing")
+ {
+ ParserImpl sax{};
+ json::sax_parse("12.34", &sax);
+ CHECK(sax.float_string_copy == "12.34");
+ }
+ }
+ else
+ {
+ MESSAGE("locale de_DE is not usable");
+ }
+}
diff --git a/json4cpp/tests/src/unit-merge_patch.cpp b/json4cpp/tests/src/unit-merge_patch.cpp
new file mode 100644
index 0000000000..f02a1e9919
--- /dev/null
+++ b/json4cpp/tests/src/unit-merge_patch.cpp
@@ -0,0 +1,244 @@
+// __ _____ _____ _____
+// __| | __| | | | 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
+
+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);
+ }
+ }
+ }
+}
diff --git a/json4cpp/tests/src/unit-meta.cpp b/json4cpp/tests/src/unit-meta.cpp
new file mode 100644
index 0000000000..4ce2c3c1f3
--- /dev/null
+++ b/json4cpp/tests/src/unit-meta.cpp
@@ -0,0 +1,36 @@
+// __ _____ _____ _____
+// __| | __| | | | 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;
+
+TEST_CASE("version information")
+{
+ SECTION("meta()")
+ {
+ json j = json::meta();
+
+ CHECK(j["name"] == "JSON for Modern C++");
+ CHECK(j["copyright"] == "(C) 2013-2026 Niels Lohmann");
+ CHECK(j["url"] == "https://github.com/nlohmann/json");
+ CHECK(j["version"] == json(
+ {
+ {"string", "3.12.0"},
+ {"major", 3},
+ {"minor", 12},
+ {"patch", 0}
+ }));
+
+ CHECK(j.find("platform") != j.end());
+ CHECK(j.at("compiler").find("family") != j.at("compiler").end());
+ CHECK(j.at("compiler").find("version") != j.at("compiler").end());
+ CHECK(j.at("compiler").find("c++") != j.at("compiler").end());
+ }
+}
diff --git a/json4cpp/tests/src/unit-modifiers.cpp b/json4cpp/tests/src/unit-modifiers.cpp
new file mode 100644
index 0000000000..18185ec00b
--- /dev/null
+++ b/json4cpp/tests/src/unit-modifiers.cpp
@@ -0,0 +1,952 @@
+// __ _____ _____ _____
+// __| | __| | | | 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;
+
+TEST_CASE("modifiers")
+{
+ SECTION("clear()")
+ {
+ SECTION("boolean")
+ {
+ json j = true;
+ json const k = j;
+
+ j.clear();
+ CHECK(j == json(json::value_t::boolean));
+ CHECK(j == json(k.type()));
+ }
+
+ SECTION("string")
+ {
+ json j = "hello world";
+ json const k = j;
+
+ j.clear();
+ CHECK(j == json(json::value_t::string));
+ CHECK(j == json(k.type()));
+ }
+
+ SECTION("array")
+ {
+ SECTION("empty array")
+ {
+ json j = json::array();
+ json const k = j;
+
+ j.clear();
+ CHECK(j.empty());
+ CHECK(j == json(json::value_t::array));
+ CHECK(j == json(k.type()));
+ }
+
+ SECTION("filled array")
+ {
+ json j = {1, 2, 3};
+ json const k = j;
+
+ j.clear();
+ CHECK(j.empty());
+ CHECK(j == json(json::value_t::array));
+ CHECK(j == json(k.type()));
+ }
+ }
+
+ SECTION("object")
+ {
+ SECTION("empty object")
+ {
+ json j = json::object();
+ json const k = j;
+
+ j.clear();
+ CHECK(j.empty());
+ CHECK(j == json(json::value_t::object));
+ CHECK(j == json(k.type()));
+ }
+
+ SECTION("filled object")
+ {
+ json j = {{"one", 1}, {"two", 2}, {"three", 3}};
+ json const k = j;
+
+ j.clear();
+ CHECK(j.empty());
+ CHECK(j == json(json::value_t::object));
+ CHECK(j == json(k.type()));
+ }
+ }
+
+ SECTION("binary")
+ {
+ SECTION("empty binary")
+ {
+ json j = json::binary({});
+ json const k = j;
+
+ j.clear();
+ CHECK(!j.empty());
+ CHECK(j == json(json::value_t::binary));
+ CHECK(j == json(k.type()));
+ }
+
+ SECTION("filled binary")
+ {
+ json j = json::binary({1, 2, 3, 4, 5});
+ json const k = j;
+
+ j.clear();
+ CHECK(!j.empty());
+ CHECK(j == json(json::value_t::binary));
+ CHECK(j == json(k.type()));
+ }
+ }
+
+ SECTION("number (integer)")
+ {
+ json j = 23;
+ json const k = j;
+
+ j.clear();
+ CHECK(j == json(json::value_t::number_integer));
+ CHECK(j == json(k.type()));
+ }
+
+ SECTION("number (unsigned)")
+ {
+ json j = 23u;
+ json const k = j;
+
+ j.clear();
+ CHECK(j == json(json::value_t::number_integer));
+ CHECK(j == json(k.type()));
+ }
+
+ SECTION("number (float)")
+ {
+ json j = 23.42;
+ json const k = j;
+
+ j.clear();
+ CHECK(j == json(json::value_t::number_float));
+ CHECK(j == json(k.type()));
+ }
+
+ SECTION("null")
+ {
+ json j = nullptr;
+ json const k = j;
+
+ j.clear();
+ CHECK(j == json(json::value_t::null));
+ CHECK(j == json(k.type()));
+ }
+ }
+
+ SECTION("push_back()")
+ {
+ SECTION("to array")
+ {
+ SECTION("json&&")
+ {
+ SECTION("null")
+ {
+ json j;
+ j.push_back(1);
+ j.push_back(2);
+ CHECK(j.type() == json::value_t::array);
+ CHECK(j == json({1, 2}));
+ }
+
+ SECTION("array")
+ {
+ json j = {1, 2, 3};
+ j.push_back("Hello");
+ CHECK(j.type() == json::value_t::array);
+ CHECK(j == json({1, 2, 3, "Hello"}));
+ }
+
+ SECTION("other type")
+ {
+ json j = 1;
+ CHECK_THROWS_WITH_AS(j.push_back("Hello"), "[json.exception.type_error.308] cannot use push_back() with number", json::type_error&);
+ }
+ }
+
+ SECTION("const json&")
+ {
+ SECTION("null")
+ {
+ json j;
+ json const k(1);
+ j.push_back(k);
+ j.push_back(k);
+ CHECK(j.type() == json::value_t::array);
+ CHECK(j == json({1, 1}));
+ }
+
+ SECTION("array")
+ {
+ json j = {1, 2, 3};
+ json const k("Hello");
+ j.push_back(k);
+ CHECK(j.type() == json::value_t::array);
+ CHECK(j == json({1, 2, 3, "Hello"}));
+ }
+
+ SECTION("other type")
+ {
+ json j = 1;
+ json const k("Hello");
+ CHECK_THROWS_WITH_AS(j.push_back(k), "[json.exception.type_error.308] cannot use push_back() with number", json::type_error&);
+ }
+ }
+ }
+
+ SECTION("to object")
+ {
+ SECTION("null")
+ {
+ json j;
+ j.push_back(json::object_t::value_type({"one", 1}));
+ j.push_back(json::object_t::value_type({"two", 2}));
+ CHECK(j.type() == json::value_t::object);
+ CHECK(j.size() == 2);
+ CHECK(j["one"] == json(1));
+ CHECK(j["two"] == json(2));
+ }
+
+ SECTION("object")
+ {
+ json j(json::value_t::object);
+ j.push_back(json::object_t::value_type({"one", 1}));
+ j.push_back(json::object_t::value_type({"two", 2}));
+ CHECK(j.size() == 2);
+ CHECK(j["one"] == json(1));
+ CHECK(j["two"] == json(2));
+ }
+
+ SECTION("other type")
+ {
+ json j = 1;
+ json const k("Hello");
+ CHECK_THROWS_WITH_AS(j.push_back(json::object_t::value_type({"one", 1})), "[json.exception.type_error.308] cannot use push_back() with number", json::type_error&);
+ }
+ }
+
+ SECTION("with initializer_list")
+ {
+ SECTION("null")
+ {
+ json j;
+ j.push_back({"foo", "bar"});
+ CHECK(j == json::array({{"foo", "bar"}}));
+
+ json k;
+ k.push_back({1, 2, 3});
+ CHECK(k == json::array({{1, 2, 3}}));
+ }
+
+ SECTION("array")
+ {
+ json j = {1, 2, 3};
+ j.push_back({"foo", "bar"});
+ CHECK(j == json({1, 2, 3, {"foo", "bar"}}));
+
+ json k = {1, 2, 3};
+ k.push_back({1, 2, 3});
+ CHECK(k == json({1, 2, 3, {1, 2, 3}}));
+ }
+
+ SECTION("object")
+ {
+ json j = {{"key1", 1}};
+ j.push_back({"key2", "bar"});
+ CHECK(j == json({{"key1", 1}, {"key2", "bar"}}));
+
+ // invalid values (no string/val pair)
+ CHECK_THROWS_WITH_AS(j.push_back({1}), "[json.exception.type_error.308] cannot use push_back() with object", json::type_error&);
+ CHECK_THROWS_WITH_AS(j.push_back({1, 2}), "[json.exception.type_error.308] cannot use push_back() with object", json::type_error&);
+ CHECK_THROWS_WITH_AS(j.push_back({1, 2, 3, 4}), "[json.exception.type_error.308] cannot use push_back() with object", json::type_error&);
+ }
+ }
+ }
+
+ SECTION("emplace_back()")
+ {
+ SECTION("to array")
+ {
+ SECTION("null")
+ {
+ json j;
+ auto& x1 = j.emplace_back(1);
+ CHECK(x1 == 1);
+ auto& x2 = j.emplace_back(2);
+ CHECK(x2 == 2);
+ CHECK(j.type() == json::value_t::array);
+ CHECK(j == json({1, 2}));
+ }
+
+ SECTION("array")
+ {
+ json j = {1, 2, 3};
+ auto& x = j.emplace_back("Hello");
+ CHECK(x == "Hello");
+ CHECK(j.type() == json::value_t::array);
+ CHECK(j == json({1, 2, 3, "Hello"}));
+ }
+
+ SECTION("multiple values")
+ {
+ json j;
+ auto& x = j.emplace_back(3, "foo");
+ CHECK(x == json({"foo", "foo", "foo"}));
+ CHECK(j.type() == json::value_t::array);
+ CHECK(j == json({{"foo", "foo", "foo"}}));
+ }
+ }
+
+ SECTION("other type")
+ {
+ json j = 1;
+ CHECK_THROWS_WITH_AS(j.emplace_back("Hello"), "[json.exception.type_error.311] cannot use emplace_back() with number", json::type_error&);
+ }
+ }
+
+ SECTION("emplace()")
+ {
+ SECTION("to object")
+ {
+ SECTION("null")
+ {
+ // start with a null value
+ json j;
+
+ // add a new key
+ auto res1 = j.emplace("foo", "bar");
+ CHECK(res1.second == true);
+ CHECK(*res1.first == "bar");
+
+ // the null value is changed to an object
+ CHECK(j.type() == json::value_t::object);
+
+ // add a new key
+ auto res2 = j.emplace("baz", "bam");
+ CHECK(res2.second == true);
+ CHECK(*res2.first == "bam");
+
+ // we try to insert at given key - no change
+ auto res3 = j.emplace("baz", "bad");
+ CHECK(res3.second == false);
+ CHECK(*res3.first == "bam");
+
+ // the final object
+ CHECK(j == json({{"baz", "bam"}, {"foo", "bar"}}));
+ }
+
+ SECTION("object")
+ {
+ // start with an object
+ json j = {{"foo", "bar"}};
+
+ // add a new key
+ auto res1 = j.emplace("baz", "bam");
+ CHECK(res1.second == true);
+ CHECK(*res1.first == "bam");
+
+ // add an existing key
+ auto res2 = j.emplace("foo", "bad");
+ CHECK(res2.second == false);
+ CHECK(*res2.first == "bar");
+
+ // check final object
+ CHECK(j == json({{"baz", "bam"}, {"foo", "bar"}}));
+ }
+ }
+
+ SECTION("other type")
+ {
+ json j = 1;
+ CHECK_THROWS_WITH_AS(j.emplace("foo", "bar"), "[json.exception.type_error.311] cannot use emplace() with number", json::type_error&);
+ }
+ }
+
+ SECTION("operator+=")
+ {
+ SECTION("to array")
+ {
+ SECTION("json&&")
+ {
+ SECTION("null")
+ {
+ json j;
+ j += 1;
+ j += 2;
+ CHECK(j.type() == json::value_t::array);
+ CHECK(j == json({1, 2}));
+ }
+
+ SECTION("array")
+ {
+ json j = {1, 2, 3};
+ j += "Hello";
+ CHECK(j.type() == json::value_t::array);
+ CHECK(j == json({1, 2, 3, "Hello"}));
+ }
+
+ SECTION("other type")
+ {
+ json j = 1;
+ CHECK_THROWS_WITH_AS(j += "Hello", "[json.exception.type_error.308] cannot use push_back() with number", json::type_error&);
+ }
+ }
+
+ SECTION("const json&")
+ {
+ SECTION("null")
+ {
+ json j;
+ json const k(1);
+ j += k;
+ j += k;
+ CHECK(j.type() == json::value_t::array);
+ CHECK(j == json({1, 1}));
+ }
+
+ SECTION("array")
+ {
+ json j = {1, 2, 3};
+ json const k("Hello");
+ j += k;
+ CHECK(j.type() == json::value_t::array);
+ CHECK(j == json({1, 2, 3, "Hello"}));
+ }
+
+ SECTION("other type")
+ {
+ json j = 1;
+ json const k("Hello");
+ CHECK_THROWS_WITH_AS(j += k, "[json.exception.type_error.308] cannot use push_back() with number", json::type_error&);
+ }
+ }
+ }
+
+ SECTION("to object")
+ {
+ SECTION("null")
+ {
+ json j;
+ j += json::object_t::value_type({"one", 1});
+ j += json::object_t::value_type({"two", 2});
+ CHECK(j.type() == json::value_t::object);
+ CHECK(j.size() == 2);
+ CHECK(j["one"] == json(1));
+ CHECK(j["two"] == json(2));
+ }
+
+ SECTION("object")
+ {
+ json j(json::value_t::object);
+ j += json::object_t::value_type({"one", 1});
+ j += json::object_t::value_type({"two", 2});
+ CHECK(j.size() == 2);
+ CHECK(j["one"] == json(1));
+ CHECK(j["two"] == json(2));
+ }
+
+ SECTION("other type")
+ {
+ json j = 1;
+ json const k("Hello");
+ CHECK_THROWS_WITH_AS(j += json::object_t::value_type({"one", 1}), "[json.exception.type_error.308] cannot use push_back() with number", json::type_error&);
+ }
+ }
+
+ SECTION("with initializer_list")
+ {
+ SECTION("null")
+ {
+ json j;
+ j += {"foo", "bar"};
+ CHECK(j == json::array({{"foo", "bar"}}));
+
+ json k;
+ k += {1, 2, 3};
+ CHECK(k == json::array({{1, 2, 3}}));
+ }
+
+ SECTION("array")
+ {
+ json j = {1, 2, 3};
+ j += {"foo", "bar"};
+ CHECK(j == json({1, 2, 3, {"foo", "bar"}}));
+
+ json k = {1, 2, 3};
+ k += {1, 2, 3};
+ CHECK(k == json({1, 2, 3, {1, 2, 3}}));
+ }
+
+ SECTION("object")
+ {
+ json j = {{"key1", 1}};
+ j += {"key2", "bar"};
+ CHECK(j == json({{"key1", 1}, {"key2", "bar"}}));
+
+ json k = {{"key1", 1}};
+ CHECK_THROWS_WITH_AS((k += {1, 2, 3, 4}), "[json.exception.type_error.308] cannot use push_back() with object", json::type_error&);
+ }
+ }
+ }
+
+ SECTION("insert()")
+ {
+ json j_array = {1, 2, 3, 4};
+ json j_value = 5;
+
+ SECTION("value at position")
+ {
+ SECTION("insert before begin()")
+ {
+ auto it = j_array.insert(j_array.begin(), j_value);
+ CHECK(j_array.size() == 5);
+ CHECK(*it == j_value);
+ CHECK(j_array.begin() == it);
+ CHECK(j_array == json({5, 1, 2, 3, 4}));
+ }
+
+ SECTION("insert in the middle")
+ {
+ auto it = j_array.insert(j_array.begin() + 2, j_value);
+ CHECK(j_array.size() == 5);
+ CHECK(*it == j_value);
+ CHECK((it - j_array.begin()) == 2);
+ CHECK(j_array == json({1, 2, 5, 3, 4}));
+ }
+
+ SECTION("insert before end()")
+ {
+ auto it = j_array.insert(j_array.end(), j_value);
+ CHECK(j_array.size() == 5);
+ CHECK(*it == j_value);
+ CHECK((j_array.end() - it) == 1);
+ CHECK(j_array == json({1, 2, 3, 4, 5}));
+ }
+ }
+
+ SECTION("rvalue at position")
+ {
+ SECTION("insert before begin()")
+ {
+ auto it = j_array.insert(j_array.begin(), 5);
+ CHECK(j_array.size() == 5);
+ CHECK(*it == j_value);
+ CHECK(j_array.begin() == it);
+ CHECK(j_array == json({5, 1, 2, 3, 4}));
+ }
+
+ SECTION("insert in the middle")
+ {
+ auto it = j_array.insert(j_array.begin() + 2, 5);
+ CHECK(j_array.size() == 5);
+ CHECK(*it == j_value);
+ CHECK((it - j_array.begin()) == 2);
+ CHECK(j_array == json({1, 2, 5, 3, 4}));
+ }
+
+ SECTION("insert before end()")
+ {
+ auto it = j_array.insert(j_array.end(), 5);
+ CHECK(j_array.size() == 5);
+ CHECK(*it == j_value);
+ CHECK((j_array.end() - it) == 1);
+ CHECK(j_array == json({1, 2, 3, 4, 5}));
+ }
+ }
+
+ SECTION("copies at position")
+ {
+ SECTION("insert before begin()")
+ {
+ auto it = j_array.insert(j_array.begin(), 3, 5);
+ CHECK(j_array.size() == 7);
+ CHECK(*it == j_value);
+ CHECK(j_array.begin() == it);
+ CHECK(j_array == json({5, 5, 5, 1, 2, 3, 4}));
+ }
+
+ SECTION("insert in the middle")
+ {
+ auto it = j_array.insert(j_array.begin() + 2, 3, 5);
+ CHECK(j_array.size() == 7);
+ CHECK(*it == j_value);
+ CHECK((it - j_array.begin()) == 2);
+ CHECK(j_array == json({1, 2, 5, 5, 5, 3, 4}));
+ }
+
+ SECTION("insert before end()")
+ {
+ auto it = j_array.insert(j_array.end(), 3, 5);
+ CHECK(j_array.size() == 7);
+ CHECK(*it == j_value);
+ CHECK((j_array.end() - it) == 3);
+ CHECK(j_array == json({1, 2, 3, 4, 5, 5, 5}));
+ }
+
+ SECTION("insert nothing (count = 0)")
+ {
+ auto it = j_array.insert(j_array.end(), 0, 5);
+ CHECK(j_array.size() == 4);
+ // the returned iterator points to the first inserted element;
+ // there were 4 elements, so it should point to the 5th
+ CHECK(it == j_array.begin() + 4);
+ CHECK(j_array == json({1, 2, 3, 4}));
+ }
+ }
+
+ SECTION("range for array")
+ {
+ json j_other_array = {"first", "second"};
+
+ SECTION("proper usage")
+ {
+ auto it = j_array.insert(j_array.end(), j_other_array.begin(), j_other_array.end());
+ CHECK(j_array.size() == 6);
+ CHECK(*it == *j_other_array.begin());
+ CHECK((j_array.end() - it) == 2);
+ CHECK(j_array == json({1, 2, 3, 4, "first", "second"}));
+ }
+
+ SECTION("empty range")
+ {
+ auto it = j_array.insert(j_array.end(), j_other_array.begin(), j_other_array.begin());
+ CHECK(j_array.size() == 4);
+ CHECK(it == j_array.end());
+ CHECK(j_array == json({1, 2, 3, 4}));
+ }
+
+ SECTION("invalid iterators")
+ {
+ json j_other_array2 = {"first", "second"};
+
+ CHECK_THROWS_WITH_AS(j_array.insert(j_array.end(), j_array.begin(), j_array.end()), "[json.exception.invalid_iterator.211] passed iterators may not belong to container",
+ json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(j_array.insert(j_array.end(), j_other_array.begin(), j_other_array2.end()), "[json.exception.invalid_iterator.210] iterators do not fit",
+ json::invalid_iterator&);
+ }
+ }
+
+ SECTION("range for object")
+ {
+ json j_object1 = {{"one", "eins"}, {"two", "zwei"}};
+ json j_object2 = {{"eleven", "elf"}, {"seventeen", "siebzehn"}};
+
+ SECTION("proper usage")
+ {
+ j_object1.insert(j_object2.begin(), j_object2.end());
+ CHECK(j_object1.size() == 4);
+ }
+
+ SECTION("empty range")
+ {
+ j_object1.insert(j_object2.begin(), j_object2.begin());
+ CHECK(j_object1.size() == 2);
+ }
+
+ SECTION("invalid iterators")
+ {
+ json const j_other_array2 = {"first", "second"};
+
+ CHECK_THROWS_WITH_AS(j_array.insert(j_object2.begin(), j_object2.end()), "[json.exception.type_error.309] cannot use insert() with array", json::type_error&);
+ CHECK_THROWS_WITH_AS(j_object1.insert(j_object1.begin(), j_object2.end()), "[json.exception.invalid_iterator.210] iterators do not fit", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(j_object1.insert(j_array.begin(), j_array.end()), "[json.exception.invalid_iterator.202] iterators first and last must point to objects", json::invalid_iterator&);
+ }
+ }
+
+ SECTION("initializer list at position")
+ {
+ SECTION("insert before begin()")
+ {
+ auto it = j_array.insert(j_array.begin(), {7, 8, 9});
+ CHECK(j_array.size() == 7);
+ CHECK(*it == json(7));
+ CHECK(j_array.begin() == it);
+ CHECK(j_array == json({7, 8, 9, 1, 2, 3, 4}));
+ }
+
+ SECTION("insert in the middle")
+ {
+ auto it = j_array.insert(j_array.begin() + 2, {7, 8, 9});
+ CHECK(j_array.size() == 7);
+ CHECK(*it == json(7));
+ CHECK((it - j_array.begin()) == 2);
+ CHECK(j_array == json({1, 2, 7, 8, 9, 3, 4}));
+ }
+
+ SECTION("insert before end()")
+ {
+ auto it = j_array.insert(j_array.end(), {7, 8, 9});
+ CHECK(j_array.size() == 7);
+ CHECK(*it == json(7));
+ CHECK((j_array.end() - it) == 3);
+ CHECK(j_array == json({1, 2, 3, 4, 7, 8, 9}));
+ }
+ }
+
+ SECTION("invalid iterator")
+ {
+ // pass iterator to a different array
+ json j_another_array = {1, 2};
+ json j_yet_another_array = {"first", "second"};
+ CHECK_THROWS_WITH_AS(j_array.insert(j_another_array.end(), 10), "[json.exception.invalid_iterator.202] iterator does not fit current value", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(j_array.insert(j_another_array.end(), j_value), "[json.exception.invalid_iterator.202] iterator does not fit current value", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(j_array.insert(j_another_array.end(), 10, 11), "[json.exception.invalid_iterator.202] iterator does not fit current value", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(j_array.insert(j_another_array.end(), j_yet_another_array.begin(), j_yet_another_array.end()), "[json.exception.invalid_iterator.202] iterator does not fit current value", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(j_array.insert(j_another_array.end(), {1, 2, 3, 4}), "[json.exception.invalid_iterator.202] iterator does not fit current value", json::invalid_iterator&);
+ }
+
+ SECTION("non-array type")
+ {
+ // call insert on a non-array type
+ json j_nonarray = 3;
+ json j_yet_another_array = {"first", "second"};
+ CHECK_THROWS_WITH_AS(j_nonarray.insert(j_nonarray.end(), 10), "[json.exception.type_error.309] cannot use insert() with number", json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonarray.insert(j_nonarray.end(), j_value), "[json.exception.type_error.309] cannot use insert() with number", json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonarray.insert(j_nonarray.end(), 10, 11), "[json.exception.type_error.309] cannot use insert() with number", json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonarray.insert(j_nonarray.end(), j_yet_another_array.begin(), j_yet_another_array.end()), "[json.exception.type_error.309] cannot use insert() with number", json::type_error&);
+ CHECK_THROWS_WITH_AS(j_nonarray.insert(j_nonarray.end(), {1, 2, 3, 4}), "[json.exception.type_error.309] cannot use insert() with number", json::type_error&);
+ }
+ }
+
+ SECTION("update()")
+ {
+ SECTION("non-recursive (default)")
+ {
+ json j_object1 = {{"one", "eins"}, {"two", "zwei"}};
+ json j_object2 = {{"three", "drei"}, {"two", "zwo"}};
+ json j_array = {1, 2, 3, 4};
+
+ SECTION("const reference")
+ {
+ SECTION("proper usage")
+ {
+ j_object1.update(j_object2);
+ CHECK(j_object1 == json({{"one", "eins"}, {"two", "zwo"}, {"three", "drei"}}));
+
+ json j_null;
+ j_null.update(j_object2);
+ CHECK(j_null == j_object2);
+ }
+
+ SECTION("wrong types")
+ {
+ CHECK_THROWS_WITH_AS(j_array.update(j_object1), "[json.exception.type_error.312] cannot use update() with array", json::type_error&);
+
+ CHECK_THROWS_WITH_AS(j_object1.update(j_array), "[json.exception.type_error.312] cannot use update() with array", json::type_error&);
+ }
+ }
+
+ SECTION("iterator range")
+ {
+ SECTION("proper usage")
+ {
+ j_object1.update(j_object2.begin(), j_object2.end());
+ CHECK(j_object1 == json({{"one", "eins"}, {"two", "zwo"}, {"three", "drei"}}));
+
+ json j_null;
+ j_null.update(j_object2.begin(), j_object2.end());
+ CHECK(j_null == j_object2);
+ }
+
+ SECTION("empty range")
+ {
+ j_object1.update(j_object2.begin(), j_object2.begin());
+ CHECK(j_object1 == json({{"one", "eins"}, {"two", "zwei"}}));
+ }
+
+ SECTION("invalid iterators")
+ {
+ json const j_other_array2 = {"first", "second"};
+
+ CHECK_THROWS_WITH_AS(j_array.update(j_object2.begin(), j_object2.end()), "[json.exception.type_error.312] cannot use update() with array", json::type_error&);
+ CHECK_THROWS_WITH_AS(j_object1.update(j_object1.begin(), j_object2.end()), "[json.exception.invalid_iterator.210] iterators do not fit", json::invalid_iterator&);
+ CHECK_THROWS_WITH_AS(j_object1.update(j_array.begin(), j_array.end()), "[json.exception.type_error.312] cannot use update() with array", json::type_error&);
+ }
+ }
+ }
+
+ SECTION("recursive")
+ {
+ SECTION("const reference")
+ {
+ SECTION("extend object")
+ {
+ json j1 = {{"string", "s"}, {"numbers", {{"one", 1}}}};
+ json const j2 = {{"string", "t"}, {"numbers", {{"two", 2}}}};
+ j1.update(j2, true);
+ CHECK(j1 == json({{"string", "t"}, {"numbers", {{"one", 1}, {"two", 2}}}}));
+ }
+
+ SECTION("replace object")
+ {
+ json j1 = {{"string", "s"}, {"numbers", {{"one", 1}}}};
+ json const j2 = {{"string", "t"}, {"numbers", 1}};
+ j1.update(j2, true);
+ CHECK(j1 == json({{"string", "t"}, {"numbers", 1}}));
+ }
+ }
+ }
+ }
+
+ SECTION("swap()")
+ {
+ SECTION("json")
+ {
+ SECTION("member swap")
+ {
+ json j("hello world");
+ json k(42.23);
+
+ j.swap(k);
+
+ CHECK(j == json(42.23));
+ CHECK(k == json("hello world"));
+ }
+
+ SECTION("nonmember swap")
+ {
+ json j("hello world");
+ json k(42.23);
+
+ using std::swap;
+ swap(j, k);
+
+ CHECK(j == json(42.23));
+ CHECK(k == json("hello world"));
+ }
+ }
+
+ SECTION("array_t")
+ {
+ SECTION("array_t type")
+ {
+ json j = {1, 2, 3, 4};
+ json::array_t a = {"foo", "bar", "baz"};
+
+ j.swap(a);
+
+ CHECK(j == json({"foo", "bar", "baz"}));
+
+ j.swap(a);
+
+ CHECK(j == json({1, 2, 3, 4}));
+ }
+
+ SECTION("non-array_t type")
+ {
+ json j = 17;
+ json::array_t a = {"foo", "bar", "baz"};
+
+ CHECK_THROWS_WITH_AS(j.swap(a), "[json.exception.type_error.310] cannot use swap(array_t&) with number", json::type_error&);
+ }
+ }
+
+ SECTION("object_t")
+ {
+ SECTION("object_t type")
+ {
+ json j = {{"one", 1}, {"two", 2}};
+ json::object_t o = {{"cow", "Kuh"}, {"chicken", "Huhn"}};
+
+ j.swap(o);
+
+ CHECK(j == json({{"cow", "Kuh"}, {"chicken", "Huhn"}}));
+
+ j.swap(o);
+
+ CHECK(j == json({{"one", 1}, {"two", 2}}));
+ }
+
+ SECTION("non-object_t type")
+ {
+ json j = 17;
+ json::object_t o = {{"cow", "Kuh"}, {"chicken", "Huhn"}};
+
+ CHECK_THROWS_WITH_AS(j.swap(o), "[json.exception.type_error.310] cannot use swap(object_t&) with number", json::type_error&);
+ }
+ }
+
+ SECTION("string_t")
+ {
+ SECTION("string_t type")
+ {
+ json j = "Hello world";
+ json::string_t s = "Hallo Welt";
+
+ j.swap(s);
+
+ CHECK(j == json("Hallo Welt"));
+
+ j.swap(s);
+
+ CHECK(j == json("Hello world"));
+ }
+
+ SECTION("non-string_t type")
+ {
+ json j = 17;
+ json::string_t s = "Hallo Welt";
+
+ CHECK_THROWS_WITH_AS(j.swap(s), "[json.exception.type_error.310] cannot use swap(string_t&) with number", json::type_error&);
+ }
+ }
+
+ SECTION("binary_t")
+ {
+ SECTION("binary_t type")
+ {
+ json j = json::binary({1, 2, 3, 4});
+ json::binary_t s = {{5, 6, 7, 8}};
+
+ j.swap(s);
+
+ CHECK(j == json::binary({5, 6, 7, 8}));
+
+ j.swap(s);
+
+ CHECK(j == json::binary({1, 2, 3, 4}));
+ }
+
+ SECTION("binary_t::container_type type")
+ {
+ json j = json::binary({1, 2, 3, 4});
+ std::vector<std::uint8_t> s = {{5, 6, 7, 8}};
+
+ j.swap(s);
+
+ CHECK(j == json::binary({5, 6, 7, 8}));
+
+ j.swap(s);
+
+ CHECK(j == json::binary({1, 2, 3, 4}));
+ }
+
+ SECTION("non-binary_t type")
+ {
+ json j = 17;
+ json::binary_t s1 = {{1, 2, 3, 4}};
+ std::vector<std::uint8_t> s2 = {{5, 6, 7, 8}};
+
+ CHECK_THROWS_WITH_AS(j.swap(s1), "[json.exception.type_error.310] cannot use swap(binary_t&) with number", json::type_error);
+ CHECK_THROWS_WITH_AS(j.swap(s2), "[json.exception.type_error.310] cannot use swap(binary_t::container_type&) with number", json::type_error);
+ }
+ }
+ }
+}
diff --git a/json4cpp/tests/src/unit-msgpack.cpp b/json4cpp/tests/src/unit-msgpack.cpp
new file mode 100644
index 0000000000..02fb7a2e40
--- /dev/null
+++ b/json4cpp/tests/src/unit-msgpack.cpp
@@ -0,0 +1,1961 @@
+// __ _____ _____ _____
+// __| | __| | | | 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 <fstream>
+#include <sstream>
+#include <iomanip>
+#include <limits>
+#include <set>
+#include "make_test_data_available.hpp"
+#include "test_utils.hpp"
+
+namespace
+{
+class SaxCountdown
+{
+ public:
+ explicit SaxCountdown(const int count) : events_left(count)
+ {}
+
+ bool null()
+ {
+ return events_left-- > 0;
+ }
+
+ bool boolean(bool /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool number_integer(json::number_integer_t /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool number_unsigned(json::number_unsigned_t /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool number_float(json::number_float_t /*unused*/, const std::string& /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool string(std::string& /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool binary(std::vector<std::uint8_t>& /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool start_object(std::size_t /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool key(std::string& /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool end_object()
+ {
+ return events_left-- > 0;
+ }
+
+ bool start_array(std::size_t /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool end_array()
+ {
+ return events_left-- > 0;
+ }
+
+ bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const json::exception& /*unused*/) // NOLINT(readability-convert-member-functions-to-static)
+ {
+ return false;
+ }
+
+ private:
+ int events_left = 0;
+};
+} // namespace
+
+TEST_CASE("MessagePack")
+{
+ SECTION("individual values")
+ {
+ SECTION("discarded")
+ {
+ // discarded values are not serialized
+ json const j = json::value_t::discarded;
+ const auto result = json::to_msgpack(j);
+ CHECK(result.empty());
+ }
+
+ SECTION("null")
+ {
+ json const j = nullptr;
+ std::vector<uint8_t> const expected = {0xc0};
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+
+ SECTION("boolean")
+ {
+ SECTION("true")
+ {
+ json const j = true;
+ std::vector<uint8_t> const expected = {0xc3};
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+
+ SECTION("false")
+ {
+ json const j = false;
+ std::vector<uint8_t> const expected = {0xc2};
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+ }
+
+ SECTION("number")
+ {
+ SECTION("signed")
+ {
+ SECTION("-32..-1 (negative fixnum)")
+ {
+ for (auto i = -32; i <= -1; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json const j = i;
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ static_cast<uint8_t>(i)
+ };
+
+ // compare result + size
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 1);
+
+ // check individual bytes
+ CHECK(static_cast<int8_t>(result[0]) == i);
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+ }
+
+ SECTION("0..127 (positive fixnum)")
+ {
+ for (size_t i = 0; i <= 127; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json j = -1;
+ j.get_ref<json::number_integer_t&>() = static_cast<json::number_integer_t>(i);
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected{static_cast<uint8_t>(i)};
+
+ // compare result + size
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 1);
+
+ // check individual bytes
+ CHECK(result[0] == i);
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+ }
+
+ SECTION("128..255 (int 8)")
+ {
+ for (size_t i = 128; i <= 255; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json j = -1;
+ j.get_ref<json::number_integer_t&>() = static_cast<json::number_integer_t>(i);
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ 0xcc,
+ static_cast<uint8_t>(i),
+ };
+
+ // compare result + size
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 2);
+
+ // check individual bytes
+ CHECK(result[0] == 0xcc);
+ auto const restored = static_cast<uint8_t>(result[1]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+ }
+
+ SECTION("256..65535 (int 16)")
+ {
+ for (size_t i = 256; i <= 65535; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json j = -1;
+ j.get_ref<json::number_integer_t&>() = static_cast<json::number_integer_t>(i);
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ 0xcd,
+ static_cast<uint8_t>((i >> 8) & 0xff),
+ static_cast<uint8_t>(i & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 3);
+
+ // check individual bytes
+ CHECK(result[0] == 0xcd);
+ auto const restored = static_cast<uint16_t>((static_cast<uint8_t>(result[1]) * 256) + static_cast<uint8_t>(result[2]));
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+ }
+
+ SECTION("65536..4294967295 (int 32)")
+ {
+ for (uint32_t i :
+ {
+ 65536u, 77777u, 1048576u, 4294967295u
+ })
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json j = -1;
+ j.get_ref<json::number_integer_t&>() = static_cast<json::number_integer_t>(i);
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ 0xce,
+ static_cast<uint8_t>((i >> 24) & 0xff),
+ static_cast<uint8_t>((i >> 16) & 0xff),
+ static_cast<uint8_t>((i >> 8) & 0xff),
+ static_cast<uint8_t>(i & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 5);
+
+ // check individual bytes
+ CHECK(result[0] == 0xce);
+ uint32_t const restored = (static_cast<uint32_t>(result[1]) << 030) +
+ (static_cast<uint32_t>(result[2]) << 020) +
+ (static_cast<uint32_t>(result[3]) << 010) +
+ static_cast<uint32_t>(result[4]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+ }
+
+ SECTION("4294967296..9223372036854775807 (int 64)")
+ {
+ for (uint64_t i :
+ {
+ 4294967296LU, 9223372036854775807LU
+ })
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json j = -1;
+ j.get_ref<json::number_integer_t&>() = static_cast<json::number_integer_t>(i);
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ 0xcf,
+ static_cast<uint8_t>((i >> 070) & 0xff),
+ static_cast<uint8_t>((i >> 060) & 0xff),
+ static_cast<uint8_t>((i >> 050) & 0xff),
+ static_cast<uint8_t>((i >> 040) & 0xff),
+ static_cast<uint8_t>((i >> 030) & 0xff),
+ static_cast<uint8_t>((i >> 020) & 0xff),
+ static_cast<uint8_t>((i >> 010) & 0xff),
+ static_cast<uint8_t>(i & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 9);
+
+ // check individual bytes
+ CHECK(result[0] == 0xcf);
+ uint64_t const restored = (static_cast<uint64_t>(result[1]) << 070) +
+ (static_cast<uint64_t>(result[2]) << 060) +
+ (static_cast<uint64_t>(result[3]) << 050) +
+ (static_cast<uint64_t>(result[4]) << 040) +
+ (static_cast<uint64_t>(result[5]) << 030) +
+ (static_cast<uint64_t>(result[6]) << 020) +
+ (static_cast<uint64_t>(result[7]) << 010) +
+ static_cast<uint64_t>(result[8]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+ }
+
+ SECTION("-128..-33 (int 8)")
+ {
+ for (auto i = -128; i <= -33; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json const j = i;
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ 0xd0,
+ static_cast<uint8_t>(i),
+ };
+
+ // compare result + size
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 2);
+
+ // check individual bytes
+ CHECK(result[0] == 0xd0);
+ CHECK(static_cast<int8_t>(result[1]) == i);
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+ }
+
+ SECTION("-9263 (int 16)")
+ {
+ json const j = -9263;
+ std::vector<uint8_t> const expected = {0xd1, 0xdb, 0xd1};
+
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+
+ auto const restored = static_cast<int16_t>((result[1] << 8) + result[2]);
+ CHECK(restored == -9263);
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+
+ SECTION("-32768..-129 (int 16)")
+ {
+ for (int16_t i = -32768; i <= static_cast<std::int16_t>(-129); ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json const j = i;
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ 0xd1,
+ static_cast<uint8_t>((i >> 8) & 0xff),
+ static_cast<uint8_t>(i & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 3);
+
+ // check individual bytes
+ CHECK(result[0] == 0xd1);
+ auto const restored = static_cast<int16_t>((result[1] << 8) + result[2]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+ }
+
+ SECTION("-32769..-2147483648")
+ {
+ std::vector<int32_t> const numbers
+ {
+ -32769,
+ -65536,
+ -77777,
+ -1048576,
+ -2147483648LL,
+ };
+ for (auto i : numbers)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json const j = i;
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ 0xd2,
+ static_cast<uint8_t>((i >> 24) & 0xff),
+ static_cast<uint8_t>((i >> 16) & 0xff),
+ static_cast<uint8_t>((i >> 8) & 0xff),
+ static_cast<uint8_t>(i & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 5);
+
+ // check individual bytes
+ CHECK(result[0] == 0xd2);
+ uint32_t const restored = (static_cast<uint32_t>(result[1]) << 030) +
+ (static_cast<uint32_t>(result[2]) << 020) +
+ (static_cast<uint32_t>(result[3]) << 010) +
+ static_cast<uint32_t>(result[4]);
+ CHECK(static_cast<std::int32_t>(restored) == i);
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+ }
+
+ SECTION("-9223372036854775808..-2147483649 (int 64)")
+ {
+ std::vector<int64_t> const numbers
+ {
+ (std::numeric_limits<int64_t>::min)(),
+ -2147483649LL,
+ };
+ for (auto i : numbers)
+ {
+ CAPTURE(i)
+
+ // create JSON value with unsigned integer number
+ json const j = i;
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ 0xd3,
+ static_cast<uint8_t>((i >> 070) & 0xff),
+ static_cast<uint8_t>((i >> 060) & 0xff),
+ static_cast<uint8_t>((i >> 050) & 0xff),
+ static_cast<uint8_t>((i >> 040) & 0xff),
+ static_cast<uint8_t>((i >> 030) & 0xff),
+ static_cast<uint8_t>((i >> 020) & 0xff),
+ static_cast<uint8_t>((i >> 010) & 0xff),
+ static_cast<uint8_t>(i & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 9);
+
+ // check individual bytes
+ CHECK(result[0] == 0xd3);
+ int64_t const restored = (static_cast<int64_t>(result[1]) << 070) +
+ (static_cast<int64_t>(result[2]) << 060) +
+ (static_cast<int64_t>(result[3]) << 050) +
+ (static_cast<int64_t>(result[4]) << 040) +
+ (static_cast<int64_t>(result[5]) << 030) +
+ (static_cast<int64_t>(result[6]) << 020) +
+ (static_cast<int64_t>(result[7]) << 010) +
+ static_cast<int64_t>(result[8]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+ }
+ }
+
+ SECTION("unsigned")
+ {
+ SECTION("0..127 (positive fixnum)")
+ {
+ for (size_t i = 0; i <= 127; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with unsigned integer number
+ json const j = i;
+
+ // check type
+ CHECK(j.is_number_unsigned());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected{static_cast<uint8_t>(i)};
+
+ // compare result + size
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 1);
+
+ // check individual bytes
+ CHECK(result[0] == i);
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+ }
+
+ SECTION("128..255 (uint 8)")
+ {
+ for (size_t i = 128; i <= 255; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with unsigned integer number
+ json const j = i;
+
+ // check type
+ CHECK(j.is_number_unsigned());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ 0xcc,
+ static_cast<uint8_t>(i),
+ };
+
+ // compare result + size
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 2);
+
+ // check individual bytes
+ CHECK(result[0] == 0xcc);
+ auto const restored = static_cast<uint8_t>(result[1]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+ }
+
+ SECTION("256..65535 (uint 16)")
+ {
+ for (size_t i = 256; i <= 65535; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with unsigned integer number
+ json const j = i;
+
+ // check type
+ CHECK(j.is_number_unsigned());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ 0xcd,
+ static_cast<uint8_t>((i >> 8) & 0xff),
+ static_cast<uint8_t>(i & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 3);
+
+ // check individual bytes
+ CHECK(result[0] == 0xcd);
+ auto const restored = static_cast<uint16_t>((static_cast<uint8_t>(result[1]) * 256) + static_cast<uint8_t>(result[2]));
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+ }
+
+ SECTION("65536..4294967295 (uint 32)")
+ {
+ for (const uint32_t i :
+ {
+ 65536u, 77777u, 1048576u, 4294967295u
+ })
+ {
+ CAPTURE(i)
+
+ // create JSON value with unsigned integer number
+ json const j = i;
+
+ // check type
+ CHECK(j.is_number_unsigned());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ 0xce,
+ static_cast<uint8_t>((i >> 24) & 0xff),
+ static_cast<uint8_t>((i >> 16) & 0xff),
+ static_cast<uint8_t>((i >> 8) & 0xff),
+ static_cast<uint8_t>(i & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 5);
+
+ // check individual bytes
+ CHECK(result[0] == 0xce);
+ uint32_t const restored = (static_cast<uint32_t>(result[1]) << 030) +
+ (static_cast<uint32_t>(result[2]) << 020) +
+ (static_cast<uint32_t>(result[3]) << 010) +
+ static_cast<uint32_t>(result[4]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+ }
+
+ SECTION("4294967296..18446744073709551615 (uint 64)")
+ {
+ for (const uint64_t i :
+ {
+ 4294967296LU, 18446744073709551615LU
+ })
+ {
+ CAPTURE(i)
+
+ // create JSON value with unsigned integer number
+ json const j = i;
+
+ // check type
+ CHECK(j.is_number_unsigned());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ 0xcf,
+ static_cast<uint8_t>((i >> 070) & 0xff),
+ static_cast<uint8_t>((i >> 060) & 0xff),
+ static_cast<uint8_t>((i >> 050) & 0xff),
+ static_cast<uint8_t>((i >> 040) & 0xff),
+ static_cast<uint8_t>((i >> 030) & 0xff),
+ static_cast<uint8_t>((i >> 020) & 0xff),
+ static_cast<uint8_t>((i >> 010) & 0xff),
+ static_cast<uint8_t>(i & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 9);
+
+ // check individual bytes
+ CHECK(result[0] == 0xcf);
+ uint64_t const restored = (static_cast<uint64_t>(result[1]) << 070) +
+ (static_cast<uint64_t>(result[2]) << 060) +
+ (static_cast<uint64_t>(result[3]) << 050) +
+ (static_cast<uint64_t>(result[4]) << 040) +
+ (static_cast<uint64_t>(result[5]) << 030) +
+ (static_cast<uint64_t>(result[6]) << 020) +
+ (static_cast<uint64_t>(result[7]) << 010) +
+ static_cast<uint64_t>(result[8]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+ }
+ }
+
+ SECTION("float")
+ {
+ SECTION("3.1415925")
+ {
+ double const v = 3.1415925;
+ json const j = v;
+ std::vector<uint8_t> const expected =
+ {
+ 0xcb, 0x40, 0x09, 0x21, 0xfb, 0x3f, 0xa6, 0xde, 0xfc
+ };
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result) == v);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+
+ SECTION("1.0")
+ {
+ double const v = 1.0;
+ json const j = v;
+ std::vector<uint8_t> const expected =
+ {
+ 0xca, 0x3f, 0x80, 0x00, 0x00
+ };
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result) == v);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+
+ SECTION("128.128")
+ {
+ double const v = 128.1280059814453125;
+ json const j = v;
+ std::vector<uint8_t> const expected =
+ {
+ 0xca, 0x43, 0x00, 0x20, 0xc5
+ };
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result) == v);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+ }
+ }
+
+ SECTION("string")
+ {
+ SECTION("N = 0..31")
+ {
+ // explicitly enumerate the first byte for all 32 strings
+ const std::vector<uint8_t> first_bytes =
+ {
+ 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
+ 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1,
+ 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba,
+ 0xbb, 0xbc, 0xbd, 0xbe, 0xbf
+ };
+
+ for (size_t N = 0; N < first_bytes.size(); ++N)
+ {
+ CAPTURE(N)
+
+ // create JSON value with string containing of N * 'x'
+ const auto s = std::string(N, 'x');
+ json const j = s;
+
+ // create expected byte vector
+ std::vector<uint8_t> expected;
+ expected.push_back(first_bytes[N]);
+ for (size_t i = 0; i < N; ++i)
+ {
+ expected.push_back('x');
+ }
+
+ // check first byte
+ CHECK((first_bytes[N] & 0x1f) == N);
+
+ // compare result + size
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 1);
+ // check that no null byte is appended
+ if (N > 0)
+ {
+ CHECK(result.back() != '\x00');
+ }
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+ }
+
+ SECTION("N = 32..255")
+ {
+ for (size_t N = 32; N <= 255; ++N)
+ {
+ CAPTURE(N)
+
+ // create JSON value with string containing of N * 'x'
+ const auto s = std::string(N, 'x');
+ json const j = s;
+
+ // create expected byte vector
+ std::vector<uint8_t> expected;
+ expected.push_back(0xd9);
+ expected.push_back(static_cast<uint8_t>(N));
+ for (size_t i = 0; i < N; ++i)
+ {
+ expected.push_back('x');
+ }
+
+ // compare result + size
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 2);
+ // check that no null byte is appended
+ CHECK(result.back() != '\x00');
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+ }
+
+ SECTION("N = 256..65535")
+ {
+ for (size_t N :
+ {
+ 256u, 999u, 1025u, 3333u, 2048u, 65535u
+ })
+ {
+ CAPTURE(N)
+
+ // create JSON value with string containing of N * 'x'
+ const auto s = std::string(N, 'x');
+ json const j = s;
+
+ // create expected byte vector (hack: create string first)
+ std::vector<uint8_t> expected(N, 'x');
+ // reverse order of commands, because we insert at begin()
+ expected.insert(expected.begin(), static_cast<uint8_t>(N & 0xff));
+ expected.insert(expected.begin(), static_cast<uint8_t>((N >> 8) & 0xff));
+ expected.insert(expected.begin(), 0xda);
+
+ // compare result + size
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 3);
+ // check that no null byte is appended
+ CHECK(result.back() != '\x00');
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+ }
+
+ SECTION("N = 65536..4294967295")
+ {
+ for (size_t N :
+ {
+ 65536u, 77777u, 1048576u
+ })
+ {
+ CAPTURE(N)
+
+ // create JSON value with string containing of N * 'x'
+ const auto s = std::string(N, 'x');
+ json const j = s;
+
+ // create expected byte vector (hack: create string first)
+ std::vector<uint8_t> expected(N, 'x');
+ // reverse order of commands, because we insert at begin()
+ expected.insert(expected.begin(), static_cast<uint8_t>(N & 0xff));
+ expected.insert(expected.begin(), static_cast<uint8_t>((N >> 8) & 0xff));
+ expected.insert(expected.begin(), static_cast<uint8_t>((N >> 16) & 0xff));
+ expected.insert(expected.begin(), static_cast<uint8_t>((N >> 24) & 0xff));
+ expected.insert(expected.begin(), 0xdb);
+
+ // compare result + size
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 5);
+ // check that no null byte is appended
+ CHECK(result.back() != '\x00');
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+ }
+ }
+
+ SECTION("array")
+ {
+ SECTION("empty")
+ {
+ json const j = json::array();
+ std::vector<uint8_t> const expected = {0x90};
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+
+ SECTION("[null]")
+ {
+ json const j = {nullptr};
+ std::vector<uint8_t> const expected = {0x91, 0xc0};
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+
+ SECTION("[1,2,3,4,5]")
+ {
+ json const j = json::parse("[1,2,3,4,5]");
+ std::vector<uint8_t> const expected = {0x95, 0x01, 0x02, 0x03, 0x04, 0x05};
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+
+ SECTION("[[[[]]]]")
+ {
+ json const j = json::parse("[[[[]]]]");
+ std::vector<uint8_t> const expected = {0x91, 0x91, 0x91, 0x90};
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+
+ SECTION("array 16")
+ {
+ json j(16, nullptr);
+ std::vector<uint8_t> expected(j.size() + 3, 0xc0); // all null
+ expected[0] = 0xdc; // array 16
+ expected[1] = 0x00; // size (0x0010), byte 0
+ expected[2] = 0x10; // size (0x0010), byte 1
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+
+ SECTION("array 32")
+ {
+ json j(65536, nullptr);
+ std::vector<uint8_t> expected(j.size() + 5, 0xc0); // all null
+ expected[0] = 0xdd; // array 32
+ expected[1] = 0x00; // size (0x00100000), byte 0
+ expected[2] = 0x01; // size (0x00100000), byte 1
+ expected[3] = 0x00; // size (0x00100000), byte 2
+ expected[4] = 0x00; // size (0x00100000), byte 3
+ const auto result = json::to_msgpack(j);
+ //CHECK(result == expected);
+
+ CHECK(result.size() == expected.size());
+ for (size_t i = 0; i < expected.size(); ++i)
+ {
+ CAPTURE(i)
+ CHECK(result[i] == expected[i]);
+ }
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+ }
+
+ SECTION("object")
+ {
+ SECTION("empty")
+ {
+ json const j = json::object();
+ std::vector<uint8_t> const expected = {0x80};
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+
+ SECTION("{\"\":null}")
+ {
+ json const j = {{"", nullptr}};
+ std::vector<uint8_t> const expected = {0x81, 0xa0, 0xc0};
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+
+ SECTION("{\"a\": {\"b\": {\"c\": {}}}}")
+ {
+ json const j = json::parse(R"({"a": {"b": {"c": {}}}})");
+ std::vector<uint8_t> const expected =
+ {
+ 0x81, 0xa1, 0x61, 0x81, 0xa1, 0x62, 0x81, 0xa1, 0x63, 0x80
+ };
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+
+ SECTION("map 16")
+ {
+ json const j = R"({"00": null, "01": null, "02": null, "03": null,
+ "04": null, "05": null, "06": null, "07": null,
+ "08": null, "09": null, "10": null, "11": null,
+ "12": null, "13": null, "14": null, "15": null})"_json;
+
+ const auto result = json::to_msgpack(j);
+
+ // Checking against an expected vector byte by byte is
+ // difficult, because no assumption on the order of key/value
+ // pairs are made. We therefore only check the prefix (type and
+ // size) and the overall size. The rest is then handled in the
+ // roundtrip check.
+ CHECK(result.size() == 67); // 1 type, 2 size, 16*4 content
+ CHECK(result[0] == 0xde); // map 16
+ CHECK(result[1] == 0x00); // byte 0 of size (0x0010)
+ CHECK(result[2] == 0x10); // byte 1 of size (0x0010)
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+
+ SECTION("map 32")
+ {
+ json j;
+ for (auto i = 0; i < 65536; ++i)
+ {
+ // format i to a fixed width of 5
+ // each entry will need 7 bytes: 6 for fixstr, 1 for null
+ std::stringstream ss;
+ ss << std::setw(5) << std::setfill('0') << i;
+ j.emplace(ss.str(), nullptr);
+ }
+
+ const auto result = json::to_msgpack(j);
+
+ // Checking against an expected vector byte by byte is
+ // difficult, because no assumption on the order of key/value
+ // pairs are made. We therefore only check the prefix (type and
+ // size) and the overall size. The rest is then handled in the
+ // roundtrip check.
+ CHECK(result.size() == 458757); // 1 type, 4 size, 65536*7 content
+ CHECK(result[0] == 0xdf); // map 32
+ CHECK(result[1] == 0x00); // byte 0 of size (0x00010000)
+ CHECK(result[2] == 0x01); // byte 1 of size (0x00010000)
+ CHECK(result[3] == 0x00); // byte 2 of size (0x00010000)
+ CHECK(result[4] == 0x00); // byte 3 of size (0x00010000)
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+ }
+
+ SECTION("extension")
+ {
+ SECTION("N = 0..255")
+ {
+ for (size_t N = 0; N <= 0xFF; ++N)
+ {
+ CAPTURE(N)
+
+ // create JSON value with byte array containing of N * 'x'
+ const auto s = std::vector<uint8_t>(N, 'x');
+ json j = json::binary(s);
+ std::uint8_t const subtype = 42;
+ j.get_binary().set_subtype(subtype);
+
+ // create expected byte vector
+ std::vector<uint8_t> expected;
+ switch (N)
+ {
+ case 1:
+ expected.push_back(static_cast<std::uint8_t>(0xD4));
+ break;
+ case 2:
+ expected.push_back(static_cast<std::uint8_t>(0xD5));
+ break;
+ case 4:
+ expected.push_back(static_cast<std::uint8_t>(0xD6));
+ break;
+ case 8:
+ expected.push_back(static_cast<std::uint8_t>(0xD7));
+ break;
+ case 16:
+ expected.push_back(static_cast<std::uint8_t>(0xD8));
+ break;
+ default:
+ expected.push_back(static_cast<std::uint8_t>(0xC7));
+ expected.push_back(static_cast<std::uint8_t>(N));
+ break;
+ }
+ expected.push_back(subtype);
+
+ for (size_t i = 0; i < N; ++i)
+ {
+ expected.push_back(0x78);
+ }
+
+ // compare result + size
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+ switch (N)
+ {
+ case 1:
+ case 2:
+ case 4:
+ case 8:
+ case 16:
+ CHECK(result.size() == N + 2);
+ break;
+ default:
+ CHECK(result.size() == N + 3);
+ break;
+ }
+
+ // check that no null byte is appended
+ if (N > 0)
+ {
+ CHECK(result.back() != '\x00');
+ }
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+ }
+
+ SECTION("N = 256..65535")
+ {
+ for (std::size_t N :
+ {
+ 256u, 999u, 1025u, 3333u, 2048u, 65535u
+ })
+ {
+ CAPTURE(N)
+
+ // create JSON value with string containing of N * 'x'
+ const auto s = std::vector<uint8_t>(N, 'x');
+ json j = json::binary(s);
+ std::uint8_t const subtype = 42;
+ j.get_binary().set_subtype(subtype);
+
+ // create expected byte vector (hack: create string first)
+ std::vector<uint8_t> expected(N, 'x');
+ // reverse order of commands, because we insert at begin()
+ expected.insert(expected.begin(), subtype);
+ expected.insert(expected.begin(), static_cast<uint8_t>(N & 0xff));
+ expected.insert(expected.begin(), static_cast<uint8_t>((N >> 8) & 0xff));
+ expected.insert(expected.begin(), 0xC8);
+
+ // compare result + size
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 4);
+ // check that no null byte is appended
+ CHECK(result.back() != '\x00');
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+ }
+
+ SECTION("N = 65536..4294967295")
+ {
+ for (std::size_t N :
+ {
+ 65536u, 77777u, 1048576u
+ })
+ {
+ CAPTURE(N)
+
+ // create JSON value with string containing of N * 'x'
+ const auto s = std::vector<uint8_t>(N, 'x');
+ json j = json::binary(s);
+ std::uint8_t const subtype = 42;
+ j.get_binary().set_subtype(subtype);
+
+ // create expected byte vector (hack: create string first)
+ std::vector<uint8_t> expected(N, 'x');
+ // reverse order of commands, because we insert at begin()
+ expected.insert(expected.begin(), subtype);
+ expected.insert(expected.begin(), static_cast<uint8_t>(N & 0xff));
+ expected.insert(expected.begin(), static_cast<uint8_t>((N >> 8) & 0xff));
+ expected.insert(expected.begin(), static_cast<uint8_t>((N >> 16) & 0xff));
+ expected.insert(expected.begin(), static_cast<uint8_t>((N >> 24) & 0xff));
+ expected.insert(expected.begin(), 0xC9);
+
+ // compare result + size
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 6);
+ // check that no null byte is appended
+ CHECK(result.back() != '\x00');
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+ }
+ }
+
+ SECTION("binary")
+ {
+ SECTION("N = 0..255")
+ {
+ for (std::size_t N = 0; N <= 0xFF; ++N)
+ {
+ CAPTURE(N)
+
+ // create JSON value with byte array containing of N * 'x'
+ const auto s = std::vector<uint8_t>(N, 'x');
+ json const j = json::binary(s);
+
+ // create expected byte vector
+ std::vector<std::uint8_t> expected;
+ expected.push_back(static_cast<std::uint8_t>(0xC4));
+ expected.push_back(static_cast<std::uint8_t>(N));
+ for (size_t i = 0; i < N; ++i)
+ {
+ expected.push_back(0x78);
+ }
+
+ // compare result + size
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 2);
+ // check that no null byte is appended
+ if (N > 0)
+ {
+ CHECK(result.back() != '\x00');
+ }
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+ }
+
+ SECTION("N = 256..65535")
+ {
+ for (std::size_t N :
+ {
+ 256u, 999u, 1025u, 3333u, 2048u, 65535u
+ })
+ {
+ CAPTURE(N)
+
+ // create JSON value with string containing of N * 'x'
+ const auto s = std::vector<std::uint8_t>(N, 'x');
+ json const j = json::binary(s);
+
+ // create expected byte vector (hack: create string first)
+ std::vector<std::uint8_t> expected(N, 'x');
+ // reverse order of commands, because we insert at begin()
+ expected.insert(expected.begin(), static_cast<std::uint8_t>(N & 0xff));
+ expected.insert(expected.begin(), static_cast<std::uint8_t>((N >> 8) & 0xff));
+ expected.insert(expected.begin(), 0xC5);
+
+ // compare result + size
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 3);
+ // check that no null byte is appended
+ CHECK(result.back() != '\x00');
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+ }
+
+ SECTION("N = 65536..4294967295")
+ {
+ for (std::size_t N :
+ {
+ 65536u, 77777u, 1048576u
+ })
+ {
+ CAPTURE(N)
+
+ // create JSON value with string containing of N * 'x'
+ const auto s = std::vector<std::uint8_t>(N, 'x');
+ json const j = json::binary(s);
+
+ // create expected byte vector (hack: create string first)
+ std::vector<uint8_t> expected(N, 'x');
+ // reverse order of commands, because we insert at begin()
+ expected.insert(expected.begin(), static_cast<std::uint8_t>(N & 0xff));
+ expected.insert(expected.begin(), static_cast<std::uint8_t>((N >> 8) & 0xff));
+ expected.insert(expected.begin(), static_cast<std::uint8_t>((N >> 16) & 0xff));
+ expected.insert(expected.begin(), static_cast<std::uint8_t>((N >> 24) & 0xff));
+ expected.insert(expected.begin(), 0xC6);
+
+ // compare result + size
+ const auto result = json::to_msgpack(j);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 5);
+ // check that no null byte is appended
+ CHECK(result.back() != '\x00');
+
+ // roundtrip
+ CHECK(json::from_msgpack(result) == j);
+ CHECK(json::from_msgpack(result, true, false) == j);
+ }
+ }
+ }
+ }
+
+ SECTION("from float32")
+ {
+ auto given = std::vector<uint8_t>({0xca, 0x41, 0xc8, 0x00, 0x01});
+ json const j = json::from_msgpack(given);
+ CHECK(j.get<double>() == Approx(25.0000019073486));
+ }
+
+ SECTION("errors")
+ {
+ SECTION("empty byte vector")
+ {
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>()), "[json.exception.parse_error.110] parse error at byte 1: syntax error while parsing MessagePack value: unexpected end of input", json::parse_error&);
+ CHECK(json::from_msgpack(std::vector<uint8_t>(), true, false).is_discarded());
+ }
+
+ SECTION("too short byte vector")
+ {
+ json _;
+
+ CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0x87})),
+ "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing MessagePack string: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0xcc})),
+ "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing MessagePack number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0xcd})),
+ "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing MessagePack number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0xcd, 0x00})),
+ "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing MessagePack number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0xce})),
+ "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing MessagePack number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0xce, 0x00})),
+ "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing MessagePack number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0xce, 0x00, 0x00})),
+ "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing MessagePack number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0xce, 0x00, 0x00, 0x00})),
+ "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing MessagePack number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0xcf})),
+ "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing MessagePack number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0xcf, 0x00})),
+ "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing MessagePack number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0xcf, 0x00, 0x00})),
+ "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing MessagePack number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0xcf, 0x00, 0x00, 0x00})),
+ "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing MessagePack number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0xcf, 0x00, 0x00, 0x00, 0x00})),
+ "[json.exception.parse_error.110] parse error at byte 6: syntax error while parsing MessagePack number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0xcf, 0x00, 0x00, 0x00, 0x00, 0x00})),
+ "[json.exception.parse_error.110] parse error at byte 7: syntax error while parsing MessagePack number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0xcf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})),
+ "[json.exception.parse_error.110] parse error at byte 8: syntax error while parsing MessagePack number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0xcf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})),
+ "[json.exception.parse_error.110] parse error at byte 9: syntax error while parsing MessagePack number: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0xa5, 0x68, 0x65})),
+ "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing MessagePack string: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0x92, 0x01})),
+ "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing MessagePack value: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0x81, 0xa1, 0x61})),
+ "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing MessagePack value: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0xc4, 0x02})),
+ "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing MessagePack binary: unexpected end of input", json::parse_error&);
+
+ CHECK(json::from_msgpack(std::vector<uint8_t>({0x87}), true, false).is_discarded());
+ CHECK(json::from_msgpack(std::vector<uint8_t>({0xcc}), true, false).is_discarded());
+ CHECK(json::from_msgpack(std::vector<uint8_t>({0xcd}), true, false).is_discarded());
+ CHECK(json::from_msgpack(std::vector<uint8_t>({0xcd, 0x00}), true, false).is_discarded());
+ CHECK(json::from_msgpack(std::vector<uint8_t>({0xce}), true, false).is_discarded());
+ CHECK(json::from_msgpack(std::vector<uint8_t>({0xce, 0x00}), true, false).is_discarded());
+ CHECK(json::from_msgpack(std::vector<uint8_t>({0xce, 0x00, 0x00}), true, false).is_discarded());
+ CHECK(json::from_msgpack(std::vector<uint8_t>({0xce, 0x00, 0x00, 0x00}), true, false).is_discarded());
+ CHECK(json::from_msgpack(std::vector<uint8_t>({0xcf}), true, false).is_discarded());
+ CHECK(json::from_msgpack(std::vector<uint8_t>({0xcf, 0x00}), true, false).is_discarded());
+ CHECK(json::from_msgpack(std::vector<uint8_t>({0xcf, 0x00, 0x00}), true, false).is_discarded());
+ CHECK(json::from_msgpack(std::vector<uint8_t>({0xcf, 0x00, 0x00, 0x00}), true, false).is_discarded());
+ CHECK(json::from_msgpack(std::vector<uint8_t>({0xcf, 0x00, 0x00, 0x00, 0x00}), true, false).is_discarded());
+ CHECK(json::from_msgpack(std::vector<uint8_t>({0xcf, 0x00, 0x00, 0x00, 0x00, 0x00}), true, false).is_discarded());
+ CHECK(json::from_msgpack(std::vector<uint8_t>({0xcf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), true, false).is_discarded());
+ CHECK(json::from_msgpack(std::vector<uint8_t>({0xcf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), true, false).is_discarded());
+ CHECK(json::from_msgpack(std::vector<uint8_t>({0xa5, 0x68, 0x65}), true, false).is_discarded());
+ CHECK(json::from_msgpack(std::vector<uint8_t>({0x92, 0x01}), true, false).is_discarded());
+ CHECK(json::from_msgpack(std::vector<uint8_t>({0x81, 0xA1, 0x61}), true, false).is_discarded());
+ CHECK(json::from_msgpack(std::vector<uint8_t>({0xc4, 0x02}), true, false).is_discarded());
+ CHECK(json::from_msgpack(std::vector<uint8_t>({0xc4}), true, false).is_discarded());
+ }
+
+ SECTION("unexpected end inside int with stream")
+ {
+ json _;
+ const std::string data = {static_cast<char>(0xd2u), static_cast<char>(0x12u), static_cast<char>(0x34u), static_cast<char>(0x56u)};
+ CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::istringstream(data, std::ios::binary)),
+ "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing MessagePack number: unexpected end of input", json::parse_error&);
+ }
+ SECTION("misuse wchar for binary")
+ {
+ json _;
+ // creates 0xd2 after UTF-8 decoding, triggers get_elements in wide_string_input_adapter for code coverage
+ const std::u32string data = {static_cast<char32_t>(0x0280)};
+ CHECK_THROWS_WITH_AS(_ = json::from_msgpack(data),
+ "[json.exception.parse_error.112] parse error at byte 1: wide string type cannot be interpreted as binary data", json::parse_error&);
+ }
+
+ SECTION("unsupported bytes")
+ {
+ SECTION("concrete examples")
+ {
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0xc1})), "[json.exception.parse_error.112] parse error at byte 1: syntax error while parsing MessagePack value: invalid byte: 0xC1", json::parse_error&);
+ }
+
+ SECTION("all unsupported bytes")
+ {
+ for (auto byte :
+ {
+ // never used
+ 0xc1
+ })
+ {
+ json _;
+ CHECK_THROWS_AS(_ = json::from_msgpack(std::vector<uint8_t>({static_cast<uint8_t>(byte)})), json::parse_error&);
+ CHECK(json::from_msgpack(std::vector<uint8_t>({static_cast<uint8_t>(byte)}), true, false).is_discarded());
+ }
+ }
+ }
+
+ SECTION("invalid string in map")
+ {
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0x81, 0xff, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack string: expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0xFF", json::parse_error&);
+ CHECK(json::from_msgpack(std::vector<uint8_t>({0x81, 0xff, 0x01}), true, false).is_discarded());
+ }
+
+ SECTION("strict mode")
+ {
+ std::vector<uint8_t> const vec = {0xc0, 0xc0};
+ SECTION("non-strict mode")
+ {
+ const auto result = json::from_msgpack(vec, false);
+ CHECK(result == json());
+ }
+
+ SECTION("strict mode")
+ {
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_msgpack(vec), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing MessagePack value: expected end of input; last byte: 0xC0", json::parse_error&);
+ CHECK(json::from_msgpack(vec, true, false).is_discarded());
+ }
+ }
+ }
+
+ SECTION("SAX aborts")
+ {
+ SECTION("start_array(len)")
+ {
+ std::vector<uint8_t> const v = {0x93, 0x01, 0x02, 0x03};
+ SaxCountdown scp(0);
+ CHECK(!json::sax_parse(v, &scp, json::input_format_t::msgpack));
+ }
+
+ SECTION("start_object(len)")
+ {
+ std::vector<uint8_t> const v = {0x81, 0xa3, 0x66, 0x6F, 0x6F, 0xc2};
+ SaxCountdown scp(0);
+ CHECK(!json::sax_parse(v, &scp, json::input_format_t::msgpack));
+ }
+
+ SECTION("key()")
+ {
+ std::vector<uint8_t> const v = {0x81, 0xa3, 0x66, 0x6F, 0x6F, 0xc2};
+ SaxCountdown scp(1);
+ CHECK(!json::sax_parse(v, &scp, json::input_format_t::msgpack));
+ }
+ }
+}
+
+// use this testcase outside [hide] to run it with Valgrind
+TEST_CASE("single MessagePack roundtrip")
+{
+ SECTION("sample.json")
+ {
+ std::string const filename = TEST_DATA_DIRECTORY "/json_testsuite/sample.json";
+
+ // parse JSON file
+ std::ifstream f_json(filename);
+ const json j1 = json::parse(f_json);
+
+ // parse MessagePack file
+ auto packed = utils::read_binary_file(filename + ".msgpack");
+ json j2;
+ CHECK_NOTHROW(j2 = json::from_msgpack(packed));
+
+ // compare parsed JSON values
+ CHECK(j1 == j2);
+
+ SECTION("roundtrips")
+ {
+ SECTION("std::ostringstream")
+ {
+ std::basic_ostringstream<char> ss;
+ json::to_msgpack(j1, ss);
+ json j3 = json::from_msgpack(ss.str());
+ CHECK(j1 == j3);
+ }
+
+ SECTION("std::string")
+ {
+ std::string s;
+ json::to_msgpack(j1, s);
+ json j3 = json::from_msgpack(s);
+ CHECK(j1 == j3);
+ }
+ }
+
+ // check with different start index
+ packed.insert(packed.begin(), 5, 0xff);
+ CHECK(j1 == json::from_msgpack(packed.begin() + 5, packed.end()));
+ }
+}
+
+TEST_CASE("MessagePack roundtrips" * doctest::skip())
+{
+ SECTION("input from msgpack-python")
+ {
+ // most of these are excluded due to differences in key order (not a real problem)
+ std::set<std::string> exclude_packed;
+ exclude_packed.insert(TEST_DATA_DIRECTORY "/json.org/1.json");
+ exclude_packed.insert(TEST_DATA_DIRECTORY "/json.org/2.json");
+ exclude_packed.insert(TEST_DATA_DIRECTORY "/json.org/3.json");
+ exclude_packed.insert(TEST_DATA_DIRECTORY "/json.org/4.json");
+ exclude_packed.insert(TEST_DATA_DIRECTORY "/json.org/5.json");
+ exclude_packed.insert(TEST_DATA_DIRECTORY "/json_testsuite/sample.json"); // kills AppVeyor
+ exclude_packed.insert(TEST_DATA_DIRECTORY "/json_tests/pass1.json");
+ exclude_packed.insert(TEST_DATA_DIRECTORY "/regression/working_file.json");
+ exclude_packed.insert(TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object.json");
+ exclude_packed.insert(TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_basic.json");
+ exclude_packed.insert(TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_duplicated_key.json");
+ exclude_packed.insert(TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_long_strings.json");
+ exclude_packed.insert(TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_simple.json");
+ exclude_packed.insert(TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_string_unicode.json");
+
+ for (std::string filename :
+ {
+ TEST_DATA_DIRECTORY "/json_nlohmann_tests/all_unicode.json",
+ TEST_DATA_DIRECTORY "/json.org/1.json",
+ TEST_DATA_DIRECTORY "/json.org/2.json",
+ TEST_DATA_DIRECTORY "/json.org/3.json",
+ TEST_DATA_DIRECTORY "/json.org/4.json",
+ TEST_DATA_DIRECTORY "/json.org/5.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip01.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip02.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip03.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip04.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip05.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip06.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip07.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip08.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip09.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip10.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip11.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip12.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip13.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip14.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip15.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip16.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip17.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip18.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip19.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip20.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip21.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip22.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip23.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip24.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip25.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip26.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip27.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip28.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip29.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip30.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip31.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip32.json",
+ TEST_DATA_DIRECTORY "/json_testsuite/sample.json", // kills AppVeyor
+ TEST_DATA_DIRECTORY "/json_tests/pass1.json",
+ TEST_DATA_DIRECTORY "/json_tests/pass2.json",
+ TEST_DATA_DIRECTORY "/json_tests/pass3.json",
+ TEST_DATA_DIRECTORY "/regression/floats.json",
+ TEST_DATA_DIRECTORY "/regression/signed_ints.json",
+ TEST_DATA_DIRECTORY "/regression/unsigned_ints.json",
+ TEST_DATA_DIRECTORY "/regression/working_file.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_arraysWithSpaces.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_empty-string.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_empty.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_ending_with_newline.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_false.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_heterogeneous.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_null.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_with_1_and_newline.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_with_leading_space.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_with_several_null.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_with_trailing_space.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_0e+1.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_0e1.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_after_space.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_double_close_to_zero.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_double_huge_neg_exp.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_huge_exp.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_int_with_exp.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_minus_zero.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_negative_int.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_negative_one.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_negative_zero.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_capital_e.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_capital_e_neg_exp.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_capital_e_pos_exp.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_exponent.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_fraction_exponent.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_neg_exp.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_neg_overflow.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_pos_exponent.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_pos_overflow.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_underflow.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_simple_int.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_simple_real.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_too_big_neg_int.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_too_big_pos_int.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_very_big_negative_int.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_basic.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_duplicated_key.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_duplicated_key_and_value.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_empty.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_empty_key.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_escaped_null_in_key.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_extreme_numbers.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_long_strings.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_simple.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_string_unicode.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_with_newlines.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_1_2_3_bytes_UTF-8_sequences.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_UTF-16_Surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_accepted_surrogate_pair.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_accepted_surrogate_pairs.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_allowed_escapes.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_backslash_and_u_escaped_zero.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_backslash_doublequotes.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_comments.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_double_escape_a.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_double_escape_n.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_escaped_control_character.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_escaped_noncharacter.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_in_array.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_in_array_with_leading_space.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_last_surrogates_1_and_2.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_newline_uescaped.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+10FFFF.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+1FFFF.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+FFFF.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_null_escape.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_one-byte-utf-8.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_pi.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_simple_ascii.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_space.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_three-byte-utf-8.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_two-byte-utf-8.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_u+2028_line_sep.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_u+2029_par_sep.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_uEscape.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unescaped_char_delete.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicodeEscapedBackslash.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode_2.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode_U+2064_invisible_plus.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode_escaped_double_quote.json",
+ // TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_utf16.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_utf8.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_with_del_character.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_false.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_int.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_negative_real.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_null.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_string.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_true.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_string_empty.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_trailing_newline.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_true_in_array.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_whitespace_array.json"
+ })
+ {
+ CAPTURE(filename)
+
+ {
+ INFO_WITH_TEMP(filename + ": std::vector<uint8_t>");
+ // parse JSON file
+ std::ifstream f_json(filename);
+ const json j1 = json::parse(f_json);
+
+ // parse MessagePack file
+ auto packed = utils::read_binary_file(filename + ".msgpack");
+ json j2;
+ CHECK_NOTHROW(j2 = json::from_msgpack(packed));
+
+ // compare parsed JSON values
+ CHECK(j1 == j2);
+ }
+
+ {
+ INFO_WITH_TEMP(filename + ": std::ifstream");
+ // parse JSON file
+ std::ifstream f_json(filename);
+ const json j1 = json::parse(f_json);
+
+ // parse MessagePack file
+ std::ifstream f_msgpack(filename + ".msgpack", std::ios::binary);
+ json j2;
+ CHECK_NOTHROW(j2 = json::from_msgpack(f_msgpack));
+
+ // compare parsed JSON values
+ CHECK(j1 == j2);
+ }
+
+ {
+ INFO_WITH_TEMP(filename + ": uint8_t* and size");
+ // parse JSON file
+ std::ifstream f_json(filename);
+ const json j1 = json::parse(f_json);
+
+ // parse MessagePack file
+ auto packed = utils::read_binary_file(filename + ".msgpack");
+ json j2;
+ CHECK_NOTHROW(j2 = json::from_msgpack({packed.data(), packed.size()}));
+
+ // compare parsed JSON values
+ CHECK(j1 == j2);
+ }
+
+ {
+ INFO_WITH_TEMP(filename + ": output to output adapters");
+ // parse JSON file
+ std::ifstream f_json(filename);
+ json const j1 = json::parse(f_json);
+
+ // parse MessagePack file
+ auto packed = utils::read_binary_file(filename + ".msgpack");
+
+ if (exclude_packed.count(filename) == 0u)
+ {
+ {
+ INFO_WITH_TEMP(filename + ": output adapters: std::vector<uint8_t>");
+ std::vector<uint8_t> vec;
+ json::to_msgpack(j1, vec);
+ CHECK(vec == packed);
+ }
+ }
+ }
+ }
+ }
+}
+
+#ifdef JSON_HAS_CPP_17
+// Test suite for verifying MessagePack handling with std::byte input
+TEST_CASE("MessagePack with std::byte")
+{
+
+ SECTION("std::byte compatibility")
+ {
+ SECTION("vector roundtrip")
+ {
+ json original =
+ {
+ {"name", "test"},
+ {"value", 42},
+ {"array", {1, 2, 3}}
+ };
+
+ std::vector<uint8_t> temp = json::to_msgpack(original);
+ // Convert the uint8_t vector to std::byte vector
+ std::vector<std::byte> msgpack_data(temp.size());
+ for (size_t i = 0; i < temp.size(); ++i)
+ {
+ msgpack_data[i] = std::byte(temp[i]);
+ }
+ // Deserialize from std::byte vector back to JSON
+ json from_bytes;
+ CHECK_NOTHROW(from_bytes = json::from_msgpack(msgpack_data));
+
+ CHECK(from_bytes == original);
+ }
+
+ SECTION("empty vector")
+ {
+ const std::vector<std::byte> empty_data;
+ CHECK_THROWS_WITH_AS([&]()
+ {
+ [[maybe_unused]] auto result = json::from_msgpack(empty_data);
+ return true;
+ }
+ (),
+ "[json.exception.parse_error.110] parse error at byte 1: syntax error while parsing MessagePack value: unexpected end of input",
+ json::parse_error&);
+ }
+
+ SECTION("comparison with workaround")
+ {
+ json original =
+ {
+ {"string", "hello"},
+ {"integer", 42},
+ {"float", 3.14},
+ {"boolean", true},
+ {"null", nullptr},
+ {"array", {1, 2, 3}},
+ {"object", {{"key", "value"}}}
+ };
+
+ std::vector<uint8_t> temp = json::to_msgpack(original);
+
+ std::vector<std::byte> msgpack_data(temp.size());
+ for (size_t i = 0; i < temp.size(); ++i)
+ {
+ msgpack_data[i] = std::byte(temp[i]);
+ }
+ // Attempt direct deserialization using std::byte input
+ const json direct_result = json::from_msgpack(msgpack_data);
+
+ // Test the workaround approach: reinterpret as unsigned char* and use iterator range
+ const auto* const char_start = reinterpret_cast<unsigned char const*>(msgpack_data.data());
+ const auto* const char_end = char_start + msgpack_data.size();
+ json workaround_result = json::from_msgpack(char_start, char_end);
+
+ // Verify that the final deserialized JSON matches the original JSON
+ CHECK(direct_result == workaround_result);
+ CHECK(direct_result == original);
+ }
+ }
+}
+#endif
diff --git a/json4cpp/tests/src/unit-no-mem-leak-on-adl-serialize.cpp b/json4cpp/tests/src/unit-no-mem-leak-on-adl-serialize.cpp
new file mode 100644
index 0000000000..469fc2c75f
--- /dev/null
+++ b/json4cpp/tests/src/unit-no-mem-leak-on-adl-serialize.cpp
@@ -0,0 +1,86 @@
+// __ _____ _____ _____
+// __| | __| | | | 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>
+#include <exception>
+#include <iostream>
+
+struct Foo
+{
+ int a;
+ int b;
+};
+
+namespace nlohmann
+{
+template <>
+struct adl_serializer<Foo>
+{
+ static void to_json(json& j, Foo const& f)
+ {
+ switch (f.b)
+ {
+ case 0:
+ j["a"] = f.a;
+ break;
+ case 1:
+ j[0] = f.a;
+ break;
+ default:
+ j = "test";
+ }
+ if (f.a == 1)
+ {
+ throw std::runtime_error("b is invalid");
+ }
+ }
+};
+} // namespace nlohmann
+
+TEST_CASE("check_for_mem_leak_on_adl_to_json-1")
+{
+ try
+ {
+ const nlohmann::json j = Foo {1, 0};
+ std::cout << j.dump() << "\n";
+ }
+ catch (...) // NOLINT(bugprone-empty-catch)
+ {
+ // just ignore the exception in this POC
+ }
+}
+
+TEST_CASE("check_for_mem_leak_on_adl_to_json-2")
+{
+ try
+ {
+ const nlohmann::json j = Foo {1, 1};
+ std::cout << j.dump() << "\n";
+ }
+ catch (...) // NOLINT(bugprone-empty-catch)
+ {
+ // just ignore the exception in this POC
+ }
+}
+
+TEST_CASE("check_for_mem_leak_on_adl_to_json-2")
+{
+ try
+ {
+ const nlohmann::json j = Foo {1, 2};
+ std::cout << j.dump() << "\n";
+ }
+ catch (...) // NOLINT(bugprone-empty-catch)
+ {
+ // just ignore the exception in this POC
+ }
+}
+
+
diff --git a/json4cpp/tests/src/unit-noexcept.cpp b/json4cpp/tests/src/unit-noexcept.cpp
new file mode 100644
index 0000000000..5e330c0a12
--- /dev/null
+++ b/json4cpp/tests/src/unit-noexcept.cpp
@@ -0,0 +1,83 @@
+// __ _____ _____ _____
+// __| | __| | | | 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"
+
+// disable -Wnoexcept due to struct pod_bis
+DOCTEST_GCC_SUPPRESS_WARNING_PUSH
+DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept")
+
+// skip tests if JSON_DisableEnumSerialization=ON (#4384)
+#if defined(JSON_DISABLE_ENUM_SERIALIZATION) && (JSON_DISABLE_ENUM_SERIALIZATION == 1)
+ #define SKIP_TESTS_FOR_ENUM_SERIALIZATION
+#endif
+
+#include <nlohmann/json.hpp>
+
+using nlohmann::json;
+
+namespace
+{
+enum test {}; // NOLINT(cppcoreguidelines-use-enum-class)
+
+struct pod {};
+struct pod_bis {};
+
+void to_json(json& /*unused*/, pod /*unused*/) noexcept;
+void to_json(json& /*unused*/, pod_bis /*unused*/);
+void from_json(const json& /*unused*/, pod /*unused*/) noexcept;
+void from_json(const json& /*unused*/, pod_bis /*unused*/);
+void to_json(json& /*unused*/, pod /*unused*/) noexcept {}
+void to_json(json& /*unused*/, pod_bis /*unused*/) {}
+void from_json(const json& /*unused*/, pod /*unused*/) noexcept {}
+void from_json(const json& /*unused*/, pod_bis /*unused*/) {}
+
+static_assert(noexcept(json{}), "");
+static_assert(noexcept(nlohmann::to_json(std::declval<json&>(), 2)), "");
+static_assert(noexcept(nlohmann::to_json(std::declval<json&>(), 2.5)), "");
+static_assert(noexcept(nlohmann::to_json(std::declval<json&>(), true)), "");
+#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
+static_assert(noexcept(nlohmann::to_json(std::declval<json&>(), test {})), "");
+#endif
+static_assert(noexcept(nlohmann::to_json(std::declval<json&>(), pod {})), "");
+static_assert(!noexcept(nlohmann::to_json(std::declval<json&>(), pod_bis{})), "");
+static_assert(noexcept(json(2)), "");
+#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
+static_assert(noexcept(json(test {})), "");
+#endif
+static_assert(noexcept(json(pod {})), "");
+static_assert(noexcept(std::declval<json>().get<pod>()), "");
+static_assert(!noexcept(std::declval<json>().get<pod_bis>()), "");
+static_assert(noexcept(json(pod{})), "");
+} // namespace
+
+TEST_CASE("noexcept")
+{
+ // silence -Wunneeded-internal-declaration errors
+ static_cast<void>(static_cast<void(*)(json&, pod)>(&to_json));
+ static_cast<void>(static_cast<void(*)(json&, pod_bis)>(&to_json));
+ static_cast<void>(static_cast<void(*)(const json&, pod)>(&from_json));
+ static_cast<void>(static_cast<void(*)(const json&, pod_bis)>(&from_json));
+
+ SECTION("nothrow-copy-constructible exceptions")
+ {
+ // for ERR60-CPP (https://github.com/nlohmann/json/issues/531):
+ // Exceptions should be nothrow-copy-constructible. However, compilers
+ // treat std::runtime_exception differently in this regard. Therefore,
+ // we can only demand nothrow-copy-constructibility for our exceptions
+ // if std::runtime_exception is.
+ CHECK(std::is_nothrow_copy_constructible<json::exception>::value == std::is_nothrow_copy_constructible<std::runtime_error>::value);
+ CHECK(std::is_nothrow_copy_constructible<json::parse_error>::value == std::is_nothrow_copy_constructible<std::runtime_error>::value);
+ CHECK(std::is_nothrow_copy_constructible<json::invalid_iterator>::value == std::is_nothrow_copy_constructible<std::runtime_error>::value);
+ CHECK(std::is_nothrow_copy_constructible<json::type_error>::value == std::is_nothrow_copy_constructible<std::runtime_error>::value);
+ CHECK(std::is_nothrow_copy_constructible<json::out_of_range>::value == std::is_nothrow_copy_constructible<std::runtime_error>::value);
+ CHECK(std::is_nothrow_copy_constructible<json::other_error>::value == std::is_nothrow_copy_constructible<std::runtime_error>::value);
+ }
+}
+
+DOCTEST_GCC_SUPPRESS_WARNING_POP
diff --git a/json4cpp/tests/src/unit-ordered_json.cpp b/json4cpp/tests/src/unit-ordered_json.cpp
new file mode 100644
index 0000000000..10bb037bc8
--- /dev/null
+++ b/json4cpp/tests/src/unit-ordered_json.cpp
@@ -0,0 +1,71 @@
+// __ _____ _____ _____
+// __| | __| | | | 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;
+
+TEST_CASE("ordered_json")
+{
+ json j;
+ ordered_json oj;
+
+ j["element3"] = 3;
+ j["element1"] = 1;
+ j["element2"] = 2;
+
+ oj["element3"] = 3;
+ oj["element1"] = 1;
+ oj["element2"] = 2;
+
+ CHECK(j.dump() == "{\"element1\":1,\"element2\":2,\"element3\":3}");
+ CHECK(oj.dump() == "{\"element3\":3,\"element1\":1,\"element2\":2}");
+
+ CHECK(j == json(oj));
+ CHECK(ordered_json(json(oj)) == ordered_json(j));
+
+ j.erase("element1");
+ oj.erase("element1");
+
+ CHECK(j.dump() == "{\"element2\":2,\"element3\":3}");
+ CHECK(oj.dump() == "{\"element3\":3,\"element2\":2}");
+
+ // remove again and nothing changes
+ j.erase("element1");
+ oj.erase("element1");
+
+ CHECK(j.dump() == "{\"element2\":2,\"element3\":3}");
+ CHECK(oj.dump() == "{\"element3\":3,\"element2\":2}");
+
+ // There are no dup keys cause constructor calls emplace...
+ json const multi {{"z", 1}, {"m", 2}, {"m", 3}, {"y", 4}, {"m", 5}};
+ CHECK(multi.size() == 3);
+ CHECK(multi.dump() == "{\"m\":2,\"y\":4,\"z\":1}");
+
+ ordered_json multi_ordered {{"z", 1}, {"m", 2}, {"m", 3}, {"y", 4}, {"m", 5}};
+ CHECK(multi_ordered.size() == 3);
+ CHECK(multi_ordered.dump() == "{\"z\":1,\"m\":2,\"y\":4}");
+ CHECK(multi_ordered.erase("m") == 1);
+ CHECK(multi_ordered.dump() == "{\"z\":1,\"y\":4}");
+
+ // Ranged insert test.
+ // It seems that values shouldn't be overwritten. Only new values are added
+ json j1 {{"c", 1}, {"b", 2}, {"a", 3}};
+ const json j2 {{"c", 77}, {"d", 42}, {"a", 4}};
+ j1.insert( j2.cbegin(), j2.cend() );
+ CHECK(j1.size() == 4);
+ CHECK(j1.dump() == "{\"a\":3,\"b\":2,\"c\":1,\"d\":42}");
+
+ ordered_json oj1 {{"c", 1}, {"b", 2}, {"a", 3}};
+ const ordered_json oj2 {{"c", 77}, {"d", 42}, {"a", 4}};
+ oj1.insert( oj2.cbegin(), oj2.cend() );
+ CHECK(oj1.size() == 4);
+ CHECK(oj1.dump() == "{\"c\":1,\"b\":2,\"a\":3,\"d\":42}");
+}
diff --git a/json4cpp/tests/src/unit-ordered_map.cpp b/json4cpp/tests/src/unit-ordered_map.cpp
new file mode 100644
index 0000000000..76ed4f34f3
--- /dev/null
+++ b/json4cpp/tests/src/unit-ordered_map.cpp
@@ -0,0 +1,310 @@
+// __ _____ _____ _____
+// __| | __| | | | 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::ordered_map;
+
+TEST_CASE("ordered_map")
+{
+ SECTION("constructor")
+ {
+ SECTION("constructor from iterator range")
+ {
+ std::map<std::string, std::string> m {{"eins", "one"}, {"zwei", "two"}, {"drei", "three"}};
+ ordered_map<std::string, std::string> const om(m.begin(), m.end());
+ CHECK(om.size() == 3);
+ }
+
+ SECTION("copy assignment")
+ {
+ std::map<std::string, std::string> m {{"eins", "one"}, {"zwei", "two"}, {"drei", "three"}};
+ ordered_map<std::string, std::string> om(m.begin(), m.end());
+ const auto com = om;
+ om.clear(); // silence a warning by forbidding having "const auto& com = om;"
+ CHECK(com.size() == 3);
+ }
+ }
+
+ SECTION("at")
+ {
+ std::map<std::string, std::string> m {{"eins", "one"}, {"zwei", "two"}, {"drei", "three"}};
+ ordered_map<std::string, std::string> om(m.begin(), m.end());
+ const auto com = om; // NOLINT(performance-unnecessary-copy-initialization)
+
+ SECTION("with Key&&")
+ {
+ CHECK(om.at(std::string("eins")) == std::string("one"));
+ CHECK(com.at(std::string("eins")) == std::string("one"));
+ CHECK_THROWS_AS(om.at(std::string("vier")), std::out_of_range);
+ CHECK_THROWS_AS(com.at(std::string("vier")), std::out_of_range);
+ }
+
+ SECTION("with const Key&&")
+ {
+ const std::string eins = "eins";
+ const std::string vier = "vier";
+ CHECK(om.at(eins) == std::string("one"));
+ CHECK(com.at(eins) == std::string("one"));
+ CHECK_THROWS_AS(om.at(vier), std::out_of_range);
+ CHECK_THROWS_AS(com.at(vier), std::out_of_range);
+ }
+
+ SECTION("with string literal")
+ {
+ CHECK(om.at("eins") == std::string("one"));
+ CHECK(com.at("eins") == std::string("one"));
+ CHECK_THROWS_AS(om.at("vier"), std::out_of_range);
+ CHECK_THROWS_AS(com.at("vier"), std::out_of_range);
+ }
+ }
+
+ SECTION("operator[]")
+ {
+ std::map<std::string, std::string> m {{"eins", "one"}, {"zwei", "two"}, {"drei", "three"}};
+ ordered_map<std::string, std::string> om(m.begin(), m.end());
+ const auto com = om; // NOLINT(performance-unnecessary-copy-initialization)
+
+ SECTION("with Key&&")
+ {
+ CHECK(om[std::string("eins")] == std::string("one"));
+ CHECK(com[std::string("eins")] == std::string("one"));
+
+ CHECK(om[std::string("vier")] == std::string(""));
+ CHECK(om.size() == 4);
+ }
+
+ SECTION("with const Key&&")
+ {
+ const std::string eins = "eins";
+ const std::string vier = "vier";
+
+ CHECK(om[eins] == std::string("one"));
+ CHECK(com[eins] == std::string("one"));
+
+ CHECK(om[vier] == std::string(""));
+ CHECK(om.size() == 4);
+ }
+
+ SECTION("with string literal")
+ {
+ CHECK(om["eins"] == std::string("one"));
+ CHECK(com["eins"] == std::string("one"));
+
+ CHECK(om["vier"] == std::string(""));
+ CHECK(om.size() == 4);
+ }
+ }
+
+ SECTION("erase")
+ {
+ ordered_map<std::string, std::string> om;
+ om["eins"] = "one";
+ om["zwei"] = "two";
+ om["drei"] = "three";
+
+ {
+ auto it = om.begin();
+ CHECK(it->first == "eins");
+ ++it;
+ CHECK(it->first == "zwei");
+ ++it;
+ CHECK(it->first == "drei");
+ ++it;
+ CHECK(it == om.end());
+ }
+
+ SECTION("with Key&&")
+ {
+ CHECK(om.size() == 3);
+ CHECK(om.erase(std::string("eins")) == 1);
+ CHECK(om.size() == 2);
+ CHECK(om.erase(std::string("vier")) == 0);
+ CHECK(om.size() == 2);
+
+ auto it = om.begin();
+ CHECK(it->first == "zwei");
+ ++it;
+ CHECK(it->first == "drei");
+ ++it;
+ CHECK(it == om.end());
+ }
+
+ SECTION("with const Key&&")
+ {
+ const std::string eins = "eins";
+ const std::string vier = "vier";
+ CHECK(om.size() == 3);
+ CHECK(om.erase(eins) == 1);
+ CHECK(om.size() == 2);
+ CHECK(om.erase(vier) == 0);
+ CHECK(om.size() == 2);
+
+ auto it = om.begin();
+ CHECK(it->first == "zwei");
+ ++it;
+ CHECK(it->first == "drei");
+ ++it;
+ CHECK(it == om.end());
+ }
+
+ SECTION("with string literal")
+ {
+ CHECK(om.size() == 3);
+ CHECK(om.erase("eins") == 1);
+ CHECK(om.size() == 2);
+ CHECK(om.erase("vier") == 0);
+ CHECK(om.size() == 2);
+
+ auto it = om.begin();
+ CHECK(it->first == "zwei");
+ ++it;
+ CHECK(it->first == "drei");
+ ++it;
+ CHECK(it == om.end());
+ }
+
+ SECTION("with iterator")
+ {
+ CHECK(om.size() == 3);
+ CHECK(om.begin()->first == "eins");
+ CHECK(std::next(om.begin(), 1)->first == "zwei");
+ CHECK(std::next(om.begin(), 2)->first == "drei");
+
+ auto it = om.erase(om.begin());
+ CHECK(it->first == "zwei");
+ CHECK(om.size() == 2);
+
+ auto it2 = om.begin();
+ CHECK(it2->first == "zwei");
+ ++it2;
+ CHECK(it2->first == "drei");
+ ++it2;
+ CHECK(it2 == om.end());
+ }
+
+ SECTION("with iterator pair")
+ {
+ SECTION("range in the middle")
+ {
+ // need more elements
+ om["vier"] = "four";
+ om["fünf"] = "five";
+
+ // delete "zwei" and "drei"
+ auto it = om.erase(om.begin() + 1, om.begin() + 3);
+ CHECK(it->first == "vier");
+ CHECK(om.size() == 3);
+ }
+
+ SECTION("range at the beginning")
+ {
+ // need more elements
+ om["vier"] = "four";
+ om["fünf"] = "five";
+
+ // delete "eins" and "zwei"
+ auto it = om.erase(om.begin(), om.begin() + 2);
+ CHECK(it->first == "drei");
+ CHECK(om.size() == 3);
+ }
+
+ SECTION("range at the end")
+ {
+ // need more elements
+ om["vier"] = "four";
+ om["fünf"] = "five";
+
+ // delete "vier" and "fünf"
+ auto it = om.erase(om.begin() + 3, om.end());
+ CHECK(it == om.end());
+ CHECK(om.size() == 3);
+ }
+ }
+ }
+
+ SECTION("count")
+ {
+ ordered_map<std::string, std::string> om;
+ om["eins"] = "one";
+ om["zwei"] = "two";
+ om["drei"] = "three";
+
+ const std::string eins("eins");
+ const std::string vier("vier");
+ CHECK(om.count("eins") == 1);
+ CHECK(om.count(std::string("eins")) == 1);
+ CHECK(om.count(eins) == 1);
+ CHECK(om.count("vier") == 0);
+ CHECK(om.count(std::string("vier")) == 0);
+ CHECK(om.count(vier) == 0);
+ }
+
+ SECTION("find")
+ {
+ ordered_map<std::string, std::string> om;
+ om["eins"] = "one";
+ om["zwei"] = "two";
+ om["drei"] = "three";
+ const auto com = om;
+
+ const std::string eins("eins");
+ const std::string vier("vier");
+ CHECK(om.find("eins") == om.begin());
+ CHECK(om.find(std::string("eins")) == om.begin());
+ CHECK(om.find(eins) == om.begin());
+ CHECK(om.find("vier") == om.end());
+ CHECK(om.find(std::string("vier")) == om.end());
+ CHECK(om.find(vier) == om.end());
+
+ CHECK(com.find("eins") == com.begin());
+ CHECK(com.find(std::string("eins")) == com.begin());
+ CHECK(com.find(eins) == com.begin());
+ CHECK(com.find("vier") == com.end());
+ CHECK(com.find(std::string("vier")) == com.end());
+ CHECK(com.find(vier) == com.end());
+ }
+
+ SECTION("insert")
+ {
+ ordered_map<std::string, std::string> om;
+ om["eins"] = "one";
+ om["zwei"] = "two";
+ om["drei"] = "three";
+
+ SECTION("const value_type&")
+ {
+ ordered_map<std::string, std::string>::value_type const vt1 {"eins", "1"};
+ ordered_map<std::string, std::string>::value_type const vt4 {"vier", "four"};
+
+ auto res1 = om.insert(vt1);
+ CHECK(res1.first == om.begin());
+ CHECK(res1.second == false);
+ CHECK(om.size() == 3);
+
+ auto res4 = om.insert(vt4);
+ CHECK(res4.first == om.begin() + 3);
+ CHECK(res4.second == true);
+ CHECK(om.size() == 4);
+ }
+
+ SECTION("value_type&&")
+ {
+ auto res1 = om.insert({"eins", "1"});
+ CHECK(res1.first == om.begin());
+ CHECK(res1.second == false);
+ CHECK(om.size() == 3);
+
+ auto res4 = om.insert({"vier", "four"});
+ CHECK(res4.first == om.begin() + 3);
+ CHECK(res4.second == true);
+ CHECK(om.size() == 4);
+ }
+ }
+}
diff --git a/json4cpp/tests/src/unit-pointer_access.cpp b/json4cpp/tests/src/unit-pointer_access.cpp
new file mode 100644
index 0000000000..c1e3419171
--- /dev/null
+++ b/json4cpp/tests/src/unit-pointer_access.cpp
@@ -0,0 +1,479 @@
+// __ _____ _____ _____
+// __| | __| | | | 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;
+
+TEST_CASE("pointer access")
+{
+ SECTION("pointer access to object_t")
+ {
+ using test_type = json::object_t;
+ json value = {{"one", 1}, {"two", 2}};
+
+ // check if pointers are returned correctly
+ const test_type* p1 = value.get_ptr<test_type*>();
+ CHECK(p1 == value.get_ptr<test_type*>());
+ CHECK(*p1 == value.get<test_type>());
+
+ const test_type* p2 = value.get_ptr<const test_type*>();
+ CHECK(p2 == value.get_ptr<const test_type*>());
+ CHECK(*p2 == value.get<test_type>());
+
+ const test_type* const p3 = value.get_ptr<const test_type* const>();
+ CHECK(p3 == value.get_ptr<const test_type* const>());
+ CHECK(*p3 == value.get<test_type>());
+
+ // check if null pointers are returned correctly
+ CHECK(value.get_ptr<json::object_t*>() != nullptr);
+ CHECK(value.get_ptr<json::array_t*>() == nullptr);
+ CHECK(value.get_ptr<json::string_t*>() == nullptr);
+ CHECK(value.get_ptr<json::boolean_t*>() == nullptr);
+ CHECK(value.get_ptr<json::number_integer_t*>() == nullptr);
+ CHECK(value.get_ptr<json::number_unsigned_t*>() == nullptr);
+ CHECK(value.get_ptr<json::number_float_t*>() == nullptr);
+ CHECK(value.get_ptr<json::binary_t*>() == nullptr);
+ }
+
+ SECTION("pointer access to const object_t")
+ {
+ using test_type = const json::object_t;
+ const json value = {{"one", 1}, {"two", 2}};
+
+ // check if pointers are returned correctly
+ test_type* p1 = value.get_ptr<test_type*>();
+ CHECK(p1 == value.get_ptr<test_type*>());
+ CHECK(*p1 == value.get<test_type>());
+
+ const test_type* p2 = value.get_ptr<const test_type*>();
+ CHECK(p2 == value.get_ptr<const test_type*>());
+ CHECK(*p2 == value.get<test_type>());
+
+ const test_type* const p3 = value.get_ptr<const test_type* const>();
+ CHECK(p3 == value.get_ptr<const test_type* const>());
+ CHECK(*p3 == value.get<test_type>());
+
+ // check if null pointers are returned correctly
+ CHECK(value.get_ptr<const json::object_t*>() != nullptr);
+ CHECK(value.get_ptr<const json::array_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::string_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::boolean_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::number_integer_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::number_unsigned_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::number_float_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::binary_t*>() == nullptr);
+ }
+
+ SECTION("pointer access to array_t")
+ {
+ using test_type = json::array_t;
+ json value = {1, 2, 3, 4};
+
+ // check if pointers are returned correctly
+ const test_type* p1 = value.get_ptr<test_type*>();
+ CHECK(p1 == value.get_ptr<test_type*>());
+ CHECK(*p1 == value.get<test_type>());
+
+ const test_type* p2 = value.get_ptr<const test_type*>();
+ CHECK(p2 == value.get_ptr<const test_type*>());
+ CHECK(*p2 == value.get<test_type>());
+
+ const test_type* const p3 = value.get_ptr<const test_type* const>();
+ CHECK(p3 == value.get_ptr<const test_type* const>());
+ CHECK(*p3 == value.get<test_type>());
+
+ // check if null pointers are returned correctly
+ CHECK(value.get_ptr<json::object_t*>() == nullptr);
+ CHECK(value.get_ptr<json::array_t*>() != nullptr);
+ CHECK(value.get_ptr<json::string_t*>() == nullptr);
+ CHECK(value.get_ptr<json::boolean_t*>() == nullptr);
+ CHECK(value.get_ptr<json::number_integer_t*>() == nullptr);
+ CHECK(value.get_ptr<json::number_unsigned_t*>() == nullptr);
+ CHECK(value.get_ptr<json::number_float_t*>() == nullptr);
+ CHECK(value.get_ptr<json::binary_t*>() == nullptr);
+ }
+
+ SECTION("pointer access to const array_t")
+ {
+ using test_type = const json::array_t;
+ const json value = {1, 2, 3, 4};
+
+ // check if pointers are returned correctly
+ test_type* p1 = value.get_ptr<test_type*>();
+ CHECK(p1 == value.get_ptr<test_type*>());
+ CHECK(*p1 == value.get<test_type>());
+
+ const test_type* p2 = value.get_ptr<const test_type*>();
+ CHECK(p2 == value.get_ptr<const test_type*>());
+ CHECK(*p2 == value.get<test_type>());
+
+ const test_type* const p3 = value.get_ptr<const test_type* const>();
+ CHECK(p3 == value.get_ptr<const test_type* const>());
+ CHECK(*p3 == value.get<test_type>());
+
+ // check if null pointers are returned correctly
+ CHECK(value.get_ptr<const json::object_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::array_t*>() != nullptr);
+ CHECK(value.get_ptr<const json::string_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::boolean_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::number_integer_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::number_unsigned_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::number_float_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::binary_t*>() == nullptr);
+ }
+
+ SECTION("pointer access to string_t")
+ {
+ using test_type = json::string_t;
+ json value = "hello";
+
+ // check if pointers are returned correctly
+ const test_type* p1 = value.get_ptr<test_type*>();
+ CHECK(p1 == value.get_ptr<test_type*>());
+ CHECK(*p1 == value.get<test_type>());
+
+ const test_type* p2 = value.get_ptr<const test_type*>();
+ CHECK(p2 == value.get_ptr<const test_type*>());
+ CHECK(*p2 == value.get<test_type>());
+
+ const test_type* const p3 = value.get_ptr<const test_type* const>();
+ CHECK(p3 == value.get_ptr<const test_type* const>());
+ CHECK(*p3 == value.get<test_type>());
+
+ // check if null pointers are returned correctly
+ CHECK(value.get_ptr<json::object_t*>() == nullptr);
+ CHECK(value.get_ptr<json::array_t*>() == nullptr);
+ CHECK(value.get_ptr<json::string_t*>() != nullptr);
+ CHECK(value.get_ptr<json::boolean_t*>() == nullptr);
+ CHECK(value.get_ptr<json::number_integer_t*>() == nullptr);
+ CHECK(value.get_ptr<json::number_unsigned_t*>() == nullptr);
+ CHECK(value.get_ptr<json::number_float_t*>() == nullptr);
+ CHECK(value.get_ptr<json::binary_t*>() == nullptr);
+ }
+
+ SECTION("pointer access to const string_t")
+ {
+ using test_type = const json::string_t;
+ const json value = "hello";
+
+ // check if pointers are returned correctly
+ test_type* p1 = value.get_ptr<test_type*>();
+ CHECK(p1 == value.get_ptr<test_type*>());
+ CHECK(*p1 == value.get<test_type>());
+
+ const test_type* p2 = value.get_ptr<const test_type*>();
+ CHECK(p2 == value.get_ptr<const test_type*>());
+ CHECK(*p2 == value.get<test_type>());
+
+ const test_type* const p3 = value.get_ptr<const test_type* const>();
+ CHECK(p3 == value.get_ptr<const test_type* const>());
+ CHECK(*p3 == value.get<test_type>());
+
+ // check if null pointers are returned correctly
+ CHECK(value.get_ptr<const json::object_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::array_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::string_t*>() != nullptr);
+ CHECK(value.get_ptr<const json::boolean_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::number_integer_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::number_unsigned_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::number_float_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::binary_t*>() == nullptr);
+ }
+
+ SECTION("pointer access to boolean_t")
+ {
+ using test_type = json::boolean_t;
+ json value = false;
+
+ // check if pointers are returned correctly
+ const test_type* p1 = value.get_ptr<test_type*>();
+ CHECK(p1 == value.get_ptr<test_type*>());
+ CHECK(*p1 == value.get<test_type>());
+
+ const test_type* p2 = value.get_ptr<const test_type*>();
+ CHECK(p2 == value.get_ptr<const test_type*>());
+ CHECK(*p2 == value.get<test_type>());
+
+ const test_type* const p3 = value.get_ptr<const test_type* const>();
+ CHECK(p3 == value.get_ptr<const test_type* const>());
+ CHECK(*p3 == value.get<test_type>());
+
+ // check if null pointers are returned correctly
+ CHECK(value.get_ptr<json::object_t*>() == nullptr);
+ CHECK(value.get_ptr<json::array_t*>() == nullptr);
+ CHECK(value.get_ptr<json::string_t*>() == nullptr);
+ CHECK(value.get_ptr<json::boolean_t*>() != nullptr);
+ CHECK(value.get_ptr<json::number_integer_t*>() == nullptr);
+ CHECK(value.get_ptr<json::number_unsigned_t*>() == nullptr);
+ CHECK(value.get_ptr<json::number_float_t*>() == nullptr);
+ CHECK(value.get_ptr<json::binary_t*>() == nullptr);
+ }
+
+ SECTION("pointer access to const boolean_t")
+ {
+ using test_type = const json::boolean_t;
+ const json value = false;
+
+ // check if pointers are returned correctly
+ test_type* p1 = value.get_ptr<test_type*>();
+ CHECK(p1 == value.get_ptr<test_type*>());
+ //CHECK(*p1 == value.get<test_type>());
+
+ const test_type* p2 = value.get_ptr<const test_type*>();
+ CHECK(p2 == value.get_ptr<const test_type*>());
+ CHECK(*p2 == value.get<test_type>());
+
+ const test_type* const p3 = value.get_ptr<const test_type* const>();
+ CHECK(p3 == value.get_ptr<const test_type* const>());
+ CHECK(*p3 == value.get<test_type>());
+
+ // check if null pointers are returned correctly
+ CHECK(value.get_ptr<const json::object_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::array_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::string_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::boolean_t*>() != nullptr);
+ CHECK(value.get_ptr<const json::number_integer_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::number_unsigned_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::number_float_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::binary_t*>() == nullptr);
+ }
+
+ SECTION("pointer access to number_integer_t")
+ {
+ using test_type = json::number_integer_t;
+ json value = 23;
+
+ // check if pointers are returned correctly
+ const test_type* p1 = value.get_ptr<test_type*>();
+ CHECK(p1 == value.get_ptr<test_type*>());
+ CHECK(*p1 == value.get<test_type>());
+
+ const test_type* p2 = value.get_ptr<const test_type*>();
+ CHECK(p2 == value.get_ptr<const test_type*>());
+ CHECK(*p2 == value.get<test_type>());
+
+ const test_type* const p3 = value.get_ptr<const test_type* const>();
+ CHECK(p3 == value.get_ptr<const test_type* const>());
+ CHECK(*p3 == value.get<test_type>());
+
+ // check if null pointers are returned correctly
+ CHECK(value.get_ptr<json::object_t*>() == nullptr);
+ CHECK(value.get_ptr<json::array_t*>() == nullptr);
+ CHECK(value.get_ptr<json::string_t*>() == nullptr);
+ CHECK(value.get_ptr<json::boolean_t*>() == nullptr);
+ CHECK(value.get_ptr<json::number_integer_t*>() != nullptr);
+ CHECK(value.get_ptr<json::number_unsigned_t*>() == nullptr);
+ CHECK(value.get_ptr<json::number_float_t*>() == nullptr);
+ CHECK(value.get_ptr<json::binary_t*>() == nullptr);
+ }
+
+ SECTION("pointer access to const number_integer_t")
+ {
+ using test_type = const json::number_integer_t;
+ const json value = 23;
+
+ // check if pointers are returned correctly
+ test_type* p1 = value.get_ptr<test_type*>();
+ CHECK(p1 == value.get_ptr<test_type*>());
+ CHECK(*p1 == value.get<test_type>());
+
+ const test_type* p2 = value.get_ptr<const test_type*>();
+ CHECK(p2 == value.get_ptr<const test_type*>());
+ CHECK(*p2 == value.get<test_type>());
+
+ const test_type* const p3 = value.get_ptr<const test_type* const>();
+ CHECK(p3 == value.get_ptr<const test_type* const>());
+ CHECK(*p3 == value.get<test_type>());
+
+ // check if null pointers are returned correctly
+ CHECK(value.get_ptr<const json::object_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::array_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::string_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::boolean_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::number_integer_t*>() != nullptr);
+ CHECK(value.get_ptr<const json::number_unsigned_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::number_float_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::binary_t*>() == nullptr);
+ }
+
+ SECTION("pointer access to number_unsigned_t")
+ {
+ using test_type = json::number_unsigned_t;
+ json value = 23u;
+
+ // check if pointers are returned correctly
+ const test_type* p1 = value.get_ptr<test_type*>();
+ CHECK(p1 == value.get_ptr<test_type*>());
+ CHECK(*p1 == value.get<test_type>());
+
+ const test_type* p2 = value.get_ptr<const test_type*>();
+ CHECK(p2 == value.get_ptr<const test_type*>());
+ CHECK(*p2 == value.get<test_type>());
+
+ const test_type* const p3 = value.get_ptr<const test_type* const>();
+ CHECK(p3 == value.get_ptr<const test_type* const>());
+ CHECK(*p3 == value.get<test_type>());
+
+ // check if null pointers are returned correctly
+ CHECK(value.get_ptr<json::object_t*>() == nullptr);
+ CHECK(value.get_ptr<json::array_t*>() == nullptr);
+ CHECK(value.get_ptr<json::string_t*>() == nullptr);
+ CHECK(value.get_ptr<json::boolean_t*>() == nullptr);
+ CHECK(value.get_ptr<json::number_integer_t*>() == nullptr);
+ CHECK(value.get_ptr<json::number_unsigned_t*>() != nullptr);
+ CHECK(value.get_ptr<json::number_float_t*>() == nullptr);
+ CHECK(value.get_ptr<json::binary_t*>() == nullptr);
+ }
+
+ SECTION("pointer access to const number_unsigned_t")
+ {
+ using test_type = const json::number_unsigned_t;
+ const json value = 23u;
+
+ // check if pointers are returned correctly
+ test_type* p1 = value.get_ptr<test_type*>();
+ CHECK(p1 == value.get_ptr<test_type*>());
+ CHECK(*p1 == value.get<test_type>());
+
+ const test_type* p2 = value.get_ptr<const test_type*>();
+ CHECK(p2 == value.get_ptr<const test_type*>());
+ CHECK(*p2 == value.get<test_type>());
+
+ const test_type* const p3 = value.get_ptr<const test_type* const>();
+ CHECK(p3 == value.get_ptr<const test_type* const>());
+ CHECK(*p3 == value.get<test_type>());
+
+ // check if null pointers are returned correctly
+ CHECK(value.get_ptr<const json::object_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::array_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::string_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::boolean_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::number_integer_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::number_unsigned_t*>() != nullptr);
+ CHECK(value.get_ptr<const json::number_float_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::binary_t*>() == nullptr);
+ }
+
+ SECTION("pointer access to number_float_t")
+ {
+ using test_type = json::number_float_t;
+ json value = 42.23;
+
+ // check if pointers are returned correctly
+ const test_type* p1 = value.get_ptr<test_type*>();
+ CHECK(p1 == value.get_ptr<test_type*>());
+ CHECK(*p1 == Approx(value.get<test_type>()));
+
+ const test_type* p2 = value.get_ptr<const test_type*>();
+ CHECK(p2 == value.get_ptr<const test_type*>());
+ CHECK(*p2 == Approx(value.get<test_type>()));
+
+ const test_type* const p3 = value.get_ptr<const test_type* const>();
+ CHECK(p3 == value.get_ptr<const test_type* const>());
+ CHECK(*p3 == Approx(value.get<test_type>()));
+
+ // check if null pointers are returned correctly
+ CHECK(value.get_ptr<json::object_t*>() == nullptr);
+ CHECK(value.get_ptr<json::array_t*>() == nullptr);
+ CHECK(value.get_ptr<json::string_t*>() == nullptr);
+ CHECK(value.get_ptr<json::boolean_t*>() == nullptr);
+ CHECK(value.get_ptr<json::number_integer_t*>() == nullptr);
+ CHECK(value.get_ptr<json::number_unsigned_t*>() == nullptr);
+ CHECK(value.get_ptr<json::number_float_t*>() != nullptr);
+ CHECK(value.get_ptr<json::binary_t*>() == nullptr);
+ }
+
+ SECTION("pointer access to const number_float_t")
+ {
+ using test_type = const json::number_float_t;
+ const json value = 42.23;
+
+ // check if pointers are returned correctly
+ test_type* p1 = value.get_ptr<test_type*>();
+ CHECK(p1 == value.get_ptr<test_type*>());
+ CHECK(*p1 == Approx(value.get<test_type>()));
+
+ const test_type* p2 = value.get_ptr<const test_type*>();
+ CHECK(p2 == value.get_ptr<const test_type*>());
+ CHECK(*p2 == Approx(value.get<test_type>()));
+
+ const test_type* const p3 = value.get_ptr<const test_type* const>();
+ CHECK(p3 == value.get_ptr<const test_type* const>());
+ CHECK(*p3 == Approx(value.get<test_type>()));
+
+ // check if null pointers are returned correctly
+ CHECK(value.get_ptr<const json::object_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::array_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::string_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::boolean_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::number_integer_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::number_unsigned_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::number_float_t*>() != nullptr);
+ CHECK(value.get_ptr<const json::binary_t*>() == nullptr);
+ }
+
+ SECTION("pointer access to const binary_t")
+ {
+ using test_type = const json::binary_t;
+ const json value = json::binary({1, 2, 3});
+
+ // check if pointers are returned correctly
+ test_type* p1 = value.get_ptr<test_type*>();
+ CHECK(p1 == value.get_ptr<test_type*>());
+ CHECK(*p1 == value.get<test_type>());
+
+ const test_type* p2 = value.get_ptr<const test_type*>();
+ CHECK(p2 == value.get_ptr<const test_type*>());
+ CHECK(*p2 == value.get<test_type>());
+
+ const test_type* const p3 = value.get_ptr<const test_type* const>();
+ CHECK(p3 == value.get_ptr<const test_type* const>());
+ CHECK(*p3 == value.get<test_type>());
+
+ // check if null pointers are returned correctly
+ CHECK(value.get_ptr<const json::object_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::array_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::string_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::boolean_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::number_integer_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::number_unsigned_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::number_float_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::binary_t*>() != nullptr);
+ }
+
+ SECTION("pointer access to const binary_t")
+ {
+ using test_type = const json::binary_t;
+ const json value = json::binary({});
+
+ // check if pointers are returned correctly
+ test_type* p1 = value.get_ptr<test_type*>();
+ CHECK(p1 == value.get_ptr<test_type*>());
+ CHECK(*p1 == value.get<test_type>());
+
+ const test_type* p2 = value.get_ptr<const test_type*>();
+ CHECK(p2 == value.get_ptr<const test_type*>());
+ CHECK(*p2 == value.get<test_type>());
+
+ const test_type* const p3 = value.get_ptr<const test_type* const>();
+ CHECK(p3 == value.get_ptr<const test_type* const>());
+ CHECK(*p3 == value.get<test_type>());
+
+ // check if null pointers are returned correctly
+ CHECK(value.get_ptr<const json::object_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::array_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::string_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::boolean_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::number_integer_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::number_unsigned_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::number_float_t*>() == nullptr);
+ CHECK(value.get_ptr<const json::binary_t*>() != nullptr);
+ }
+}
diff --git a/json4cpp/tests/src/unit-readme.cpp b/json4cpp/tests/src/unit-readme.cpp
new file mode 100644
index 0000000000..1d1dc72281
--- /dev/null
+++ b/json4cpp/tests/src/unit-readme.cpp
@@ -0,0 +1,304 @@
+// __ _____ _____ _____
+// __| | __| | | | 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 <deque>
+#include <forward_list>
+#include <list>
+#include <set>
+#include <unordered_map>
+#include <unordered_set>
+#include <iostream>
+#include <sstream>
+#include <iomanip>
+
+// local variable is initialized but not referenced
+DOCTEST_MSVC_SUPPRESS_WARNING_PUSH
+DOCTEST_MSVC_SUPPRESS_WARNING(4189)
+
+TEST_CASE("README" * doctest::skip())
+{
+ {
+ // redirect std::cout for the README file
+ auto* old_cout_buffer = std::cout.rdbuf();
+ std::ostringstream const new_stream;
+ std::cout.rdbuf(new_stream.rdbuf());
+ {
+ // create an empty structure (null)
+ json j;
+
+ // add a number that is stored as double (note the implicit conversion of j to an object)
+ j["pi"] = 3.141;
+
+ // add a Boolean that is stored as bool
+ j["happy"] = true;
+
+ // add a string that is stored as std::string
+ j["name"] = "Niels";
+
+ // add another null object by passing nullptr
+ j["nothing"] = nullptr;
+
+ // add an object inside the object
+ j["answer"]["everything"] = 42;
+
+ // add an array that is stored as std::vector (using an initializer list)
+ j["list"] = { 1, 0, 2 };
+
+ // add another object (using an initializer list of pairs)
+ j["object"] = { {"currency", "USD"}, {"value", 42.99} };
+
+ // instead, you could also write (which looks very similar to the JSON above)
+ json const j2 =
+ {
+ {"pi", 3.141},
+ {"happy", true},
+ {"name", "Niels"},
+ {"nothing", nullptr},
+ {
+ "answer", {
+ {"everything", 42}
+ }
+ },
+ {"list", {1, 0, 2}},
+ {
+ "object", {
+ {"currency", "USD"},
+ {"value", 42.99}
+ }
+ }
+ };
+ }
+
+ {
+ // ways to express the empty array []
+ json const empty_array_implicit = {{}};
+ CHECK(empty_array_implicit.is_array());
+ json const empty_array_explicit = json::array();
+ CHECK(empty_array_explicit.is_array());
+
+ // a way to express the empty object {}
+ json const empty_object_explicit = json::object();
+ CHECK(empty_object_explicit.is_object());
+
+ // a way to express an _array_ of key/value pairs [["currency", "USD"], ["value", 42.99]]
+ json array_not_object = json::array({ {"currency", "USD"}, {"value", 42.99} });
+ CHECK(array_not_object.is_array());
+ CHECK(array_not_object.size() == 2);
+ CHECK(array_not_object[0].is_array());
+ CHECK(array_not_object[1].is_array());
+ }
+
+ {
+ // create object from string literal
+ json const j = "{ \"happy\": true, \"pi\": 3.141 }"_json; // NOLINT(modernize-raw-string-literal)
+
+ // or even nicer with a raw string literal
+ auto j2 = R"({
+ "happy": true,
+ "pi": 3.141
+ })"_json;
+
+ // or explicitly
+ auto j3 = json::parse(R"({"happy": true, "pi": 3.141})");
+
+ // explicit conversion to string
+ std::string const s = j.dump(); // NOLINT(bugprone-unused-local-non-trivial-variable) // {\"happy\":true,\"pi\":3.141}
+
+ // serialization with pretty printing
+ // pass in the amount of spaces to indent
+ std::cout << j.dump(4) << std::endl; // NOLINT(performance-avoid-endl)
+ // {
+ // "happy": true,
+ // "pi": 3.141
+ // }
+
+ std::cout << std::setw(2) << j << std::endl; // NOLINT(performance-avoid-endl)
+ }
+
+ {
+ // create an array using push_back
+ json j;
+ j.push_back("foo");
+ j.push_back(1);
+ j.push_back(true);
+
+ // comparison
+ const bool x = (j == R"(["foo", 1, true])"_json); // true
+ CHECK(x == true);
+
+ // iterate the array
+ for (json::iterator it = j.begin(); it != j.end(); ++it) // NOLINT(modernize-loop-convert)
+ {
+ std::cout << *it << '\n';
+ }
+
+ // range-based for
+ for (auto& element : j)
+ {
+ std::cout << element << '\n';
+ }
+
+ // getter/setter
+ const auto tmp = j[0].get<std::string>(); // NOLINT(bugprone-unused-local-non-trivial-variable)
+ j[1] = 42;
+ const bool foo{j.at(2)};
+ CHECK(foo == true);
+
+ // other stuff
+ CHECK(j.size() == 3); // 3 entries
+ CHECK_FALSE(j.empty()); // false
+ CHECK(j.type() == json::value_t::array); // json::value_t::array
+ j.clear(); // the array is empty again
+
+ // create an object
+ json o;
+ o["foo"] = 23;
+ o["bar"] = false;
+ o["baz"] = 3.141;
+
+ // find an entry
+ CHECK(o.find("foo") != o.end());
+ if (o.find("foo") != o.end()) // NOLINT(readability-container-contains)
+ {
+ // there is an entry with key "foo"
+ }
+ }
+
+ {
+ std::vector<int> const c_vector {1, 2, 3, 4};
+ json const j_vec(c_vector);
+ // [1, 2, 3, 4]
+
+ std::deque<float> const c_deque {1.2f, 2.3f, 3.4f, 5.6f};
+ json const j_deque(c_deque);
+ // [1.2, 2.3, 3.4, 5.6]
+
+ std::list<bool> const c_list {true, true, false, true};
+ json const j_list(c_list);
+ // [true, true, false, true]
+
+ std::forward_list<int64_t> const c_flist {12345678909876, 23456789098765, 34567890987654, 45678909876543};
+ json const j_flist(c_flist);
+ // [12345678909876, 23456789098765, 34567890987654, 45678909876543]
+
+ std::array<unsigned long, 4> const c_array {{1, 2, 3, 4}};
+ json const j_array(c_array);
+ // [1, 2, 3, 4]
+
+ std::set<std::string> const c_set {"one", "two", "three", "four", "one"};
+ json const j_set(c_set); // only one entry for "one" is used
+ // ["four", "one", "three", "two"]
+
+ std::unordered_set<std::string> const c_uset {"one", "two", "three", "four", "one"};
+ json const j_uset(c_uset); // only one entry for "one" is used
+ // maybe ["two", "three", "four", "one"]
+
+ std::multiset<std::string> const c_mset {"one", "two", "one", "four"};
+ json const j_mset(c_mset); // both entries for "one" are used
+ // maybe ["one", "two", "one", "four"]
+
+ std::unordered_multiset<std::string> const c_umset {"one", "two", "one", "four"};
+ json const j_umset(c_umset); // both entries for "one" are used
+ // maybe ["one", "two", "one", "four"]
+ }
+
+ {
+ std::map<std::string, int> const c_map { {"one", 1}, {"two", 2}, {"three", 3} };
+ json const j_map(c_map);
+ // {"one": 1, "two": 2, "three": 3}
+
+ std::unordered_map<const char*, float> const c_umap { {"one", 1.2f}, {"two", 2.3f}, {"three", 3.4f} };
+ json const j_umap(c_umap);
+ // {"one": 1.2, "two": 2.3, "three": 3.4}
+
+ std::multimap<std::string, bool> const c_mmap { {"one", true}, {"two", true}, {"three", false}, {"three", true} };
+ json const j_mmap(c_mmap); // only one entry for key "three" is used
+ // maybe {"one": true, "two": true, "three": true}
+
+ std::unordered_multimap<std::string, bool> const c_ummap { {"one", true}, {"two", true}, {"three", false}, {"three", true} };
+ json const j_ummap(c_ummap); // only one entry for key "three" is used
+ // maybe {"one": true, "two": true, "three": true}
+ }
+
+ {
+ // strings
+ std::string const s1 = "Hello, world!";
+ json const js = s1;
+ auto s2 = js.get<std::string>(); // NOLINT(bugprone-unused-local-non-trivial-variable)
+
+ // Booleans
+ bool const b1 = true;
+ json const jb = b1;
+ const bool b2{jb};
+ CHECK(b2 == true);
+
+ // numbers
+ int const i = 42;
+ json const jn = i;
+ const double f{jn};
+ CHECK(f == 42);
+
+ // etc.
+
+ std::string const vs = js.get<std::string>(); // NOLINT(bugprone-unused-local-non-trivial-variable)
+ const bool vb = jb.get<bool>();
+ CHECK(vb == true);
+ const int vi = jn.get<int>();
+ CHECK(vi == 42);
+
+ // etc.
+ }
+
+ {
+ // a JSON value
+ json j_original = R"({
+ "baz": ["one", "two", "three"],
+ "foo": "bar"
+ })"_json;
+
+ // access members with a JSON pointer (RFC 6901)
+ j_original["/baz/1"_json_pointer];
+ // "two"
+
+ // a JSON patch (RFC 6902)
+ json const j_patch = R"([
+ { "op": "replace", "path": "/baz", "value": "boo" },
+ { "op": "add", "path": "/hello", "value": ["world"] },
+ { "op": "remove", "path": "/foo"}
+ ])"_json;
+
+ // apply the patch
+ json const j_result = j_original.patch(j_patch);
+ // {
+ // "baz": "boo",
+ // "hello": ["world"]
+ // }
+
+ // calculate a JSON patch from two JSON values
+ auto res = json::diff(j_result, j_original);
+ // [
+ // { "op":" replace", "path": "/baz", "value": ["one", "two", "three"] },
+ // { "op":"remove","path":"/hello" },
+ // { "op":"add","path":"/foo","value":"bar" }
+ // ]
+ }
+
+ // restore old std::cout
+ std::cout.rdbuf(old_cout_buffer);
+ }
+}
+
+DOCTEST_MSVC_SUPPRESS_WARNING_POP
diff --git a/json4cpp/tests/src/unit-reference_access.cpp b/json4cpp/tests/src/unit-reference_access.cpp
new file mode 100644
index 0000000000..95fd0cbacf
--- /dev/null
+++ b/json4cpp/tests/src/unit-reference_access.cpp
@@ -0,0 +1,247 @@
+// __ _____ _____ _____
+// __| | __| | | | 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;
+
+TEST_CASE("reference access")
+{
+ // create a JSON value with different types
+ const json json_types =
+ {
+ {"boolean", true},
+ {
+ "number", {
+ {"integer", 42},
+ {"floating-point", 17.23}
+ }
+ },
+ {"string", "Hello, world!"},
+ {"array", {1, 2, 3, 4, 5}},
+ {"null", nullptr}
+ };
+
+ SECTION("reference access to object_t")
+ {
+ using test_type = json::object_t;
+ json value = {{"one", 1}, {"two", 2}};
+
+ // check if references are returned correctly
+ auto& p1 = value.get_ref<test_type&>();
+ CHECK(&p1 == value.get_ptr<test_type*>());
+ CHECK(p1 == value.get<test_type>());
+
+ const auto& p2 = value.get_ref<const test_type&>();
+ CHECK(&p2 == value.get_ptr<const test_type*>());
+ CHECK(p2 == value.get<test_type>());
+
+ // check if mismatching references throw correctly
+ CHECK_NOTHROW(value.get_ref<json::object_t&>());
+ CHECK_THROWS_WITH_AS(value.get_ref<json::array_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is object", json::type_error&);
+ CHECK_THROWS_WITH_AS(value.get_ref<json::string_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is object", json::type_error&);
+ CHECK_THROWS_WITH_AS(value.get_ref<json::boolean_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is object", json::type_error&);
+ CHECK_THROWS_WITH_AS(value.get_ref<json::number_integer_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is object", json::type_error&);
+ CHECK_THROWS_WITH_AS(value.get_ref<json::number_unsigned_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is object", json::type_error&);
+ CHECK_THROWS_WITH_AS(value.get_ref<json::number_float_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is object", json::type_error&);
+ }
+
+ SECTION("const reference access to const object_t")
+ {
+ using test_type = json::object_t;
+ const json value = {{"one", 1}, {"two", 2}};
+
+ // this should not compile
+ // test_type& p1 = value.get_ref<test_type&>();
+
+ // check if references are returned correctly
+ const auto& p2 = value.get_ref<const test_type&>();
+ CHECK(&p2 == value.get_ptr<const test_type*>());
+ CHECK(p2 == value.get<test_type>());
+ }
+
+ SECTION("reference access to array_t")
+ {
+ using test_type = json::array_t;
+ json value = {1, 2, 3, 4};
+
+ // check if references are returned correctly
+ auto& p1 = value.get_ref<test_type&>();
+ CHECK(&p1 == value.get_ptr<test_type*>());
+ CHECK(p1 == value.get<test_type>());
+
+ const auto& p2 = value.get_ref<const test_type&>();
+ CHECK(&p2 == value.get_ptr<const test_type*>());
+ CHECK(p2 == value.get<test_type>());
+
+ // check if mismatching references throw correctly
+ CHECK_THROWS_WITH_AS(value.get_ref<json::object_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is array", json::type_error&);
+ CHECK_NOTHROW(value.get_ref<json::array_t&>());
+ CHECK_THROWS_WITH_AS(value.get_ref<json::string_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is array", json::type_error&);
+ CHECK_THROWS_WITH_AS(value.get_ref<json::boolean_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is array", json::type_error&);
+ CHECK_THROWS_WITH_AS(value.get_ref<json::number_integer_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is array", json::type_error&);
+ CHECK_THROWS_WITH_AS(value.get_ref<json::number_unsigned_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is array", json::type_error&);
+ CHECK_THROWS_WITH_AS(value.get_ref<json::number_float_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is array", json::type_error&);
+ }
+
+ SECTION("reference access to string_t")
+ {
+ using test_type = json::string_t;
+ json value = "hello";
+
+ // check if references are returned correctly
+ auto& p1 = value.get_ref<test_type&>();
+ CHECK(&p1 == value.get_ptr<test_type*>());
+ CHECK(p1 == value.get<test_type>());
+
+ const auto& p2 = value.get_ref<const test_type&>();
+ CHECK(&p2 == value.get_ptr<const test_type*>());
+ CHECK(p2 == value.get<test_type>());
+
+ // check if mismatching references throw correctly
+ CHECK_THROWS_WITH_AS(value.get_ref<json::object_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is string", json::type_error&);
+ CHECK_THROWS_WITH_AS(value.get_ref<json::array_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is string", json::type_error&);
+ CHECK_NOTHROW(value.get_ref<json::string_t&>());
+ CHECK_THROWS_WITH_AS(value.get_ref<json::boolean_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is string", json::type_error&);
+ CHECK_THROWS_WITH_AS(value.get_ref<json::number_integer_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is string", json::type_error&);
+ CHECK_THROWS_WITH_AS(value.get_ref<json::number_unsigned_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is string", json::type_error&);
+ CHECK_THROWS_WITH_AS(value.get_ref<json::number_float_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is string", json::type_error&);
+ }
+
+ SECTION("reference access to boolean_t")
+ {
+ using test_type = json::boolean_t;
+ json value = false;
+
+ // check if references are returned correctly
+ auto& p1 = value.get_ref<test_type&>();
+ CHECK(&p1 == value.get_ptr<test_type*>());
+ CHECK(p1 == value.get<test_type>());
+
+ const auto& p2 = value.get_ref<const test_type&>();
+ CHECK(&p2 == value.get_ptr<const test_type*>());
+ CHECK(p2 == value.get<test_type>());
+
+ // check if mismatching references throw correctly
+ CHECK_THROWS_WITH_AS(value.get_ref<json::object_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is boolean", json::type_error&);
+ CHECK_THROWS_WITH_AS(value.get_ref<json::array_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is boolean", json::type_error&);
+ CHECK_THROWS_WITH_AS(value.get_ref<json::string_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is boolean", json::type_error&);
+ CHECK_NOTHROW(value.get_ref<json::boolean_t&>());
+ CHECK_THROWS_WITH_AS(value.get_ref<json::number_integer_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is boolean", json::type_error&);
+ CHECK_THROWS_WITH_AS(value.get_ref<json::number_unsigned_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is boolean", json::type_error&);
+ CHECK_THROWS_WITH_AS(value.get_ref<json::number_float_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is boolean", json::type_error&);
+ }
+
+ SECTION("reference access to number_integer_t")
+ {
+ using test_type = json::number_integer_t;
+ json value = -23;
+
+ // check if references are returned correctly
+ auto& p1 = value.get_ref<test_type&>();
+ CHECK(&p1 == value.get_ptr<test_type*>());
+ CHECK(p1 == value.get<test_type>());
+
+ const auto& p2 = value.get_ref<const test_type&>();
+ CHECK(&p2 == value.get_ptr<const test_type*>());
+ CHECK(p2 == value.get<test_type>());
+
+ // check if mismatching references throw correctly
+ CHECK_THROWS_WITH_AS(value.get_ref<json::object_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is number", json::type_error&);
+ CHECK_THROWS_WITH_AS(value.get_ref<json::array_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is number", json::type_error&);
+ CHECK_THROWS_WITH_AS(value.get_ref<json::string_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is number", json::type_error&);
+ CHECK_THROWS_WITH_AS(value.get_ref<json::boolean_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is number", json::type_error&);
+ CHECK_NOTHROW(value.get_ref<json::number_integer_t&>());
+ CHECK_THROWS_WITH_AS(value.get_ref<json::number_unsigned_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is number", json::type_error&);
+ CHECK_THROWS_WITH_AS(value.get_ref<json::number_float_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is number", json::type_error&);
+ }
+
+ SECTION("reference access to number_unsigned_t")
+ {
+ using test_type = json::number_unsigned_t;
+ json value = 23u;
+
+ // check if references are returned correctly
+ auto& p1 = value.get_ref<test_type&>();
+ CHECK(&p1 == value.get_ptr<test_type*>());
+ CHECK(p1 == value.get<test_type>());
+
+ const auto& p2 = value.get_ref<const test_type&>();
+ CHECK(&p2 == value.get_ptr<const test_type*>());
+ CHECK(p2 == value.get<test_type>());
+
+ // check if mismatching references throw correctly
+ CHECK_THROWS_WITH_AS(value.get_ref<json::object_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is number", json::type_error&);
+ CHECK_THROWS_WITH_AS(value.get_ref<json::array_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is number", json::type_error&);
+ CHECK_THROWS_WITH_AS(value.get_ref<json::string_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is number", json::type_error&);
+ CHECK_THROWS_WITH_AS(value.get_ref<json::boolean_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is number", json::type_error&);
+ CHECK_THROWS_WITH_AS(value.get_ref<json::number_integer_t&>(),
+ "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is number", json::type_error&);
+ CHECK_NOTHROW(value.get_ref<json::number_unsigned_t&>());
+ CHECK_THROWS_WITH_AS(value.get_ref<json::number_float_t&>(), "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is number", json::type_error&);
+ }
+
+ SECTION("reference access to number_float_t")
+ {
+ using test_type = json::number_float_t;
+ json value = 42.23;
+
+ // check if references are returned correctly
+ auto& p1 = value.get_ref<test_type&>();
+ CHECK(&p1 == value.get_ptr<test_type*>());
+ CHECK(p1 == value.get<test_type>());
+
+ const auto& p2 = value.get_ref<const test_type&>();
+ CHECK(&p2 == value.get_ptr<const test_type*>());
+ CHECK(p2 == value.get<test_type>());
+
+ // check if mismatching references throw correctly
+ CHECK_THROWS_WITH_AS(value.get_ref<json::object_t&>(), "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is number", json::type_error&);
+ CHECK_THROWS_WITH_AS(value.get_ref<json::array_t&>(), "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is number", json::type_error&);
+ CHECK_THROWS_WITH_AS(value.get_ref<json::string_t&>(), "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is number", json::type_error&);
+ CHECK_THROWS_WITH_AS(value.get_ref<json::boolean_t&>(), "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is number", json::type_error&);
+ CHECK_THROWS_WITH_AS(value.get_ref<json::number_integer_t&>(), "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is number", json::type_error&);
+ CHECK_THROWS_WITH_AS(value.get_ref<json::number_unsigned_t&>(), "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is number", json::type_error&);
+ CHECK_NOTHROW(value.get_ref<json::number_float_t&>());
+ }
+}
diff --git a/json4cpp/tests/src/unit-regression1.cpp b/json4cpp/tests/src/unit-regression1.cpp
new file mode 100644
index 0000000000..475ef511fd
--- /dev/null
+++ b/json4cpp/tests/src/unit-regression1.cpp
@@ -0,0 +1,1530 @@
+// __ _____ _____ _____
+// __| | __| | | | 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"
+
+// for some reason including this after the json header leads to linker errors with VS 2017...
+#include <locale>
+
+// skip tests if JSON_DisableEnumSerialization=ON (#4384)
+#if defined(JSON_DISABLE_ENUM_SERIALIZATION) && (JSON_DISABLE_ENUM_SERIALIZATION == 1)
+ #define SKIP_TESTS_FOR_ENUM_SERIALIZATION
+#endif
+
+#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 <fstream>
+#include <sstream>
+#include <list>
+#include <limits>
+#include <cstdio>
+#include "make_test_data_available.hpp"
+
+#ifdef JSON_HAS_CPP_17
+ #include <variant>
+#endif
+
+#include "fifo_map.hpp"
+
+/////////////////////////////////////////////////////////////////////
+// for #972
+/////////////////////////////////////////////////////////////////////
+
+template<class K, class V, class dummy_compare, class A>
+using my_workaround_fifo_map = nlohmann::fifo_map<K, V, nlohmann::fifo_map_compare<K>, A>;
+using my_json = nlohmann::basic_json<my_workaround_fifo_map>;
+
+/////////////////////////////////////////////////////////////////////
+// for #977
+/////////////////////////////////////////////////////////////////////
+
+namespace ns
+{
+struct foo
+{
+ int x;
+};
+
+template <typename, typename SFINAE = void>
+struct foo_serializer;
+
+template<typename T>
+struct foo_serializer<T, typename std::enable_if<std::is_same<foo, T>::value>::type>
+{
+ template <typename BasicJsonType>
+ static void to_json(BasicJsonType& j, const T& value)
+ {
+ j = BasicJsonType{{"x", value.x}};
+ }
+ template <typename BasicJsonType>
+ static void from_json(const BasicJsonType& j, T& value) // !!!
+ {
+ nlohmann::from_json(j.at("x"), value.x);
+ }
+};
+
+template<typename T>
+struct foo_serializer < T, typename std::enable_if < !std::is_same<foo, T>::value >::type >
+{
+ template <typename BasicJsonType>
+ static void to_json(BasicJsonType& j, const T& value) noexcept // NOLINT(bugprone-exception-escape)
+ {
+ ::nlohmann::to_json(j, value);
+ }
+ template <typename BasicJsonType>
+ static void from_json(const BasicJsonType& j, T& value) //!!!
+ {
+ ::nlohmann::from_json(j, value);
+ }
+};
+} // namespace ns
+
+using foo_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t,
+ std::uint64_t, double, std::allocator, ns::foo_serializer, std::vector<std::uint8_t>>;
+
+/////////////////////////////////////////////////////////////////////
+// for #805
+/////////////////////////////////////////////////////////////////////
+
+namespace
+{
+struct nocopy // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions)
+{
+ nocopy() = default;
+ nocopy(const nocopy&) = delete;
+ nocopy(nocopy&&) = delete;
+ nocopy& operator=(const nocopy&) = delete;
+ nocopy& operator=(nocopy&&) = delete;
+
+ int val = 0;
+
+ friend void to_json(json& j, const nocopy& n)
+ {
+ j = {{"val", n.val}};
+ }
+};
+} // namespace
+
+TEST_CASE("regression tests 1")
+{
+ SECTION("issue #60 - Double quotation mark is not parsed correctly")
+ {
+ SECTION("escape_doublequote")
+ {
+ const auto* s = R"(["\"foo\""])";
+ const json j = json::parse(s);
+ auto expected = R"(["\"foo\""])"_json;
+ CHECK(j == expected);
+ }
+ }
+
+ SECTION("issue #70 - Handle infinity and NaN cases")
+ {
+ // previously, NAN/INFINITY created a null value; now, the values are
+ // properly stored, but are dumped as "null"
+ SECTION("NAN value")
+ {
+ CHECK(json(NAN).dump() == "null");
+ CHECK(json(json::number_float_t(NAN)).dump() == "null");
+ }
+
+ SECTION("infinity")
+ {
+ CHECK(json(INFINITY).dump() == "null");
+ CHECK(json(json::number_float_t(INFINITY)).dump() == "null");
+ }
+
+ // With 3.0.0, the semantics of this changed: NAN and infinity are
+ // stored properly inside the JSON value (no exception or conversion
+ // to null), but are serialized as null.
+ SECTION("NAN value")
+ {
+ json const j1 = NAN;
+ CHECK(j1.is_number_float());
+ json::number_float_t const f1{j1};
+ CHECK(std::isnan(f1));
+
+ json const j2 = static_cast<json::number_float_t>(NAN);
+ CHECK(j2.is_number_float());
+ json::number_float_t const f2{j2};
+ CHECK(std::isnan(f2));
+ }
+
+ SECTION("infinity")
+ {
+ json const j1 = INFINITY;
+ CHECK(j1.is_number_float());
+ json::number_float_t const f1{j1};
+ CHECK(!std::isfinite(f1));
+
+ json const j2 = static_cast<json::number_float_t>(INFINITY);
+ CHECK(j2.is_number_float());
+ json::number_float_t const f2{j2};
+ CHECK(!std::isfinite(f2));
+ }
+ }
+
+#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
+ SECTION("pull request #71 - handle enum type")
+ {
+ enum { t = 0, u = 102}; // NOLINT(cppcoreguidelines-use-enum-class)
+ json j = json::array();
+ j.push_back(t);
+
+ // maybe this is not the place to test this?
+ const json j2 = u;
+
+ auto anon_enum_value = j2.get<decltype(u)>();
+ CHECK(u == anon_enum_value);
+
+ // check if the actual value was stored
+ CHECK(j2 == 102);
+
+ static_assert(std::is_same<decltype(anon_enum_value), decltype(u)>::value, "types must be the same");
+
+ j.push_back(json::object(
+ {
+ {"game_type", t}
+ }));
+ }
+#endif
+
+ SECTION("issue #76 - dump() / parse() not idempotent")
+ {
+ // create JSON object
+ json fields;
+ fields["one"] = std::string("one");
+ fields["two"] = std::string("two three");
+ fields["three"] = std::string("three \"four\"");
+
+ // create another JSON object by deserializing the serialization
+ std::string const payload = fields.dump();
+ json parsed_fields = json::parse(payload);
+
+ // check individual fields to match both objects
+ CHECK(parsed_fields["one"] == fields["one"]);
+ CHECK(parsed_fields["two"] == fields["two"]);
+ CHECK(parsed_fields["three"] == fields["three"]);
+
+ // check individual fields to match original input
+ CHECK(parsed_fields["one"] == std::string("one"));
+ CHECK(parsed_fields["two"] == std::string("two three"));
+ CHECK(parsed_fields["three"] == std::string("three \"four\""));
+
+ // check equality of the objects
+ CHECK(parsed_fields == fields);
+
+ // check equality of the serialized objects
+ CHECK(fields.dump() == parsed_fields.dump());
+
+ // check everything in one line
+ CHECK(fields == json::parse(fields.dump()));
+ }
+
+ SECTION("issue #82 - lexer::get_number return NAN")
+ {
+ const auto* const content = R"(
+ {
+ "Test":"Test1",
+ "Number":100,
+ "Foo":42.42
+ })";
+
+ std::stringstream ss;
+ ss << content;
+ json j;
+ ss >> j;
+
+ auto test = j["Test"].get<std::string>();
+ CHECK(test == "Test1");
+ const int number{j["Number"]};
+ CHECK(number == 100);
+ const float foo{j["Foo"]};
+ CHECK(static_cast<double>(foo) == Approx(42.42));
+ }
+
+ SECTION("issue #89 - nonstandard integer type")
+ {
+ // create JSON class with nonstandard integer number type
+ using custom_json =
+ nlohmann::basic_json<std::map, std::vector, std::string, bool, int32_t, uint32_t, float>;
+ custom_json j;
+ j["int_1"] = 1;
+ CHECK(j["int_1"] == 1);
+
+ // tests for correct handling of non-standard integers that overflow the type selected by the user
+
+ // unsigned integer object creation - expected to wrap and still be stored as an integer
+ j = 4294967296U; // 2^32
+ CHECK(static_cast<int>(j.type()) == static_cast<int>(custom_json::value_t::number_unsigned));
+ CHECK(j.get<uint32_t>() == 0); // Wrap
+
+ // unsigned integer parsing - expected to overflow and be stored as a float
+ j = custom_json::parse("4294967296"); // 2^32
+ CHECK(static_cast<int>(j.type()) == static_cast<int>(custom_json::value_t::number_float));
+ CHECK(j.get<float>() == 4294967296.0f);
+
+ // integer object creation - expected to wrap and still be stored as an integer
+ j = -2147483649LL; // -2^31-1
+ CHECK(static_cast<int>(j.type()) == static_cast<int>(custom_json::value_t::number_integer));
+ CHECK(j.get<int32_t>() == 2147483647); // Wrap
+
+ // integer parsing - expected to overflow and be stored as a float with rounding
+ j = custom_json::parse("-2147483649"); // -2^31
+ CHECK(static_cast<int>(j.type()) == static_cast<int>(custom_json::value_t::number_float));
+ CHECK(j.get<float>() == -2147483650.0f);
+ }
+
+ SECTION("issue #93 reverse_iterator operator inheritance problem")
+ {
+ {
+ json a = {1, 2, 3};
+ json::reverse_iterator rit = a.rbegin();
+ ++rit;
+ CHECK(*rit == json(2));
+ CHECK(rit.value() == json(2));
+ }
+ {
+ json a = {1, 2, 3};
+ json::reverse_iterator const rit = ++a.rbegin();
+ CHECK(*rit == json(2));
+ CHECK(rit.value() == json(2));
+ }
+ {
+ json a = {1, 2, 3};
+ json::reverse_iterator rit = a.rbegin();
+ ++rit;
+ json b = {0, 0, 0};
+ std::transform(rit, a.rend(), b.rbegin(), [](json el)
+ {
+ return el;
+ });
+ CHECK(b == json({0, 1, 2}));
+ }
+ {
+ json a = {1, 2, 3};
+ json b = {0, 0, 0};
+ std::transform(++a.rbegin(), a.rend(), b.rbegin(), [](json el)
+ {
+ return el;
+ });
+ CHECK(b == json({0, 1, 2}));
+ }
+ }
+
+ SECTION("issue #100 - failed to iterator json object with reverse_iterator")
+ {
+ json config =
+ {
+ { "111", 111 },
+ { "112", 112 },
+ { "113", 113 }
+ };
+
+ std::stringstream ss;
+
+ for (auto it = config.begin(); it != config.end(); ++it)
+ {
+ ss << it.key() << ": " << it.value() << '\n';
+ }
+
+ for (auto it = config.rbegin(); it != config.rend(); ++it)
+ {
+ ss << it.key() << ": " << it.value() << '\n';
+ }
+
+ CHECK(ss.str() == "111: 111\n112: 112\n113: 113\n113: 113\n112: 112\n111: 111\n");
+ }
+
+ SECTION("issue #101 - binary string causes numbers to be dumped as hex")
+ {
+ int64_t const number = 10;
+ std::string const bytes{"\x00" "asdf\n", 6};
+ json j;
+ j["int64"] = number;
+ j["binary string"] = bytes;
+ // make sure the number is really printed as decimal "10" and not as
+ // hexadecimal "a"
+ CHECK(j.dump() == "{\"binary string\":\"\\u0000asdf\\n\",\"int64\":10}");
+ }
+
+ SECTION("issue #111 - subsequent unicode chars")
+ {
+ std::string const bytes{0x7, 0x7};
+ json j;
+ j["string"] = bytes;
+ CHECK(j["string"] == "\u0007\u0007");
+ }
+
+#if JSON_USE_IMPLICIT_CONVERSIONS
+ SECTION("issue #144 - implicit assignment to std::string fails")
+ {
+ json o = {{"name", "value"}};
+
+ const std::string s1 = o["name"];
+ CHECK(s1 == "value");
+
+ std::string s2;
+ s2 = o["name"];
+
+ CHECK(s2 == "value");
+
+ // improve coverage
+ o["int"] = 1;
+#if JSON_DIAGNOSTICS
+ CHECK_THROWS_WITH_AS(s2 = o["int"], "[json.exception.type_error.302] (/int) type must be string, but is number", json::type_error);
+#else
+ CHECK_THROWS_WITH_AS(s2 = o["int"], "[json.exception.type_error.302] type must be string, but is number", json::type_error);
+#endif
+ }
+#endif
+
+ SECTION("issue #146 - character following a surrogate pair is skipped")
+ {
+ CHECK(json::parse("\"\\ud80c\\udc60abc\"").get<json::string_t>() == "\xf0\x93\x81\xa0\x61\x62\x63");
+ }
+
+ SECTION("issue #171 - Cannot index by key of type static constexpr const char*")
+ {
+ json j;
+
+ // Non-const access with key as "char []"
+ char array_key[] = "Key1"; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
+ CHECK_NOTHROW(j[array_key] = 1);
+ CHECK(j[array_key] == json(1));
+
+ // Non-const access with key as "const char[]"
+ const char const_array_key[] = "Key2"; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
+ CHECK_NOTHROW(j[const_array_key] = 2);
+ CHECK(j[const_array_key] == json(2));
+
+ // Non-const access with key as "char *"
+ char _ptr_key[] = "Key3"; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
+ char* ptr_key = &_ptr_key[0]; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
+ CHECK_NOTHROW(j[ptr_key] = 3);
+ CHECK(j[ptr_key] == json(3));
+
+ // Non-const access with key as "const char *"
+ const char* const_ptr_key = "Key4";
+ CHECK_NOTHROW(j[const_ptr_key] = 4);
+ CHECK(j[const_ptr_key] == json(4));
+
+ // Non-const access with key as "static constexpr const char *"
+ static constexpr const char* constexpr_ptr_key = "Key5";
+ CHECK_NOTHROW(j[constexpr_ptr_key] = 5);
+ CHECK(j[constexpr_ptr_key] == json(5));
+
+ const json j_const = j;
+
+ // Const access with key as "char []"
+ CHECK(j_const[array_key] == json(1));
+
+ // Const access with key as "const char[]"
+ CHECK(j_const[const_array_key] == json(2));
+
+ // Const access with key as "char *"
+ CHECK(j_const[ptr_key] == json(3));
+
+ // Const access with key as "const char *"
+ CHECK(j_const[const_ptr_key] == json(4));
+
+ // Const access with key as "static constexpr const char *"
+ CHECK(j_const[constexpr_ptr_key] == json(5));
+ }
+
+ SECTION("issue #186 miloyip/nativejson-benchmark: floating-point parsing")
+ {
+ json j;
+
+ j = json::parse("-0.0");
+ CHECK(j.get<double>() == -0.0);
+
+ j = json::parse("2.22507385850720113605740979670913197593481954635164564e-308");
+ CHECK(j.get<double>() == 2.2250738585072009e-308);
+
+ j = json::parse("0.999999999999999944488848768742172978818416595458984374");
+ CHECK(j.get<double>() == 0.99999999999999989);
+
+ j = json::parse("1.00000000000000011102230246251565404236316680908203126");
+ CHECK(j.get<double>() == 1.00000000000000022);
+
+ j = json::parse("7205759403792793199999e-5");
+ CHECK(j.get<double>() == 72057594037927928.0);
+
+ j = json::parse("922337203685477529599999e-5");
+ CHECK(j.get<double>() == 9223372036854774784.0);
+
+ j = json::parse("1014120480182583464902367222169599999e-5");
+ CHECK(j.get<double>() == 10141204801825834086073718800384.0);
+
+ j = json::parse("5708990770823839207320493820740630171355185151999e-3");
+ CHECK(j.get<double>() == 5708990770823838890407843763683279797179383808.0);
+
+ // create JSON class with nonstandard float number type
+
+ // float
+ nlohmann::basic_json<std::map, std::vector, std::string, bool, int32_t, uint32_t, float> const j_float =
+ 1.23e25f;
+ CHECK(j_float.get<float>() == 1.23e25f);
+
+ // double
+ nlohmann::basic_json<std::map, std::vector, std::string, bool, int64_t, uint64_t, double> const j_double =
+ 1.23e35;
+ CHECK(j_double.get<double>() == 1.23e35);
+
+ // long double
+ nlohmann::basic_json<std::map, std::vector, std::string, bool, int64_t, uint64_t, long double>
+ const j_long_double = 1.23e45L;
+ CHECK(j_long_double.get<long double>() == 1.23e45L);
+ }
+
+ SECTION("issue #228 - double values are serialized with commas as decimal points")
+ {
+ json const j1a = 2312.42;
+ json const j1b = json::parse("2312.42");
+
+ json const j2a = 2342e-2;
+ //issue #230
+ //json j2b = json::parse("2342e-2");
+
+ json const j3a = 10E3;
+ json const j3b = json::parse("10E3");
+ json const j3c = json::parse("10e3");
+
+ // class to create a locale that would use a comma for decimals
+ class CommaDecimalSeparator : public std::numpunct<char>
+ {
+ protected:
+ char do_decimal_point() const override
+ {
+ return ',';
+ }
+
+ char do_thousands_sep() const override
+ {
+ return '.';
+ }
+
+ std::string do_grouping() const override
+ {
+ return "\03";
+ }
+ };
+
+ // change locale to mess with decimal points
+ auto orig_locale = std::locale::global(std::locale(std::locale(), new CommaDecimalSeparator));
+
+ CHECK(j1a.dump() == "2312.42");
+ CHECK(j1b.dump() == "2312.42");
+
+ // check if locale is properly reset
+ std::stringstream ss;
+ ss.imbue(std::locale(std::locale(), new CommaDecimalSeparator));
+ ss << 4712.11;
+ CHECK(ss.str() == "4.712,11");
+ ss << j1a;
+ CHECK(ss.str() == "4.712,112312.42");
+ ss << 47.11;
+ CHECK(ss.str() == "4.712,112312.4247,11");
+
+ CHECK(j2a.dump() == "23.42");
+ //issue #230
+ //CHECK(j2b.dump() == "23.42");
+
+ CHECK(j3a.dump() == "10000.0");
+ CHECK(j3b.dump() == "10000.0");
+ CHECK(j3c.dump() == "10000.0");
+ //CHECK(j3b.dump() == "1E04"); // roundtrip error
+ //CHECK(j3c.dump() == "1e04"); // roundtrip error
+
+ std::locale::global(orig_locale);
+ }
+
+ SECTION("issue #378 - locale-independent num-to-str")
+ {
+ static_cast<void>(setlocale(LC_NUMERIC, "de_DE.UTF-8"));
+
+ // verify that dumped correctly with '.' and no grouping
+ const json j1 = 12345.67;
+ CHECK(json(12345.67).dump() == "12345.67");
+ static_cast<void>(setlocale(LC_NUMERIC, "C"));
+ }
+
+ SECTION("issue #379 - locale-independent str-to-num")
+ {
+ static_cast<void>(setlocale(LC_NUMERIC, "de_DE.UTF-8"));
+
+ // verify that parsed correctly despite using strtod internally
+ CHECK(json::parse("3.14").get<double>() == 3.14);
+
+ // check a different code path
+ CHECK(json::parse("1.000000000000000000000000000000000000000000000000000000000000000000000000").get<double>() == 1.0);
+ }
+
+ SECTION("issue #233 - Can't use basic_json::iterator as a base iterator for std::move_iterator")
+ {
+ json source = {"a", "b", "c"};
+ json expected = {"a", "b"};
+ json dest;
+
+ std::copy_n(std::make_move_iterator(source.begin()), 2, std::back_inserter(dest));
+
+ CHECK(dest == expected);
+ }
+
+ SECTION("issue #235 - ambiguous overload for 'push_back' and 'operator+='")
+ {
+ json data = {{"key", "value"}};
+ data.push_back({"key2", "value2"});
+ data += {"key3", "value3"};
+
+ CHECK(data == json({{"key", "value"}, {"key2", "value2"}, {"key3", "value3"}}));
+ }
+
+ SECTION("issue #269 - diff generates incorrect patch when removing multiple array elements")
+ {
+ json const doc = R"( { "arr1": [1, 2, 3, 4] } )"_json;
+ json expected = R"( { "arr1": [1, 2] } )"_json;
+
+ // check roundtrip
+ CHECK(doc.patch(json::diff(doc, expected)) == expected);
+ }
+
+ SECTION("issue #283 - value() does not work with _json_pointer types")
+ {
+ json j =
+ {
+ {"object", {{"key1", 1}, {"key2", 2}}},
+ };
+
+ const int at_integer{j.at("/object/key2"_json_pointer)};
+ const int val_integer = j.value("/object/key2"_json_pointer, 0);
+
+ CHECK(at_integer == val_integer);
+ }
+
+ SECTION("issue #304 - Unused variable warning")
+ {
+ // code triggered a "warning: unused variable" warning and is left
+ // here to avoid the warning in the future
+ json object;
+ json const patch = json::array();
+ object = object.patch(patch);
+ }
+
+ SECTION("issue #306 - Parsing fails without space at end of file")
+ {
+ for (const auto* filename :
+ {
+ TEST_DATA_DIRECTORY "/regression/broken_file.json",
+ TEST_DATA_DIRECTORY "/regression/working_file.json"
+ })
+ {
+ CAPTURE(filename)
+ json j;
+ std::ifstream f(filename);
+ CHECK_NOTHROW(f >> j);
+ }
+ }
+
+ SECTION("issue #310 - make json_benchmarks no longer working in 2.0.4")
+ {
+ for (const auto* filename :
+ {
+ TEST_DATA_DIRECTORY "/regression/floats.json",
+ TEST_DATA_DIRECTORY "/regression/signed_ints.json",
+ TEST_DATA_DIRECTORY "/regression/unsigned_ints.json",
+ TEST_DATA_DIRECTORY "/regression/small_signed_ints.json"
+ })
+ {
+ CAPTURE(filename)
+ json j;
+ std::ifstream f(filename);
+ CHECK_NOTHROW(f >> j);
+ }
+ }
+
+ SECTION("issue #323 - add nested object capabilities to pointers")
+ {
+ json j;
+ j["/this/that/2"_json_pointer] = 27;
+ CHECK(j == json({{"this", {{"that", {nullptr, nullptr, 27}}}}}));
+ }
+
+ SECTION("issue #329 - serialized value not always can be parsed")
+ {
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::parse("22e2222"), "[json.exception.out_of_range.406] number overflow parsing '22e2222'", json::out_of_range&);
+ }
+
+ SECTION("issue #360 - Loss of precision when serializing <double>")
+ {
+ auto check_roundtrip = [](double number)
+ {
+ CAPTURE(number)
+
+ json j = number;
+ CHECK(j.is_number_float());
+
+ std::stringstream ss;
+ ss << j;
+
+ CHECK_NOTHROW(ss >> j);
+ CHECK(j.is_number_float());
+ CHECK(j.get<json::number_float_t>() == number);
+ };
+
+ check_roundtrip(100000000000.1236);
+ check_roundtrip((std::numeric_limits<json::number_float_t>::max)());
+
+ // Some more numbers which fail to roundtrip when serialized with digits10 significand digits (instead of max_digits10)
+ check_roundtrip(1.541888611948064e-17);
+ check_roundtrip(5.418771028591015e-16);
+ check_roundtrip(9.398685592608595e-15);
+ check_roundtrip(8.826843952762347e-14);
+ check_roundtrip(8.143291313475335e-13);
+ check_roundtrip(4.851328172762508e-12);
+ check_roundtrip(6.677850998084358e-11);
+ check_roundtrip(3.995398518174525e-10);
+ check_roundtrip(1.960452605645124e-9);
+ check_roundtrip(3.551812586302883e-8);
+ check_roundtrip(2.947988411689261e-7);
+ check_roundtrip(8.210166748056192e-6);
+ check_roundtrip(6.104889704266753e-5);
+ check_roundtrip(0.0008629954631330876);
+ check_roundtrip(0.004936993881051611);
+ check_roundtrip(0.08309725102608073);
+ check_roundtrip(0.5210494268499783);
+ check_roundtrip(6.382927930939767);
+ check_roundtrip(59.94947245358671);
+ check_roundtrip(361.0838651266122);
+ check_roundtrip(4678.354596181877);
+ check_roundtrip(61412.17658956043);
+ check_roundtrip(725696.0799057782);
+ check_roundtrip(2811732.583399828);
+ check_roundtrip(30178351.07533605);
+ check_roundtrip(689684880.3235844);
+ check_roundtrip(5714887673.555147);
+ check_roundtrip(84652038821.18808);
+ check_roundtrip(156510583431.7721);
+ check_roundtrip(5938450569021.732);
+ check_roundtrip(83623297654460.33);
+ check_roundtrip(701466573254773.6);
+ check_roundtrip(1369013370304513);
+ check_roundtrip(96963648023094720); // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+ check_roundtrip(3.478237409280108e+17);
+ }
+
+ SECTION("issue #366 - json::parse on failed stream gets stuck")
+ {
+ std::ifstream f("file_not_found.json");
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::parse(f), "[json.exception.parse_error.101] parse error at line 1, column 1: attempting to parse an empty input; check that your input string or stream contains the expected JSON", json::parse_error&);
+ }
+
+ SECTION("issue #367 - calling stream at EOF")
+ {
+ std::stringstream ss;
+ json j;
+ ss << "123";
+ CHECK_NOTHROW(ss >> j);
+
+ // see https://github.com/nlohmann/json/issues/367#issuecomment-262841893:
+ // ss is not at EOF; this yielded an error before the fix
+ // (threw basic_string::append). No, it should just throw
+ // a parse error because of the EOF.
+ CHECK_THROWS_WITH_AS(ss >> j, "[json.exception.parse_error.101] parse error at line 1, column 1: attempting to parse an empty input; check that your input string or stream contains the expected JSON", json::parse_error&);
+ }
+
+ SECTION("issue #367 - behavior of operator>> should more closely resemble that of built-in overloads")
+ {
+ SECTION("(empty)")
+ {
+ std::stringstream ss;
+ json j;
+ CHECK_THROWS_WITH_AS(ss >> j, "[json.exception.parse_error.101] parse error at line 1, column 1: attempting to parse an empty input; check that your input string or stream contains the expected JSON", json::parse_error&);
+ }
+
+ SECTION("(whitespace)")
+ {
+ std::stringstream ss;
+ ss << " ";
+ json j;
+ CHECK_THROWS_WITH_AS(ss >> j,
+ "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - unexpected end of input; expected '[', '{', or a literal", json::parse_error&);
+ }
+
+ SECTION("one value")
+ {
+ std::stringstream ss;
+ ss << "111";
+ json j;
+ CHECK_NOTHROW(ss >> j);
+ CHECK(j == 111);
+
+ CHECK_THROWS_WITH_AS(ss >> j, "[json.exception.parse_error.101] parse error at line 1, column 1: attempting to parse an empty input; check that your input string or stream contains the expected JSON", json::parse_error&);
+ }
+
+ SECTION("one value + whitespace")
+ {
+ std::stringstream ss;
+ ss << "222 \t\n";
+ json j;
+ CHECK_NOTHROW(ss >> j);
+ CHECK(j == 222);
+
+ CHECK_THROWS_WITH_AS(ss >> j,
+ "[json.exception.parse_error.101] parse error at line 2, column 1: syntax error while parsing value - unexpected end of input; expected '[', '{', or a literal", json::parse_error&);
+ }
+
+ SECTION("whitespace + one value")
+ {
+ std::stringstream ss;
+ ss << "\n\t 333";
+ json j;
+ CHECK_NOTHROW(ss >> j);
+ CHECK(j == 333);
+
+ CHECK_THROWS_WITH_AS(ss >> j, "[json.exception.parse_error.101] parse error at line 1, column 1: attempting to parse an empty input; check that your input string or stream contains the expected JSON", json::parse_error&);
+ }
+
+ SECTION("three values")
+ {
+ std::stringstream ss;
+ ss << " 111 \n222\n \n 333";
+ json j;
+ CHECK_NOTHROW(ss >> j);
+ CHECK(j == 111);
+ CHECK_NOTHROW(ss >> j);
+ CHECK(j == 222);
+ CHECK_NOTHROW(ss >> j);
+ CHECK(j == 333);
+
+ CHECK_THROWS_WITH_AS(ss >> j, "[json.exception.parse_error.101] parse error at line 1, column 1: attempting to parse an empty input; check that your input string or stream contains the expected JSON", json::parse_error&);
+ }
+
+ SECTION("literals without whitespace")
+ {
+ std::stringstream ss;
+ ss << "truefalsenull\"\"";
+ json j;
+ CHECK_NOTHROW(ss >> j);
+ CHECK(j == true);
+ CHECK_NOTHROW(ss >> j);
+ CHECK(j == false);
+ CHECK_NOTHROW(ss >> j);
+ CHECK(j == nullptr);
+ CHECK_NOTHROW(ss >> j);
+ CHECK(j == "");
+
+ CHECK_THROWS_WITH_AS(ss >> j, "[json.exception.parse_error.101] parse error at line 1, column 1: attempting to parse an empty input; check that your input string or stream contains the expected JSON", json::parse_error&);
+ }
+
+ SECTION("example from #529")
+ {
+ std::stringstream ss;
+ ss << "{\n \"one\" : 1,\n \"two\" : 2\n}\n{\n \"three\" : 3\n}";
+ json j;
+ CHECK_NOTHROW(ss >> j);
+ CHECK(j == json({{"one", 1}, {"two", 2}}));
+ CHECK_NOTHROW(ss >> j);
+ CHECK(j == json({{"three", 3}}));
+
+ CHECK_THROWS_WITH_AS(ss >> j, "[json.exception.parse_error.101] parse error at line 1, column 1: attempting to parse an empty input; check that your input string or stream contains the expected JSON", json::parse_error&);
+ }
+
+ SECTION("second example from #529")
+ {
+ std::string const str = "{\n\"one\" : 1,\n\"two\" : 2\n}\n{\n\"three\" : 3\n}";
+
+ {
+ std::ofstream file("test.json");
+ file << str;
+ }
+
+ std::ifstream stream("test.json", std::ifstream::in);
+ json val;
+
+ size_t i = 0;
+ while (stream.peek() != EOF)
+ {
+ CAPTURE(i)
+ CHECK_NOTHROW(stream >> val);
+
+ CHECK(i < 2);
+
+ if (i == 0)
+ {
+ CHECK(val == json({{"one", 1}, {"two", 2}}));
+ }
+
+ if (i == 1)
+ {
+ CHECK(val == json({{"three", 3}}));
+ }
+
+ ++i;
+ }
+
+ static_cast<void>(std::remove("test.json"));
+ }
+ }
+
+ SECTION("issue #389 - Integer-overflow (OSS-Fuzz issue 267)")
+ {
+ // original test case
+ json const j1 = json::parse("-9223372036854775808");
+ CHECK(j1.is_number_integer());
+ CHECK(j1.get<json::number_integer_t>() == (std::numeric_limits<std::int64_t>::min)());
+
+ // edge case (+1; still an integer)
+ json const j2 = json::parse("-9223372036854775807");
+ CHECK(j2.is_number_integer());
+ CHECK(j2.get<json::number_integer_t>() == (std::numeric_limits<std::int64_t>::min)() + 1);
+
+ // edge case (-1; overflow -> floats)
+ json const j3 = json::parse("-9223372036854775809");
+ CHECK(j3.is_number_float());
+ }
+
+ SECTION("issue #380 - bug in overflow detection when parsing integers")
+ {
+ json const j = json::parse("166020696663385964490");
+ CHECK(j.is_number_float());
+ CHECK(j.get<json::number_float_t>() == 166020696663385964490.0);
+ }
+
+ SECTION("issue #405 - Heap-buffer-overflow (OSS-Fuzz issue 342)")
+ {
+ // original test case
+ std::vector<uint8_t> const vec {0x65, 0xf5, 0x0a, 0x48, 0x21};
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec), "[json.exception.parse_error.110] parse error at byte 6: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
+ }
+
+ SECTION("issue #407 - Heap-buffer-overflow (OSS-Fuzz issue 343)")
+ {
+ json _;
+
+ // original test case: incomplete float64
+ std::vector<uint8_t> const vec1 {0xcb, 0x8f, 0x0a};
+ CHECK_THROWS_WITH_AS(_ = json::from_msgpack(vec1), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing MessagePack number: unexpected end of input", json::parse_error&);
+
+ // related test case: incomplete float32
+ std::vector<uint8_t> const vec2 {0xca, 0x8f, 0x0a};
+ CHECK_THROWS_WITH_AS(_ = json::from_msgpack(vec2), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing MessagePack number: unexpected end of input", json::parse_error&);
+
+ // related test case: incomplete Half-Precision Float (CBOR)
+ std::vector<uint8_t> const vec3 {0xf9, 0x8f};
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec3), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
+
+ // related test case: incomplete Single-Precision Float (CBOR)
+ std::vector<uint8_t> const vec4 {0xfa, 0x8f, 0x0a};
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec4), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
+
+ // related test case: incomplete Double-Precision Float (CBOR)
+ std::vector<uint8_t> const vec5 {0xfb, 0x8f, 0x0a};
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec5), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
+ }
+
+ SECTION("issue #408 - Heap-buffer-overflow (OSS-Fuzz issue 344)")
+ {
+ json _;
+
+ // original test case
+ std::vector<uint8_t> const vec1 {0x87};
+ CHECK_THROWS_WITH_AS(_ = json::from_msgpack(vec1), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing MessagePack string: unexpected end of input", json::parse_error&);
+
+ // more test cases for MessagePack
+ for (auto b :
+ {
+ 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, // fixmap
+ 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, // fixarray
+ 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, // fixstr
+ 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf
+ })
+ {
+ std::vector<uint8_t> const vec(1, static_cast<uint8_t>(b));
+ CHECK_THROWS_AS(_ = json::from_msgpack(vec), json::parse_error&);
+ }
+
+ // more test cases for CBOR
+ for (auto b :
+ {
+ 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
+ 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, // UTF-8 string
+ 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
+ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, // array
+ 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
+ 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7 // map
+ })
+ {
+ std::vector<uint8_t> const vec(1, static_cast<uint8_t>(b));
+ CHECK_THROWS_AS(_ = json::from_cbor(vec), json::parse_error&);
+ }
+
+ // special case: empty input
+ std::vector<uint8_t> const vec2;
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec2), "[json.exception.parse_error.110] parse error at byte 1: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
+ CHECK_THROWS_WITH_AS(_ = json::from_msgpack(vec2), "[json.exception.parse_error.110] parse error at byte 1: syntax error while parsing MessagePack value: unexpected end of input", json::parse_error&);
+ }
+
+ SECTION("issue #411 - Heap-buffer-overflow (OSS-Fuzz issue 366)")
+ {
+ json _;
+
+ // original test case: empty UTF-8 string (indefinite length)
+ std::vector<uint8_t> const vec1 {0x7f};
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec1), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
+
+ // related test case: empty array (indefinite length)
+ std::vector<uint8_t> const vec2 {0x9f};
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec2), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
+
+ // related test case: empty map (indefinite length)
+ std::vector<uint8_t> const vec3 {0xbf};
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec3), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
+ }
+
+ SECTION("issue #412 - Heap-buffer-overflow (OSS-Fuzz issue 367)")
+ {
+ // original test case
+ std::vector<uint8_t> const vec
+ {
+ 0xab, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98,
+ 0x98, 0x98, 0x98, 0x98, 0x98, 0x00, 0x00, 0x00,
+ 0x60, 0xab, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98,
+ 0x98, 0x98, 0x98, 0x98, 0x98, 0x00, 0x00, 0x00,
+ 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
+ 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
+ 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
+ 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
+ 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
+ 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
+ 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0xa0, 0x9f,
+ 0x9f, 0x97, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
+ 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
+ 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
+ 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
+ 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
+ 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60
+ };
+
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x98", json::parse_error&);
+
+ // related test case: nonempty UTF-8 string (indefinite length)
+ std::vector<uint8_t> const vec1 {0x7f, 0x61, 0x61};
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec1), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
+
+ // related test case: nonempty array (indefinite length)
+ std::vector<uint8_t> const vec2 {0x9f, 0x01};
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec2), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
+
+ // related test case: nonempty map (indefinite length)
+ std::vector<uint8_t> const vec3 {0xbf, 0x61, 0x61, 0x01};
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec3), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
+ }
+
+ SECTION("issue #414 - compare with literal 0)")
+ {
+#define CHECK_TYPE(v) \
+ CHECK((json(v) == (v)));\
+ CHECK(((v) == json(v)));\
+ CHECK_FALSE((json(v) != (v)));\
+ CHECK_FALSE(((v) != json(v)));
+
+ CHECK_TYPE(nullptr)
+ CHECK_TYPE(0)
+ CHECK_TYPE(0u)
+ CHECK_TYPE(0L)
+ CHECK_TYPE(0.0)
+ CHECK_TYPE("") // NOLINT(readability-container-size-empty)
+
+#undef CHECK_TYPE
+ }
+
+ SECTION("issue #416 - Use-of-uninitialized-value (OSS-Fuzz issue 377)")
+ {
+ // original test case
+ std::vector<uint8_t> const vec1
+ {
+ 0x94, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa,
+ 0x3a, 0x96, 0x96, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4,
+ 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0x71,
+ 0xb4, 0xb4, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0x3a,
+ 0x96, 0x96, 0xb4, 0xb4, 0xfa, 0x94, 0x94, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0xfa
+ };
+
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec1), "[json.exception.parse_error.113] parse error at byte 13: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0xB4", json::parse_error&);
+
+ // related test case: double-precision
+ std::vector<uint8_t> const vec2
+ {
+ 0x94, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa,
+ 0x3a, 0x96, 0x96, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4,
+ 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0x71,
+ 0xb4, 0xb4, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0x3a,
+ 0x96, 0x96, 0xb4, 0xb4, 0xfa, 0x94, 0x94, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0xfb
+ };
+ CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec2), "[json.exception.parse_error.113] parse error at byte 13: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0xB4", json::parse_error&);
+ }
+
+ SECTION("issue #452 - Heap-buffer-overflow (OSS-Fuzz issue 585)")
+ {
+ std::vector<uint8_t> const vec = {'-', '0', '1', '2', '2', '7', '4'};
+ json _;
+ CHECK_THROWS_AS(_ = json::parse(vec), json::parse_error&);
+ }
+
+ SECTION("issue #454 - doubles are printed as integers")
+ {
+ json j = R"({"bool_value":true,"double_value":2.0,"int_value":10,"level1":{"list_value":[3,"hi",false],"tmp":5.0},"string_value":"hello"})"_json;
+ CHECK(j["double_value"].is_number_float());
+ }
+
+#if JSON_USE_IMPLICIT_CONVERSIONS
+ SECTION("issue #464 - VS2017 implicit to std::string conversion fix")
+ {
+ const json v = "test";
+ std::string test;
+ test = v;
+ CHECK(v == "test");
+ }
+#endif
+
+ SECTION("issue #465 - roundtrip error while parsing 1000000000000000010E5")
+ {
+ json const j1 = json::parse("1000000000000000010E5");
+ const std::string s1 = j1.dump();
+ json const j2 = json::parse(s1);
+ const std::string s2 = j2.dump();
+ CHECK(s1 == s2);
+ }
+
+#if JSON_USE_IMPLICIT_CONVERSIONS
+ SECTION("issue #473 - inconsistent behavior in conversion to array type")
+ {
+ json const j_array = {1, 2, 3, 4};
+ json const j_number = 42;
+ json const j_null = nullptr;
+
+ SECTION("std::vector")
+ {
+ auto create = [](const json & j)
+ {
+ std::vector<int> const v = j;
+ };
+
+ CHECK_NOTHROW(create(j_array));
+ CHECK_THROWS_WITH_AS(create(j_number), "[json.exception.type_error.302] type must be array, but is number", json::type_error&);
+ CHECK_THROWS_WITH_AS(create(j_null), "[json.exception.type_error.302] type must be array, but is null", json::type_error&);
+ }
+
+ SECTION("std::list")
+ {
+ auto create = [](const json & j)
+ {
+ std::list<int> const v = j;
+ };
+
+ CHECK_NOTHROW(create(j_array));
+ CHECK_THROWS_WITH_AS(create(j_number), "[json.exception.type_error.302] type must be array, but is number", json::type_error&);
+ CHECK_THROWS_WITH_AS(create(j_null), "[json.exception.type_error.302] type must be array, but is null", json::type_error&);
+ }
+
+ SECTION("std::forward_list")
+ {
+ auto create = [](const json & j)
+ {
+ std::forward_list<int> const v = j;
+ };
+
+ CHECK_NOTHROW(create(j_array));
+ CHECK_THROWS_WITH_AS(create(j_number), "[json.exception.type_error.302] type must be array, but is number", json::type_error&);
+ CHECK_THROWS_WITH_AS(create(j_null), "[json.exception.type_error.302] type must be array, but is null", json::type_error&);
+ }
+ }
+#endif
+
+ SECTION("issue #486 - json::value_t can't be a map's key type in VC++ 2015")
+ {
+ // the code below must compile with MSVC
+ std::map<json::value_t, std::string> jsonTypes ;
+ jsonTypes[json::value_t::array] = "array";
+ }
+
+ SECTION("issue #494 - conversion from vector<bool> to json fails to build")
+ {
+ std::vector<bool> const boolVector = {false, true, false, false};
+ json j;
+ j["bool_vector"] = boolVector;
+
+ CHECK(j["bool_vector"].dump() == "[false,true,false,false]");
+ }
+
+ SECTION("issue #504 - assertion error (OSS-Fuzz 856)")
+ {
+ std::vector<uint8_t> const vec1 = {0xf9, 0xff, 0xff, 0x4a, 0x3a, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x37, 0x02, 0x38};
+ json const j1 = json::from_cbor(vec1, false);
+
+ // step 2: round trip
+ std::vector<uint8_t> vec2 = json::to_cbor(j1);
+
+ // parse serialization
+ json const j2 = json::from_cbor(vec2);
+
+ // NaN is dumped to "null"
+ CHECK(j2.is_number_float());
+ CHECK(std::isnan(j2.get<json::number_float_t>()));
+ CHECK(j2.dump() == "null");
+
+ // check if serializations match
+ CHECK(json::to_cbor(j2) == vec2);
+ }
+
+ SECTION("issue #512 - use of overloaded operator '<=' is ambiguous")
+ {
+ json j;
+ j["a"] = 5;
+
+ // json op scalar
+ CHECK(j["a"] == 5);
+ CHECK(j["a"] != 4);
+
+ CHECK(j["a"] <= 7);
+ CHECK(j["a"] < 7);
+ CHECK(j["a"] >= 3);
+ CHECK(j["a"] > 3);
+
+ CHECK(!(j["a"] <= 4));
+ CHECK(!(j["a"] < 4));
+ CHECK(!(j["a"] >= 6));
+ CHECK(!(j["a"] > 6));
+
+ // scalar op json
+ CHECK(5 == j["a"]);
+ CHECK(4 != j["a"]);
+
+ CHECK(7 >= j["a"]);
+ CHECK(7 > j["a"]);
+ CHECK(3 <= j["a"]);
+ CHECK(3 < j["a"]);
+
+ CHECK(!(4 >= j["a"]));
+ CHECK(!(4 > j["a"]));
+ CHECK(!(6 <= j["a"]));
+ CHECK(!(6 < j["a"]));
+ }
+
+ SECTION("issue #575 - heap-buffer-overflow (OSS-Fuzz 1400)")
+ {
+ json _;
+ std::vector<uint8_t> const vec = {'"', '\\', '"', 'X', '"', '"'};
+ CHECK_THROWS_AS(_ = json::parse(vec), json::parse_error&);
+ }
+
+#if JSON_USE_IMPLICIT_CONVERSIONS
+ SECTION("issue #600 - how does one convert a map in Json back to std::map?")
+ {
+ SECTION("example 1")
+ {
+ // create a map
+ const std::map<std::string, int> m1 {{"key", 1}};
+
+ // create and print a JSON from the map
+ json const j = m1;
+
+ // get the map out of JSON
+ std::map<std::string, int> m2 = j;
+
+ // make sure the roundtrip succeeds
+ CHECK(m1 == m2);
+ }
+
+ SECTION("example 2")
+ {
+ // create a map
+ const std::map<std::string, std::string> m1 {{"key", "val"}};
+
+ // create and print a JSON from the map
+ json const j = m1;
+
+ // get the map out of JSON
+ std::map<std::string, std::string> m2 = j;
+
+ // make sure the roundtrip succeeds
+ CHECK(m1 == m2);
+ }
+ }
+#endif
+
+ SECTION("issue #602 - BOM not skipped when using json:parse(iterator)")
+ {
+ std::string i = "\xef\xbb\xbf{\n \"foo\": true\n}";
+ json _;
+ CHECK_NOTHROW(_ = json::parse(i.begin(), i.end()));
+ }
+
+#if JSON_USE_IMPLICIT_CONVERSIONS
+ SECTION("issue #702 - conversion from valarray<double> to json fails to build")
+ {
+ SECTION("original example")
+ {
+ std::valarray<double> const v;
+ nlohmann::json j;
+ j["test"] = v;
+ }
+
+ SECTION("full example")
+ {
+ std::valarray<double> v = {1.2, 2.3, 3.4, 4.5};
+ json j = v;
+ std::valarray<double> vj = j;
+
+ CHECK(j == json(vj));
+ CHECK(v.size() == vj.size());
+ for (size_t i = 0; i < v.size(); ++i)
+ {
+ CHECK(v[i] == vj[i]);
+ CHECK(v[i] == j[i]);
+ }
+
+ CHECK_THROWS_WITH_AS(json().get<std::valarray<double>>(), "[json.exception.type_error.302] type must be array, but is null", json::type_error&);
+ }
+ }
+#endif
+
+ SECTION("issue #367 - Behavior of operator>> should more closely resemble that of built-in overloads.")
+ {
+ SECTION("example 1")
+ {
+ std::istringstream i1_2_3( R"({"first": "one" }{"second": "two"}3)" );
+ json j1;
+ json j2;
+ json j3;
+ i1_2_3 >> j1;
+ i1_2_3 >> j2;
+ i1_2_3 >> j3;
+
+ auto m1 = j1.get<std::map<std::string, std::string>>();
+ auto m2 = j2.get<std::map<std::string, std::string>>();
+ const int i3{j3};
+
+ CHECK( m1 == ( std::map<std::string, std::string> {{ "first", "one" }} ));
+ CHECK( m2 == ( std::map<std::string, std::string> {{ "second", "two" }} ));
+ CHECK( i3 == 3 );
+ }
+ }
+
+ SECTION("issue #714 - throw std::ios_base::failure exception when failbit set to true")
+ {
+ {
+ std::ifstream is;
+ is.exceptions(
+ is.exceptions()
+ | std::ios_base::failbit
+ | std::ios_base::badbit
+ ); // handle different exceptions as 'file not found', 'permission denied'
+
+ is.open(TEST_DATA_DIRECTORY "/regression/working_file.json");
+ json _;
+ CHECK_NOTHROW(_ = nlohmann::json::parse(is));
+ }
+
+ {
+ std::ifstream is;
+ is.exceptions(
+ is.exceptions()
+ | std::ios_base::failbit
+ | std::ios_base::badbit
+ ); // handle different exceptions as 'file not found', 'permission denied'
+
+ is.open(TEST_DATA_DIRECTORY "/json_nlohmann_tests/all_unicode.json.cbor",
+ std::ios_base::in | std::ios_base::binary);
+ json _;
+ CHECK_NOTHROW(_ = nlohmann::json::from_cbor(is));
+ }
+ }
+
+ SECTION("issue #805 - copy constructor is used with std::initializer_list constructor.")
+ {
+ nocopy n;
+ json j;
+ j = {{"nocopy", n}};
+ CHECK(j["nocopy"]["val"] == 0);
+ }
+
+ SECTION("issue #838 - incorrect parse error with binary data in keys")
+ {
+ std::array<uint8_t, 28> key1 = {{ 103, 92, 117, 48, 48, 48, 55, 92, 114, 215, 126, 214, 95, 92, 34, 174, 40, 71, 38, 174, 40, 71, 38, 223, 134, 247, 127, 0 }};
+ std::string const key1_str(reinterpret_cast<char*>(key1.data()));
+ json const j = key1_str;
+ CHECK_THROWS_WITH_AS(j.dump(), "[json.exception.type_error.316] invalid UTF-8 byte at index 10: 0x7E", json::type_error&);
+ }
+
+#if JSON_USE_IMPLICIT_CONVERSIONS
+ SECTION("issue #843 - converting to array not working")
+ {
+ json j;
+ std::array<int, 4> ar = {{1, 1, 1, 1}};
+ j = ar;
+ ar = j;
+ }
+#endif
+
+ SECTION("issue #894 - invalid RFC6902 copy operation succeeds")
+ {
+ auto model = R"({
+ "one": {
+ "two": {
+ "three": "hello",
+ "four": 42
+ }
+ }
+ })"_json;
+
+ auto p1 = R"([{"op": "move",
+ "from": "/one/two/three",
+ "path": "/a/b/c"}])"_json;
+#if JSON_DIAGNOSTIC_POSITIONS
+ CHECK_THROWS_WITH_AS(model.patch(p1),
+ "[json.exception.out_of_range.403] (bytes 0-158) key 'a' not found", json::out_of_range&);
+#else
+ CHECK_THROWS_WITH_AS(model.patch(p1),
+ "[json.exception.out_of_range.403] key 'a' not found", json::out_of_range&);
+#endif
+
+ auto p2 = R"([{"op": "copy",
+ "from": "/one/two/three",
+ "path": "/a/b/c"}])"_json;
+#if JSON_DIAGNOSTIC_POSITIONS
+ CHECK_THROWS_WITH_AS(model.patch(p2),
+ "[json.exception.out_of_range.403] (bytes 0-158) key 'a' not found", json::out_of_range&);
+#else
+ CHECK_THROWS_WITH_AS(model.patch(p2),
+ "[json.exception.out_of_range.403] key 'a' not found", json::out_of_range&);
+#endif
+ }
+
+ SECTION("issue #961 - incorrect parsing of indefinite length CBOR strings")
+ {
+ std::vector<uint8_t> const v_cbor =
+ {
+ 0x7F,
+ 0x64,
+ 'a', 'b', 'c', 'd',
+ 0x63,
+ '1', '2', '3',
+ 0xFF
+ };
+ const json j = json::from_cbor(v_cbor);
+ CHECK(j == "abcd123");
+ }
+
+ SECTION("issue #962 - Timeout (OSS-Fuzz 6034)")
+ {
+ json _;
+ std::vector<uint8_t> v_ubjson = {'[', '$', 'Z', '#', 'L', 0x78, 0x28, 0x00, 0x68, 0x28, 0x69, 0x69, 0x17};
+ CHECK_THROWS_AS(_ = json::from_ubjson(v_ubjson), json::out_of_range&);
+ //CHECK_THROWS_WITH(json::from_ubjson(v_ubjson),
+ // "[json.exception.out_of_range.408] excessive array size: 8658170730974374167");
+
+ v_ubjson[0] = '{';
+ CHECK_THROWS_AS(_ = json::from_ubjson(v_ubjson), json::out_of_range&);
+ //CHECK_THROWS_WITH(json::from_ubjson(v_ubjson),
+ // "[json.exception.out_of_range.408] excessive object size: 8658170730974374167");
+ }
+
+ SECTION("issue #971 - Add a SAX parser - late bug")
+ {
+ // a JSON text
+ const auto* text = R"(
+ {
+ "Image": {
+ "Width": 800,
+ "Height": 600,
+ "Title": "View from 15th Floor",
+ "Thumbnail": {
+ "Url": "http://www.example.com/image/481989943",
+ "Height": 125,
+ "Width": 100
+ },
+ "Animated" : false,
+ "IDs": [116, 943, 234, 38793]
+ }
+ }
+ )";
+
+ // define parser callback
+ json::parser_callback_t const cb = [](int /*depth*/, json::parse_event_t event, json & parsed)
+ {
+ // skip object elements with key "Thumbnail"
+ return !(event == json::parse_event_t::key && parsed == json("Thumbnail"));
+ };
+
+ // parse (with callback) and serialize JSON
+ const json j_filtered = json::parse(text, cb);
+
+ CHECK(j_filtered == R"({"Image":{"Animated":false,"Height":600,"IDs":[116,943,234,38793], "Title":"View from 15th Floor","Width":800}})"_json);
+ }
+
+ SECTION("issue #972 - Segmentation fault on G++ when trying to assign json string literal to custom json type")
+ {
+ my_json const foo = R"([1, 2, 3])"_json;
+ }
+
+ SECTION("issue #977 - Assigning between different json types")
+ {
+ foo_json lj = ns::foo{3};
+ const ns::foo ff(lj);
+ CHECK(lj.is_object());
+ CHECK(lj.size() == 1);
+ CHECK(lj["x"] == 3);
+ CHECK(ff.x == 3);
+ nlohmann::json const nj = lj; // This line works as expected
+ }
+}
+
+#if !defined(JSON_NOEXCEPTION)
+TEST_CASE("regression tests, exceptions dependent")
+{
+ SECTION("issue #1340 - eof not set on exhausted input stream")
+ {
+ std::stringstream s("{}{}");
+ json j;
+ s >> j;
+ s >> j;
+ CHECK_THROWS_AS(s >> j, json::parse_error const&);
+ CHECK(s.eof());
+ }
+}
+#endif
+
+/////////////////////////////////////////////////////////////////////
+// for #1642
+/////////////////////////////////////////////////////////////////////
+
+// the code below fails with Clang on Windows, so we need to exclude it there
+#if DOCTEST_CLANG && (defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__))
+#else
+template <typename T> class array {};
+template <typename T> class object {};
+template <typename T> class string {};
+template <typename T> class number_integer {};
+template <typename T> class number_unsigned {};
+template <typename T> class number_float {};
+#endif
diff --git a/json4cpp/tests/src/unit-regression2.cpp b/json4cpp/tests/src/unit-regression2.cpp
new file mode 100644
index 0000000000..39dd52378b
--- /dev/null
+++ b/json4cpp/tests/src/unit-regression2.cpp
@@ -0,0 +1,1206 @@
+// __ _____ _____ _____
+// __| | __| | | | 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
+
+// cmake/test.cmake selects the C++ standard versions with which to build a
+// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
+// When using macros that are only defined for particular versions of the standard
+// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
+// version macro in a comment close by, like this:
+// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
+
+#include "doctest_compatibility.h"
+
+// for some reason including this after the json header leads to linker errors with VS 2017...
+#include <locale>
+
+#define JSON_TESTS_PRIVATE
+#include <nlohmann/json.hpp>
+using json = nlohmann::json;
+using ordered_json = nlohmann::ordered_json;
+#ifdef JSON_TEST_NO_GLOBAL_UDLS
+ using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
+#endif
+
+#include <cstdio>
+#include <list>
+#include <type_traits>
+#include <utility>
+
+#ifdef JSON_HAS_CPP_17
+ #include <any>
+ #include <variant>
+#endif
+
+#ifdef JSON_HAS_CPP_17
+ #if __has_include(<optional>)
+ #include <optional>
+ #elif __has_include(<experimental/optional>)
+ #include <experimental/optional>
+ #endif
+
+ /////////////////////////////////////////////////////////////////////
+ // for #4804
+ /////////////////////////////////////////////////////////////////////
+ using json_4804 = nlohmann::basic_json<std::map, // ObjectType
+ std::vector, // ArrayType
+ std::string, // StringType
+ bool, // BooleanType
+ std::int64_t, // NumberIntegerType
+ std::uint64_t, // NumberUnsignedType
+ double, // NumberFloatType
+ std::allocator, // AllocatorType
+ nlohmann::adl_serializer, // JSONSerializer
+ std::vector<std::byte>, // BinaryType
+ void // CustomBaseClass
+ >;
+#endif
+
+#ifdef JSON_HAS_CPP_20
+ #if __has_include(<span>)
+ #include <span>
+ #endif
+#endif
+
+// NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair
+DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
+
+/////////////////////////////////////////////////////////////////////
+// for #1021
+/////////////////////////////////////////////////////////////////////
+
+using float_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>;
+
+/////////////////////////////////////////////////////////////////////
+// for #1647
+/////////////////////////////////////////////////////////////////////
+namespace
+{
+struct NonDefaultFromJsonStruct
+{};
+
+inline bool operator==(NonDefaultFromJsonStruct const& /*unused*/, NonDefaultFromJsonStruct const& /*unused*/)
+{
+ return true;
+}
+
+enum class for_1647
+{
+ one,
+ two
+};
+
+// NOLINTNEXTLINE(misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): this is a false positive
+NLOHMANN_JSON_SERIALIZE_ENUM(for_1647,
+{
+ {for_1647::one, "one"},
+ {for_1647::two, "two"},
+})
+} // namespace
+
+/////////////////////////////////////////////////////////////////////
+// for #1299
+/////////////////////////////////////////////////////////////////////
+
+struct Data
+{
+ Data() = default;
+ Data(std::string a_, std::string b_)
+ : a(std::move(a_))
+ , b(std::move(b_))
+ {}
+ std::string a{}; // NOLINT(readability-redundant-member-init)
+ std::string b{}; // NOLINT(readability-redundant-member-init)
+};
+
+void from_json(const json& j, Data& data); // NOLINT(misc-use-internal-linkage)
+void from_json(const json& j, Data& data)
+{
+ j["a"].get_to(data.a);
+ j["b"].get_to(data.b);
+}
+
+bool operator==(Data const& lhs, Data const& rhs); // NOLINT(misc-use-internal-linkage)
+bool operator==(Data const& lhs, Data const& rhs)
+{
+ return lhs.a == rhs.a && lhs.b == rhs.b;
+}
+
+//bool operator!=(Data const& lhs, Data const& rhs)
+//{
+// return !(lhs == rhs);
+//}
+
+namespace nlohmann
+{
+template<>
+struct adl_serializer<NonDefaultFromJsonStruct>
+{
+ static NonDefaultFromJsonStruct from_json(json const& /*unused*/) noexcept
+ {
+ return {};
+ }
+};
+} // namespace nlohmann
+
+/////////////////////////////////////////////////////////////////////
+// for #1805
+/////////////////////////////////////////////////////////////////////
+
+struct NotSerializableData
+{
+ int mydata;
+ float myfloat;
+};
+
+/////////////////////////////////////////////////////////////////////
+// for #2574
+/////////////////////////////////////////////////////////////////////
+
+struct NonDefaultConstructible
+{
+ explicit NonDefaultConstructible(int a)
+ : x(a)
+ {}
+ int x;
+};
+
+namespace nlohmann
+{
+template<>
+struct adl_serializer<NonDefaultConstructible>
+{
+ static NonDefaultConstructible from_json(json const& j)
+ {
+ return NonDefaultConstructible(j.get<int>());
+ }
+};
+} // namespace nlohmann
+
+/////////////////////////////////////////////////////////////////////
+// for #2824
+/////////////////////////////////////////////////////////////////////
+
+class sax_no_exception : public nlohmann::detail::json_sax_dom_parser<json, nlohmann::detail::string_input_adapter_type>
+{
+ public:
+ explicit sax_no_exception(json& j)
+ : nlohmann::detail::json_sax_dom_parser<json, nlohmann::detail::string_input_adapter_type>(j, false)
+ {}
+
+ static bool parse_error(std::size_t /*position*/, const std::string& /*last_token*/, const json::exception& ex)
+ {
+ error_string = new std::string(ex.what()); // NOLINT(cppcoreguidelines-owning-memory)
+ return false;
+ }
+
+ static std::string* error_string;
+};
+
+std::string* sax_no_exception::error_string = nullptr;
+
+/////////////////////////////////////////////////////////////////////
+// for #2982
+/////////////////////////////////////////////////////////////////////
+
+template<class T>
+class my_allocator : public std::allocator<T>
+{
+ public:
+ using std::allocator<T>::allocator;
+
+ my_allocator() = default;
+ template<class U> my_allocator(const my_allocator<U>& /*unused*/) { }
+
+ template <class U>
+ struct rebind
+ {
+ using other = my_allocator<U>;
+ };
+};
+
+/////////////////////////////////////////////////////////////////////
+// for #3077
+/////////////////////////////////////////////////////////////////////
+
+class FooAlloc
+{};
+
+class Foo
+{
+ public:
+ explicit Foo(const FooAlloc& /* unused */ = FooAlloc()) {}
+
+ bool value = false;
+};
+
+class FooBar
+{
+ public:
+ Foo foo{}; // NOLINT(readability-redundant-member-init)
+};
+
+inline void from_json(const nlohmann::json& j, FooBar& fb) // NOLINT(misc-use-internal-linkage)
+{
+ j.at("value").get_to(fb.foo.value);
+}
+
+/////////////////////////////////////////////////////////////////////
+// for #3171
+/////////////////////////////////////////////////////////////////////
+
+struct for_3171_base // NOLINT(cppcoreguidelines-special-member-functions)
+{
+ for_3171_base(const std::string& /*unused*/ = {}) {}
+ virtual ~for_3171_base();
+
+ for_3171_base(const for_3171_base& other) // NOLINT(hicpp-use-equals-default,modernize-use-equals-default)
+ : str(other.str)
+ {}
+
+ for_3171_base& operator=(const for_3171_base& other)
+ {
+ if (this != &other)
+ {
+ str = other.str;
+ }
+ return *this;
+ }
+
+ for_3171_base(for_3171_base&& other) noexcept
+ : str(std::move(other.str))
+ {}
+
+ for_3171_base& operator=(for_3171_base&& other) noexcept
+ {
+ if (this != &other)
+ {
+ str = std::move(other.str);
+ }
+ return *this;
+ }
+
+ virtual void _from_json(const json& j)
+ {
+ j.at("str").get_to(str);
+ }
+
+ std::string str{}; // NOLINT(readability-redundant-member-init)
+};
+
+for_3171_base::~for_3171_base() = default;
+
+struct for_3171_derived : public for_3171_base
+{
+ for_3171_derived() = default;
+ ~for_3171_derived() override;
+ explicit for_3171_derived(const std::string& /*unused*/) { }
+
+ for_3171_derived(const for_3171_derived& other) // NOLINT(hicpp-use-equals-default,modernize-use-equals-default)
+ : for_3171_base(other)
+ {}
+
+ for_3171_derived& operator=(const for_3171_derived& other)
+ {
+ if (this != &other)
+ {
+ for_3171_base::operator=(other); // Call base class assignment operator
+ }
+ return *this;
+ }
+
+ for_3171_derived(for_3171_derived&& other) noexcept
+ : for_3171_base(std::move(other))
+ {}
+
+ for_3171_derived& operator=(for_3171_derived&& other) noexcept
+ {
+ if (this != &other)
+ {
+ for_3171_base::operator=(std::move(other)); // Call base class move assignment operator
+ }
+ return *this;
+ }
+};
+
+for_3171_derived::~for_3171_derived() = default;
+
+inline void from_json(const json& j, for_3171_base& tb) // NOLINT(misc-use-internal-linkage)
+{
+ tb._from_json(j);
+}
+
+/////////////////////////////////////////////////////////////////////
+// for #3312
+/////////////////////////////////////////////////////////////////////
+
+#ifdef JSON_HAS_CPP_20
+struct for_3312
+{
+ std::string name;
+};
+
+inline void from_json(const json& j, for_3312& obj) // NOLINT(misc-use-internal-linkage)
+{
+ j.at("name").get_to(obj.name);
+}
+#endif
+
+/////////////////////////////////////////////////////////////////////
+// for #3204
+/////////////////////////////////////////////////////////////////////
+
+struct for_3204_foo
+{
+ for_3204_foo() = default;
+ explicit for_3204_foo(std::string /*unused*/) {} // NOLINT(performance-unnecessary-value-param)
+};
+
+struct for_3204_bar
+{
+ enum constructed_from_t // NOLINT(cppcoreguidelines-use-enum-class)
+ {
+ constructed_from_none = 0,
+ constructed_from_foo = 1,
+ constructed_from_json = 2
+ };
+
+ explicit for_3204_bar(std::function<void(for_3204_foo)> /*unused*/) noexcept // NOLINT(performance-unnecessary-value-param)
+ : constructed_from(constructed_from_foo) {}
+ explicit for_3204_bar(std::function<void(json)> /*unused*/) noexcept // NOLINT(performance-unnecessary-value-param)
+ : constructed_from(constructed_from_json) {}
+
+ constructed_from_t constructed_from = constructed_from_none;
+};
+
+/////////////////////////////////////////////////////////////////////
+// for #3333
+/////////////////////////////////////////////////////////////////////
+
+struct for_3333 final
+{
+ for_3333(int x_ = 0, int y_ = 0) : x(x_), y(y_) {}
+
+ template <class T>
+ for_3333(const T& /*unused*/)
+ {
+ CHECK(false);
+ }
+
+ int x = 0;
+ int y = 0;
+};
+
+template <>
+inline for_3333::for_3333(const json& j)
+ : for_3333(j.value("x", 0), j.value("y", 0))
+{}
+
+/////////////////////////////////////////////////////////////////////
+// for #3810
+/////////////////////////////////////////////////////////////////////
+
+struct Example_3810
+{
+ int bla{};
+
+ Example_3810() = default;
+};
+
+NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Example_3810, bla) // NOLINT(misc-use-internal-linkage)
+
+/////////////////////////////////////////////////////////////////////
+// for #4740
+/////////////////////////////////////////////////////////////////////
+
+#ifdef JSON_HAS_CPP_17
+struct Example_4740
+{
+ std::optional<std::string> host = std::nullopt;
+ std::optional<int> port = std::nullopt;
+ NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Example_4740, host, port)
+};
+#endif
+
+TEST_CASE("regression tests 2")
+{
+ SECTION("issue #1001 - Fix memory leak during parser callback")
+ {
+ const auto* geojsonExample = R"(
+ { "type": "FeatureCollection",
+ "features": [
+ { "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [102.0, 0.5]},
+ "properties": {"prop0": "value0"}
+ },
+ { "type": "Feature",
+ "geometry": {
+ "type": "LineString",
+ "coordinates": [
+ [102.0, 0.0], [103.0, 1.0], [104.0, 0.0], [105.0, 1.0]
+ ]
+ },
+ "properties": {
+ "prop0": "value0",
+ "prop1": 0.0
+ }
+ },
+ { "type": "Feature",
+ "geometry": {
+ "type": "Polygon",
+ "coordinates": [
+ [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0],
+ [100.0, 1.0], [100.0, 0.0] ]
+ ]
+ },
+ "properties": {
+ "prop0": "value0",
+ "prop1": {"this": "that"}
+ }
+ }
+ ]
+ })";
+
+ const json::parser_callback_t cb = [&](int /*level*/, json::parse_event_t event, json & parsed) noexcept
+ {
+ // skip uninteresting events
+ if (event == json::parse_event_t::value && !parsed.is_primitive())
+ {
+ return false;
+ }
+
+ switch (event)
+ {
+ case json::parse_event_t::key:
+ {
+ return true;
+ }
+ case json::parse_event_t::value:
+ {
+ return false;
+ }
+ case json::parse_event_t::object_start:
+ {
+ return true;
+ }
+ case json::parse_event_t::object_end:
+ {
+ return false;
+ }
+ case json::parse_event_t::array_start:
+ {
+ return true;
+ }
+ case json::parse_event_t::array_end:
+ {
+ return false;
+ }
+
+ default:
+ {
+ return true;
+ }
+ }
+ };
+
+ auto j = json::parse(geojsonExample, cb, true);
+ CHECK(j == json());
+ }
+
+ SECTION("issue #1021 - to/from_msgpack only works with standard typization")
+ {
+ float_json j = 1000.0;
+ CHECK(float_json::from_cbor(float_json::to_cbor(j)) == j);
+ CHECK(float_json::from_msgpack(float_json::to_msgpack(j)) == j);
+ CHECK(float_json::from_ubjson(float_json::to_ubjson(j)) == j);
+
+ float_json j2 = {1000.0, 2000.0, 3000.0};
+ CHECK(float_json::from_ubjson(float_json::to_ubjson(j2, true, true)) == j2);
+ }
+
+ SECTION("issue #1045 - Using STL algorithms with JSON containers with expected results?")
+ {
+ json diffs = nlohmann::json::array();
+ json m1{{"key1", 42}};
+ json m2{{"key2", 42}};
+ auto p1 = m1.items();
+ auto p2 = m2.items();
+
+ using it_type = decltype(p1.begin());
+
+ std::set_difference(
+ p1.begin(),
+ p1.end(),
+ p2.begin(),
+ p2.end(),
+ std::inserter(diffs, diffs.end()),
+ [&](const it_type & e1, const it_type & e2) -> bool
+ {
+ using comper_pair = std::pair<std::string, decltype(e1.value())>; // Trying to avoid unneeded copy
+ return comper_pair(e1.key(), e1.value()) < comper_pair(e2.key(), e2.value()); // Using pair comper
+ });
+
+ CHECK(diffs.size() == 1); // Note the change here, was 2
+ }
+
+#ifdef JSON_HAS_CPP_17
+ SECTION("issue #1292 - Serializing std::variant causes stack overflow")
+ {
+ static_assert(!std::is_constructible<json, std::variant<int, float>>::value, "unexpected value");
+ }
+#endif
+
+ SECTION("issue #1299 - compile error in from_json converting to container "
+ "with std::pair")
+ {
+ const json j =
+ {
+ {"1", {{"a", "testa_1"}, {"b", "testb_1"}}},
+ {"2", {{"a", "testa_2"}, {"b", "testb_2"}}},
+ {"3", {{"a", "testa_3"}, {"b", "testb_3"}}},
+ };
+
+ const std::map<std::string, Data> expected
+ {
+ {"1", {"testa_1", "testb_1"}},
+ {"2", {"testa_2", "testb_2"}},
+ {"3", {"testa_3", "testb_3"}},
+ };
+ const auto data = j.get<decltype(expected)>();
+ CHECK(expected == data);
+ }
+
+ SECTION("issue #1445 - buffer overflow in dumping invalid utf-8 strings")
+ {
+ SECTION("a bunch of -1, ensure_ascii=true")
+ {
+ const auto length = 300;
+
+ json dump_test;
+ dump_test["1"] = std::string(length, static_cast<std::string::value_type>(-1));
+
+ std::string expected = R"({"1":")";
+ for (int i = 0; i < length; ++i)
+ {
+ expected += "\\ufffd";
+ }
+ expected += "\"}";
+
+ auto s = dump_test.dump(-1, ' ', true, nlohmann::json::error_handler_t::replace);
+ CHECK(s == expected);
+ }
+ SECTION("a bunch of -2, ensure_ascii=false")
+ {
+ const auto length = 500;
+
+ json dump_test;
+ dump_test["1"] = std::string(length, static_cast<std::string::value_type>(-2));
+
+ std::string expected = R"({"1":")";
+ for (int i = 0; i < length; ++i)
+ {
+ expected += "\xEF\xBF\xBD";
+ }
+ expected += "\"}";
+
+ auto s = dump_test.dump(-1, ' ', false, nlohmann::json::error_handler_t::replace);
+ CHECK(s == expected);
+ }
+ SECTION("test case in issue #1445")
+ {
+ nlohmann::json dump_test;
+ const std::array<int, 108> data =
+ {
+ {109, 108, 103, 125, -122, -53, 115, 18, 3, 0, 102, 19, 1, 15, -110, 13, -3, -1, -81, 32, 2, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -80, 2, 0, 0, 96, -118, 46, -116, 46, 109, -84, -87, 108, 14, 109, -24, -83, 13, -18, -51, -83, -52, -115, 14, 6, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 3, 0, 0, 0, 35, -74, -73, 55, 57, -128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, -96, -54, -28, -26}
+ };
+ std::string s;
+ for (const int i : data)
+ {
+ s += static_cast<char>(i);
+ }
+ dump_test["1"] = s;
+ dump_test.dump(-1, ' ', true, nlohmann::json::error_handler_t::replace);
+ }
+ }
+
+ SECTION("issue #1447 - Integer Overflow (OSS-Fuzz 12506)")
+ {
+ const json j = json::parse("[-9223372036854775808]");
+ CHECK(j.dump() == "[-9223372036854775808]");
+ }
+
+ SECTION("issue #1708 - minimum value of int64_t can be outputted")
+ {
+ constexpr auto smallest = (std::numeric_limits<int64_t>::min)();
+ const json j = smallest;
+ CHECK(j.dump() == std::to_string(smallest));
+ }
+
+ SECTION("issue #1727 - Contains with non-const lvalue json_pointer picks the wrong overload")
+ {
+ const json j = {{"root", {{"settings", {{"logging", true}}}}}};
+
+ auto jptr1 = "/root/settings/logging"_json_pointer;
+ auto jptr2 = json::json_pointer{"/root/settings/logging"};
+
+ CHECK(j.contains(jptr1));
+ CHECK(j.contains(jptr2));
+ }
+
+ SECTION("issue #1647 - compile error when deserializing enum if both non-default from_json and non-member operator== exists for other type")
+ {
+ // does not compile on ICPC when targeting C++20
+#if !(defined(__INTEL_COMPILER) && __cplusplus >= 202000)
+ {
+ const json j;
+ const NonDefaultFromJsonStruct x(j);
+ NonDefaultFromJsonStruct y;
+ CHECK(x == y);
+ }
+#endif
+
+ auto val = nlohmann::json("one").get<for_1647>();
+ CHECK(val == for_1647::one);
+ const json j = val;
+ }
+
+ SECTION("issue #1715 - json::from_cbor does not respect allow_exceptions = false when input is string literal")
+ {
+ SECTION("string literal")
+ {
+ const json cbor = json::from_cbor("B", true, false);
+ CHECK(cbor.is_discarded());
+ }
+
+ SECTION("string array")
+ {
+ const std::array<char, 2> input = {{'B', 0x00}};
+ const json cbor = json::from_cbor(input, true, false);
+ CHECK(cbor.is_discarded());
+ }
+
+ SECTION("std::string")
+ {
+ const json cbor = json::from_cbor(std::string("B"), true, false);
+ CHECK(cbor.is_discarded());
+ }
+ }
+
+ SECTION("issue #1805 - A pair<T1, T2> is json constructible only if T1 and T2 are json constructible")
+ {
+ static_assert(!std::is_constructible<json, std::pair<std::string, NotSerializableData>>::value, "unexpected result");
+ static_assert(!std::is_constructible<json, std::pair<NotSerializableData, std::string>>::value, "unexpected result");
+ static_assert(std::is_constructible<json, std::pair<int, std::string>>::value, "unexpected result");
+ }
+ SECTION("issue #1825 - A tuple<Args..> is json constructible only if all T in Args are json constructible")
+ {
+ static_assert(!std::is_constructible<json, std::tuple<std::string, NotSerializableData>>::value, "unexpected result");
+ static_assert(!std::is_constructible<json, std::tuple<NotSerializableData, std::string>>::value, "unexpected result");
+ static_assert(std::is_constructible<json, std::tuple<int, std::string>>::value, "unexpected result");
+ }
+
+ SECTION("issue #1983 - JSON patch diff for op=add formation is not as per standard (RFC 6902)")
+ {
+ const auto source = R"({ "foo": [ "1", "2" ] })"_json;
+ const auto target = R"({"foo": [ "1", "2", "3" ]})"_json;
+ const auto result = json::diff(source, target);
+ CHECK(result.dump() == R"([{"op":"add","path":"/foo/-","value":"3"}])");
+ }
+
+ SECTION("issue #2067 - cannot serialize binary data to text JSON")
+ {
+ const std::array<unsigned char, 23> data = {{0x81, 0xA4, 0x64, 0x61, 0x74, 0x61, 0xC4, 0x0F, 0x33, 0x30, 0x30, 0x32, 0x33, 0x34, 0x30, 0x31, 0x30, 0x37, 0x30, 0x35, 0x30, 0x31, 0x30}};
+ const json j = json::from_msgpack(data.data(), data.size());
+ CHECK_NOTHROW(
+ j.dump(4, // Indent
+ ' ', // Indent char
+ false, // Ensure ascii
+ json::error_handler_t::strict // Error
+ ));
+ }
+
+ SECTION("PR #2181 - regression bug with lvalue")
+ {
+ // see https://github.com/nlohmann/json/pull/2181#issuecomment-653326060
+ const json j{{"x", "test"}};
+ const std::string defval = "default value";
+ auto val = j.value("x", defval); // NOLINT(bugprone-unused-local-non-trivial-variable)
+ auto val2 = j.value("y", defval); // NOLINT(bugprone-unused-local-non-trivial-variable)
+ }
+
+ SECTION("issue #2293 - eof doesn't cause parsing to stop")
+ {
+ const std::vector<uint8_t> data =
+ {
+ 0x7B,
+ 0x6F,
+ 0x62,
+ 0x6A,
+ 0x65,
+ 0x63,
+ 0x74,
+ 0x20,
+ 0x4F,
+ 0x42
+ };
+ const json result = json::from_cbor(data, true, false);
+ CHECK(result.is_discarded());
+ }
+
+ SECTION("issue #2315 - json.update and vector<pair>does not work with ordered_json")
+ {
+ nlohmann::ordered_json jsonAnimals = {{"animal", "dog"}};
+ const nlohmann::ordered_json jsonCat = {{"animal", "cat"}};
+ jsonAnimals.update(jsonCat);
+ CHECK(jsonAnimals["animal"] == "cat");
+
+ auto jsonAnimals_parsed = nlohmann::ordered_json::parse(jsonAnimals.dump());
+ CHECK(jsonAnimals == jsonAnimals_parsed);
+
+ const std::vector<std::pair<std::string, int64_t>> intData = {std::make_pair("aaaa", 11),
+ std::make_pair("bbb", 222)
+ };
+ nlohmann::ordered_json jsonObj;
+ for (const auto& data : intData)
+ {
+ jsonObj[data.first] = data.second;
+ }
+ CHECK(jsonObj["aaaa"] == 11);
+ CHECK(jsonObj["bbb"] == 222);
+ }
+
+ SECTION("issue #2330 - ignore_comment=true fails on multiple consecutive lines starting with comments")
+ {
+ const std::string ss = "//\n//\n{\n}\n";
+ const json j = json::parse(ss, nullptr, true, true);
+ CHECK(j.dump() == "{}");
+ }
+
+#ifdef JSON_HAS_CPP_20
+#ifndef _LIBCPP_VERSION // see https://github.com/nlohmann/json/issues/4490
+#if __has_include(<span>)
+ SECTION("issue #2546 - parsing containers of std::byte")
+ {
+ const char DATA[] = R"("Hello, world!")"; // NOLINT(misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
+ const auto s = std::as_bytes(std::span(DATA));
+ const json j = json::parse(s);
+ CHECK(j.dump() == "\"Hello, world!\"");
+ }
+#endif
+#endif
+#endif
+
+ SECTION("issue #2574 - Deserialization to std::array, std::pair, and std::tuple with non-default constructable types fails")
+ {
+ SECTION("std::array")
+ {
+ {
+ const json j = {7, 4};
+ auto arr = j.get<std::array<NonDefaultConstructible, 2>>();
+ CHECK(arr[0].x == 7);
+ CHECK(arr[1].x == 4);
+ }
+
+ {
+ const json j = 7;
+ CHECK_THROWS_AS((j.get<std::array<NonDefaultConstructible, 1>>()), json::type_error);
+ }
+ }
+
+ SECTION("std::pair")
+ {
+ {
+ const json j = {3, 8};
+ auto p = j.get<std::pair<NonDefaultConstructible, NonDefaultConstructible>>();
+ CHECK(p.first.x == 3);
+ CHECK(p.second.x == 8);
+ }
+
+ {
+ const json j = {4, 1};
+ auto p = j.get<std::pair<int, NonDefaultConstructible>>();
+ CHECK(p.first == 4);
+ CHECK(p.second.x == 1);
+ }
+
+ {
+ const json j = {6, 7};
+ auto p = j.get<std::pair<NonDefaultConstructible, int>>();
+ CHECK(p.first.x == 6);
+ CHECK(p.second == 7);
+ }
+
+ {
+ const json j = 7;
+ CHECK_THROWS_AS((j.get<std::pair<NonDefaultConstructible, int>>()), json::type_error);
+ }
+ }
+
+ SECTION("std::tuple")
+ {
+ {
+ const json j = {9};
+ auto t = j.get<std::tuple<NonDefaultConstructible>>();
+ CHECK(std::get<0>(t).x == 9);
+ }
+
+ {
+ const json j = {9, 8, 7};
+ auto t = j.get<std::tuple<NonDefaultConstructible, int, NonDefaultConstructible>>();
+ CHECK(std::get<0>(t).x == 9);
+ CHECK(std::get<1>(t) == 8);
+ CHECK(std::get<2>(t).x == 7);
+ }
+
+ {
+ const json j = 7;
+ CHECK_THROWS_AS((j.get<std::tuple<NonDefaultConstructible>>()), json::type_error);
+ }
+ }
+ }
+
+ SECTION("issue #4530 - Serialization of empty tuple")
+ {
+ const auto source_tuple = std::tuple<>();
+ const nlohmann::json j = source_tuple;
+
+ CHECK(j.get<decltype(source_tuple)>() == source_tuple);
+ CHECK("[]" == j.dump());
+ }
+
+ SECTION("issue #2865 - ASAN detects memory leaks")
+ {
+ // the code below is expected to not leak memory
+ {
+ nlohmann::json o;
+ const std::string s = "bar";
+
+ nlohmann::to_json(o["foo"], s);
+
+ nlohmann::json p = o;
+
+ // call to_json with a non-null JSON value
+ nlohmann::to_json(p["foo"], s);
+ }
+
+ {
+ nlohmann::json o;
+ const std::string s = "bar";
+
+ nlohmann::to_json(o["foo"], s);
+
+ // call to_json with a non-null JSON value
+ nlohmann::to_json(o["foo"], s);
+ }
+ }
+
+ SECTION("issue #2824 - encoding of json::exception::what()")
+ {
+ json j;
+ sax_no_exception sax(j);
+
+ CHECK(!json::sax_parse("xyz", &sax));
+ CHECK(*sax_no_exception::error_string == "[json.exception.parse_error.101] parse error at line 1, column 1: syntax error while parsing value - invalid literal; last read: 'x'");
+ delete sax_no_exception::error_string; // NOLINT(cppcoreguidelines-owning-memory)
+ }
+
+ SECTION("issue #2825 - Properly constrain the basic_json conversion operator")
+ {
+ static_assert(std::is_copy_assignable<nlohmann::ordered_json>::value, "ordered_json must be copy assignable");
+ }
+
+ SECTION("issue #2958 - Inserting in unordered json using a pointer retains the leading slash")
+ {
+ const std::string p = "/root";
+
+ json test1;
+ test1[json::json_pointer(p)] = json::object();
+ CHECK(test1.dump() == "{\"root\":{}}");
+
+ ordered_json test2;
+ test2[ordered_json::json_pointer(p)] = json::object();
+ CHECK(test2.dump() == "{\"root\":{}}");
+
+ // json::json_pointer and ordered_json::json_pointer are the same type; behave as above
+ ordered_json test3;
+ test3[json::json_pointer(p)] = json::object();
+ CHECK(std::is_same<json::json_pointer::string_t, ordered_json::json_pointer::string_t>::value);
+ CHECK(test3.dump() == "{\"root\":{}}");
+ }
+
+ SECTION("issue #2982 - to_{binary format} does not provide a mechanism for specifying a custom allocator for the returned type")
+ {
+ std::vector<std::uint8_t, my_allocator<std::uint8_t>> my_vector;
+ const json j = {1, 2, 3, 4};
+ json::to_cbor(j, my_vector);
+ json k = json::from_cbor(my_vector);
+ CHECK(j == k);
+ }
+
+#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
+ // JSON_HAS_CPP_17 (do not remove; see note at top of file)
+ SECTION("issue #3070 - Version 3.10.3 breaks backward-compatibility with 3.10.2 ")
+ {
+ nlohmann::detail::std_fs::path text_path("/tmp/text.txt");
+ const json j(text_path);
+
+ const auto j_path = j.get<nlohmann::detail::std_fs::path>();
+ CHECK(j_path == text_path);
+
+#if DOCTEST_CLANG || DOCTEST_GCC >= DOCTEST_COMPILER(8, 4, 0)
+ // only known to work on Clang and GCC >=8.4
+ CHECK_THROWS_WITH_AS(nlohmann::detail::std_fs::path(json(1)), "[json.exception.type_error.302] type must be string, but is number", json::type_error);
+#endif
+ }
+#endif
+
+ SECTION("issue #3077 - explicit constructor with default does not compile")
+ {
+ json j;
+ j[0]["value"] = true;
+ std::vector<FooBar> foo;
+ j.get_to(foo);
+ }
+
+ SECTION("issue #3108 - ordered_json doesn't support range based erase")
+ {
+ ordered_json j = {1, 2, 2, 4};
+
+ auto last = std::unique(j.begin(), j.end());
+ j.erase(last, j.end());
+
+ CHECK(j.dump() == "[1,2,4]");
+
+ j.erase(std::remove_if(j.begin(), j.end(), [](const ordered_json & val)
+ {
+ return val == 2;
+ }), j.end());
+
+ CHECK(j.dump() == "[1,4]");
+ }
+
+ SECTION("issue #3343 - json and ordered_json are not interchangeable")
+ {
+ json::object_t jobj({ { "product", "one" } });
+ ordered_json::object_t ojobj({{"product", "one"}});
+
+ auto jit = jobj.begin();
+ auto ojit = ojobj.begin();
+
+ CHECK(jit->first == ojit->first);
+ CHECK(jit->second.get<std::string>() == ojit->second.get<std::string>());
+ }
+
+ SECTION("issue #3171 - if class is_constructible from std::string wrong from_json overload is being selected, compilation failed")
+ {
+ const json j{{ "str", "value"}};
+
+ // failed with: error: no match for ‘operator=’ (operand types are ‘for_3171_derived’ and ‘const nlohmann::basic_json<>::string_t’
+ // {aka ‘const std::__cxx11::basic_string<char>’})
+ // s = *j.template get_ptr<const typename BasicJsonType::string_t*>();
+ auto td = j.get<for_3171_derived>();
+
+ CHECK(td.str == "value");
+ }
+
+#ifdef JSON_HAS_CPP_20
+ SECTION("issue #3312 - Parse to custom class from unordered_json breaks on G++11.2.0 with C++20")
+ {
+ // see test for #3171
+ const ordered_json j = {{"name", "class"}};
+ for_3312 obj{};
+
+ j.get_to(obj);
+
+ CHECK(obj.name == "class");
+ }
+#endif
+
+#if defined(JSON_HAS_CPP_17) && JSON_USE_IMPLICIT_CONVERSIONS
+ SECTION("issue #3428 - Error occurred when converting nlohmann::json to std::any")
+ {
+ const json j;
+ const std::any a1 = j;
+ std::any&& a2 = j;
+
+ CHECK(a1.type() == typeid(j));
+ CHECK(a2.type() == typeid(j));
+ }
+#endif
+
+ SECTION("issue #3204 - ambiguous regression")
+ {
+ const for_3204_bar bar_from_foo([](for_3204_foo) noexcept {}); // NOLINT(performance-unnecessary-value-param)
+ const for_3204_bar bar_from_json([](json) noexcept {}); // NOLINT(performance-unnecessary-value-param)
+
+ CHECK(bar_from_foo.constructed_from == for_3204_bar::constructed_from_foo);
+ CHECK(bar_from_json.constructed_from == for_3204_bar::constructed_from_json);
+ }
+
+ SECTION("issue #3333 - Ambiguous conversion from nlohmann::basic_json<> to custom class")
+ {
+ const json j
+ {
+ {"x", 1},
+ {"y", 2}
+ };
+ const for_3333 p = j;
+
+ CHECK(p.x == 1);
+ CHECK(p.y == 2);
+ }
+
+ SECTION("issue #3810 - ordered_json doesn't support construction from C array of custom type")
+ {
+ Example_3810 states[45]; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
+
+ // fix "not used" warning
+ states[0].bla = 1;
+
+ const auto* const expected = R"([{"bla":1},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0}])";
+
+ // This works:
+ nlohmann::json j;
+ j["test"] = states;
+ CHECK(j["test"].dump() == expected);
+
+ // This doesn't compile:
+ nlohmann::ordered_json oj;
+ oj["test"] = states;
+ CHECK(oj["test"].dump() == expected);
+ }
+
+#ifdef JSON_HAS_CPP_17
+ SECTION("issue #4740 - build issue with std::optional")
+ {
+ const auto t1 = Example_4740();
+ const auto j1 = nlohmann::json(t1);
+ CHECK(j1.dump() == "{\"host\":null,\"port\":null}");
+ const auto t2 = j1.get<Example_4740>();
+ CHECK(!t2.host.has_value());
+ CHECK(!t2.port.has_value());
+
+ // improve coverage
+ auto t3 = Example_4740();
+ t3.port = 80;
+ t3.host = "example.com";
+ const auto j2 = nlohmann::json(t3);
+ CHECK(j2.dump() == "{\"host\":\"example.com\",\"port\":80}");
+ const auto t4 = j2.get<Example_4740>();
+ CHECK(t4.host.has_value());
+ CHECK(t4.port.has_value());
+ }
+#endif
+
+#if !defined(_MSVC_LANG)
+ // MSVC returns garbage on invalid enum values, so this test is excluded
+ // there.
+ SECTION("issue #4762 - json exception 302 with unhelpful explanation : type must be number, but is number")
+ {
+ // In #4762, the main issue was that a json object with an invalid type
+ // returned "number" as type_name(), because this was the default case.
+ // This test makes sure we now return "invalid" instead.
+ json j;
+ j.m_data.m_type = static_cast<json::value_t>(100); // NOLINT(clang-analyzer-optin.core.EnumCastOutOfRange)
+ CHECK(j.type_name() == "invalid");
+ }
+#endif
+
+#ifdef JSON_HAS_CPP_17
+ SECTION("issue #4804: from_cbor incompatible with std::vector<std::byte> as binary_t")
+ {
+ const std::vector<std::uint8_t> data = {0x80};
+ const auto decoded = json_4804::from_cbor(data);
+ CHECK((decoded == json_4804::array()));
+ }
+
+ SECTION("issue #5046 - implicit conversion of return json to std::optional no longer implicit")
+ {
+ const json jval{};
+ auto GetValue = [](const json & valRoot) -> std::optional<json>
+ {
+ if (valRoot.contains("default"))
+ {
+ return valRoot.at("default");
+ }
+ return std::nullopt;
+ };
+ auto result = GetValue(jval);
+ CHECK(!result.has_value());
+ }
+#endif
+}
+
+TEST_CASE_TEMPLATE("issue #4798 - nlohmann::json::to_msgpack() encode float NaN as double", T, double, float) // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
+{
+ // With issue #4798, we encode NaN, infinity, and -infinity as float instead
+ // of double to allow for smaller encodings.
+ const json jx = std::numeric_limits<T>::quiet_NaN();
+ const json jy = std::numeric_limits<T>::infinity();
+ const json jz = -std::numeric_limits<T>::infinity();
+
+ /////////////////////////////////////////////////////////////////////////
+ // MessagePack
+ /////////////////////////////////////////////////////////////////////////
+
+ // expected MessagePack values
+ const std::vector<std::uint8_t> msgpack_x = {{0xCA, 0x7F, 0xC0, 0x00, 0x00}};
+ const std::vector<std::uint8_t> msgpack_y = {{0xCA, 0x7F, 0x80, 0x00, 0x00}};
+ const std::vector<std::uint8_t> msgpack_z = {{0xCA, 0xFF, 0x80, 0x00, 0x00}};
+
+ CHECK(json::to_msgpack(jx) == msgpack_x);
+ CHECK(json::to_msgpack(jy) == msgpack_y);
+ CHECK(json::to_msgpack(jz) == msgpack_z);
+
+ CHECK(std::isnan(json::from_msgpack(msgpack_x).get<T>()));
+ CHECK(json::from_msgpack(msgpack_y).get<T>() == std::numeric_limits<T>::infinity());
+ CHECK(json::from_msgpack(msgpack_z).get<T>() == -std::numeric_limits<T>::infinity());
+
+ // Make sure the other MessagePakc encodings for NaN, infinity, and
+ // -infinity are still supported.
+ const std::vector<std::uint8_t> msgpack_x_2 = {{0xCB, 0x7F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ const std::vector<std::uint8_t> msgpack_y_2 = {{0xCB, 0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ const std::vector<std::uint8_t> msgpack_z_2 = {{0xCB, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CHECK(std::isnan(json::from_msgpack(msgpack_x_2).get<T>()));
+ CHECK(json::from_msgpack(msgpack_y_2).get<T>() == std::numeric_limits<T>::infinity());
+ CHECK(json::from_msgpack(msgpack_z_2).get<T>() == -std::numeric_limits<T>::infinity());
+
+ /////////////////////////////////////////////////////////////////////////
+ // CBOR
+ /////////////////////////////////////////////////////////////////////////
+
+ // expected CBOR values
+ const std::vector<std::uint8_t> cbor_x = {{0xF9, 0x7E, 0x00}};
+ const std::vector<std::uint8_t> cbor_y = {{0xF9, 0x7C, 0x00}};
+ const std::vector<std::uint8_t> cbor_z = {{0xF9, 0xfC, 0x00}};
+
+ CHECK(json::to_cbor(jx) == cbor_x);
+ CHECK(json::to_cbor(jy) == cbor_y);
+ CHECK(json::to_cbor(jz) == cbor_z);
+
+ CHECK(std::isnan(json::from_cbor(cbor_x).get<T>()));
+ CHECK(json::from_cbor(cbor_y).get<T>() == std::numeric_limits<T>::infinity());
+ CHECK(json::from_cbor(cbor_z).get<T>() == -std::numeric_limits<T>::infinity());
+
+ // Make sure the other CBOR encodings for NaN, infinity, and -infinity are
+ // still supported.
+ const std::vector<std::uint8_t> cbor_x_2 = {{0xFA, 0x7F, 0xC0, 0x00, 0x00}};
+ const std::vector<std::uint8_t> cbor_y_2 = {{0xFA, 0x7F, 0x80, 0x00, 0x00}};
+ const std::vector<std::uint8_t> cbor_z_2 = {{0xFA, 0xFF, 0x80, 0x00, 0x00}};
+ const std::vector<std::uint8_t> cbor_x_3 = {{0xFB, 0x7F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ const std::vector<std::uint8_t> cbor_y_3 = {{0xFB, 0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ const std::vector<std::uint8_t> cbor_z_3 = {{0xFB, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+ CHECK(std::isnan(json::from_cbor(cbor_x_2).get<T>()));
+ CHECK(json::from_cbor(cbor_y_2).get<T>() == std::numeric_limits<T>::infinity());
+ CHECK(json::from_cbor(cbor_z_2).get<T>() == -std::numeric_limits<T>::infinity());
+ CHECK(std::isnan(json::from_cbor(cbor_x_3).get<T>()));
+ CHECK(json::from_cbor(cbor_y_3).get<T>() == std::numeric_limits<T>::infinity());
+ CHECK(json::from_cbor(cbor_z_3).get<T>() == -std::numeric_limits<T>::infinity());
+}
+
+DOCTEST_CLANG_SUPPRESS_WARNING_POP
diff --git a/json4cpp/tests/src/unit-serialization.cpp b/json4cpp/tests/src/unit-serialization.cpp
new file mode 100644
index 0000000000..5c2ab81482
--- /dev/null
+++ b/json4cpp/tests/src/unit-serialization.cpp
@@ -0,0 +1,297 @@
+// __ _____ _____ _____
+// __| | __| | | | 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;
+
+#include <sstream>
+#include <iomanip>
+
+TEST_CASE("serialization")
+{
+ SECTION("operator<<")
+ {
+ SECTION("no given width")
+ {
+ std::stringstream ss;
+ const json j = {"foo", 1, 2, 3, false, {{"one", 1}}};
+ ss << j;
+ CHECK(ss.str() == "[\"foo\",1,2,3,false,{\"one\":1}]");
+ }
+
+ SECTION("given width")
+ {
+ std::stringstream ss;
+ const json j = {"foo", 1, 2, 3, false, {{"one", 1}}};
+ ss << std::setw(4) << j;
+ CHECK(ss.str() ==
+ "[\n \"foo\",\n 1,\n 2,\n 3,\n false,\n {\n \"one\": 1\n }\n]");
+ }
+
+ SECTION("given fill")
+ {
+ std::stringstream ss;
+ const json j = {"foo", 1, 2, 3, false, {{"one", 1}}};
+ ss << std::setw(1) << std::setfill('\t') << j;
+ CHECK(ss.str() ==
+ "[\n\t\"foo\",\n\t1,\n\t2,\n\t3,\n\tfalse,\n\t{\n\t\t\"one\": 1\n\t}\n]");
+ }
+ }
+
+ SECTION("operator>>")
+ {
+ SECTION("no given width")
+ {
+ std::stringstream ss;
+ const json j = {"foo", 1, 2, 3, false, {{"one", 1}}};
+ j >> ss;
+ CHECK(ss.str() == "[\"foo\",1,2,3,false,{\"one\":1}]");
+ }
+
+ SECTION("given width")
+ {
+ std::stringstream ss;
+ const json j = {"foo", 1, 2, 3, false, {{"one", 1}}};
+ ss.width(4);
+ j >> ss;
+ CHECK(ss.str() ==
+ "[\n \"foo\",\n 1,\n 2,\n 3,\n false,\n {\n \"one\": 1\n }\n]");
+ }
+
+ SECTION("given fill")
+ {
+ std::stringstream ss;
+ const json j = {"foo", 1, 2, 3, false, {{"one", 1}}};
+ ss.width(1);
+ ss.fill('\t');
+ j >> ss;
+ CHECK(ss.str() ==
+ "[\n\t\"foo\",\n\t1,\n\t2,\n\t3,\n\tfalse,\n\t{\n\t\t\"one\": 1\n\t}\n]");
+ }
+ }
+
+ SECTION("dump")
+ {
+ SECTION("invalid character")
+ {
+ const json j = "ä\xA9ü";
+
+ CHECK_THROWS_WITH_AS(j.dump(), "[json.exception.type_error.316] invalid UTF-8 byte at index 2: 0xA9", json::type_error&);
+ CHECK_THROWS_WITH_AS(j.dump(1, ' ', false, json::error_handler_t::strict), "[json.exception.type_error.316] invalid UTF-8 byte at index 2: 0xA9", json::type_error&);
+ CHECK(j.dump(-1, ' ', false, json::error_handler_t::ignore) == "\"äü\"");
+ CHECK(j.dump(-1, ' ', false, json::error_handler_t::replace) == "\"ä\xEF\xBF\xBDü\"");
+ CHECK(j.dump(-1, ' ', true, json::error_handler_t::replace) == "\"\\u00e4\\ufffd\\u00fc\"");
+ }
+
+ SECTION("ending with incomplete character")
+ {
+ const json j = "123\xC2";
+
+ CHECK_THROWS_WITH_AS(j.dump(), "[json.exception.type_error.316] incomplete UTF-8 string; last byte: 0xC2", json::type_error&);
+ CHECK_THROWS_AS(j.dump(1, ' ', false, json::error_handler_t::strict), json::type_error&);
+ CHECK(j.dump(-1, ' ', false, json::error_handler_t::ignore) == "\"123\"");
+ CHECK(j.dump(-1, ' ', false, json::error_handler_t::replace) == "\"123\xEF\xBF\xBD\"");
+ CHECK(j.dump(-1, ' ', true, json::error_handler_t::replace) == "\"123\\ufffd\"");
+ }
+
+ SECTION("unexpected character")
+ {
+ const json j = "123\xF1\xB0\x34\x35\x36";
+
+ CHECK_THROWS_WITH_AS(j.dump(), "[json.exception.type_error.316] invalid UTF-8 byte at index 5: 0x34", json::type_error&);
+ CHECK_THROWS_AS(j.dump(1, ' ', false, json::error_handler_t::strict), json::type_error&);
+ CHECK(j.dump(-1, ' ', false, json::error_handler_t::ignore) == "\"123456\"");
+ CHECK(j.dump(-1, ' ', false, json::error_handler_t::replace) == "\"123\xEF\xBF\xBD\x34\x35\x36\"");
+ CHECK(j.dump(-1, ' ', true, json::error_handler_t::replace) == "\"123\\ufffd456\"");
+ }
+
+ SECTION("U+FFFD Substitution of Maximal Subparts")
+ {
+ // Some tests (mostly) from
+ // https://www.unicode.org/versions/Unicode11.0.0/ch03.pdf
+ // Section 3.9 -- U+FFFD Substitution of Maximal Subparts
+
+ auto test = [&](std::string const & input, std::string const & expected)
+ {
+ const json j = input;
+ CHECK(j.dump(-1, ' ', true, json::error_handler_t::replace) == "\"" + expected + "\"");
+ };
+
+ test("\xC2", "\\ufffd");
+ test("\xC2\x41\x42", "\\ufffd" "\x41" "\x42");
+ test("\xC2\xF4", "\\ufffd" "\\ufffd");
+
+ test("\xF0\x80\x80\x41", "\\ufffd" "\\ufffd" "\\ufffd" "\x41");
+ test("\xF1\x80\x80\x41", "\\ufffd" "\x41");
+ test("\xF2\x80\x80\x41", "\\ufffd" "\x41");
+ test("\xF3\x80\x80\x41", "\\ufffd" "\x41");
+ test("\xF4\x80\x80\x41", "\\ufffd" "\x41");
+ test("\xF5\x80\x80\x41", "\\ufffd" "\\ufffd" "\\ufffd" "\x41");
+
+ test("\xF0\x90\x80\x41", "\\ufffd" "\x41");
+ test("\xF1\x90\x80\x41", "\\ufffd" "\x41");
+ test("\xF2\x90\x80\x41", "\\ufffd" "\x41");
+ test("\xF3\x90\x80\x41", "\\ufffd" "\x41");
+ test("\xF4\x90\x80\x41", "\\ufffd" "\\ufffd" "\\ufffd" "\x41");
+ test("\xF5\x90\x80\x41", "\\ufffd" "\\ufffd" "\\ufffd" "\x41");
+
+ test("\xC0\xAF\xE0\x80\xBF\xF0\x81\x82\x41", "\\ufffd" "\\ufffd" "\\ufffd" "\\ufffd" "\\ufffd" "\\ufffd" "\\ufffd" "\\ufffd" "\x41");
+ test("\xED\xA0\x80\xED\xBF\xBF\xED\xAF\x41", "\\ufffd" "\\ufffd" "\\ufffd" "\\ufffd" "\\ufffd" "\\ufffd" "\\ufffd" "\\ufffd" "\x41");
+ test("\xF4\x91\x92\x93\xFF\x41\x80\xBF\x42", "\\ufffd" "\\ufffd" "\\ufffd" "\\ufffd" "\\ufffd" "\x41" "\\ufffd""\\ufffd" "\x42");
+ test("\xE1\x80\xE2\xF0\x91\x92\xF1\xBF\x41", "\\ufffd" "\\ufffd" "\\ufffd" "\\ufffd" "\x41");
+ }
+ }
+
+ SECTION("to_string")
+ {
+ auto test = [&](std::string const & input, std::string const & expected)
+ {
+ using std::to_string;
+ const json j = input;
+ CHECK(to_string(j) == "\"" + expected + "\"");
+ };
+
+ test(R"({"x":5,"y":6})", R"({\"x\":5,\"y\":6})");
+ test("{\"x\":[10,null,null,null]}", R"({\"x\":[10,null,null,null]})");
+ test("test", "test");
+ test("[3,\"false\",false]", R"([3,\"false\",false])");
+ }
+}
+
+TEST_CASE_TEMPLATE("serialization for extreme integer values", T, int32_t, uint32_t, int64_t, uint64_t) // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
+{
+ SECTION("minimum")
+ {
+ constexpr auto minimum = (std::numeric_limits<T>::min)();
+ const json j = minimum;
+ CHECK(j.dump() == std::to_string(minimum));
+ }
+
+ SECTION("maximum")
+ {
+ constexpr auto maximum = (std::numeric_limits<T>::max)();
+ const json j = maximum;
+ CHECK(j.dump() == std::to_string(maximum));
+ }
+}
+
+TEST_CASE("dump with binary values")
+{
+ auto binary = json::binary({1, 2, 3, 4});
+ auto binary_empty = json::binary({});
+ auto binary_with_subtype = json::binary({1, 2, 3, 4}, 128);
+ auto binary_empty_with_subtype = json::binary({}, 128);
+
+ const json object = {{"key", binary}};
+ const json object_empty = {{"key", binary_empty}};
+ const json object_with_subtype = {{"key", binary_with_subtype}};
+ const json object_empty_with_subtype = {{"key", binary_empty_with_subtype}};
+
+ const json array = {"value", 1, binary};
+ const json array_empty = {"value", 1, binary_empty};
+ const json array_with_subtype = {"value", 1, binary_with_subtype};
+ const json array_empty_with_subtype = {"value", 1, binary_empty_with_subtype};
+
+ SECTION("normal")
+ {
+ CHECK(binary.dump() == "{\"bytes\":[1,2,3,4],\"subtype\":null}");
+ CHECK(binary_empty.dump() == "{\"bytes\":[],\"subtype\":null}");
+ CHECK(binary_with_subtype.dump() == "{\"bytes\":[1,2,3,4],\"subtype\":128}");
+ CHECK(binary_empty_with_subtype.dump() == "{\"bytes\":[],\"subtype\":128}");
+
+ CHECK(object.dump() == "{\"key\":{\"bytes\":[1,2,3,4],\"subtype\":null}}");
+ CHECK(object_empty.dump() == "{\"key\":{\"bytes\":[],\"subtype\":null}}");
+ CHECK(object_with_subtype.dump() == "{\"key\":{\"bytes\":[1,2,3,4],\"subtype\":128}}");
+ CHECK(object_empty_with_subtype.dump() == "{\"key\":{\"bytes\":[],\"subtype\":128}}");
+
+ CHECK(array.dump() == "[\"value\",1,{\"bytes\":[1,2,3,4],\"subtype\":null}]");
+ CHECK(array_empty.dump() == "[\"value\",1,{\"bytes\":[],\"subtype\":null}]");
+ CHECK(array_with_subtype.dump() == "[\"value\",1,{\"bytes\":[1,2,3,4],\"subtype\":128}]");
+ CHECK(array_empty_with_subtype.dump() == "[\"value\",1,{\"bytes\":[],\"subtype\":128}]");
+ }
+
+ SECTION("pretty-printed")
+ {
+ CHECK(binary.dump(4) == "{\n"
+ " \"bytes\": [1, 2, 3, 4],\n"
+ " \"subtype\": null\n"
+ "}");
+ CHECK(binary_empty.dump(4) == "{\n"
+ " \"bytes\": [],\n"
+ " \"subtype\": null\n"
+ "}");
+ CHECK(binary_with_subtype.dump(4) == "{\n"
+ " \"bytes\": [1, 2, 3, 4],\n"
+ " \"subtype\": 128\n"
+ "}");
+ CHECK(binary_empty_with_subtype.dump(4) == "{\n"
+ " \"bytes\": [],\n"
+ " \"subtype\": 128\n"
+ "}");
+
+ CHECK(object.dump(4) == "{\n"
+ " \"key\": {\n"
+ " \"bytes\": [1, 2, 3, 4],\n"
+ " \"subtype\": null\n"
+ " }\n"
+ "}");
+ CHECK(object_empty.dump(4) == "{\n"
+ " \"key\": {\n"
+ " \"bytes\": [],\n"
+ " \"subtype\": null\n"
+ " }\n"
+ "}");
+ CHECK(object_with_subtype.dump(4) == "{\n"
+ " \"key\": {\n"
+ " \"bytes\": [1, 2, 3, 4],\n"
+ " \"subtype\": 128\n"
+ " }\n"
+ "}");
+ CHECK(object_empty_with_subtype.dump(4) == "{\n"
+ " \"key\": {\n"
+ " \"bytes\": [],\n"
+ " \"subtype\": 128\n"
+ " }\n"
+ "}");
+
+ CHECK(array.dump(4) == "[\n"
+ " \"value\",\n"
+ " 1,\n"
+ " {\n"
+ " \"bytes\": [1, 2, 3, 4],\n"
+ " \"subtype\": null\n"
+ " }\n"
+ "]");
+ CHECK(array_empty.dump(4) == "[\n"
+ " \"value\",\n"
+ " 1,\n"
+ " {\n"
+ " \"bytes\": [],\n"
+ " \"subtype\": null\n"
+ " }\n"
+ "]");
+ CHECK(array_with_subtype.dump(4) == "[\n"
+ " \"value\",\n"
+ " 1,\n"
+ " {\n"
+ " \"bytes\": [1, 2, 3, 4],\n"
+ " \"subtype\": 128\n"
+ " }\n"
+ "]");
+ CHECK(array_empty_with_subtype.dump(4) == "[\n"
+ " \"value\",\n"
+ " 1,\n"
+ " {\n"
+ " \"bytes\": [],\n"
+ " \"subtype\": 128\n"
+ " }\n"
+ "]");
+ }
+}
diff --git a/json4cpp/tests/src/unit-testsuites.cpp b/json4cpp/tests/src/unit-testsuites.cpp
new file mode 100644
index 0000000000..6729819675
--- /dev/null
+++ b/json4cpp/tests/src/unit-testsuites.cpp
@@ -0,0 +1,1392 @@
+// __ _____ _____ _____
+// __| | __| | | | 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;
+
+#include <fstream>
+#include "make_test_data_available.hpp"
+
+TEST_CASE("compliance tests from json.org")
+{
+ // test cases are from https://json.org/JSON_checker/
+
+ SECTION("expected failures")
+ {
+ for (const auto* filename :
+ {
+ //TEST_DATA_DIRECTORY "/json_tests/fail1.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail2.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail3.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail4.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail5.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail6.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail7.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail8.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail9.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail10.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail11.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail12.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail13.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail14.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail15.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail16.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail17.json",
+ //TEST_DATA_DIRECTORY "/json_tests/fail18.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail19.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail20.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail21.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail22.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail23.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail24.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail25.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail26.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail27.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail28.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail29.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail30.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail31.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail32.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail33.json"
+ })
+ {
+ CAPTURE(filename)
+ std::ifstream f(filename);
+ json _;
+ CHECK_THROWS_AS(_ = json::parse(f), json::parse_error&);
+ }
+ }
+
+ SECTION("no failures with trailing literals (relaxed)")
+ {
+ // these tests fail above, because the parser does not end on EOF;
+ // they succeed when the operator>> is used, because it does not
+ // have this constraint
+ for (const auto* filename :
+ {
+ TEST_DATA_DIRECTORY "/json_tests/fail7.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail8.json",
+ TEST_DATA_DIRECTORY "/json_tests/fail10.json",
+ })
+ {
+ CAPTURE(filename)
+ std::ifstream f(filename);
+ json j;
+ CHECK_NOTHROW(f >> j);
+ }
+ }
+
+ SECTION("expected passes")
+ {
+ for (const auto* filename :
+ {
+ TEST_DATA_DIRECTORY "/json_tests/pass1.json",
+ TEST_DATA_DIRECTORY "/json_tests/pass2.json",
+ TEST_DATA_DIRECTORY "/json_tests/pass3.json"
+ })
+ {
+ CAPTURE(filename)
+ std::ifstream f(filename);
+ json j;
+ CHECK_NOTHROW(f >> j);
+ }
+ }
+}
+
+TEST_CASE("compliance tests from nativejson-benchmark")
+{
+ // test cases from https://github.com/miloyip/nativejson-benchmark/blob/master/src/main.cpp
+
+ SECTION("doubles")
+ {
+ auto TEST_DOUBLE = [](const std::string & json_string, const double expected)
+ {
+ CAPTURE(json_string)
+ CAPTURE(expected)
+ CHECK(json::parse(json_string)[0].get<double>() == Approx(expected));
+ };
+
+ TEST_DOUBLE("[0.0]", 0.0);
+ TEST_DOUBLE("[-0.0]", -0.0);
+ TEST_DOUBLE("[1.0]", 1.0);
+ TEST_DOUBLE("[-1.0]", -1.0);
+ TEST_DOUBLE("[1.5]", 1.5);
+ TEST_DOUBLE("[-1.5]", -1.5);
+ TEST_DOUBLE("[3.1416]", 3.1416);
+ TEST_DOUBLE("[1E10]", 1E10);
+ TEST_DOUBLE("[1e10]", 1e10);
+ TEST_DOUBLE("[1E+10]", 1E+10);
+ TEST_DOUBLE("[1E-10]", 1E-10);
+ TEST_DOUBLE("[-1E10]", -1E10);
+ TEST_DOUBLE("[-1e10]", -1e10);
+ TEST_DOUBLE("[-1E+10]", -1E+10);
+ TEST_DOUBLE("[-1E-10]", -1E-10);
+ TEST_DOUBLE("[1.234E+10]", 1.234E+10);
+ TEST_DOUBLE("[1.234E-10]", 1.234E-10);
+ TEST_DOUBLE("[1.79769e+308]", 1.79769e+308);
+ TEST_DOUBLE("[2.22507e-308]", 2.22507e-308);
+ TEST_DOUBLE("[-1.79769e+308]", -1.79769e+308);
+ TEST_DOUBLE("[-2.22507e-308]", -2.22507e-308);
+ TEST_DOUBLE("[4.9406564584124654e-324]", 4.9406564584124654e-324); // minimum denormal
+ TEST_DOUBLE("[2.2250738585072009e-308]", 2.2250738585072009e-308); // Max subnormal double
+ TEST_DOUBLE("[2.2250738585072014e-308]", 2.2250738585072014e-308); // Min normal positive double
+ TEST_DOUBLE("[1.7976931348623157e+308]", 1.7976931348623157e+308); // Max double
+ TEST_DOUBLE("[1e-10000]", 0.0); // must underflow
+ TEST_DOUBLE("[18446744073709551616]",
+ 18446744073709551616.0); // 2^64 (max of uint64_t + 1, force to use double)
+ TEST_DOUBLE("[-9223372036854775809]",
+ -9223372036854775809.0); // -2^63 - 1(min of int64_t + 1, force to use double)
+ TEST_DOUBLE("[0.9868011474609375]",
+ 0.9868011474609375); // https://github.com/miloyip/rapidjson/issues/120
+ TEST_DOUBLE("[123e34]", 123e34); // Fast Path Cases In Disguise
+ TEST_DOUBLE("[45913141877270640000.0]", 45913141877270640000.0);
+ TEST_DOUBLE("[2.2250738585072011e-308]",
+ 2.2250738585072011e-308);
+ //TEST_DOUBLE("[1e-00011111111111]", 0.0);
+ //TEST_DOUBLE("[-1e-00011111111111]", -0.0);
+ TEST_DOUBLE("[1e-214748363]", 0.0);
+ TEST_DOUBLE("[1e-214748364]", 0.0);
+ //TEST_DOUBLE("[1e-21474836311]", 0.0);
+ TEST_DOUBLE("[0.017976931348623157e+310]", 1.7976931348623157e+308); // Max double in another form
+
+ // Since
+ // abs((2^-1022 - 2^-1074) - 2.2250738585072012e-308) = 3.109754131239141401123495768877590405345064751974375599... ¡Á 10^-324
+ // abs((2^-1022) - 2.2250738585072012e-308) = 1.830902327173324040642192159804623318305533274168872044... ¡Á 10 ^ -324
+ // So 2.2250738585072012e-308 should round to 2^-1022 = 2.2250738585072014e-308
+ TEST_DOUBLE("[2.2250738585072012e-308]",
+ 2.2250738585072014e-308);
+
+ // Closer to normal/subnormal boundary
+ // boundary = 2^-1022 - 2^-1075 = 2.225073858507201136057409796709131975934819546351645648... ¡Á 10^-308
+ TEST_DOUBLE("[2.22507385850720113605740979670913197593481954635164564e-308]",
+ 2.2250738585072009e-308);
+ TEST_DOUBLE("[2.22507385850720113605740979670913197593481954635164565e-308]",
+ 2.2250738585072014e-308);
+
+ // 1.0 is in (1.0 - 2^-54, 1.0 + 2^-53)
+ // 1.0 - 2^-54 = 0.999999999999999944488848768742172978818416595458984375
+ TEST_DOUBLE("[0.999999999999999944488848768742172978818416595458984375]", 1.0); // round to even
+ TEST_DOUBLE("[0.999999999999999944488848768742172978818416595458984374]",
+ 0.99999999999999989); // previous double
+ TEST_DOUBLE("[0.999999999999999944488848768742172978818416595458984376]", 1.0); // next double
+ // 1.0 + 2^-53 = 1.00000000000000011102230246251565404236316680908203125
+ TEST_DOUBLE("[1.00000000000000011102230246251565404236316680908203125]", 1.0); // round to even
+ TEST_DOUBLE("[1.00000000000000011102230246251565404236316680908203124]", 1.0); // previous double
+ TEST_DOUBLE("[1.00000000000000011102230246251565404236316680908203126]",
+ 1.00000000000000022); // next double
+
+ // Numbers from https://github.com/floitsch/double-conversion/blob/master/test/cctest/test-strtod.cc
+
+ TEST_DOUBLE("[72057594037927928.0]", 72057594037927928.0);
+ TEST_DOUBLE("[72057594037927936.0]", 72057594037927936.0);
+ TEST_DOUBLE("[72057594037927932.0]", 72057594037927936.0);
+ TEST_DOUBLE("[7205759403792793199999e-5]", 72057594037927928.0);
+ TEST_DOUBLE("[7205759403792793200001e-5]", 72057594037927936.0);
+
+ TEST_DOUBLE("[9223372036854774784.0]", 9223372036854774784.0);
+ TEST_DOUBLE("[9223372036854775808.0]", 9223372036854775808.0);
+ TEST_DOUBLE("[9223372036854775296.0]", 9223372036854775808.0);
+ TEST_DOUBLE("[922337203685477529599999e-5]", 9223372036854774784.0);
+ TEST_DOUBLE("[922337203685477529600001e-5]", 9223372036854775808.0);
+
+ TEST_DOUBLE("[10141204801825834086073718800384]", 10141204801825834086073718800384.0);
+ TEST_DOUBLE("[10141204801825835211973625643008]", 10141204801825835211973625643008.0);
+ TEST_DOUBLE("[10141204801825834649023672221696]", 10141204801825835211973625643008.0);
+ TEST_DOUBLE("[1014120480182583464902367222169599999e-5]", 10141204801825834086073718800384.0);
+ TEST_DOUBLE("[1014120480182583464902367222169600001e-5]", 10141204801825835211973625643008.0);
+
+ TEST_DOUBLE("[5708990770823838890407843763683279797179383808]",
+ 5708990770823838890407843763683279797179383808.0);
+ TEST_DOUBLE("[5708990770823839524233143877797980545530986496]",
+ 5708990770823839524233143877797980545530986496.0);
+ TEST_DOUBLE("[5708990770823839207320493820740630171355185152]",
+ 5708990770823839524233143877797980545530986496.0);
+ TEST_DOUBLE("[5708990770823839207320493820740630171355185151999e-3]",
+ 5708990770823838890407843763683279797179383808.0);
+ TEST_DOUBLE("[5708990770823839207320493820740630171355185152001e-3]",
+ 5708990770823839524233143877797980545530986496.0);
+
+ {
+ std::string n1e308(312, '0'); // '1' followed by 308 '0'
+ n1e308[0] = '[';
+ n1e308[1] = '1';
+ n1e308[310] = ']';
+ n1e308[311] = '\0';
+ TEST_DOUBLE(n1e308, 1E308);
+ }
+
+ // Cover trimming
+ TEST_DOUBLE(
+ "[2.22507385850720113605740979670913197593481954635164564802342610972482222202107694551652952390813508"
+ "7914149158913039621106870086438694594645527657207407820621743379988141063267329253552286881372149012"
+ "9811224514518898490572223072852551331557550159143974763979834118019993239625482890171070818506906306"
+ "6665599493827577257201576306269066333264756530000924588831643303777979186961204949739037782970490505"
+ "1080609940730262937128958950003583799967207254304360284078895771796150945516748243471030702609144621"
+ "5722898802581825451803257070188608721131280795122334262883686223215037756666225039825343359745688844"
+ "2390026549819838548794829220689472168983109969836584681402285424333066033985088644580400103493397042"
+ "7567186443383770486037861622771738545623065874679014086723327636718751234567890123456789012345678901"
+ "e-308]",
+ 2.2250738585072014e-308);
+ }
+
+ SECTION("strings")
+ {
+ auto TEST_STRING = [](const std::string & json_string, const std::string & expected)
+ {
+ CAPTURE(json_string)
+ CAPTURE(expected)
+ CHECK(json::parse(json_string)[0].get<std::string>() == expected);
+ };
+
+ TEST_STRING("[\"\"]", "");
+ TEST_STRING("[\"Hello\"]", "Hello");
+ TEST_STRING(R"(["Hello\nWorld"])", "Hello\nWorld");
+ //TEST_STRING("[\"Hello\\u0000World\"]", "Hello\0World");
+ TEST_STRING(R"(["\"\\/\b\f\n\r\t"])", "\"\\/\b\f\n\r\t");
+ TEST_STRING(R"(["\u0024"])", "$"); // Dollar sign U+0024
+ TEST_STRING(R"(["\u00A2"])", "\xC2\xA2"); // Cents sign U+00A2
+ TEST_STRING(R"(["\u20AC"])", "\xE2\x82\xAC"); // Euro sign U+20AC
+ TEST_STRING(R"(["\uD834\uDD1E"])", "\xF0\x9D\x84\x9E"); // G clef sign U+1D11E
+ }
+
+ SECTION("roundtrip")
+ {
+ // test cases are from https://github.com/miloyip/nativejson-benchmark/tree/master/test/data/roundtrip
+
+ for (const auto* filename :
+ {
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip01.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip02.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip03.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip04.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip05.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip06.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip07.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip08.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip09.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip10.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip11.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip12.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip13.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip14.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip15.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip16.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip17.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip18.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip19.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip20.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip21.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip22.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip23.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip24.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip25.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip26.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip27.json",
+ //TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip28.json", // incompatible with roundtrip24
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip29.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip30.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip31.json"
+ //TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip32.json" // same as roundtrip31
+ })
+ {
+ CAPTURE(filename)
+ std::ifstream f(filename);
+ std::string json_string( (std::istreambuf_iterator<char>(f) ),
+ (std::istreambuf_iterator<char>()) );
+
+ CAPTURE(json_string)
+ const json j = json::parse(json_string);
+ CHECK(j.dump() == json_string);
+ }
+ }
+}
+
+TEST_CASE("test suite from json-test-suite")
+{
+ SECTION("read all sample.json")
+ {
+ // read a file with all Unicode characters stored as single-character
+ // strings in a JSON array
+ std::ifstream f(TEST_DATA_DIRECTORY "/json_testsuite/sample.json");
+ json j;
+ CHECK_NOTHROW(f >> j);
+
+ // the array has 3 elements
+ CHECK(j.size() == 3);
+ }
+}
+
+TEST_CASE("json.org examples")
+{
+ // here, we list all JSON values from https://json.org/example
+ using FilePtr = std::unique_ptr<FILE, int(*)(FILE*)>;
+
+ SECTION("1.json")
+ {
+ std::ifstream f(TEST_DATA_DIRECTORY "/json.org/1.json");
+ json j;
+ CHECK_NOTHROW(f >> j);
+ }
+
+ SECTION("2.json")
+ {
+ std::ifstream f(TEST_DATA_DIRECTORY "/json.org/2.json");
+ json j;
+ CHECK_NOTHROW(f >> j);
+ }
+
+ SECTION("3.json")
+ {
+ std::ifstream f(TEST_DATA_DIRECTORY "/json.org/3.json");
+ json j;
+ CHECK_NOTHROW(f >> j);
+ }
+
+ SECTION("4.json")
+ {
+ std::ifstream f(TEST_DATA_DIRECTORY "/json.org/4.json");
+ json j;
+ CHECK_NOTHROW(f >> j);
+ }
+
+ SECTION("5.json")
+ {
+ std::ifstream f(TEST_DATA_DIRECTORY "/json.org/5.json");
+ json j;
+ CHECK_NOTHROW(f >> j);
+ }
+ SECTION("FILE 1.json")
+ {
+ const FilePtr f(std::fopen(TEST_DATA_DIRECTORY "/json.org/1.json", "r"), &std::fclose);
+ json _;
+ CHECK_NOTHROW(_ = json::parse(f.get()));
+ }
+
+ SECTION("FILE 2.json")
+ {
+ const FilePtr f(std::fopen(TEST_DATA_DIRECTORY "/json.org/2.json", "r"), &std::fclose);
+ json _;
+ CHECK_NOTHROW(_ = json::parse(f.get()));
+ }
+
+ SECTION("FILE 3.json")
+ {
+ const FilePtr f(std::fopen(TEST_DATA_DIRECTORY "/json.org/3.json", "r"), &std::fclose);
+ json _;
+ CHECK_NOTHROW(_ = json::parse(f.get()));
+ }
+
+ SECTION("FILE 4.json")
+ {
+ const FilePtr f(std::fopen(TEST_DATA_DIRECTORY "/json.org/4.json", "r"), &std::fclose);
+ json _;
+ CHECK_NOTHROW(_ = json::parse(f.get()));
+ }
+
+ SECTION("FILE 5.json")
+ {
+ const FilePtr f(std::fopen(TEST_DATA_DIRECTORY "/json.org/5.json", "r"), &std::fclose);
+ json _;
+ CHECK_NOTHROW(_ = json::parse(f.get()));
+ }
+}
+
+TEST_CASE("RFC 8259 examples")
+{
+ // here, we list all JSON values from the RFC 8259 document
+
+ SECTION("7. Strings")
+ {
+ CHECK(json::parse("\"\\u005C\"") == json("\\"));
+ CHECK(json::parse("\"\\uD834\\uDD1E\"") == json("𝄞"));
+ CHECK(json::parse("\"𝄞\"") == json("𝄞"));
+ }
+
+ SECTION("8.3 String Comparison")
+ {
+ CHECK(json::parse("\"a\\b\"") == json::parse("\"a\u005Cb\""));
+ }
+
+ SECTION("13 Examples")
+ {
+ {
+ const auto* json_contents = R"(
+ {
+ "Image": {
+ "Width": 800,
+ "Height": 600,
+ "Title": "View from 15th Floor",
+ "Thumbnail": {
+ "Url": "http://www.example.com/image/481989943",
+ "Height": 125,
+ "Width": 100
+ },
+ "Animated" : false,
+ "IDs": [116, 943, 234, 38793]
+ }
+ }
+ )";
+
+ CHECK_NOTHROW(json(json_contents));
+ }
+
+ {
+ const auto* json_contents = R"(
+ [
+ {
+ "precision": "zip",
+ "Latitude": 37.7668,
+ "Longitude": -122.3959,
+ "Address": "",
+ "City": "SAN FRANCISCO",
+ "State": "CA",
+ "Zip": "94107",
+ "Country": "US"
+ },
+ {
+ "precision": "zip",
+ "Latitude": 37.371991,
+ "Longitude": -122.026020,
+ "Address": "",
+ "City": "SUNNYVALE",
+ "State": "CA",
+ "Zip": "94085",
+ "Country": "US"
+ }
+ ])";
+ CHECK_NOTHROW(json(json_contents));
+ }
+
+ CHECK(json::parse("\"Hello world!\"") == json("Hello world!"));
+ CHECK(json::parse("42") == json(42));
+ CHECK(json::parse("true") == json(true));
+ }
+}
+
+TEST_CASE("nst's JSONTestSuite")
+{
+ SECTION("test_parsing")
+ {
+ SECTION("y")
+ {
+ for (const auto* filename :
+ {
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_arraysWithSpaces.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_empty-string.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_empty.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_ending_with_newline.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_false.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_heterogeneous.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_null.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_with_1_and_newline.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_with_leading_space.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_with_several_null.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_with_trailing_space.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_0e+1.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_0e1.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_after_space.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_double_close_to_zero.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_double_huge_neg_exp.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_int_with_exp.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_minus_zero.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_negative_int.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_negative_one.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_negative_zero.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_capital_e.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_capital_e_neg_exp.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_capital_e_pos_exp.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_exponent.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_fraction_exponent.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_neg_exp.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_pos_exponent.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_underflow.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_simple_int.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_simple_real.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_too_big_neg_int.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_too_big_pos_int.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_very_big_negative_int.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_basic.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_duplicated_key.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_duplicated_key_and_value.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_empty.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_empty_key.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_escaped_null_in_key.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_extreme_numbers.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_long_strings.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_simple.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_string_unicode.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_with_newlines.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_1_2_3_bytes_UTF-8_sequences.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_UTF-16_Surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_accepted_surrogate_pair.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_accepted_surrogate_pairs.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_allowed_escapes.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_backslash_and_u_escaped_zero.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_backslash_doublequotes.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_comments.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_double_escape_a.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_double_escape_n.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_escaped_control_character.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_escaped_noncharacter.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_in_array.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_in_array_with_leading_space.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_last_surrogates_1_and_2.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_newline_uescaped.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+10FFFF.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+1FFFF.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+FFFF.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_null_escape.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_one-byte-utf-8.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_pi.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_simple_ascii.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_space.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_three-byte-utf-8.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_two-byte-utf-8.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_u+2028_line_sep.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_u+2029_par_sep.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_uEscape.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unescaped_char_delete.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicodeEscapedBackslash.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode_2.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode_U+2064_invisible_plus.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode_escaped_double_quote.json",
+ // TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_utf16.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_utf8.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_with_del_character.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_false.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_int.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_negative_real.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_null.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_string.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_true.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_string_empty.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_trailing_newline.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_true_in_array.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_whitespace_array.json"
+ }
+ )
+ {
+ CAPTURE(filename)
+ std::ifstream f(filename);
+ json j;
+ CHECK_NOTHROW(f >> j);
+ }
+ }
+
+ SECTION("n")
+ {
+ for (const auto* filename :
+ {
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_array_1_true_without_comma.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_array_a_invalid_utf8.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_array_colon_instead_of_comma.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_array_comma_after_close.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_array_comma_and_number.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_array_double_comma.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_array_double_extra_comma.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_array_extra_close.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_array_extra_comma.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_array_incomplete.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_array_incomplete_invalid_value.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_array_inner_array_no_comma.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_array_invalid_utf8.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_array_items_separated_by_semicolon.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_array_just_comma.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_array_just_minus.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_array_missing_value.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_array_newlines_unclosed.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_array_number_and_comma.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_array_number_and_several_commas.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_array_spaces_vertical_tab_formfeed.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_array_star_inside.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_array_unclosed.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_array_unclosed_trailing_comma.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_array_unclosed_with_new_lines.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_array_unclosed_with_object_inside.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_incomplete_false.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_incomplete_null.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_incomplete_true.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_++.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_+1.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_+Inf.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_-01.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_-1.0..json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_-2..json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_-NaN.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_.-1.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_.2e-3.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_0.1.2.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_0.3e+.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_0.3e.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_0.e1.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_0_capital_E+.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_0_capital_E.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_0e+.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_0e.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_1.0e+.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_1.0e-.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_1.0e.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_1_000.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_1eE2.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_2.e+3.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_2.e-3.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_2.e3.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_9.e+.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_Inf.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_NaN.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_U+FF11_fullwidth_digit_one.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_expression.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_hex_1_digit.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_hex_2_digits.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_infinity.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_invalid+-.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_invalid-negative-real.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_invalid-utf-8-in-bigger-int.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_invalid-utf-8-in-exponent.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_invalid-utf-8-in-int.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_minus_infinity.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_minus_sign_with_trailing_garbage.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_minus_space_1.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_neg_int_starting_with_zero.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_neg_real_without_int_part.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_neg_with_garbage_at_end.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_real_garbage_after_e.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_real_with_invalid_utf8_after_e.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_real_without_fractional_part.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_starting_with_dot.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_then_00.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_with_alpha.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_with_alpha_char.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_number_with_leading_zero.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_bad_value.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_bracket_key.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_comma_instead_of_colon.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_double_colon.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_emoji.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_garbage_at_end.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_key_with_single_quotes.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_missing_colon.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_missing_key.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_missing_semicolon.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_missing_value.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_no-colon.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_non_string_key.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_non_string_key_but_huge_number_instead.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_pi_in_key_and_trailing_comma.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_repeated_null_null.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_several_trailing_commas.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_single_quote.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_trailing_comma.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_trailing_comment.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_trailing_comment_open.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_trailing_comment_slash_open.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_trailing_comment_slash_open_incomplete.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_two_commas_in_a_row.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_unquoted_key.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_unterminated-value.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_with_single_string.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_with_trailing_garbage.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_single_space.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_1_surrogate_then_escape u.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_1_surrogate_then_escape u1.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_1_surrogate_then_escape u1x.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_1_surrogate_then_escape.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_UTF-16_incomplete_surrogate.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_UTF8_surrogate_U+D800.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_accentuated_char_no_quotes.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_backslash_00.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_escape_x.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_escaped_backslash_bad.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_escaped_ctrl_char_tab.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_escaped_emoji.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_incomplete_escape.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_incomplete_escaped_character.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_incomplete_surrogate_escape_invalid.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_invalid-utf-8-in-escape.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_invalid_backslash_esc.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_invalid_unicode_escape.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_invalid_utf-8.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_invalid_utf8_after_escape.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_iso_latin_1.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_leading_uescaped_thinspace.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_lone_utf8_continuation_byte.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_no_quotes_with_bad_escape.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_overlong_sequence_2_bytes.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_overlong_sequence_6_bytes.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_overlong_sequence_6_bytes_null.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_single_doublequote.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_single_quote.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_single_string_no_double_quotes.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_start_escape_unclosed.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_unescaped_crtl_char.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_unescaped_newline.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_unescaped_tab.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_unicode_CapitalU.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_with_trailing_garbage.json",
+ //!TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_100000_opening_arrays.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_3C.3E.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_3Cnull3E.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_U+2060_word_joined.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_UTF8_BOM_no_data.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_array_trailing_garbage.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_array_with_extra_array_close.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_array_with_unclosed_string.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_ascii-unicode-identifier.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_capitalized_True.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_close_unopened_array.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_comma_instead_of_closing_brace.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_double_array.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_end_array.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_incomplete_UTF8_BOM.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_lone-invalid-utf-8.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_lone-open-bracket.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_no_data.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_null-byte-outside-string.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_number_with_trailing_garbage.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_object_followed_by_closing_object.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_object_unclosed_no_value.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_object_with_comment.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_object_with_trailing_garbage.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_open_array_apostrophe.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_open_array_comma.json",
+ //!TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_open_array_object.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_open_array_open_object.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_open_array_open_string.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_open_array_string.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_open_object.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_open_object_close_array.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_open_object_comma.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_open_object_open_array.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_open_object_open_string.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_open_object_string_with_apostrophes.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_open_open.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_single_point.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_single_star.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_trailing_#.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_uescaped_LF_before_string.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_unclosed_array.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_unclosed_array_partial_null.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_unclosed_array_unfinished_false.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_unclosed_array_unfinished_true.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_unclosed_object.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_unicode-identifier.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_whitespace_U+2060_word_joiner.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_whitespace_formfeed.json"
+ }
+ )
+ {
+ CAPTURE(filename)
+ std::ifstream f(filename);
+ json _;
+ CHECK_THROWS_AS(_ = json::parse(f), json::parse_error&);
+ }
+ }
+
+ SECTION("n -> y (relaxed)")
+ {
+ // these tests fail above, because the parser does not end on EOF;
+ // they succeed when the operator>> is used, because it does not
+ // have this constraint
+ for (const auto* filename :
+ {
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_array_comma_after_close.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_array_extra_close.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_trailing_comment.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_trailing_comment_open.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_trailing_comment_slash_open.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_trailing_comment_slash_open_incomplete.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_object_with_trailing_garbage.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_string_with_trailing_garbage.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_array_trailing_garbage.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_array_with_extra_array_close.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_close_unopened_array.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_double_array.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_number_with_trailing_garbage.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_object_followed_by_closing_object.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_object_with_trailing_garbage.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/n_structure_trailing_#.json"
+ }
+ )
+ {
+ CAPTURE(filename)
+ std::ifstream f(filename);
+ json j;
+ CHECK_NOTHROW(f >> j);
+ }
+ }
+
+ SECTION("i -> y")
+ {
+ for (const auto* filename :
+ {
+ // we do not pose a limit on nesting
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/i_structure_500_nested_arrays.json",
+ // we silently ignore BOMs
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/i_structure_UTF-8_BOM_empty_object.json",
+ // we accept and forward non-characters
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/i_string_unicode_U+10FFFE_nonchar.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/i_string_unicode_U+1FFFE_nonchar.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/i_string_unicode_U+FDD0_nonchar.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/i_string_unicode_U+FFFE_nonchar.json"
+ }
+ )
+ {
+ CAPTURE(filename)
+ std::ifstream f(filename);
+ json j;
+ CHECK_NOTHROW(f >> j);
+ }
+ }
+
+ // numbers that overflow during parsing
+ SECTION("i/y -> n (out of range)")
+ {
+ for (const auto* filename :
+ {
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/i_number_neg_int_huge_exp.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/i_number_pos_double_huge_exp.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_huge_exp.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_neg_overflow.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_pos_overflow.json"
+ }
+ )
+ {
+ CAPTURE(filename)
+ std::ifstream f(filename);
+ json j;
+ CHECK_THROWS_AS(f >> j, json::out_of_range&);
+ }
+ }
+
+ SECTION("i -> n")
+ {
+ for (const auto* filename :
+ {
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/i_object_key_lone_2nd_surrogate.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/i_string_1st_surrogate_but_2nd_missing.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/i_string_1st_valid_surrogate_2nd_invalid.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/i_string_UTF-16_invalid_lonely_surrogate.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/i_string_UTF-16_invalid_surrogate.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/i_string_UTF-8_invalid_sequence.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/i_string_incomplete_surrogate_and_escape_valid.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/i_string_incomplete_surrogate_pair.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/i_string_incomplete_surrogates_escape_valid.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/i_string_inverted_surrogates_U+1D11E.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/i_string_lone_second_surrogate.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/i_string_not_in_unicode_range.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/i_string_truncated-utf-8.json"
+ }
+ )
+ {
+ CAPTURE(filename)
+ std::ifstream f(filename);
+ json j;
+ CHECK_THROWS_AS(f >> j, json::parse_error&);
+ }
+ }
+ }
+}
+
+TEST_CASE("nst's JSONTestSuite (2)")
+{
+ SECTION("test_parsing")
+ {
+ SECTION("y")
+ {
+ for (const auto* filename :
+ {
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_array_arraysWithSpaces.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_array_empty-string.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_array_empty.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_array_ending_with_newline.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_array_false.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_array_heterogeneous.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_array_null.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_array_with_1_and_newline.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_array_with_leading_space.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_array_with_several_null.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_array_with_trailing_space.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_number.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_number_0e+1.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_number_0e1.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_number_after_space.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_number_double_close_to_zero.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_number_int_with_exp.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_number_minus_zero.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_number_negative_int.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_number_negative_one.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_number_negative_zero.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_number_real_capital_e.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_number_real_capital_e_neg_exp.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_number_real_capital_e_pos_exp.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_number_real_exponent.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_number_real_fraction_exponent.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_number_real_neg_exp.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_number_real_pos_exponent.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_number_simple_int.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_number_simple_real.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_object.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_object_basic.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_object_duplicated_key.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_object_duplicated_key_and_value.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_object_empty.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_object_empty_key.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_object_escaped_null_in_key.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_object_extreme_numbers.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_object_long_strings.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_object_simple.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_object_string_unicode.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_object_with_newlines.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_1_2_3_bytes_UTF-8_sequences.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_accepted_surrogate_pair.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_accepted_surrogate_pairs.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_allowed_escapes.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_backslash_and_u_escaped_zero.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_backslash_doublequotes.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_comments.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_double_escape_a.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_double_escape_n.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_escaped_control_character.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_escaped_noncharacter.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_in_array.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_in_array_with_leading_space.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_last_surrogates_1_and_2.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_nbsp_uescaped.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_nonCharacterInUTF-8_U+10FFFF.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_nonCharacterInUTF-8_U+FFFF.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_null_escape.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_one-byte-utf-8.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_pi.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_reservedCharacterInUTF-8_U+1BFFF.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_simple_ascii.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_space.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_three-byte-utf-8.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_two-byte-utf-8.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_u+2028_line_sep.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_u+2029_par_sep.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_uEscape.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_uescaped_newline.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_unescaped_char_delete.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_unicode.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_unicodeEscapedBackslash.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_unicode_2.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_unicode_U+10FFFE_nonchar.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_unicode_U+1FFFE_nonchar.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_unicode_U+2064_invisible_plus.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_unicode_U+FDD0_nonchar.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_unicode_U+FFFE_nonchar.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_unicode_escaped_double_quote.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_utf8.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_string_with_del_character.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_structure_lonely_false.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_structure_lonely_int.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_structure_lonely_negative_real.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_structure_lonely_null.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_structure_lonely_string.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_structure_lonely_true.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_structure_string_empty.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_structure_trailing_newline.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_structure_true_in_array.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/y_structure_whitespace_array.json"
+ }
+ )
+ {
+ CAPTURE(filename)
+ std::ifstream f(filename);
+ json _;
+ CHECK_NOTHROW(_ = json::parse(f));
+ std::ifstream f2(filename);
+ CHECK(json::accept(f2));
+ }
+ }
+
+ SECTION("n")
+ {
+ for (const auto* filename :
+ {
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_array_1_true_without_comma.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_array_a_invalid_utf8.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_array_colon_instead_of_comma.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_array_comma_after_close.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_array_comma_and_number.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_array_double_comma.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_array_double_extra_comma.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_array_extra_close.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_array_extra_comma.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_array_incomplete.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_array_incomplete_invalid_value.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_array_inner_array_no_comma.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_array_invalid_utf8.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_array_items_separated_by_semicolon.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_array_just_comma.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_array_just_minus.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_array_missing_value.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_array_newlines_unclosed.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_array_number_and_comma.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_array_number_and_several_commas.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_array_spaces_vertical_tab_formfeed.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_array_star_inside.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_array_unclosed.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_array_unclosed_trailing_comma.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_array_unclosed_with_new_lines.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_array_unclosed_with_object_inside.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_incomplete_false.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_incomplete_null.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_incomplete_true.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_multidigit_number_then_00.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_++.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_+1.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_+Inf.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_-01.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_-1.0..json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_-2..json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_-NaN.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_.-1.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_.2e-3.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_0.1.2.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_0.3e+.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_0.3e.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_0.e1.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_0_capital_E+.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_0_capital_E.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_0e+.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_0e.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_1.0e+.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_1.0e-.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_1.0e.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_1_000.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_1eE2.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_2.e+3.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_2.e-3.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_2.e3.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_9.e+.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_Inf.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_NaN.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_U+FF11_fullwidth_digit_one.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_expression.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_hex_1_digit.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_hex_2_digits.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_infinity.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_invalid+-.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_invalid-negative-real.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_invalid-utf-8-in-bigger-int.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_invalid-utf-8-in-exponent.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_invalid-utf-8-in-int.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_minus_infinity.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_minus_sign_with_trailing_garbage.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_minus_space_1.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_neg_int_starting_with_zero.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_neg_real_without_int_part.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_neg_with_garbage_at_end.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_real_garbage_after_e.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_real_with_invalid_utf8_after_e.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_real_without_fractional_part.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_starting_with_dot.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_with_alpha.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_with_alpha_char.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_number_with_leading_zero.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_object_bad_value.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_object_bracket_key.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_object_comma_instead_of_colon.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_object_double_colon.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_object_emoji.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_object_garbage_at_end.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_object_key_with_single_quotes.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_object_lone_continuation_byte_in_key_and_trailing_comma.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_object_missing_colon.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_object_missing_key.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_object_missing_semicolon.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_object_missing_value.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_object_no-colon.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_object_non_string_key.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_object_non_string_key_but_huge_number_instead.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_object_repeated_null_null.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_object_several_trailing_commas.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_object_single_quote.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_object_trailing_comma.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_object_trailing_comment.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_object_trailing_comment_open.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_object_trailing_comment_slash_open.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_object_trailing_comment_slash_open_incomplete.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_object_two_commas_in_a_row.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_object_unquoted_key.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_object_unterminated-value.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_object_with_single_string.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_object_with_trailing_garbage.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_single_space.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_string_1_surrogate_then_escape.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_string_1_surrogate_then_escape_u.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_string_1_surrogate_then_escape_u1.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_string_1_surrogate_then_escape_u1x.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_string_accentuated_char_no_quotes.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_string_backslash_00.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_string_escape_x.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_string_escaped_backslash_bad.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_string_escaped_ctrl_char_tab.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_string_escaped_emoji.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_string_incomplete_escape.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_string_incomplete_escaped_character.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_string_incomplete_surrogate.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_string_incomplete_surrogate_escape_invalid.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_string_invalid-utf-8-in-escape.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_string_invalid_backslash_esc.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_string_invalid_unicode_escape.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_string_invalid_utf8_after_escape.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_string_leading_uescaped_thinspace.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_string_no_quotes_with_bad_escape.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_string_single_doublequote.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_string_single_quote.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_string_single_string_no_double_quotes.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_string_start_escape_unclosed.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_string_unescaped_crtl_char.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_string_unescaped_newline.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_string_unescaped_tab.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_string_unicode_CapitalU.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_string_with_trailing_garbage.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_U+2060_word_joined.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_UTF8_BOM_no_data.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_angle_bracket_..json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_angle_bracket_null.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_array_trailing_garbage.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_array_with_extra_array_close.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_array_with_unclosed_string.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_ascii-unicode-identifier.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_capitalized_True.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_close_unopened_array.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_comma_instead_of_closing_brace.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_double_array.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_end_array.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_incomplete_UTF8_BOM.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_lone-invalid-utf-8.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_lone-open-bracket.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_no_data.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_null-byte-outside-string.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_number_with_trailing_garbage.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_object_followed_by_closing_object.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_object_unclosed_no_value.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_object_with_comment.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_object_with_trailing_garbage.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_open_array_apostrophe.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_open_array_comma.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_open_array_open_object.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_open_array_open_string.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_open_array_string.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_open_object.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_open_object_close_array.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_open_object_comma.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_open_object_open_array.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_open_object_open_string.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_open_object_string_with_apostrophes.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_open_open.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_single_eacute.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_single_star.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_trailing_#.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_uescaped_LF_before_string.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_unclosed_array.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_unclosed_array_partial_null.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_unclosed_array_unfinished_false.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_unclosed_array_unfinished_true.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_unclosed_object.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_unicode-identifier.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_whitespace_U+2060_word_joiner.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_whitespace_formfeed.json"
+ }
+ )
+ {
+ CAPTURE(filename)
+ std::ifstream f(filename);
+ json _;
+ CHECK_THROWS_AS(_ = json::parse(f), json::parse_error&);
+ std::ifstream f2(filename);
+ CHECK(!json::accept(f2));
+ }
+ }
+
+ SECTION("n (previously overflowed)")
+ {
+ for (const auto* filename :
+ {
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_100000_opening_arrays.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/n_structure_open_array_object.json"
+ }
+ )
+ {
+ CAPTURE(filename)
+ std::ifstream f(filename);
+ CHECK(!json::accept(f));
+ }
+ }
+
+ SECTION("i -> y")
+ {
+ for (const auto* filename :
+ {
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_number_double_huge_neg_exp.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_number_huge_exp.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_number_neg_int_huge_exp.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_number_pos_double_huge_exp.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_number_real_neg_overflow.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_number_real_pos_overflow.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_number_real_underflow.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_number_too_big_neg_int.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_number_too_big_pos_int.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_number_very_big_negative_int.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_object_key_lone_2nd_surrogate.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_1st_surrogate_but_2nd_missing.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_1st_valid_surrogate_2nd_invalid.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_UTF-16LE_with_BOM.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_UTF-8_invalid_sequence.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_UTF8_surrogate_U+D800.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_incomplete_surrogate_and_escape_valid.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_incomplete_surrogate_pair.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_incomplete_surrogates_escape_valid.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_invalid_lonely_surrogate.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_invalid_surrogate.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_invalid_utf-8.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_inverted_surrogates_U+1D11E.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_iso_latin_1.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_lone_second_surrogate.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_lone_utf8_continuation_byte.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_not_in_unicode_range.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_overlong_sequence_2_bytes.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_overlong_sequence_6_bytes.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_overlong_sequence_6_bytes_null.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_truncated-utf-8.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_utf16BE_no_BOM.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_utf16LE_no_BOM.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_structure_500_nested_arrays.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_structure_UTF-8_BOM_empty_object.json"
+ }
+ )
+ {
+ CAPTURE(filename)
+ std::ifstream f(filename);
+ json _;
+ CHECK_NOTHROW(_ = json::parse(f));
+ std::ifstream f2(filename);
+ CHECK(json::accept(f2));
+ }
+ }
+
+ SECTION("i -> n")
+ {
+ for (const auto* filename :
+ {
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_number_double_huge_neg_exp.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_number_huge_exp.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_number_neg_int_huge_exp.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_number_pos_double_huge_exp.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_number_real_neg_overflow.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_number_real_pos_overflow.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_number_real_underflow.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_number_too_big_neg_int.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_number_too_big_pos_int.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_number_very_big_negative_int.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_object_key_lone_2nd_surrogate.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_1st_surrogate_but_2nd_missing.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_1st_valid_surrogate_2nd_invalid.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_UTF-16LE_with_BOM.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_UTF-8_invalid_sequence.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_UTF8_surrogate_U+D800.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_incomplete_surrogate_and_escape_valid.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_incomplete_surrogate_pair.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_incomplete_surrogates_escape_valid.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_invalid_lonely_surrogate.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_invalid_surrogate.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_invalid_utf-8.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_inverted_surrogates_U+1D11E.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_iso_latin_1.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_lone_second_surrogate.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_lone_utf8_continuation_byte.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_not_in_unicode_range.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_overlong_sequence_2_bytes.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_overlong_sequence_6_bytes.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_overlong_sequence_6_bytes_null.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_truncated-utf-8.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_utf16BE_no_BOM.json",
+ TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_string_utf16LE_no_BOM.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_structure_500_nested_arrays.json",
+ //TEST_DATA_DIRECTORY "/nst_json_testsuite2/test_parsing/i_structure_UTF-8_BOM_empty_object.json"
+ }
+ )
+ {
+ CAPTURE(filename)
+ std::ifstream f(filename);
+ json _;
+ CHECK_THROWS_AS(_ = json::parse(f), json::exception&); // could be parse_error or out_of_range
+ std::ifstream f2(filename);
+ CHECK(!json::accept(f2));
+ }
+ }
+ }
+}
+
+namespace
+{
+std::string trim(const std::string& str);
+
+// from https://stackoverflow.com/a/25829178/266378
+std::string trim(const std::string& str)
+{
+ const size_t first = str.find_first_not_of(' ');
+ if (std::string::npos == first)
+ {
+ return str;
+ }
+ const size_t last = str.find_last_not_of(' ');
+ return str.substr(first, (last - first + 1));
+}
+} // namespace
+
+TEST_CASE("Big List of Naughty Strings")
+{
+ // test from https://github.com/minimaxir/big-list-of-naughty-strings
+ SECTION("parsing blns.json")
+ {
+ std::ifstream f(TEST_DATA_DIRECTORY "/big-list-of-naughty-strings/blns.json");
+ json j;
+ CHECK_NOTHROW(f >> j);
+ }
+
+ // check if parsed strings roundtrip
+ // https://www.reddit.com/r/cpp/comments/5qpbie/json_form_modern_c_version_210/dd12mpq/
+ SECTION("roundtripping")
+ {
+ std::ifstream f(TEST_DATA_DIRECTORY "/big-list-of-naughty-strings/blns.json");
+ std::string line;
+
+ // read lines one by one, bail out on error or eof
+ while (getline(f, line))
+ {
+ // trim whitespace
+ line = trim(line);
+
+ // remove trailing comma
+ line = line.substr(0, line.find_last_of(','));
+
+ // discard lines without at least two characters (quotes)
+ if (line.size() < 2)
+ {
+ continue;
+ }
+
+ // check roundtrip
+ CAPTURE(line)
+ const json j = json::parse(line);
+ CHECK(j.dump() == line);
+ }
+ }
+}
diff --git a/json4cpp/tests/src/unit-to_chars.cpp b/json4cpp/tests/src/unit-to_chars.cpp
new file mode 100644
index 0000000000..def2793c2a
--- /dev/null
+++ b/json4cpp/tests/src/unit-to_chars.cpp
@@ -0,0 +1,516 @@
+// __ _____ _____ _____
+// __| | __| | | | 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
+
+// XXX:
+// Only compile these tests if 'float' and 'double' are IEEE-754 single- and
+// double-precision numbers, resp.
+
+#include "doctest_compatibility.h"
+
+#include <nlohmann/json.hpp>
+using nlohmann::detail::dtoa_impl::reinterpret_bits;
+
+namespace
+{
+float make_float(uint32_t sign_bit, uint32_t biased_exponent, uint32_t significand)
+{
+ assert(sign_bit == 0 || sign_bit == 1);
+ assert(biased_exponent <= 0xFF);
+ assert(significand <= 0x007FFFFF);
+
+ uint32_t bits = 0;
+
+ bits |= sign_bit << 31;
+ bits |= biased_exponent << 23;
+ bits |= significand;
+
+ return reinterpret_bits<float>(bits);
+}
+
+// ldexp -- convert f * 2^e to IEEE single precision
+float make_float(uint64_t f, int e)
+{
+ constexpr uint64_t kHiddenBit = 0x00800000;
+ constexpr uint64_t kSignificandMask = 0x007FFFFF;
+ constexpr int kPhysicalSignificandSize = 23; // Excludes the hidden bit.
+ constexpr int kExponentBias = 0x7F + kPhysicalSignificandSize;
+ constexpr int kDenormalExponent = 1 - kExponentBias;
+ constexpr int kMaxExponent = 0xFF - kExponentBias;
+
+ while (f > kHiddenBit + kSignificandMask)
+ {
+ f >>= 1;
+ e++;
+ }
+ if (e >= kMaxExponent)
+ {
+ return std::numeric_limits<float>::infinity();
+ }
+ if (e < kDenormalExponent)
+ {
+ return 0.0;
+ }
+ while (e > kDenormalExponent && (f & kHiddenBit) == 0)
+ {
+ f <<= 1;
+ e--;
+ }
+
+ const uint64_t biased_exponent = (e == kDenormalExponent && (f & kHiddenBit) == 0)
+ ? 0
+ : static_cast<uint64_t>(e + kExponentBias);
+
+ const uint64_t bits = (f & kSignificandMask) | (biased_exponent << kPhysicalSignificandSize);
+ return reinterpret_bits<float>(static_cast<uint32_t>(bits));
+}
+
+double make_double(uint64_t sign_bit, uint64_t biased_exponent, uint64_t significand)
+{
+ assert(sign_bit == 0 || sign_bit == 1);
+ assert(biased_exponent <= 0x7FF);
+ assert(significand <= 0x000FFFFFFFFFFFFF);
+
+ uint64_t bits = 0;
+
+ bits |= sign_bit << 63;
+ bits |= biased_exponent << 52;
+ bits |= significand;
+
+ return reinterpret_bits<double>(bits);
+}
+
+// ldexp -- convert f * 2^e to IEEE double precision
+double make_double(uint64_t f, int e)
+{
+ constexpr uint64_t kHiddenBit = 0x0010000000000000;
+ constexpr uint64_t kSignificandMask = 0x000FFFFFFFFFFFFF;
+ constexpr int kPhysicalSignificandSize = 52; // Excludes the hidden bit.
+ constexpr int kExponentBias = 0x3FF + kPhysicalSignificandSize;
+ constexpr int kDenormalExponent = 1 - kExponentBias;
+ constexpr int kMaxExponent = 0x7FF - kExponentBias;
+
+ while (f > kHiddenBit + kSignificandMask)
+ {
+ f >>= 1;
+ e++;
+ }
+ if (e >= kMaxExponent)
+ {
+ return std::numeric_limits<double>::infinity();
+ }
+ if (e < kDenormalExponent)
+ {
+ return 0.0;
+ }
+ while (e > kDenormalExponent && (f & kHiddenBit) == 0)
+ {
+ f <<= 1;
+ e--;
+ }
+
+ const uint64_t biased_exponent = (e == kDenormalExponent && (f & kHiddenBit) == 0)
+ ? 0
+ : static_cast<uint64_t>(e + kExponentBias);
+
+ const uint64_t bits = (f & kSignificandMask) | (biased_exponent << kPhysicalSignificandSize);
+ return reinterpret_bits<double>(bits);
+}
+} // namespace
+
+TEST_CASE("digit gen")
+{
+ SECTION("single precision")
+ {
+ auto check_float = [](float number, const std::string & digits, int expected_exponent)
+ {
+ CAPTURE(number)
+ CAPTURE(digits)
+ CAPTURE(expected_exponent)
+
+ std::array<char, 32> buf{};
+ int len = 0;
+ int exponent = 0;
+ nlohmann::detail::dtoa_impl::grisu2(buf.data(), len, exponent, number);
+
+ CHECK(digits == std::string(buf.data(), buf.data() + len));
+ CHECK(expected_exponent == exponent);
+ };
+
+ check_float(make_float(0, 0, 0x00000001), "1", -45); // min denormal
+ check_float(make_float(0, 0, 0x007FFFFF), "11754942", -45); // max denormal
+ check_float(make_float(0, 1, 0x00000000), "11754944", -45); // min normal
+ check_float(make_float(0, 1, 0x00000001), "11754945", -45);
+ check_float(make_float(0, 1, 0x007FFFFF), "23509886", -45);
+ check_float(make_float(0, 2, 0x00000000), "23509887", -45);
+ check_float(make_float(0, 2, 0x00000001), "2350989", -44);
+ check_float(make_float(0, 24, 0x00000000), "98607613", -39); // fail if no special case in normalized boundaries
+ check_float(make_float(0, 30, 0x00000000), "63108872", -37); // fail if no special case in normalized boundaries
+ check_float(make_float(0, 31, 0x00000000), "12621775", -36); // fail if no special case in normalized boundaries
+ check_float(make_float(0, 57, 0x00000000), "84703295", -29); // fail if no special case in normalized boundaries
+ check_float(make_float(0, 254, 0x007FFFFE), "34028233", 31);
+ check_float(make_float(0, 254, 0x007FFFFF), "34028235", 31); // max normal
+
+ // V. Paxson and W. Kahan, "A Program for Testing IEEE Binary-Decimal Conversion", manuscript, May 1991,
+ // ftp://ftp.ee.lbl.gov/testbase-report.ps.Z (report)
+ // ftp://ftp.ee.lbl.gov/testbase.tar.Z (program)
+
+ // Table 16: Stress Inputs for Converting 24-bit Binary to Decimal, < 1/2 ULP
+ check_float(make_float(12676506, -102), "25", -25);
+ check_float(make_float(12676506, -103), "125", -26);
+ check_float(make_float(15445013, 86), "1195", 30);
+ check_float(make_float(13734123, -138), "39415", -39);
+ check_float(make_float(12428269, -130), "913085", -38);
+ check_float(make_float(15334037, -146), "1719005", -43);
+ check_float(make_float(11518287, -41), "52379105", -13);
+ check_float(make_float(12584953, -145), "2821644", -43);
+ check_float(make_float(15961084, -125), "37524328", -38);
+ check_float(make_float(14915817, -146), "16721209", -44);
+ check_float(make_float(10845484, -102), "21388946", -31);
+ check_float(make_float(16431059, -61), "7125836", -18);
+
+ // Table 17: Stress Inputs for Converting 24-bit Binary to Decimal, > 1/2 ULP
+ check_float(make_float(16093626, 69), "95", 26);
+ check_float(make_float( 9983778, 25), "335", 12);
+ check_float(make_float(12745034, 104), "2585", 35);
+ check_float(make_float(12706553, 72), "60005", 24);
+ check_float(make_float(11005028, 45), "387205", 15);
+ check_float(make_float(15059547, 71), "3555835", 22);
+ check_float(make_float(16015691, -99), "25268305", -30);
+ check_float(make_float( 8667859, 56), "6245851", 17);
+ check_float(make_float(14855922, -82), "30721327", -25);
+ check_float(make_float(14855922, -83), "15360663", -25);
+ check_float(make_float(10144164, -110), "781478", -32);
+ check_float(make_float(13248074, 95), "52481028", 28);
+ }
+
+ SECTION("double precision")
+ {
+ auto check_double = [](double number, const std::string & digits, int expected_exponent)
+ {
+ CAPTURE(number)
+ CAPTURE(digits)
+ CAPTURE(expected_exponent)
+
+ std::array<char, 32> buf{};
+ int len = 0;
+ int exponent = 0;
+ nlohmann::detail::dtoa_impl::grisu2(buf.data(), len, exponent, number);
+
+ CHECK(digits == std::string(buf.data(), buf.data() + len));
+ CHECK(expected_exponent == exponent);
+ };
+
+ check_double(make_double(0, 0, 0x0000000000000001), "5", -324); // min denormal
+ check_double(make_double(0, 0, 0x000FFFFFFFFFFFFF), "2225073858507201", -323); // max denormal
+ check_double(make_double(0, 1, 0x0000000000000000), "22250738585072014", -324); // min normal
+ check_double(make_double(0, 1, 0x0000000000000001), "2225073858507202", -323);
+ check_double(make_double(0, 1, 0x000FFFFFFFFFFFFF), "44501477170144023", -324);
+ check_double(make_double(0, 2, 0x0000000000000000), "4450147717014403", -323);
+ check_double(make_double(0, 2, 0x0000000000000001), "4450147717014404", -323);
+ check_double(make_double(0, 4, 0x0000000000000000), "17800590868057611", -323); // fail if no special case in normalized boundaries
+ check_double(make_double(0, 5, 0x0000000000000000), "35601181736115222", -323); // fail if no special case in normalized boundaries
+ check_double(make_double(0, 6, 0x0000000000000000), "7120236347223045", -322); // fail if no special case in normalized boundaries
+ check_double(make_double(0, 10, 0x0000000000000000), "11392378155556871", -321); // fail if no special case in normalized boundaries
+ check_double(make_double(0, 2046, 0x000FFFFFFFFFFFFE), "17976931348623155", 292);
+ check_double(make_double(0, 2046, 0x000FFFFFFFFFFFFF), "17976931348623157", 292); // max normal
+
+ // Test different paths in DigitGen
+ check_double( 10000, "1", 4);
+ check_double( 1200000, "12", 5);
+ check_double(4.9406564584124654e-324, "5", -324); // exit integral loop
+ check_double(2.2250738585072009e-308, "2225073858507201", -323); // exit fractional loop
+ check_double( 1.82877982605164e-99, "182877982605164", -113);
+ check_double( 1.1505466208671903e-09, "11505466208671903", -25);
+ check_double( 5.5645893133766722e+20, "5564589313376672", 5);
+ check_double( 53.034830388866226, "53034830388866226", -15);
+ check_double( 0.0021066531670178605, "21066531670178605", -19);
+
+ // V. Paxson and W. Kahan, "A Program for Testing IEEE Binary-Decimal Conversion", manuscript, May 1991,
+ // ftp://ftp.ee.lbl.gov/testbase-report.ps.Z (report)
+ // ftp://ftp.ee.lbl.gov/testbase.tar.Z (program)
+
+ // Table 3: Stress Inputs for Converting 53-bit Binary to Decimal, < 1/2 ULP
+ check_double(make_double(8511030020275656, -342) /* 9.5e-088 */, "95", -89);
+ check_double(make_double(5201988407066741, -824) /* 4.65e-233 */, "465", -235);
+ check_double(make_double(6406892948269899, +237) /* 1.415e+087 */, "1415", 84);
+ check_double(make_double(8431154198732492, +72) /* 3.9815e+037 */, "39815", 33);
+ check_double(make_double(6475049196144587, +99) /* 4.10405e+045 */, "410405", 40);
+ check_double(make_double(8274307542972842, +726) /* 2.920845e+234 */, "2920845", 228);
+ check_double(make_double(5381065484265332, -456) /* 2.8919465e-122 */, "28919465", -129);
+ check_double(make_double(6761728585499734, -1057) /* 4.37877185e-303 */, "437877185", -311);
+ check_double(make_double(7976538478610756, +376) /* 1.227701635e+129 */, "1227701635", 120);
+ check_double(make_double(5982403858958067, +377) /* 1.8415524525e+129 */, "18415524525", 119);
+ check_double(make_double(5536995190630837, +93) /* 5.48357443505e+043 */, "548357443505", 32);
+ check_double(make_double(7225450889282194, +710) /* 3.891901811465e+229 */, "3891901811465", 217);
+ check_double(make_double(7225450889282194, +709) /* 1.9459509057325e+229 */, "19459509057325", 216);
+ check_double(make_double(8703372741147379, +117) /* 1.44609583816055e+051 */, "144609583816055", 37);
+ check_double(make_double(8944262675275217, -1001) /* 4.173677474585315e-286 */, "4173677474585315", -301);
+ check_double(make_double(7459803696087692, -707) /* 1.1079507728788885e-197 */, "11079507728788885", -213);
+ check_double(make_double(6080469016670379, -381) /* 1.234550136632744e-099 */, "1234550136632744", -114);
+ check_double(make_double(8385515147034757, +721) /* 9.2503171196036502e+232 */, "925031711960365", 218);
+ check_double(make_double(7514216811389786, -828) /* 4.1980471502848898e-234 */, "419804715028489", -248);
+ check_double(make_double(8397297803260511, -345) /* 1.1716315319786511e-088 */, "11716315319786511", -104);
+ check_double(make_double(6733459239310543, +202) /* 4.3281007284461249e+076 */, "4328100728446125", 61);
+ check_double(make_double(8091450587292794, -473) /* 3.3177101181600311e-127 */, "3317710118160031", -142);
+
+ // Table 4: Stress Inputs for Converting 53-bit Binary to Decimal, > 1/2 ULP
+ check_double(make_double(6567258882077402, +952) /* 2.5e+302 */, "25", 301);
+ check_double(make_double(6712731423444934, +535) /* 7.55e+176 */, "755", 174);
+ check_double(make_double(6712731423444934, +534) /* 3.775e+176 */, "3775", 173);
+ check_double(make_double(5298405411573037, -957) /* 4.3495e-273 */, "43495", -277);
+ check_double(make_double(5137311167659507, -144) /* 2.30365e-028 */, "230365", -33);
+ check_double(make_double(6722280709661868, +363) /* 1.263005e+125 */, "1263005", 119);
+ check_double(make_double(5344436398034927, -169) /* 7.1422105e-036 */, "71422105", -43);
+ check_double(make_double(8369123604277281, -853) /* 1.39345735e-241 */, "139345735", -249);
+ check_double(make_double(8995822108487663, -780) /* 1.414634485e-219 */, "1414634485", -228);
+ check_double(make_double(8942832835564782, -383) /* 4.5392779195e-100 */, "45392779195", -110);
+ check_double(make_double(8942832835564782, -384) /* 2.26963895975e-100 */, "226963895975", -111);
+ check_double(make_double(8942832835564782, -385) /* 1.134819479875e-100 */, "1134819479875", -112);
+ check_double(make_double(6965949469487146, -249) /* 7.7003665618895e-060 */, "77003665618895", -73);
+ check_double(make_double(6965949469487146, -250) /* 3.85018328094475e-060 */, "385018328094475", -74);
+ check_double(make_double(6965949469487146, -251) /* 1.925091640472375e-060 */, "1925091640472375", -75);
+ check_double(make_double(7487252720986826, +548) /* 6.8985865317742005e+180 */, "68985865317742005", 164);
+ check_double(make_double(5592117679628511, +164) /* 1.3076622631878654e+065 */, "13076622631878654", 49);
+ check_double(make_double(8887055249355788, +665) /* 1.3605202075612124e+216 */, "13605202075612124", 200);
+ check_double(make_double(6994187472632449, +690) /* 3.5928102174759597e+223 */, "35928102174759597", 207);
+ check_double(make_double(8797576579012143, +588) /* 8.9125197712484552e+192 */, "8912519771248455", 177);
+ check_double(make_double(7363326733505337, +272) /* 5.5876975736230114e+097 */, "55876975736230114", 81);
+ check_double(make_double(8549497411294502, -448) /* 1.1762578307285404e-119 */, "11762578307285404", -135);
+
+ // Table 20: Stress Inputs for Converting 56-bit Binary to Decimal, < 1/2 ULP
+ check_double(make_double(50883641005312716, -172) /* 8.4999999999999993e-036 */, "8499999999999999", -51);
+ check_double(make_double(38162730753984537, -170) /* 2.5499999999999999e-035 */, "255", -37);
+ check_double(make_double(50832789069151999, -101) /* 2.0049999999999997e-014 */, "20049999999999997", -30);
+ check_double(make_double(51822367833714164, -109) /* 7.9844999999999994e-017 */, "7984499999999999", -32);
+ check_double(make_double(66840152193508133, -172) /* 1.1165499999999999e-035 */, "11165499999999999", -51);
+ check_double(make_double(55111239245584393, -138) /* 1.581615e-025 */, "1581615", -31);
+ check_double(make_double(71704866733321482, -112) /* 1.3809855e-017 */, "13809855", -24);
+ check_double(make_double(67160949328233173, -142) /* 1.2046404499999999e-026 */, "12046404499999999", -42);
+ check_double(make_double(53237141308040189, -152) /* 9.3251405449999991e-030 */, "9325140544999999", -45);
+ check_double(make_double(62785329394975786, -112) /* 1.2092014595e-017 */, "12092014595", -27);
+ check_double(make_double(48367680154689523, -77) /* 3.2007045838499998e-007 */, "320070458385", -18);
+ check_double(make_double(42552223180606797, -102) /* 8.391946324354999e-015 */, "8391946324354999", -30);
+ check_double(make_double(63626356173011241, -112) /* 1.2253990460585e-017 */, "12253990460585", -30);
+ check_double(make_double(43566388595783643, -99) /* 6.8735641489760495e-014 */, "687356414897605", -28);
+ check_double(make_double(54512669636675272, -159) /* 7.459816430480385e-032 */, "7459816430480385", -47);
+ check_double(make_double(52306490527514614, -167) /* 2.7960588398142552e-034 */, "2796058839814255", -49);
+ check_double(make_double(52306490527514614, -168) /* 1.3980294199071276e-034 */, "13980294199071276", -50);
+ check_double(make_double(41024721590449423, -89) /* 6.6279012373057359e-011 */, "6627901237305736", -26);
+ check_double(make_double(37664020415894738, -132) /* 6.9177880043968072e-024 */, "6917788004396807", -39);
+ check_double(make_double(37549883692866294, -93) /* 3.7915693108349708e-012 */, "3791569310834971", -27);
+ check_double(make_double(69124110374399839, -104) /* 3.4080817676591365e-015 */, "34080817676591365", -31);
+ check_double(make_double(69124110374399839, -105) /* 1.7040408838295683e-015 */, "17040408838295683", -31);
+
+ // Table 21: Stress Inputs for Converting 56-bit Binary to Decimal, > 1/2 ULP
+ check_double(make_double(49517601571415211, -94) /* 2.4999999999999998e-012 */, "25", -13);
+ check_double(make_double(49517601571415211, -95) /* 1.2499999999999999e-012 */, "125", -14);
+ check_double(make_double(54390733528642804, -133) /* 4.9949999999999996e-024 */, "49949999999999996", -40); // shortest: 4995e-27
+ check_double(make_double(71805402319113924, -157) /* 3.9304999999999998e-031 */, "39304999999999998", -47); // shortest: 39305e-35
+ check_double(make_double(40435277969631694, -179) /* 5.2770499999999992e-038 */, "5277049999999999", -53);
+ check_double(make_double(57241991568619049, -165) /* 1.223955e-033 */, "1223955", -39);
+ check_double(make_double(65224162876242886, +58) /* 1.8799584999999998e+034 */, "18799584999999998", 18);
+ check_double(make_double(70173376848895368, -138) /* 2.01387715e-025 */, "201387715", -33);
+ check_double(make_double(37072848117383207, -99) /* 5.8490641049999989e-014 */, "5849064104999999", -29);
+ check_double(make_double(56845051585389697, -176) /* 5.9349003054999999e-037 */, "59349003055", -47);
+ check_double(make_double(54791673366936431, -145) /* 1.2284718039499998e-027 */, "12284718039499998", -43);
+ check_double(make_double(66800318669106231, -169) /* 8.9270767180849991e-035 */, "8927076718084999", -50);
+ check_double(make_double(66800318669106231, -170) /* 4.4635383590424995e-035 */, "44635383590424995", -51);
+ check_double(make_double(66574323440112438, -119) /* 1.0016990862549499e-019 */, "10016990862549499", -35);
+ check_double(make_double(65645179969330963, -173) /* 5.4829412628024647e-036 */, "5482941262802465", -51);
+ check_double(make_double(61847254334681076, -109) /* 9.5290783281036439e-017 */, "9529078328103644", -32);
+ check_double(make_double(39990712921393606, -145) /* 8.9662279366405553e-028 */, "8966227936640555", -43);
+ check_double(make_double(59292318184400283, -149) /* 8.3086234418058538e-029 */, "8308623441805854", -44);
+ check_double(make_double(69116558615326153, -143) /* 6.1985873566126555e-027 */, "61985873566126555", -43);
+ check_double(make_double(69116558615326153, -144) /* 3.0992936783063277e-027 */, "30992936783063277", -43);
+ check_double(make_double(39462549494468513, -152) /* 6.9123512506176015e-030 */, "6912351250617602", -45);
+ check_double(make_double(39462549494468513, -153) /* 3.4561756253088008e-030 */, "3456175625308801", -45);
+ }
+}
+
+TEST_CASE("formatting")
+{
+ SECTION("single precision")
+ {
+ auto check_float = [](float number, const std::string & expected)
+ {
+ std::array<char, 33> buf{};
+ char* end = nlohmann::detail::to_chars(buf.data(), buf.data() + 32, number); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
+ const std::string actual(buf.data(), end);
+
+ CHECK(actual == expected);
+ };
+ // %.9g
+ check_float( -1.2345e-22f, "-1.2345e-22" ); // -1.23450004e-22
+ check_float( -1.2345e-21f, "-1.2345e-21" ); // -1.23450002e-21
+ check_float( -1.2345e-20f, "-1.2345e-20" ); // -1.23450002e-20
+ check_float( -1.2345e-19f, "-1.2345e-19" ); // -1.23449999e-19
+ check_float( -1.2345e-18f, "-1.2345e-18" ); // -1.23449996e-18
+ check_float( -1.2345e-17f, "-1.2345e-17" ); // -1.23449998e-17
+ check_float( -1.2345e-16f, "-1.2345e-16" ); // -1.23449996e-16
+ check_float( -1.2345e-15f, "-1.2345e-15" ); // -1.23450002e-15
+ check_float( -1.2345e-14f, "-1.2345e-14" ); // -1.23450004e-14
+ check_float( -1.2345e-13f, "-1.2345e-13" ); // -1.23449997e-13
+ check_float( -1.2345e-12f, "-1.2345e-12" ); // -1.23450002e-12
+ check_float( -1.2345e-11f, "-1.2345e-11" ); // -1.2345e-11
+ check_float( -1.2345e-10f, "-1.2345e-10" ); // -1.2345e-10
+ check_float( -1.2345e-9f, "-1.2345e-09" ); // -1.23449995e-09
+ check_float( -1.2345e-8f, "-1.2345e-08" ); // -1.23449997e-08
+ check_float( -1.2345e-7f, "-1.2345e-07" ); // -1.23449993e-07
+ check_float( -1.2345e-6f, "-1.2345e-06" ); // -1.23450002e-06
+ check_float( -1.2345e-5f, "-1.2345e-05" ); // -1.2345e-05
+ check_float( -1.2345e-4f, "-0.00012345" ); // -0.000123449994
+ check_float( -1.2345e-3f, "-0.0012345" ); // -0.00123449997
+ check_float( -1.2345e-2f, "-0.012345" ); // -0.0123450002
+ check_float( -1.2345e-1f, "-0.12345" ); // -0.123450004
+ check_float( -0.0f, "-0.0" ); // -0
+ check_float( 0.0f, "0.0" ); // 0
+ check_float( 1.2345e+0f, "1.2345" ); // 1.23450005
+ check_float( 1.2345e+1f, "12.345" ); // 12.3450003
+ check_float( 1.2345e+2f, "123.45" ); // 123.449997
+ check_float( 1.2345e+3f, "1234.5" ); // 1234.5
+ check_float( 1.2345e+4f, "12345.0" ); // 12345
+ check_float( 1.2345e+5f, "123450.0" ); // 123450
+ check_float( 1.2345e+6f, "1.2345e+06" ); // 1234500
+ check_float( 1.2345e+7f, "1.2345e+07" ); // 12345000
+ check_float( 1.2345e+8f, "1.2345e+08" ); // 123450000
+ check_float( 1.2345e+9f, "1.2345e+09" ); // 1.23449997e+09
+ check_float( 1.2345e+10f, "1.2345e+10" ); // 1.23449999e+10
+ check_float( 1.2345e+11f, "1.2345e+11" ); // 1.23449999e+11
+ check_float( 1.2345e+12f, "1.2345e+12" ); // 1.23450006e+12
+ check_float( 1.2345e+13f, "1.2345e+13" ); // 1.23449995e+13
+ check_float( 1.2345e+14f, "1.2345e+14" ); // 1.23450002e+14
+ check_float( 1.2345e+15f, "1.2345e+15" ); // 1.23450003e+15
+ check_float( 1.2345e+16f, "1.2345e+16" ); // 1.23449998e+16
+ check_float( 1.2345e+17f, "1.2345e+17" ); // 1.23449996e+17
+ check_float( 1.2345e+18f, "1.2345e+18" ); // 1.23450004e+18
+ check_float( 1.2345e+19f, "1.2345e+19" ); // 1.23449999e+19
+ check_float( 1.2345e+20f, "1.2345e+20" ); // 1.23449999e+20
+ check_float( 1.2345e+21f, "1.2345e+21" ); // 1.23449999e+21
+ check_float( 1.2345e+22f, "1.2345e+22" ); // 1.23450005e+22
+ }
+
+ SECTION("double precision")
+ {
+ auto check_double = [](double number, const std::string & expected)
+ {
+ std::array<char, 33> buf{};
+ char* end = nlohmann::detail::to_chars(buf.data(), buf.data() + 32, number); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
+ const std::string actual(buf.data(), end);
+
+ CHECK(actual == expected);
+ };
+ // dtoa %.15g %.17g shortest
+ check_double( -1.2345e-22, "-1.2345e-22" ); // -1.2345e-22 -1.2345000000000001e-22 -1.2345e-22
+ check_double( -1.2345e-21, "-1.2345e-21" ); // -1.2345e-21 -1.2345000000000001e-21 -1.2345e-21
+ check_double( -1.2345e-20, "-1.2345e-20" ); // -1.2345e-20 -1.2345e-20 -1.2345e-20
+ check_double( -1.2345e-19, "-1.2345e-19" ); // -1.2345e-19 -1.2345000000000001e-19 -1.2345e-19
+ check_double( -1.2345e-18, "-1.2345e-18" ); // -1.2345e-18 -1.2345000000000001e-18 -1.2345e-18
+ check_double( -1.2345e-17, "-1.2345e-17" ); // -1.2345e-17 -1.2345e-17 -1.2345e-17
+ check_double( -1.2345e-16, "-1.2345e-16" ); // -1.2345e-16 -1.2344999999999999e-16 -1.2345e-16
+ check_double( -1.2345e-15, "-1.2345e-15" ); // -1.2345e-15 -1.2345e-15 -1.2345e-15
+ check_double( -1.2345e-14, "-1.2345e-14" ); // -1.2345e-14 -1.2345e-14 -1.2345e-14
+ check_double( -1.2345e-13, "-1.2345e-13" ); // -1.2345e-13 -1.2344999999999999e-13 -1.2345e-13
+ check_double( -1.2345e-12, "-1.2345e-12" ); // -1.2345e-12 -1.2345e-12 -1.2345e-12
+ check_double( -1.2345e-11, "-1.2345e-11" ); // -1.2345e-11 -1.2345e-11 -1.2345e-11
+ check_double( -1.2345e-10, "-1.2345e-10" ); // -1.2345e-10 -1.2345e-10 -1.2345e-10
+ check_double( -1.2345e-9, "-1.2345e-09" ); // -1.2345e-09 -1.2345e-09 -1.2345e-9
+ check_double( -1.2345e-8, "-1.2345e-08" ); // -1.2345e-08 -1.2345000000000001e-08 -1.2345e-8
+ check_double( -1.2345e-7, "-1.2345e-07" ); // -1.2345e-07 -1.2345000000000001e-07 -1.2345e-7
+ check_double( -1.2345e-6, "-1.2345e-06" ); // -1.2345e-06 -1.2345e-06 -1.2345e-6
+ check_double( -1.2345e-5, "-1.2345e-05" ); // -1.2345e-05 -1.2345e-05 -1.2345e-5
+ check_double( -1.2345e-4, "-0.00012345" ); // -0.00012345 -0.00012344999999999999 -0.00012345
+ check_double( -1.2345e-3, "-0.0012345" ); // -0.0012345 -0.0012344999999999999 -0.0012345
+ check_double( -1.2345e-2, "-0.012345" ); // -0.012345 -0.012345 -0.012345
+ check_double( -1.2345e-1, "-0.12345" ); // -0.12345 -0.12345 -0.12345
+ check_double( -0.0, "-0.0" ); // -0 -0 -0
+ check_double( 0.0, "0.0" ); // 0 0 0
+ check_double( 1.2345e+0, "1.2345" ); // 1.2345 1.2344999999999999 1.2345
+ check_double( 1.2345e+1, "12.345" ); // 12.345 12.345000000000001 12.345
+ check_double( 1.2345e+2, "123.45" ); // 123.45 123.45 123.45
+ check_double( 1.2345e+3, "1234.5" ); // 1234.5 1234.5 1234.5
+ check_double( 1.2345e+4, "12345.0" ); // 12345 12345 12345
+ check_double( 1.2345e+5, "123450.0" ); // 123450 123450 123450
+ check_double( 1.2345e+6, "1234500.0" ); // 1234500 1234500 1234500
+ check_double( 1.2345e+7, "12345000.0" ); // 12345000 12345000 12345000
+ check_double( 1.2345e+8, "123450000.0" ); // 123450000 123450000 123450000
+ check_double( 1.2345e+9, "1234500000.0" ); // 1234500000 1234500000 1234500000
+ check_double( 1.2345e+10, "12345000000.0" ); // 12345000000 12345000000 12345000000
+ check_double( 1.2345e+11, "123450000000.0" ); // 123450000000 123450000000 123450000000
+ check_double( 1.2345e+12, "1234500000000.0" ); // 1234500000000 1234500000000 1234500000000
+ check_double( 1.2345e+13, "12345000000000.0" ); // 12345000000000 12345000000000 12345000000000
+ check_double( 1.2345e+14, "123450000000000.0" ); // 123450000000000 123450000000000 123450000000000
+ check_double( 1.2345e+15, "1.2345e+15" ); // 1.2345e+15 1234500000000000 1.2345e15
+ check_double( 1.2345e+16, "1.2345e+16" ); // 1.2345e+16 12345000000000000 1.2345e16
+ check_double( 1.2345e+17, "1.2345e+17" ); // 1.2345e+17 1.2345e+17 1.2345e17
+ check_double( 1.2345e+18, "1.2345e+18" ); // 1.2345e+18 1.2345e+18 1.2345e18
+ check_double( 1.2345e+19, "1.2345e+19" ); // 1.2345e+19 1.2345e+19 1.2345e19
+ check_double( 1.2345e+20, "1.2345e+20" ); // 1.2345e+20 1.2345e+20 1.2345e20
+ check_double( 1.2345e+21, "1.2344999999999999e+21" ); // 1.2345e+21 1.2344999999999999e+21 1.2345e21
+ check_double( 1.2345e+22, "1.2345e+22" ); // 1.2345e+22 1.2345e+22 1.2345e22
+ }
+
+ SECTION("integer")
+ {
+ auto check_integer = [](std::int64_t number, const std::string & expected)
+ {
+ const nlohmann::json j = number;
+ CHECK(j.dump() == expected);
+ };
+
+ // edge cases
+ check_integer(INT64_MIN, "-9223372036854775808");
+ check_integer(INT64_MAX, "9223372036854775807");
+
+ // few random big integers
+ check_integer(-3456789012345678901LL, "-3456789012345678901");
+ check_integer(3456789012345678901LL, "3456789012345678901");
+ check_integer(-5678901234567890123LL, "-5678901234567890123");
+ check_integer(5678901234567890123LL, "5678901234567890123");
+
+ // integers with various digit counts
+ check_integer(-1000000000000000000LL, "-1000000000000000000");
+ check_integer(-100000000000000000LL, "-100000000000000000");
+ check_integer(-10000000000000000LL, "-10000000000000000");
+ check_integer(-1000000000000000LL, "-1000000000000000");
+ check_integer(-100000000000000LL, "-100000000000000");
+ check_integer(-10000000000000LL, "-10000000000000");
+ check_integer(-1000000000000LL, "-1000000000000");
+ check_integer(-100000000000LL, "-100000000000");
+ check_integer(-10000000000LL, "-10000000000");
+ check_integer(-1000000000LL, "-1000000000");
+ check_integer(-100000000LL, "-100000000");
+ check_integer(-10000000LL, "-10000000");
+ check_integer(-1000000LL, "-1000000");
+ check_integer(-100000LL, "-100000");
+ check_integer(-10000LL, "-10000");
+ check_integer(-1000LL, "-1000");
+ check_integer(-100LL, "-100");
+ check_integer(-10LL, "-10");
+ check_integer(-1LL, "-1");
+ check_integer(0, "0");
+ check_integer(1LL, "1");
+ check_integer(10LL, "10");
+ check_integer(100LL, "100");
+ check_integer(1000LL, "1000");
+ check_integer(10000LL, "10000");
+ check_integer(100000LL, "100000");
+ check_integer(1000000LL, "1000000");
+ check_integer(10000000LL, "10000000");
+ check_integer(100000000LL, "100000000");
+ check_integer(1000000000LL, "1000000000");
+ check_integer(10000000000LL, "10000000000");
+ check_integer(100000000000LL, "100000000000");
+ check_integer(1000000000000LL, "1000000000000");
+ check_integer(10000000000000LL, "10000000000000");
+ check_integer(100000000000000LL, "100000000000000");
+ check_integer(1000000000000000LL, "1000000000000000");
+ check_integer(10000000000000000LL, "10000000000000000");
+ check_integer(100000000000000000LL, "100000000000000000");
+ check_integer(1000000000000000000LL, "1000000000000000000");
+ }
+}
diff --git a/json4cpp/tests/src/unit-type_traits.cpp b/json4cpp/tests/src/unit-type_traits.cpp
new file mode 100644
index 0000000000..d083293dd4
--- /dev/null
+++ b/json4cpp/tests/src/unit-type_traits.cpp
@@ -0,0 +1,56 @@
+// __ _____ _____ _____
+// __| | __| | | | 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"
+
+#if JSON_TEST_USING_MULTIPLE_HEADERS
+ #include <nlohmann/detail/meta/type_traits.hpp>
+#else
+ #include <nlohmann/json.hpp>
+#endif
+
+TEST_CASE("type traits")
+{
+ SECTION("is_c_string")
+ {
+ using nlohmann::detail::is_c_string;
+ using nlohmann::detail::is_c_string_uncvref;
+
+ SECTION("char *")
+ {
+ CHECK(is_c_string<char*>::value);
+ CHECK(is_c_string<const char*>::value);
+ CHECK(is_c_string<char* const>::value);
+ CHECK(is_c_string<const char* const>::value);
+
+ CHECK_FALSE(is_c_string<char*&>::value);
+ CHECK_FALSE(is_c_string<const char*&>::value);
+ CHECK_FALSE(is_c_string<char* const&>::value);
+ CHECK_FALSE(is_c_string<const char* const&>::value);
+
+ CHECK(is_c_string_uncvref<char*&>::value);
+ CHECK(is_c_string_uncvref<const char*&>::value);
+ CHECK(is_c_string_uncvref<char* const&>::value);
+ CHECK(is_c_string_uncvref<const char* const&>::value);
+ }
+
+ SECTION("char[]")
+ {
+ // NOLINTBEGIN(hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-avoid-c-arrays)
+ CHECK(is_c_string<char[]>::value);
+ CHECK(is_c_string<const char[]>::value);
+
+ CHECK_FALSE(is_c_string<char(&)[]>::value);
+ CHECK_FALSE(is_c_string<const char(&)[]>::value);
+
+ CHECK(is_c_string_uncvref<char(&)[]>::value);
+ CHECK(is_c_string_uncvref<const char(&)[]>::value);
+ // NOLINTEND(hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-avoid-c-arrays)
+ }
+ }
+}
diff --git a/json4cpp/tests/src/unit-ubjson.cpp b/json4cpp/tests/src/unit-ubjson.cpp
new file mode 100644
index 0000000000..99d07d9669
--- /dev/null
+++ b/json4cpp/tests/src/unit-ubjson.cpp
@@ -0,0 +1,2547 @@
+// __ _____ _____ _____
+// __| | __| | | | 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;
+
+#include <iostream>
+#include <fstream>
+#include <set>
+#include "make_test_data_available.hpp"
+#include "test_utils.hpp"
+
+namespace
+{
+class SaxCountdown
+{
+ public:
+ explicit SaxCountdown(const int count) : events_left(count)
+ {}
+
+ bool null()
+ {
+ return events_left-- > 0;
+ }
+
+ bool boolean(bool /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool number_integer(json::number_integer_t /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool number_unsigned(json::number_unsigned_t /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool number_float(json::number_float_t /*unused*/, const std::string& /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool string(std::string& /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool binary(std::vector<std::uint8_t>& /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool start_object(std::size_t /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool key(std::string& /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool end_object()
+ {
+ return events_left-- > 0;
+ }
+
+ bool start_array(std::size_t /*unused*/)
+ {
+ return events_left-- > 0;
+ }
+
+ bool end_array()
+ {
+ return events_left-- > 0;
+ }
+
+ bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const json::exception& /*unused*/) // NOLINT(readability-convert-member-functions-to-static)
+ {
+ return false;
+ }
+
+ private:
+ int events_left = 0;
+};
+} // namespace
+
+TEST_CASE("UBJSON")
+{
+ SECTION("individual values")
+ {
+ SECTION("discarded")
+ {
+ // discarded values are not serialized
+ json const j = json::value_t::discarded;
+ const auto result = json::to_ubjson(j);
+ CHECK(result.empty());
+ }
+
+ SECTION("null")
+ {
+ json const j = nullptr;
+ std::vector<uint8_t> expected = {'Z'};
+ const auto result = json::to_ubjson(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+
+ SECTION("boolean")
+ {
+ SECTION("true")
+ {
+ json const j = true;
+ std::vector<uint8_t> const expected = {'T'};
+ const auto result = json::to_ubjson(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+
+ SECTION("false")
+ {
+ json const j = false;
+ std::vector<uint8_t> const expected = {'F'};
+ const auto result = json::to_ubjson(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+ }
+
+ SECTION("number")
+ {
+ SECTION("signed")
+ {
+ SECTION("-9223372036854775808..-2147483649 (int64)")
+ {
+ std::vector<int64_t> const numbers
+ {
+ (std::numeric_limits<int64_t>::min)(),
+ -1000000000000000000LL,
+ -100000000000000000LL,
+ -10000000000000000LL,
+ -1000000000000000LL,
+ -100000000000000LL,
+ -10000000000000LL,
+ -1000000000000LL,
+ -100000000000LL,
+ -10000000000LL,
+ -2147483649LL,
+ };
+ for (auto i : numbers)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json const j = i;
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ static_cast<uint8_t>('L'),
+ static_cast<uint8_t>((i >> 56) & 0xff),
+ static_cast<uint8_t>((i >> 48) & 0xff),
+ static_cast<uint8_t>((i >> 40) & 0xff),
+ static_cast<uint8_t>((i >> 32) & 0xff),
+ static_cast<uint8_t>((i >> 24) & 0xff),
+ static_cast<uint8_t>((i >> 16) & 0xff),
+ static_cast<uint8_t>((i >> 8) & 0xff),
+ static_cast<uint8_t>(i & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_ubjson(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 9);
+
+ // check individual bytes
+ CHECK(result[0] == 'L');
+ int64_t const restored = (static_cast<int64_t>(result[1]) << 070) +
+ (static_cast<int64_t>(result[2]) << 060) +
+ (static_cast<int64_t>(result[3]) << 050) +
+ (static_cast<int64_t>(result[4]) << 040) +
+ (static_cast<int64_t>(result[5]) << 030) +
+ (static_cast<int64_t>(result[6]) << 020) +
+ (static_cast<int64_t>(result[7]) << 010) +
+ static_cast<int64_t>(result[8]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+ }
+
+ SECTION("-2147483648..-32769 (int32)")
+ {
+ std::vector<int32_t> numbers;
+ numbers.push_back(-32769);
+ numbers.push_back(-100000);
+ numbers.push_back(-1000000);
+ numbers.push_back(-10000000);
+ numbers.push_back(-100000000);
+ numbers.push_back(-1000000000);
+ numbers.push_back(-2147483647 - 1); // https://stackoverflow.com/a/29356002/266378
+ for (auto i : numbers)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json const j = i;
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ static_cast<uint8_t>('l'),
+ static_cast<uint8_t>((i >> 24) & 0xff),
+ static_cast<uint8_t>((i >> 16) & 0xff),
+ static_cast<uint8_t>((i >> 8) & 0xff),
+ static_cast<uint8_t>(i & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_ubjson(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 5);
+
+ // check individual bytes
+ CHECK(result[0] == 'l');
+ int32_t const restored = (static_cast<int32_t>(result[1]) << 030) +
+ (static_cast<int32_t>(result[2]) << 020) +
+ (static_cast<int32_t>(result[3]) << 010) +
+ static_cast<int32_t>(result[4]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+ }
+
+ SECTION("-32768..-129 (int16)")
+ {
+ for (int32_t i = -32768; i <= -129; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json const j = i;
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ static_cast<uint8_t>('I'),
+ static_cast<uint8_t>((i >> 8) & 0xff),
+ static_cast<uint8_t>(i & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_ubjson(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 3);
+
+ // check individual bytes
+ CHECK(result[0] == 'I');
+ auto const restored = static_cast<int16_t>(((result[1] << 8) + result[2]));
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+ }
+
+ SECTION("-9263 (int16)")
+ {
+ json const j = -9263;
+ std::vector<uint8_t> expected = {'I', 0xdb, 0xd1};
+
+ // compare result + size
+ const auto result = json::to_ubjson(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 3);
+
+ // check individual bytes
+ CHECK(result[0] == 'I');
+ auto const restored = static_cast<int16_t>(((result[1] << 8) + result[2]));
+ CHECK(restored == -9263);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+
+ SECTION("-128..-1 (int8)")
+ {
+ for (auto i = -128; i <= -1; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json const j = i;
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ 'i',
+ static_cast<uint8_t>(i),
+ };
+
+ // compare result + size
+ const auto result = json::to_ubjson(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 2);
+
+ // check individual bytes
+ CHECK(result[0] == 'i');
+ CHECK(static_cast<int8_t>(result[1]) == i);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+ }
+
+ SECTION("0..127 (int8)")
+ {
+ for (size_t i = 0; i <= 127; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json j = -1;
+ j.get_ref<json::number_integer_t&>() = static_cast<json::number_integer_t>(i);
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ static_cast<uint8_t>('i'),
+ static_cast<uint8_t>(i),
+ };
+
+ // compare result + size
+ const auto result = json::to_ubjson(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 2);
+
+ // check individual bytes
+ CHECK(result[0] == 'i');
+ CHECK(result[1] == i);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+ }
+
+ SECTION("128..255 (uint8)")
+ {
+ for (size_t i = 128; i <= 255; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json j = -1;
+ j.get_ref<json::number_integer_t&>() = static_cast<json::number_integer_t>(i);
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ static_cast<uint8_t>('U'),
+ static_cast<uint8_t>(i),
+ };
+
+ // compare result + size
+ const auto result = json::to_ubjson(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 2);
+
+ // check individual bytes
+ CHECK(result[0] == 'U');
+ CHECK(result[1] == i);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+ }
+
+ SECTION("256..32767 (int16)")
+ {
+ for (size_t i = 256; i <= 32767; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json j = -1;
+ j.get_ref<json::number_integer_t&>() = static_cast<json::number_integer_t>(i);
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ static_cast<uint8_t>('I'),
+ static_cast<uint8_t>((i >> 8) & 0xff),
+ static_cast<uint8_t>(i & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_ubjson(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 3);
+
+ // check individual bytes
+ CHECK(result[0] == 'I');
+ auto const restored = static_cast<uint16_t>((static_cast<uint8_t>(result[1]) * 256) + static_cast<uint8_t>(result[2]));
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+ }
+
+ SECTION("65536..2147483647 (int32)")
+ {
+ for (uint32_t i :
+ {
+ 65536u, 77777u, 1048576u
+ })
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json j = -1;
+ j.get_ref<json::number_integer_t&>() = static_cast<json::number_integer_t>(i);
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ 'l',
+ static_cast<uint8_t>((i >> 24) & 0xff),
+ static_cast<uint8_t>((i >> 16) & 0xff),
+ static_cast<uint8_t>((i >> 8) & 0xff),
+ static_cast<uint8_t>(i & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_ubjson(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 5);
+
+ // check individual bytes
+ CHECK(result[0] == 'l');
+ uint32_t const restored = (static_cast<uint32_t>(result[1]) << 030) +
+ (static_cast<uint32_t>(result[2]) << 020) +
+ (static_cast<uint32_t>(result[3]) << 010) +
+ static_cast<uint32_t>(result[4]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+ }
+
+ SECTION("2147483648..9223372036854775807 (int64)")
+ {
+ std::vector<uint64_t> const v = {2147483648ul, 9223372036854775807ul};
+ for (uint64_t i : v)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json j = -1;
+ j.get_ref<json::number_integer_t&>() = static_cast<json::number_integer_t>(i);
+
+ // check type
+ CHECK(j.is_number_integer());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ 'L',
+ static_cast<uint8_t>((i >> 070) & 0xff),
+ static_cast<uint8_t>((i >> 060) & 0xff),
+ static_cast<uint8_t>((i >> 050) & 0xff),
+ static_cast<uint8_t>((i >> 040) & 0xff),
+ static_cast<uint8_t>((i >> 030) & 0xff),
+ static_cast<uint8_t>((i >> 020) & 0xff),
+ static_cast<uint8_t>((i >> 010) & 0xff),
+ static_cast<uint8_t>(i & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_ubjson(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 9);
+
+ // check individual bytes
+ CHECK(result[0] == 'L');
+ uint64_t const restored = (static_cast<uint64_t>(result[1]) << 070) +
+ (static_cast<uint64_t>(result[2]) << 060) +
+ (static_cast<uint64_t>(result[3]) << 050) +
+ (static_cast<uint64_t>(result[4]) << 040) +
+ (static_cast<uint64_t>(result[5]) << 030) +
+ (static_cast<uint64_t>(result[6]) << 020) +
+ (static_cast<uint64_t>(result[7]) << 010) +
+ static_cast<uint64_t>(result[8]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+ }
+ }
+
+ SECTION("unsigned")
+ {
+ SECTION("0..127 (int8)")
+ {
+ for (size_t i = 0; i <= 127; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with unsigned integer number
+ json const j = i;
+
+ // check type
+ CHECK(j.is_number_unsigned());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ 'i',
+ static_cast<uint8_t>(i),
+ };
+
+ // compare result + size
+ const auto result = json::to_ubjson(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 2);
+
+ // check individual bytes
+ CHECK(result[0] == 'i');
+ auto const restored = static_cast<uint8_t>(result[1]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+ }
+
+ SECTION("128..255 (uint8)")
+ {
+ for (size_t i = 128; i <= 255; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with unsigned integer number
+ json const j = i;
+
+ // check type
+ CHECK(j.is_number_unsigned());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ 'U',
+ static_cast<uint8_t>(i),
+ };
+
+ // compare result + size
+ const auto result = json::to_ubjson(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 2);
+
+ // check individual bytes
+ CHECK(result[0] == 'U');
+ auto const restored = static_cast<uint8_t>(result[1]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+ }
+
+ SECTION("256..32767 (int16)")
+ {
+ for (size_t i = 256; i <= 32767; ++i)
+ {
+ CAPTURE(i)
+
+ // create JSON value with unsigned integer number
+ json const j = i;
+
+ // check type
+ CHECK(j.is_number_unsigned());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ 'I',
+ static_cast<uint8_t>((i >> 8) & 0xff),
+ static_cast<uint8_t>(i & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_ubjson(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 3);
+
+ // check individual bytes
+ CHECK(result[0] == 'I');
+ auto const restored = static_cast<uint16_t>((static_cast<uint8_t>(result[1]) * 256) + static_cast<uint8_t>(result[2]));
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+ }
+
+ SECTION("65536..2147483647 (int32)")
+ {
+ for (uint32_t i :
+ {
+ 65536u, 77777u, 1048576u
+ })
+ {
+ CAPTURE(i)
+
+ // create JSON value with unsigned integer number
+ json const j = i;
+
+ // check type
+ CHECK(j.is_number_unsigned());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ 'l',
+ static_cast<uint8_t>((i >> 24) & 0xff),
+ static_cast<uint8_t>((i >> 16) & 0xff),
+ static_cast<uint8_t>((i >> 8) & 0xff),
+ static_cast<uint8_t>(i & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_ubjson(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 5);
+
+ // check individual bytes
+ CHECK(result[0] == 'l');
+ uint32_t const restored = (static_cast<uint32_t>(result[1]) << 030) +
+ (static_cast<uint32_t>(result[2]) << 020) +
+ (static_cast<uint32_t>(result[3]) << 010) +
+ static_cast<uint32_t>(result[4]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+ }
+
+ SECTION("2147483648..9223372036854775807 (int64)")
+ {
+ std::vector<uint64_t> const v = {2147483648ul, 9223372036854775807ul};
+ for (uint64_t i : v)
+ {
+ CAPTURE(i)
+
+ // create JSON value with integer number
+ json const j = i;
+
+ // check type
+ CHECK(j.is_number_unsigned());
+
+ // create expected byte vector
+ std::vector<uint8_t> const expected
+ {
+ 'L',
+ static_cast<uint8_t>((i >> 070) & 0xff),
+ static_cast<uint8_t>((i >> 060) & 0xff),
+ static_cast<uint8_t>((i >> 050) & 0xff),
+ static_cast<uint8_t>((i >> 040) & 0xff),
+ static_cast<uint8_t>((i >> 030) & 0xff),
+ static_cast<uint8_t>((i >> 020) & 0xff),
+ static_cast<uint8_t>((i >> 010) & 0xff),
+ static_cast<uint8_t>(i & 0xff),
+ };
+
+ // compare result + size
+ const auto result = json::to_ubjson(j);
+ CHECK(result == expected);
+ CHECK(result.size() == 9);
+
+ // check individual bytes
+ CHECK(result[0] == 'L');
+ uint64_t const restored = (static_cast<uint64_t>(result[1]) << 070) +
+ (static_cast<uint64_t>(result[2]) << 060) +
+ (static_cast<uint64_t>(result[3]) << 050) +
+ (static_cast<uint64_t>(result[4]) << 040) +
+ (static_cast<uint64_t>(result[5]) << 030) +
+ (static_cast<uint64_t>(result[6]) << 020) +
+ (static_cast<uint64_t>(result[7]) << 010) +
+ static_cast<uint64_t>(result[8]);
+ CHECK(restored == i);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+ }
+ }
+
+ SECTION("float64")
+ {
+ SECTION("3.1415925")
+ {
+ double v = 3.1415925;
+ json const j = v;
+ std::vector<uint8_t> expected =
+ {
+ 'D', 0x40, 0x09, 0x21, 0xfb, 0x3f, 0xa6, 0xde, 0xfc
+ };
+ const auto result = json::to_ubjson(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result) == v);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+ }
+
+ SECTION("high-precision number")
+ {
+ SECTION("unsigned integer number")
+ {
+ std::vector<uint8_t> const vec = {'H', 'i', 0x14, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'};
+ const auto j = json::from_ubjson(vec);
+ CHECK(j.is_number_unsigned());
+ CHECK(j.dump() == "12345678901234567890");
+ }
+
+ SECTION("signed integer number")
+ {
+ std::vector<uint8_t> const vec = {'H', 'i', 0x13, '-', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8'};
+ const auto j = json::from_ubjson(vec);
+ CHECK(j.is_number_integer());
+ CHECK(j.dump() == "-123456789012345678");
+ }
+
+ SECTION("floating-point number")
+ {
+ std::vector<uint8_t> const vec = {'H', 'i', 0x16, '3', '.', '1', '4', '1', '5', '9', '2', '6', '5', '3', '5', '8', '9', '7', '9', '3', '2', '3', '8', '4', '6'};
+ const auto j = json::from_ubjson(vec);
+ CHECK(j.is_number_float());
+ CHECK(j.dump() == "3.141592653589793");
+ }
+
+ SECTION("errors")
+ {
+ // error while parsing length
+ std::vector<uint8_t> const vec0 = {'H', 'i'};
+ CHECK(json::from_ubjson(vec0, true, false).is_discarded());
+ // error while parsing string
+ std::vector<uint8_t> const vec1 = {'H', 'i', '1'};
+ CHECK(json::from_ubjson(vec1, true, false).is_discarded());
+
+ json _;
+ std::vector<uint8_t> const vec2 = {'H', 'i', 2, '1', 'A', '3'};
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vec2), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing UBJSON high-precision number: invalid number text: 1A", json::parse_error);
+ std::vector<uint8_t> const vec3 = {'H', 'i', 2, '1', '.'};
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vec3), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing UBJSON high-precision number: invalid number text: 1.", json::parse_error);
+ std::vector<uint8_t> const vec4 = {'H', 2, '1', '0'};
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vec4), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON size: expected length type specification (U, i, I, l, L) after '#'; last byte: 0x02", json::parse_error);
+ }
+
+ SECTION("serialization")
+ {
+ // number that does not fit int64
+ json const j = 11111111111111111111ULL;
+ CHECK(j.is_number_unsigned());
+
+ // number will be serialized to high-precision number
+ const auto vec = json::to_ubjson(j);
+ std::vector<uint8_t> expected = {'H', 'i', 0x14, '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1'};
+ CHECK(vec == expected);
+
+ // roundtrip
+ CHECK(json::from_ubjson(vec) == j);
+ }
+ }
+ }
+
+ SECTION("string")
+ {
+ SECTION("N = 0..127")
+ {
+ for (size_t N = 0; N <= 127; ++N)
+ {
+ CAPTURE(N)
+
+ // create JSON value with string containing of N * 'x'
+ const auto s = std::string(N, 'x');
+ json const j = s;
+
+ // create expected byte vector
+ std::vector<uint8_t> expected;
+ expected.push_back('S');
+ expected.push_back('i');
+ expected.push_back(static_cast<uint8_t>(N));
+ for (size_t i = 0; i < N; ++i)
+ {
+ expected.push_back('x');
+ }
+
+ // compare result + size
+ const auto result = json::to_ubjson(j);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 3);
+ // check that no null byte is appended
+ if (N > 0)
+ {
+ CHECK(result.back() != '\x00');
+ }
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+ }
+
+ SECTION("N = 128..255")
+ {
+ for (size_t N = 128; N <= 255; ++N)
+ {
+ CAPTURE(N)
+
+ // create JSON value with string containing of N * 'x'
+ const auto s = std::string(N, 'x');
+ json const j = s;
+
+ // create expected byte vector
+ std::vector<uint8_t> expected;
+ expected.push_back('S');
+ expected.push_back('U');
+ expected.push_back(static_cast<uint8_t>(N));
+ for (size_t i = 0; i < N; ++i)
+ {
+ expected.push_back('x');
+ }
+
+ // compare result + size
+ const auto result = json::to_ubjson(j);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 3);
+ // check that no null byte is appended
+ CHECK(result.back() != '\x00');
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+ }
+
+ SECTION("N = 256..32767")
+ {
+ for (size_t N :
+ {
+ 256u, 999u, 1025u, 3333u, 2048u, 32767u
+ })
+ {
+ CAPTURE(N)
+
+ // create JSON value with string containing of N * 'x'
+ const auto s = std::string(N, 'x');
+ json const j = s;
+
+ // create expected byte vector (hack: create string first)
+ std::vector<uint8_t> expected(N, 'x');
+ // reverse order of commands, because we insert at begin()
+ expected.insert(expected.begin(), static_cast<uint8_t>(N & 0xff));
+ expected.insert(expected.begin(), static_cast<uint8_t>((N >> 8) & 0xff));
+ expected.insert(expected.begin(), 'I');
+ expected.insert(expected.begin(), 'S');
+
+ // compare result + size
+ const auto result = json::to_ubjson(j);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 4);
+ // check that no null byte is appended
+ CHECK(result.back() != '\x00');
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+ }
+
+ SECTION("N = 65536..2147483647")
+ {
+ for (size_t N :
+ {
+ 65536u, 77777u, 1048576u
+ })
+ {
+ CAPTURE(N)
+
+ // create JSON value with string containing of N * 'x'
+ const auto s = std::string(N, 'x');
+ json const j = s;
+
+ // create expected byte vector (hack: create string first)
+ std::vector<uint8_t> expected(N, 'x');
+ // reverse order of commands, because we insert at begin()
+ expected.insert(expected.begin(), static_cast<uint8_t>(N & 0xff));
+ expected.insert(expected.begin(), static_cast<uint8_t>((N >> 8) & 0xff));
+ expected.insert(expected.begin(), static_cast<uint8_t>((N >> 16) & 0xff));
+ expected.insert(expected.begin(), static_cast<uint8_t>((N >> 24) & 0xff));
+ expected.insert(expected.begin(), 'l');
+ expected.insert(expected.begin(), 'S');
+
+ // compare result + size
+ const auto result = json::to_ubjson(j);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 6);
+ // check that no null byte is appended
+ CHECK(result.back() != '\x00');
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+ }
+ }
+
+ SECTION("binary")
+ {
+ SECTION("N = 0..127")
+ {
+ for (std::size_t N = 0; N <= 127; ++N)
+ {
+ CAPTURE(N)
+
+ // create JSON value with byte array containing of N * 'x'
+ const auto s = std::vector<std::uint8_t>(N, 'x');
+ json const j = json::binary(s);
+
+ // create expected byte vector
+ std::vector<std::uint8_t> expected;
+ expected.push_back(static_cast<std::uint8_t>('['));
+ if (N != 0)
+ {
+ expected.push_back(static_cast<std::uint8_t>('$'));
+ expected.push_back(static_cast<std::uint8_t>('U'));
+ }
+ expected.push_back(static_cast<std::uint8_t>('#'));
+ expected.push_back(static_cast<std::uint8_t>('i'));
+ expected.push_back(static_cast<std::uint8_t>(N));
+ for (size_t i = 0; i < N; ++i)
+ {
+ expected.push_back(0x78);
+ }
+
+ // compare result + size
+ const auto result = json::to_ubjson(j, true, true);
+ CHECK(result == expected);
+ if (N == 0)
+ {
+ CHECK(result.size() == N + 4);
+ }
+ else
+ {
+ CHECK(result.size() == N + 6);
+ }
+
+ // check that no null byte is appended
+ if (N > 0)
+ {
+ CHECK(result.back() != '\x00');
+ }
+
+ // roundtrip only works to an array of numbers
+ json j_out = s;
+ CHECK(json::from_ubjson(result) == j_out);
+ CHECK(json::from_ubjson(result, true, false) == j_out);
+ }
+ }
+
+ SECTION("N = 128..255")
+ {
+ for (std::size_t N = 128; N <= 255; ++N)
+ {
+ CAPTURE(N)
+
+ // create JSON value with byte array containing of N * 'x'
+ const auto s = std::vector<std::uint8_t>(N, 'x');
+ json const j = json::binary(s);
+
+ // create expected byte vector
+ std::vector<uint8_t> expected;
+ expected.push_back(static_cast<std::uint8_t>('['));
+ expected.push_back(static_cast<std::uint8_t>('$'));
+ expected.push_back(static_cast<std::uint8_t>('U'));
+ expected.push_back(static_cast<std::uint8_t>('#'));
+ expected.push_back(static_cast<std::uint8_t>('U'));
+ expected.push_back(static_cast<std::uint8_t>(N));
+ for (size_t i = 0; i < N; ++i)
+ {
+ expected.push_back(0x78);
+ }
+
+ // compare result + size
+ const auto result = json::to_ubjson(j, true, true);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 6);
+ // check that no null byte is appended
+ CHECK(result.back() != '\x00');
+
+ // roundtrip only works to an array of numbers
+ json j_out = s;
+ CHECK(json::from_ubjson(result) == j_out);
+ CHECK(json::from_ubjson(result, true, false) == j_out);
+ }
+ }
+
+ SECTION("N = 256..32767")
+ {
+ for (std::size_t N :
+ {
+ 256u, 999u, 1025u, 3333u, 2048u, 32767u
+ })
+ {
+ CAPTURE(N)
+
+ // create JSON value with byte array containing of N * 'x'
+ const auto s = std::vector<std::uint8_t>(N, 'x');
+ json const j = json::binary(s);
+
+ // create expected byte vector
+ std::vector<std::uint8_t> expected(N + 7, 'x');
+ expected[0] = '[';
+ expected[1] = '$';
+ expected[2] = 'U';
+ expected[3] = '#';
+ expected[4] = 'I';
+ expected[5] = static_cast<std::uint8_t>((N >> 8) & 0xFF);
+ expected[6] = static_cast<std::uint8_t>(N & 0xFF);
+
+ // compare result + size
+ const auto result = json::to_ubjson(j, true, true);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 7);
+ // check that no null byte is appended
+ CHECK(result.back() != '\x00');
+
+ // roundtrip only works to an array of numbers
+ json j_out = s;
+ CHECK(json::from_ubjson(result) == j_out);
+ CHECK(json::from_ubjson(result, true, false) == j_out);
+ }
+ }
+
+ SECTION("N = 32768..2147483647")
+ {
+ for (std::size_t N :
+ {
+ 32768u, 77777u, 1048576u
+ })
+ {
+ CAPTURE(N)
+
+ // create JSON value with byte array containing of N * 'x'
+ const auto s = std::vector<std::uint8_t>(N, 'x');
+ json const j = json::binary(s);
+
+ // create expected byte vector
+ std::vector<std::uint8_t> expected(N + 9, 'x');
+ expected[0] = '[';
+ expected[1] = '$';
+ expected[2] = 'U';
+ expected[3] = '#';
+ expected[4] = 'l';
+ expected[5] = static_cast<std::uint8_t>((N >> 24) & 0xFF);
+ expected[6] = static_cast<std::uint8_t>((N >> 16) & 0xFF);
+ expected[7] = static_cast<std::uint8_t>((N >> 8) & 0xFF);
+ expected[8] = static_cast<std::uint8_t>(N & 0xFF);
+
+ // compare result + size
+ const auto result = json::to_ubjson(j, true, true);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 9);
+ // check that no null byte is appended
+ CHECK(result.back() != '\x00');
+
+ // roundtrip only works to an array of numbers
+ json j_out = s;
+ CHECK(json::from_ubjson(result) == j_out);
+ CHECK(json::from_ubjson(result, true, false) == j_out);
+ }
+ }
+
+ SECTION("Other Serializations")
+ {
+ const std::size_t N = 10;
+ const auto s = std::vector<std::uint8_t>(N, 'x');
+ json const j = json::binary(s);
+
+ SECTION("No Count No Type")
+ {
+ std::vector<uint8_t> expected;
+ expected.push_back(static_cast<std::uint8_t>('['));
+ for (std::size_t i = 0; i < N; ++i)
+ {
+ expected.push_back(static_cast<std::uint8_t>('U'));
+ expected.push_back(static_cast<std::uint8_t>(0x78));
+ }
+ expected.push_back(static_cast<std::uint8_t>(']'));
+
+ // compare result + size
+ const auto result = json::to_ubjson(j, false, false);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 12);
+ // check that no null byte is appended
+ CHECK(result.back() != '\x00');
+
+ // roundtrip only works to an array of numbers
+ json j_out = s;
+ CHECK(json::from_ubjson(result) == j_out);
+ CHECK(json::from_ubjson(result, true, false) == j_out);
+ }
+
+ SECTION("Yes Count No Type")
+ {
+ std::vector<std::uint8_t> expected;
+ expected.push_back(static_cast<std::uint8_t>('['));
+ expected.push_back(static_cast<std::uint8_t>('#'));
+ expected.push_back(static_cast<std::uint8_t>('i'));
+ expected.push_back(static_cast<std::uint8_t>(N));
+
+ for (size_t i = 0; i < N; ++i)
+ {
+ expected.push_back(static_cast<std::uint8_t>('U'));
+ expected.push_back(static_cast<std::uint8_t>(0x78));
+ }
+
+ // compare result + size
+ const auto result = json::to_ubjson(j, true, false);
+ CHECK(result == expected);
+ CHECK(result.size() == N + 14);
+ // check that no null byte is appended
+ CHECK(result.back() != '\x00');
+
+ // roundtrip only works to an array of numbers
+ json j_out = s;
+ CHECK(json::from_ubjson(result) == j_out);
+ CHECK(json::from_ubjson(result, true, false) == j_out);
+ }
+ }
+ }
+
+ SECTION("array")
+ {
+ SECTION("empty")
+ {
+ SECTION("size=false type=false")
+ {
+ json const j = json::array();
+ std::vector<uint8_t> expected = {'[', ']'};
+ const auto result = json::to_ubjson(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+
+ SECTION("size=true type=false")
+ {
+ json const j = json::array();
+ std::vector<uint8_t> expected = {'[', '#', 'i', 0};
+ const auto result = json::to_ubjson(j, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+
+ SECTION("size=true type=true")
+ {
+ json const j = json::array();
+ std::vector<uint8_t> expected = {'[', '#', 'i', 0};
+ const auto result = json::to_ubjson(j, true, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+ }
+
+ SECTION("[null]")
+ {
+ SECTION("size=false type=false")
+ {
+ json const j = {nullptr};
+ std::vector<uint8_t> expected = {'[', 'Z', ']'};
+ const auto result = json::to_ubjson(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+
+ SECTION("size=true type=false")
+ {
+ json const j = {nullptr};
+ std::vector<uint8_t> expected = {'[', '#', 'i', 1, 'Z'};
+ const auto result = json::to_ubjson(j, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+
+ SECTION("size=true type=true")
+ {
+ json const j = {nullptr};
+ std::vector<uint8_t> expected = {'[', '$', 'Z', '#', 'i', 1};
+ const auto result = json::to_ubjson(j, true, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+ }
+
+ SECTION("[1,2,3,4,5]")
+ {
+ SECTION("size=false type=false")
+ {
+ json const j = json::parse("[1,2,3,4,5]");
+ std::vector<uint8_t> expected = {'[', 'i', 1, 'i', 2, 'i', 3, 'i', 4, 'i', 5, ']'};
+ const auto result = json::to_ubjson(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+
+ SECTION("size=true type=false")
+ {
+ json const j = json::parse("[1,2,3,4,5]");
+ std::vector<uint8_t> expected = {'[', '#', 'i', 5, 'i', 1, 'i', 2, 'i', 3, 'i', 4, 'i', 5};
+ const auto result = json::to_ubjson(j, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+
+ SECTION("size=true type=true")
+ {
+ json const j = json::parse("[1,2,3,4,5]");
+ std::vector<uint8_t> expected = {'[', '$', 'i', '#', 'i', 5, 1, 2, 3, 4, 5};
+ const auto result = json::to_ubjson(j, true, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+ }
+
+ SECTION("[[[[]]]]")
+ {
+ SECTION("size=false type=false")
+ {
+ json const j = json::parse("[[[[]]]]");
+ std::vector<uint8_t> expected = {'[', '[', '[', '[', ']', ']', ']', ']'};
+ const auto result = json::to_ubjson(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+
+ SECTION("size=true type=false")
+ {
+ json const j = json::parse("[[[[]]]]");
+ std::vector<uint8_t> expected = {'[', '#', 'i', 1, '[', '#', 'i', 1, '[', '#', 'i', 1, '[', '#', 'i', 0};
+ const auto result = json::to_ubjson(j, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+
+ SECTION("size=true type=true")
+ {
+ json const j = json::parse("[[[[]]]]");
+ std::vector<uint8_t> expected = {'[', '$', '[', '#', 'i', 1, '$', '[', '#', 'i', 1, '$', '[', '#', 'i', 1, '#', 'i', 0};
+ const auto result = json::to_ubjson(j, true, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+ }
+
+ SECTION("array with uint16_t elements")
+ {
+ SECTION("size=false type=false")
+ {
+ json j(257, nullptr);
+ std::vector<uint8_t> expected(j.size() + 2, 'Z'); // all null
+ expected[0] = '['; // opening array
+ expected[258] = ']'; // closing array
+ const auto result = json::to_ubjson(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+
+ SECTION("size=true type=false")
+ {
+ json j(257, nullptr);
+ std::vector<uint8_t> expected(j.size() + 5, 'Z'); // all null
+ expected[0] = '['; // opening array
+ expected[1] = '#'; // array size
+ expected[2] = 'I'; // int16
+ expected[3] = 0x01; // 0x0101, first byte
+ expected[4] = 0x01; // 0x0101, second byte
+ const auto result = json::to_ubjson(j, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+
+ SECTION("size=true type=true")
+ {
+ json j(257, nullptr);
+ std::vector<uint8_t> expected = {'[', '$', 'Z', '#', 'I', 0x01, 0x01};
+ const auto result = json::to_ubjson(j, true, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+ }
+
+ SECTION("array with uint32_t elements")
+ {
+ SECTION("size=false type=false")
+ {
+ json j(65793, nullptr);
+ std::vector<uint8_t> expected(j.size() + 2, 'Z'); // all null
+ expected[0] = '['; // opening array
+ expected[65794] = ']'; // closing array
+ const auto result = json::to_ubjson(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+
+ SECTION("size=true type=false")
+ {
+ json j(65793, nullptr);
+ std::vector<uint8_t> expected(j.size() + 7, 'Z'); // all null
+ expected[0] = '['; // opening array
+ expected[1] = '#'; // array size
+ expected[2] = 'l'; // int32
+ expected[3] = 0x00; // 0x00010101, first byte
+ expected[4] = 0x01; // 0x00010101, second byte
+ expected[5] = 0x01; // 0x00010101, third byte
+ expected[6] = 0x01; // 0x00010101, fourth byte
+ const auto result = json::to_ubjson(j, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+
+ SECTION("size=true type=true")
+ {
+ json j(65793, nullptr);
+ std::vector<uint8_t> expected = {'[', '$', 'Z', '#', 'l', 0x00, 0x01, 0x01, 0x01};
+ const auto result = json::to_ubjson(j, true, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+ }
+ }
+
+ SECTION("object")
+ {
+ SECTION("empty")
+ {
+ SECTION("size=false type=false")
+ {
+ json const j = json::object();
+ std::vector<uint8_t> expected = {'{', '}'};
+ const auto result = json::to_ubjson(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+
+ SECTION("size=true type=false")
+ {
+ json const j = json::object();
+ std::vector<uint8_t> expected = {'{', '#', 'i', 0};
+ const auto result = json::to_ubjson(j, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+
+ SECTION("size=true type=true")
+ {
+ json const j = json::object();
+ std::vector<uint8_t> expected = {'{', '#', 'i', 0};
+ const auto result = json::to_ubjson(j, true, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+ }
+
+ SECTION("{\"\":null}")
+ {
+ SECTION("size=false type=false")
+ {
+ json const j = {{"", nullptr}};
+ std::vector<uint8_t> expected = {'{', 'i', 0, 'Z', '}'};
+ const auto result = json::to_ubjson(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+
+ SECTION("size=true type=false")
+ {
+ json const j = {{"", nullptr}};
+ std::vector<uint8_t> expected = {'{', '#', 'i', 1, 'i', 0, 'Z'};
+ const auto result = json::to_ubjson(j, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+
+ SECTION("size=true type=true")
+ {
+ json const j = {{"", nullptr}};
+ std::vector<uint8_t> expected = {'{', '$', 'Z', '#', 'i', 1, 'i', 0};
+ const auto result = json::to_ubjson(j, true, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+ }
+
+ SECTION("{\"a\": {\"b\": {\"c\": {}}}}")
+ {
+ SECTION("size=false type=false")
+ {
+ json const j = json::parse(R"({"a": {"b": {"c": {}}}})");
+ std::vector<uint8_t> expected =
+ {
+ '{', 'i', 1, 'a', '{', 'i', 1, 'b', '{', 'i', 1, 'c', '{', '}', '}', '}', '}'
+ };
+ const auto result = json::to_ubjson(j);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+
+ SECTION("size=true type=false")
+ {
+ json const j = json::parse(R"({"a": {"b": {"c": {}}}})");
+ std::vector<uint8_t> expected =
+ {
+ '{', '#', 'i', 1, 'i', 1, 'a', '{', '#', 'i', 1, 'i', 1, 'b', '{', '#', 'i', 1, 'i', 1, 'c', '{', '#', 'i', 0
+ };
+ const auto result = json::to_ubjson(j, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+
+ SECTION("size=true type=true")
+ {
+ json const j = json::parse(R"({"a": {"b": {"c": {}}}})");
+ std::vector<uint8_t> expected =
+ {
+ '{', '$', '{', '#', 'i', 1, 'i', 1, 'a', '$', '{', '#', 'i', 1, 'i', 1, 'b', '$', '{', '#', 'i', 1, 'i', 1, 'c', '#', 'i', 0
+ };
+ const auto result = json::to_ubjson(j, true, true);
+ CHECK(result == expected);
+
+ // roundtrip
+ CHECK(json::from_ubjson(result) == j);
+ CHECK(json::from_ubjson(result, true, false) == j);
+ }
+ }
+ }
+ }
+
+ SECTION("errors")
+ {
+ SECTION("strict mode")
+ {
+ std::vector<uint8_t> const vec = {'Z', 'Z'};
+ SECTION("non-strict mode")
+ {
+ const auto result = json::from_ubjson(vec, false);
+ CHECK(result == json());
+ }
+
+ SECTION("strict mode")
+ {
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vec), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing UBJSON value: expected end of input; last byte: 0x5A", json::parse_error&);
+ }
+ }
+
+ SECTION("excessive size")
+ {
+ SECTION("array")
+ {
+ std::vector<uint8_t> const v_ubjson = {'[', '$', 'Z', '#', 'L', 0x78, 0x28, 0x00, 0x68, 0x28, 0x69, 0x69, 0x17};
+ json _;
+ CHECK_THROWS_AS(_ = json::from_ubjson(v_ubjson), json::out_of_range&);
+
+ json j;
+ nlohmann::detail::json_sax_dom_callback_parser<json, decltype(nlohmann::detail::input_adapter(v_ubjson))> scp(j, [](int /*unused*/, json::parse_event_t /*unused*/, const json& /*unused*/) noexcept
+ {
+ return true;
+ });
+ CHECK_THROWS_AS(_ = json::sax_parse(v_ubjson, &scp, json::input_format_t::ubjson), json::out_of_range&);
+ }
+
+ SECTION("object")
+ {
+ std::vector<uint8_t> const v_ubjson = {'{', '$', 'Z', '#', 'L', 0x78, 0x28, 0x00, 0x68, 0x28, 0x69, 0x69, 0x17};
+ json _;
+ CHECK_THROWS_AS(_ = json::from_ubjson(v_ubjson), json::out_of_range&);
+
+ json j;
+ nlohmann::detail::json_sax_dom_callback_parser<json, decltype(nlohmann::detail::input_adapter(v_ubjson))> scp(j, [](int /*unused*/, json::parse_event_t /*unused*/, const json& /*unused*/) noexcept
+ {
+ return true;
+ });
+ CHECK_THROWS_AS(_ = json::sax_parse(v_ubjson, &scp, json::input_format_t::ubjson), json::out_of_range&);
+ }
+ }
+ }
+
+ SECTION("SAX aborts")
+ {
+ SECTION("start_array()")
+ {
+ std::vector<uint8_t> const v = {'[', 'T', 'F', ']'};
+ SaxCountdown scp(0);
+ CHECK(!json::sax_parse(v, &scp, json::input_format_t::ubjson));
+ }
+
+ SECTION("start_object()")
+ {
+ std::vector<uint8_t> const v = {'{', 'i', 3, 'f', 'o', 'o', 'F', '}'};
+ SaxCountdown scp(0);
+ CHECK(!json::sax_parse(v, &scp, json::input_format_t::ubjson));
+ }
+
+ SECTION("key() in object")
+ {
+ std::vector<uint8_t> const v = {'{', 'i', 3, 'f', 'o', 'o', 'F', '}'};
+ SaxCountdown scp(1);
+ CHECK(!json::sax_parse(v, &scp, json::input_format_t::ubjson));
+ }
+
+ SECTION("start_array(len)")
+ {
+ std::vector<uint8_t> const v = {'[', '#', 'i', '2', 'T', 'F'};
+ SaxCountdown scp(0);
+ CHECK(!json::sax_parse(v, &scp, json::input_format_t::ubjson));
+ }
+
+ SECTION("start_object(len)")
+ {
+ std::vector<uint8_t> const v = {'{', '#', 'i', '1', 3, 'f', 'o', 'o', 'F'};
+ SaxCountdown scp(0);
+ CHECK(!json::sax_parse(v, &scp, json::input_format_t::ubjson));
+ }
+
+ SECTION("key() in object with length")
+ {
+ std::vector<uint8_t> const v = {'{', 'i', 3, 'f', 'o', 'o', 'F', '}'};
+ SaxCountdown scp(1);
+ CHECK(!json::sax_parse(v, &scp, json::input_format_t::ubjson));
+ }
+ }
+
+ SECTION("parsing values")
+ {
+ SECTION("strings")
+ {
+ // create a single-character string for all number types
+ std::vector<uint8_t> s_i = {'S', 'i', 1, 'a'};
+ std::vector<uint8_t> const s_U = {'S', 'U', 1, 'a'};
+ std::vector<uint8_t> const s_I = {'S', 'I', 0, 1, 'a'};
+ std::vector<uint8_t> const s_l = {'S', 'l', 0, 0, 0, 1, 'a'};
+ std::vector<uint8_t> const s_L = {'S', 'L', 0, 0, 0, 0, 0, 0, 0, 1, 'a'};
+
+ // check if string is parsed correctly to "a"
+ CHECK(json::from_ubjson(s_i) == "a");
+ CHECK(json::from_ubjson(s_U) == "a");
+ CHECK(json::from_ubjson(s_I) == "a");
+ CHECK(json::from_ubjson(s_l) == "a");
+ CHECK(json::from_ubjson(s_L) == "a");
+
+ // roundtrip: output should be optimized
+ CHECK(json::to_ubjson(json::from_ubjson(s_i)) == s_i);
+ CHECK(json::to_ubjson(json::from_ubjson(s_U)) == s_i);
+ CHECK(json::to_ubjson(json::from_ubjson(s_I)) == s_i);
+ CHECK(json::to_ubjson(json::from_ubjson(s_l)) == s_i);
+ CHECK(json::to_ubjson(json::from_ubjson(s_L)) == s_i);
+ }
+
+ SECTION("number")
+ {
+ SECTION("float")
+ {
+ // float32
+ std::vector<uint8_t> const v_d = {'d', 0x40, 0x49, 0x0f, 0xd0};
+ CHECK(json::from_ubjson(v_d) == 3.14159f);
+
+ // float64
+ std::vector<uint8_t> const v_D = {'D', 0x40, 0x09, 0x21, 0xf9, 0xf0, 0x1b, 0x86, 0x6e};
+ CHECK(json::from_ubjson(v_D) == 3.14159);
+
+ // float32 is serialized as float64 as the library does not support float32
+ CHECK(json::to_ubjson(json::from_ubjson(v_d)) == json::to_ubjson(3.14159f));
+ }
+ }
+
+ SECTION("array")
+ {
+ SECTION("optimized version (length only)")
+ {
+ // create vector with two elements of the same type
+ std::vector<uint8_t> const v_TU = {'[', '#', 'U', 2, 'T', 'T'};
+ std::vector<uint8_t> const v_T = {'[', '#', 'i', 2, 'T', 'T'};
+ std::vector<uint8_t> const v_F = {'[', '#', 'i', 2, 'F', 'F'};
+ std::vector<uint8_t> const v_Z = {'[', '#', 'i', 2, 'Z', 'Z'};
+ std::vector<uint8_t> const v_i = {'[', '#', 'i', 2, 'i', 0x7F, 'i', 0x7F};
+ std::vector<uint8_t> const v_U = {'[', '#', 'i', 2, 'U', 0xFF, 'U', 0xFF};
+ std::vector<uint8_t> const v_I = {'[', '#', 'i', 2, 'I', 0x7F, 0xFF, 'I', 0x7F, 0xFF};
+ std::vector<uint8_t> const v_l = {'[', '#', 'i', 2, 'l', 0x7F, 0xFF, 0xFF, 0xFF, 'l', 0x7F, 0xFF, 0xFF, 0xFF};
+ std::vector<uint8_t> const v_L = {'[', '#', 'i', 2, 'L', 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 'L', 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+ std::vector<uint8_t> const v_D = {'[', '#', 'i', 2, 'D', 0x40, 0x09, 0x21, 0xfb, 0x4d, 0x12, 0xd8, 0x4a, 'D', 0x40, 0x09, 0x21, 0xfb, 0x4d, 0x12, 0xd8, 0x4a};
+ std::vector<uint8_t> const v_S = {'[', '#', 'i', 2, 'S', 'i', 1, 'a', 'S', 'i', 1, 'a'};
+ std::vector<uint8_t> const v_C = {'[', '#', 'i', 2, 'C', 'a', 'C', 'a'};
+
+ // check if vector is parsed correctly
+ CHECK(json::from_ubjson(v_TU) == json({true, true}));
+ CHECK(json::from_ubjson(v_T) == json({true, true}));
+ CHECK(json::from_ubjson(v_F) == json({false, false}));
+ CHECK(json::from_ubjson(v_Z) == json({nullptr, nullptr}));
+ CHECK(json::from_ubjson(v_i) == json({127, 127}));
+ CHECK(json::from_ubjson(v_U) == json({255, 255}));
+ CHECK(json::from_ubjson(v_I) == json({32767, 32767}));
+ CHECK(json::from_ubjson(v_l) == json({2147483647, 2147483647}));
+ CHECK(json::from_ubjson(v_L) == json({9223372036854775807, 9223372036854775807}));
+ CHECK(json::from_ubjson(v_D) == json({3.1415926, 3.1415926}));
+ CHECK(json::from_ubjson(v_S) == json({"a", "a"}));
+ CHECK(json::from_ubjson(v_C) == json({"a", "a"}));
+
+ // roundtrip: output should be optimized
+ CHECK(json::to_ubjson(json::from_ubjson(v_T), true) == v_T);
+ CHECK(json::to_ubjson(json::from_ubjson(v_F), true) == v_F);
+ CHECK(json::to_ubjson(json::from_ubjson(v_Z), true) == v_Z);
+ CHECK(json::to_ubjson(json::from_ubjson(v_i), true) == v_i);
+ CHECK(json::to_ubjson(json::from_ubjson(v_U), true) == v_U);
+ CHECK(json::to_ubjson(json::from_ubjson(v_I), true) == v_I);
+ CHECK(json::to_ubjson(json::from_ubjson(v_l), true) == v_l);
+ CHECK(json::to_ubjson(json::from_ubjson(v_L), true) == v_L);
+ CHECK(json::to_ubjson(json::from_ubjson(v_D), true) == v_D);
+ CHECK(json::to_ubjson(json::from_ubjson(v_S), true) == v_S);
+ CHECK(json::to_ubjson(json::from_ubjson(v_C), true) == v_S); // char is serialized to string
+ }
+
+ SECTION("optimized version (type and length)")
+ {
+ // create vector with two elements of the same type
+ std::vector<uint8_t> const v_N = {'[', '$', 'N', '#', 'i', 2};
+ std::vector<uint8_t> const v_T = {'[', '$', 'T', '#', 'i', 2};
+ std::vector<uint8_t> const v_F = {'[', '$', 'F', '#', 'i', 2};
+ std::vector<uint8_t> const v_Z = {'[', '$', 'Z', '#', 'i', 2};
+ std::vector<uint8_t> const v_i = {'[', '$', 'i', '#', 'i', 2, 0x7F, 0x7F};
+ std::vector<uint8_t> const v_U = {'[', '$', 'U', '#', 'i', 2, 0xFF, 0xFF};
+ std::vector<uint8_t> const v_I = {'[', '$', 'I', '#', 'i', 2, 0x7F, 0xFF, 0x7F, 0xFF};
+ std::vector<uint8_t> const v_l = {'[', '$', 'l', '#', 'i', 2, 0x7F, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF};
+ std::vector<uint8_t> const v_L = {'[', '$', 'L', '#', 'i', 2, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+ std::vector<uint8_t> const v_D = {'[', '$', 'D', '#', 'i', 2, 0x40, 0x09, 0x21, 0xfb, 0x4d, 0x12, 0xd8, 0x4a, 0x40, 0x09, 0x21, 0xfb, 0x4d, 0x12, 0xd8, 0x4a};
+ std::vector<uint8_t> const v_S = {'[', '$', 'S', '#', 'i', 2, 'i', 1, 'a', 'i', 1, 'a'};
+ std::vector<uint8_t> const v_C = {'[', '$', 'C', '#', 'i', 2, 'a', 'a'};
+
+ // check if vector is parsed correctly
+ CHECK(json::from_ubjson(v_N) == json::array());
+ CHECK(json::from_ubjson(v_T) == json({true, true}));
+ CHECK(json::from_ubjson(v_F) == json({false, false}));
+ CHECK(json::from_ubjson(v_Z) == json({nullptr, nullptr}));
+ CHECK(json::from_ubjson(v_i) == json({127, 127}));
+ CHECK(json::from_ubjson(v_U) == json({255, 255}));
+ CHECK(json::from_ubjson(v_I) == json({32767, 32767}));
+ CHECK(json::from_ubjson(v_l) == json({2147483647, 2147483647}));
+ CHECK(json::from_ubjson(v_L) == json({9223372036854775807, 9223372036854775807}));
+ CHECK(json::from_ubjson(v_D) == json({3.1415926, 3.1415926}));
+ CHECK(json::from_ubjson(v_S) == json({"a", "a"}));
+ CHECK(json::from_ubjson(v_C) == json({"a", "a"}));
+
+ // roundtrip: output should be optimized
+ std::vector<uint8_t> const v_empty = {'[', '#', 'i', 0};
+ CHECK(json::to_ubjson(json::from_ubjson(v_N), true, true) == v_empty);
+ CHECK(json::to_ubjson(json::from_ubjson(v_T), true, true) == v_T);
+ CHECK(json::to_ubjson(json::from_ubjson(v_F), true, true) == v_F);
+ CHECK(json::to_ubjson(json::from_ubjson(v_Z), true, true) == v_Z);
+ CHECK(json::to_ubjson(json::from_ubjson(v_i), true, true) == v_i);
+ CHECK(json::to_ubjson(json::from_ubjson(v_U), true, true) == v_U);
+ CHECK(json::to_ubjson(json::from_ubjson(v_I), true, true) == v_I);
+ CHECK(json::to_ubjson(json::from_ubjson(v_l), true, true) == v_l);
+ CHECK(json::to_ubjson(json::from_ubjson(v_L), true, true) == v_L);
+ CHECK(json::to_ubjson(json::from_ubjson(v_D), true, true) == v_D);
+ CHECK(json::to_ubjson(json::from_ubjson(v_S), true, true) == v_S);
+ CHECK(json::to_ubjson(json::from_ubjson(v_C), true, true) == v_S); // char is serialized to string
+ }
+ }
+ }
+
+ SECTION("parse errors")
+ {
+ SECTION("empty byte vector")
+ {
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(std::vector<uint8_t>()), "[json.exception.parse_error.110] parse error at byte 1: syntax error while parsing UBJSON value: unexpected end of input", json::parse_error&);
+ }
+
+ SECTION("char")
+ {
+ SECTION("eof after C byte")
+ {
+ std::vector<uint8_t> const v = {'C'};
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing UBJSON char: unexpected end of input", json::parse_error&);
+ }
+
+ SECTION("byte out of range")
+ {
+ std::vector<uint8_t> const v = {'C', 130};
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON char: byte after 'C' must be in range 0x00..0x7F; last byte: 0x82", json::parse_error&);
+ }
+ }
+
+ SECTION("strings")
+ {
+ SECTION("eof after S byte")
+ {
+ std::vector<uint8_t> const v = {'S'};
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing UBJSON value: unexpected end of input", json::parse_error&);
+ }
+
+ SECTION("invalid byte")
+ {
+ std::vector<uint8_t> const v = {'S', '1', 'a'};
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON string: expected length type specification (U, i, I, l, L); last byte: 0x31", json::parse_error&);
+ }
+ }
+
+ SECTION("array")
+ {
+ SECTION("optimized array: no size following type")
+ {
+ std::vector<uint8_t> const v = {'[', '$', 'i', 2};
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v), "[json.exception.parse_error.112] parse error at byte 4: syntax error while parsing UBJSON size: expected '#' after type information; last byte: 0x02", json::parse_error&);
+ }
+ }
+
+ SECTION("strings")
+ {
+ std::vector<uint8_t> const vS = {'S'};
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vS), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing UBJSON value: unexpected end of input", json::parse_error&);
+ CHECK(json::from_ubjson(vS, true, false).is_discarded());
+
+ std::vector<uint8_t> const v = {'S', 'i', '2', 'a'};
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing UBJSON string: unexpected end of input", json::parse_error&);
+ CHECK(json::from_ubjson(v, true, false).is_discarded());
+
+ std::vector<uint8_t> const vC = {'C'};
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vC), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing UBJSON char: unexpected end of input", json::parse_error&);
+ CHECK(json::from_ubjson(vC, true, false).is_discarded());
+ }
+
+ SECTION("sizes")
+ {
+ std::vector<uint8_t> const vU = {'[', '#', 'U'};
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vU), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing UBJSON number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_ubjson(vU, true, false).is_discarded());
+
+ std::vector<uint8_t> const vi = {'[', '#', 'i'};
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vi), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing UBJSON number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_ubjson(vi, true, false).is_discarded());
+
+ std::vector<uint8_t> const vI = {'[', '#', 'I'};
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vI), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing UBJSON number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_ubjson(vI, true, false).is_discarded());
+
+ std::vector<uint8_t> const vl = {'[', '#', 'l'};
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vl), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing UBJSON number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_ubjson(vl, true, false).is_discarded());
+
+ std::vector<uint8_t> const vL = {'[', '#', 'L'};
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vL), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing UBJSON number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_ubjson(vL, true, false).is_discarded());
+
+ std::vector<uint8_t> const v0 = {'[', '#', 'T', ']'};
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v0), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing UBJSON size: expected length type specification (U, i, I, l, L) after '#'; last byte: 0x54", json::parse_error&);
+ CHECK(json::from_ubjson(v0, true, false).is_discarded());
+ }
+
+ SECTION("types")
+ {
+ std::vector<uint8_t> const v0 = {'[', '$'};
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v0), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing UBJSON type: unexpected end of input", json::parse_error&);
+ CHECK(json::from_ubjson(v0, true, false).is_discarded());
+
+ std::vector<uint8_t> const vi = {'[', '$', '#'};
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vi), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing UBJSON value: unexpected end of input", json::parse_error&);
+ CHECK(json::from_ubjson(vi, true, false).is_discarded());
+
+ std::vector<uint8_t> const vT = {'[', '$', 'T'};
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vT), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing UBJSON value: unexpected end of input", json::parse_error&);
+ CHECK(json::from_ubjson(vT, true, false).is_discarded());
+ }
+
+ SECTION("arrays")
+ {
+ std::vector<uint8_t> const vST = {'[', '$', 'i', '#', 'i', 2, 1};
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vST), "[json.exception.parse_error.110] parse error at byte 8: syntax error while parsing UBJSON number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_ubjson(vST, true, false).is_discarded());
+
+ std::vector<uint8_t> const vS = {'[', '#', 'i', 2, 'i', 1};
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vS), "[json.exception.parse_error.110] parse error at byte 7: syntax error while parsing UBJSON value: unexpected end of input", json::parse_error&);
+ CHECK(json::from_ubjson(vS, true, false).is_discarded());
+
+ std::vector<uint8_t> const v = {'[', 'i', 2, 'i', 1};
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v), "[json.exception.parse_error.110] parse error at byte 6: syntax error while parsing UBJSON value: unexpected end of input", json::parse_error&);
+ CHECK(json::from_ubjson(v, true, false).is_discarded());
+ }
+
+ SECTION("objects")
+ {
+ std::vector<uint8_t> const vST = {'{', '$', 'i', '#', 'i', 2, 'i', 1, 'a', 1};
+ json _;
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vST), "[json.exception.parse_error.110] parse error at byte 11: syntax error while parsing UBJSON value: unexpected end of input", json::parse_error&);
+ CHECK(json::from_ubjson(vST, true, false).is_discarded());
+
+ std::vector<uint8_t> const vT = {'{', '$', 'i', 'i', 1, 'a', 1};
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vT), "[json.exception.parse_error.112] parse error at byte 4: syntax error while parsing UBJSON size: expected '#' after type information; last byte: 0x69", json::parse_error&);
+ CHECK(json::from_ubjson(vT, true, false).is_discarded());
+
+ std::vector<uint8_t> const vS = {'{', '#', 'i', 2, 'i', 1, 'a', 'i', 1};
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vS), "[json.exception.parse_error.110] parse error at byte 10: syntax error while parsing UBJSON value: unexpected end of input", json::parse_error&);
+ CHECK(json::from_ubjson(vS, true, false).is_discarded());
+
+ std::vector<uint8_t> const v = {'{', 'i', 1, 'a', 'i', 1};
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v), "[json.exception.parse_error.110] parse error at byte 7: syntax error while parsing UBJSON value: unexpected end of input", json::parse_error&);
+ CHECK(json::from_ubjson(v, true, false).is_discarded());
+
+ std::vector<uint8_t> const v2 = {'{', 'i', 1, 'a', 'i', 1, 'i'};
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v2), "[json.exception.parse_error.110] parse error at byte 8: syntax error while parsing UBJSON number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_ubjson(v2, true, false).is_discarded());
+
+ std::vector<uint8_t> const v3 = {'{', 'i', 1, 'a'};
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v3), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing UBJSON value: unexpected end of input", json::parse_error&);
+ CHECK(json::from_ubjson(v3, true, false).is_discarded());
+
+ std::vector<uint8_t> const vST1 = {'{', '$', 'd', '#', 'i', 2, 'i', 1, 'a'};
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vST1), "[json.exception.parse_error.110] parse error at byte 10: syntax error while parsing UBJSON number: unexpected end of input", json::parse_error&);
+ CHECK(json::from_ubjson(vST1, true, false).is_discarded());
+
+ std::vector<uint8_t> const vST2 = {'{', '#', 'i', 2, 'i', 1, 'a'};
+ CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vST2), "[json.exception.parse_error.110] parse error at byte 8: syntax error while parsing UBJSON value: unexpected end of input", json::parse_error&);
+ CHECK(json::from_ubjson(vST2, true, false).is_discarded());
+ }
+ }
+
+ SECTION("writing optimized values")
+ {
+ SECTION("integer")
+ {
+ SECTION("array of i")
+ {
+ json const j = {1, -1};
+ std::vector<uint8_t> expected = {'[', '$', 'i', '#', 'i', 2, 1, 0xff};
+ CHECK(json::to_ubjson(j, true, true) == expected);
+ }
+
+ SECTION("array of U")
+ {
+ json const j = {200, 201};
+ std::vector<uint8_t> expected = {'[', '$', 'U', '#', 'i', 2, 0xC8, 0xC9};
+ CHECK(json::to_ubjson(j, true, true) == expected);
+ }
+
+ SECTION("array of I")
+ {
+ json const j = {30000, -30000};
+ std::vector<uint8_t> expected = {'[', '$', 'I', '#', 'i', 2, 0x75, 0x30, 0x8a, 0xd0};
+ CHECK(json::to_ubjson(j, true, true) == expected);
+ }
+
+ SECTION("array of l")
+ {
+ json const j = {70000, -70000};
+ std::vector<uint8_t> expected = {'[', '$', 'l', '#', 'i', 2, 0x00, 0x01, 0x11, 0x70, 0xFF, 0xFE, 0xEE, 0x90};
+ CHECK(json::to_ubjson(j, true, true) == expected);
+ }
+
+ SECTION("array of L")
+ {
+ json const j = {5000000000, -5000000000};
+ std::vector<uint8_t> expected = {'[', '$', 'L', '#', 'i', 2, 0x00, 0x00, 0x00, 0x01, 0x2A, 0x05, 0xF2, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xD5, 0xFA, 0x0E, 0x00};
+ CHECK(json::to_ubjson(j, true, true) == expected);
+ }
+ }
+
+ SECTION("unsigned integer")
+ {
+ SECTION("array of i")
+ {
+ json const j = {1u, 2u};
+ std::vector<uint8_t> expected = {'[', '$', 'i', '#', 'i', 2, 1, 2};
+ std::vector<uint8_t> expected_size = {'[', '#', 'i', 2, 'i', 1, 'i', 2};
+ CHECK(json::to_ubjson(j, true, true) == expected);
+ CHECK(json::to_ubjson(j, true) == expected_size);
+ }
+
+ SECTION("array of U")
+ {
+ json const j = {200u, 201u};
+ std::vector<uint8_t> expected = {'[', '$', 'U', '#', 'i', 2, 0xC8, 0xC9};
+ std::vector<uint8_t> expected_size = {'[', '#', 'i', 2, 'U', 0xC8, 'U', 0xC9};
+ CHECK(json::to_ubjson(j, true, true) == expected);
+ CHECK(json::to_ubjson(j, true) == expected_size);
+ }
+
+ SECTION("array of I")
+ {
+ json const j = {30000u, 30001u};
+ std::vector<uint8_t> expected = {'[', '$', 'I', '#', 'i', 2, 0x75, 0x30, 0x75, 0x31};
+ std::vector<uint8_t> expected_size = {'[', '#', 'i', 2, 'I', 0x75, 0x30, 'I', 0x75, 0x31};
+ CHECK(json::to_ubjson(j, true, true) == expected);
+ CHECK(json::to_ubjson(j, true) == expected_size);
+ }
+
+ SECTION("array of l")
+ {
+ json const j = {70000u, 70001u};
+ std::vector<uint8_t> expected = {'[', '$', 'l', '#', 'i', 2, 0x00, 0x01, 0x11, 0x70, 0x00, 0x01, 0x11, 0x71};
+ std::vector<uint8_t> expected_size = {'[', '#', 'i', 2, 'l', 0x00, 0x01, 0x11, 0x70, 'l', 0x00, 0x01, 0x11, 0x71};
+ CHECK(json::to_ubjson(j, true, true) == expected);
+ CHECK(json::to_ubjson(j, true) == expected_size);
+ }
+
+ SECTION("array of L")
+ {
+ json const j = {5000000000u, 5000000001u};
+ std::vector<uint8_t> expected = {'[', '$', 'L', '#', 'i', 2, 0x00, 0x00, 0x00, 0x01, 0x2A, 0x05, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x01, 0x2A, 0x05, 0xF2, 0x01};
+ std::vector<uint8_t> expected_size = {'[', '#', 'i', 2, 'L', 0x00, 0x00, 0x00, 0x01, 0x2A, 0x05, 0xF2, 0x00, 'L', 0x00, 0x00, 0x00, 0x01, 0x2A, 0x05, 0xF2, 0x01};
+ CHECK(json::to_ubjson(j, true, true) == expected);
+ CHECK(json::to_ubjson(j, true) == expected_size);
+ }
+ }
+
+ SECTION("discarded")
+ {
+ json const j = {json::value_t::discarded, json::value_t::discarded};
+ std::vector<uint8_t> expected = {'[', '$', 'N', '#', 'i', 2};
+ CHECK(json::to_ubjson(j, true, true) == expected);
+ }
+ }
+}
+
+TEST_CASE("Universal Binary JSON Specification Examples 1")
+{
+ SECTION("Null Value")
+ {
+ json const j = {{"passcode", nullptr}};
+ std::vector<uint8_t> const v = {'{', 'i', 8, 'p', 'a', 's', 's', 'c', 'o', 'd', 'e', 'Z', '}'};
+ CHECK(json::to_ubjson(j) == v);
+ CHECK(json::from_ubjson(v) == j);
+ }
+
+ SECTION("No-Op Value")
+ {
+ json const j = {"foo", "bar", "baz"};
+ std::vector<uint8_t> const v = {'[', 'S', 'i', 3, 'f', 'o', 'o',
+ 'S', 'i', 3, 'b', 'a', 'r',
+ 'S', 'i', 3, 'b', 'a', 'z', ']'
+ };
+ std::vector<uint8_t> const v2 = {'[', 'S', 'i', 3, 'f', 'o', 'o', 'N',
+ 'S', 'i', 3, 'b', 'a', 'r', 'N', 'N', 'N',
+ 'S', 'i', 3, 'b', 'a', 'z', 'N', 'N', ']'
+ };
+ CHECK(json::to_ubjson(j) == v);
+ CHECK(json::from_ubjson(v) == j);
+ CHECK(json::from_ubjson(v2) == j);
+ }
+
+ SECTION("Boolean Types")
+ {
+ json const j = {{"authorized", true}, {"verified", false}};
+ std::vector<uint8_t> const v = {'{', 'i', 10, 'a', 'u', 't', 'h', 'o', 'r', 'i', 'z', 'e', 'd', 'T',
+ 'i', 8, 'v', 'e', 'r', 'i', 'f', 'i', 'e', 'd', 'F', '}'
+ };
+ CHECK(json::to_ubjson(j) == v);
+ CHECK(json::from_ubjson(v) == j);
+ }
+
+ SECTION("Numeric Types")
+ {
+ json const j =
+ {
+ {"int8", 16},
+ {"uint8", 255},
+ {"int16", 32767},
+ {"int32", 2147483647},
+ {"int64", 9223372036854775807},
+ {"float64", 113243.7863123}
+ };
+ std::vector<uint8_t> const v = {'{',
+ 'i', 7, 'f', 'l', 'o', 'a', 't', '6', '4', 'D', 0x40, 0xfb, 0xa5, 0xbc, 0x94, 0xbc, 0x34, 0xcf,
+ 'i', 5, 'i', 'n', 't', '1', '6', 'I', 0x7f, 0xff,
+ 'i', 5, 'i', 'n', 't', '3', '2', 'l', 0x7f, 0xff, 0xff, 0xff,
+ 'i', 5, 'i', 'n', 't', '6', '4', 'L', 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 'i', 4, 'i', 'n', 't', '8', 'i', 16,
+ 'i', 5, 'u', 'i', 'n', 't', '8', 'U', 0xff,
+ '}'
+ };
+ CHECK(json::to_ubjson(j) == v);
+ CHECK(json::from_ubjson(v) == j);
+ }
+
+ SECTION("Char Type")
+ {
+ json const j = {{"rolecode", "a"}, {"delim", ";"}};
+ std::vector<uint8_t> const v = {'{', 'i', 5, 'd', 'e', 'l', 'i', 'm', 'C', ';', 'i', 8, 'r', 'o', 'l', 'e', 'c', 'o', 'd', 'e', 'C', 'a', '}'};
+ //CHECK(json::to_ubjson(j) == v);
+ CHECK(json::from_ubjson(v) == j);
+ }
+
+ SECTION("String Type")
+ {
+ SECTION("English")
+ {
+ json const j = "hello";
+ std::vector<uint8_t> const v = {'S', 'i', 5, 'h', 'e', 'l', 'l', 'o'};
+ CHECK(json::to_ubjson(j) == v);
+ CHECK(json::from_ubjson(v) == j);
+ }
+
+ SECTION("Russian")
+ {
+ json const j = "привет";
+ std::vector<uint8_t> const v = {'S', 'i', 12, 0xD0, 0xBF, 0xD1, 0x80, 0xD0, 0xB8, 0xD0, 0xB2, 0xD0, 0xB5, 0xD1, 0x82};
+ CHECK(json::to_ubjson(j) == v);
+ CHECK(json::from_ubjson(v) == j);
+ }
+
+ SECTION("Russian")
+ {
+ json const j = "مرحبا";
+ std::vector<uint8_t> const v = {'S', 'i', 10, 0xD9, 0x85, 0xD8, 0xB1, 0xD8, 0xAD, 0xD8, 0xA8, 0xD8, 0xA7};
+ CHECK(json::to_ubjson(j) == v);
+ CHECK(json::from_ubjson(v) == j);
+ }
+ }
+
+ SECTION("Array Type")
+ {
+ SECTION("size=false type=false")
+ {
+ // note the float has been replaced by a double
+ json const j = {nullptr, true, false, 4782345193, 153.132, "ham"};
+ std::vector<uint8_t> const v = {'[', 'Z', 'T', 'F', 'L', 0x00, 0x00, 0x00, 0x01, 0x1D, 0x0C, 0xCB, 0xE9, 'D', 0x40, 0x63, 0x24, 0x39, 0x58, 0x10, 0x62, 0x4e, 'S', 'i', 3, 'h', 'a', 'm', ']'};
+ CHECK(json::to_ubjson(j) == v);
+ CHECK(json::from_ubjson(v) == j);
+ }
+
+ SECTION("size=true type=false")
+ {
+ // note the float has been replaced by a double
+ json const j = {nullptr, true, false, 4782345193, 153.132, "ham"};
+ std::vector<uint8_t> const v = {'[', '#', 'i', 6, 'Z', 'T', 'F', 'L', 0x00, 0x00, 0x00, 0x01, 0x1D, 0x0C, 0xCB, 0xE9, 'D', 0x40, 0x63, 0x24, 0x39, 0x58, 0x10, 0x62, 0x4e, 'S', 'i', 3, 'h', 'a', 'm'};
+ CHECK(json::to_ubjson(j, true) == v);
+ CHECK(json::from_ubjson(v) == j);
+ }
+
+ SECTION("size=true type=true")
+ {
+ // note the float has been replaced by a double
+ json const j = {nullptr, true, false, 4782345193, 153.132, "ham"};
+ std::vector<uint8_t> const v = {'[', '#', 'i', 6, 'Z', 'T', 'F', 'L', 0x00, 0x00, 0x00, 0x01, 0x1D, 0x0C, 0xCB, 0xE9, 'D', 0x40, 0x63, 0x24, 0x39, 0x58, 0x10, 0x62, 0x4e, 'S', 'i', 3, 'h', 'a', 'm'};
+ CHECK(json::to_ubjson(j, true, true) == v);
+ CHECK(json::from_ubjson(v) == j);
+ }
+ }
+
+ SECTION("Object Type")
+ {
+ SECTION("size=false type=false")
+ {
+ json const j =
+ {
+ {
+ "post", {
+ {"id", 1137},
+ {"author", "rkalla"},
+ {"timestamp", 1364482090592},
+ {"body", "I totally agree!"}
+ }
+ }
+ };
+ std::vector<uint8_t> const v = {'{', 'i', 4, 'p', 'o', 's', 't', '{',
+ 'i', 6, 'a', 'u', 't', 'h', 'o', 'r', 'S', 'i', 6, 'r', 'k', 'a', 'l', 'l', 'a',
+ 'i', 4, 'b', 'o', 'd', 'y', 'S', 'i', 16, 'I', ' ', 't', 'o', 't', 'a', 'l', 'l', 'y', ' ', 'a', 'g', 'r', 'e', 'e', '!',
+ 'i', 2, 'i', 'd', 'I', 0x04, 0x71,
+ 'i', 9, 't', 'i', 'm', 'e', 's', 't', 'a', 'm', 'p', 'L', 0x00, 0x00, 0x01, 0x3D, 0xB1, 0x78, 0x66, 0x60,
+ '}', '}'
+ };
+ CHECK(json::to_ubjson(j) == v);
+ CHECK(json::from_ubjson(v) == j);
+ }
+
+ SECTION("size=true type=false")
+ {
+ json const j =
+ {
+ {
+ "post", {
+ {"id", 1137},
+ {"author", "rkalla"},
+ {"timestamp", 1364482090592},
+ {"body", "I totally agree!"}
+ }
+ }
+ };
+ std::vector<uint8_t> const v = {'{', '#', 'i', 1, 'i', 4, 'p', 'o', 's', 't', '{', '#', 'i', 4,
+ 'i', 6, 'a', 'u', 't', 'h', 'o', 'r', 'S', 'i', 6, 'r', 'k', 'a', 'l', 'l', 'a',
+ 'i', 4, 'b', 'o', 'd', 'y', 'S', 'i', 16, 'I', ' ', 't', 'o', 't', 'a', 'l', 'l', 'y', ' ', 'a', 'g', 'r', 'e', 'e', '!',
+ 'i', 2, 'i', 'd', 'I', 0x04, 0x71,
+ 'i', 9, 't', 'i', 'm', 'e', 's', 't', 'a', 'm', 'p', 'L', 0x00, 0x00, 0x01, 0x3D, 0xB1, 0x78, 0x66, 0x60
+ };
+ CHECK(json::to_ubjson(j, true) == v);
+ CHECK(json::from_ubjson(v) == j);
+ }
+
+ SECTION("size=true type=true")
+ {
+ json const j =
+ {
+ {
+ "post", {
+ {"id", 1137},
+ {"author", "rkalla"},
+ {"timestamp", 1364482090592},
+ {"body", "I totally agree!"}
+ }
+ }
+ };
+ std::vector<uint8_t> const v = {'{', '$', '{', '#', 'i', 1, 'i', 4, 'p', 'o', 's', 't', '#', 'i', 4,
+ 'i', 6, 'a', 'u', 't', 'h', 'o', 'r', 'S', 'i', 6, 'r', 'k', 'a', 'l', 'l', 'a',
+ 'i', 4, 'b', 'o', 'd', 'y', 'S', 'i', 16, 'I', ' ', 't', 'o', 't', 'a', 'l', 'l', 'y', ' ', 'a', 'g', 'r', 'e', 'e', '!',
+ 'i', 2, 'i', 'd', 'I', 0x04, 0x71,
+ 'i', 9, 't', 'i', 'm', 'e', 's', 't', 'a', 'm', 'p', 'L', 0x00, 0x00, 0x01, 0x3D, 0xB1, 0x78, 0x66, 0x60
+ };
+ CHECK(json::to_ubjson(j, true, true) == v);
+ CHECK(json::from_ubjson(v) == j);
+ }
+ }
+
+ SECTION("Optimized Format")
+ {
+ SECTION("Array Example")
+ {
+ SECTION("No Optimization")
+ {
+ // note the floats have been replaced by doubles
+ json const j = {29.97, 31.13, 67.0, 2.113, 23.888};
+ std::vector<uint8_t> const v = {'[',
+ 'D', 0x40, 0x3d, 0xf8, 0x51, 0xeb, 0x85, 0x1e, 0xb8,
+ 'D', 0x40, 0x3f, 0x21, 0x47, 0xae, 0x14, 0x7a, 0xe1,
+ 'D', 0x40, 0x50, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 'D', 0x40, 0x00, 0xe7, 0x6c, 0x8b, 0x43, 0x95, 0x81,
+ 'D', 0x40, 0x37, 0xe3, 0x53, 0xf7, 0xce, 0xd9, 0x17,
+ ']'
+ };
+ CHECK(json::to_ubjson(j) == v);
+ CHECK(json::from_ubjson(v) == j);
+ }
+
+ SECTION("Optimized with count")
+ {
+ // note the floats have been replaced by doubles
+ json const j = {29.97, 31.13, 67.0, 2.113, 23.888};
+ std::vector<uint8_t> const v = {'[', '#', 'i', 5,
+ 'D', 0x40, 0x3d, 0xf8, 0x51, 0xeb, 0x85, 0x1e, 0xb8,
+ 'D', 0x40, 0x3f, 0x21, 0x47, 0xae, 0x14, 0x7a, 0xe1,
+ 'D', 0x40, 0x50, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 'D', 0x40, 0x00, 0xe7, 0x6c, 0x8b, 0x43, 0x95, 0x81,
+ 'D', 0x40, 0x37, 0xe3, 0x53, 0xf7, 0xce, 0xd9, 0x17
+ };
+ CHECK(json::to_ubjson(j, true) == v);
+ CHECK(json::from_ubjson(v) == j);
+ }
+
+ SECTION("Optimized with type & count")
+ {
+ // note the floats have been replaced by doubles
+ json const j = {29.97, 31.13, 67.0, 2.113, 23.888};
+ std::vector<uint8_t> const v = {'[', '$', 'D', '#', 'i', 5,
+ 0x40, 0x3d, 0xf8, 0x51, 0xeb, 0x85, 0x1e, 0xb8,
+ 0x40, 0x3f, 0x21, 0x47, 0xae, 0x14, 0x7a, 0xe1,
+ 0x40, 0x50, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0xe7, 0x6c, 0x8b, 0x43, 0x95, 0x81,
+ 0x40, 0x37, 0xe3, 0x53, 0xf7, 0xce, 0xd9, 0x17
+ };
+ CHECK(json::to_ubjson(j, true, true) == v);
+ CHECK(json::from_ubjson(v) == j);
+ }
+ }
+
+ SECTION("Object Example")
+ {
+ SECTION("No Optimization")
+ {
+ // note the floats have been replaced by doubles
+ json const j = { {"lat", 29.976}, {"long", 31.131}, {"alt", 67.0} };
+ std::vector<uint8_t> const v = {'{',
+ 'i', 3, 'a', 'l', 't', 'D', 0x40, 0x50, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 'i', 3, 'l', 'a', 't', 'D', 0x40, 0x3d, 0xf9, 0xdb, 0x22, 0xd0, 0xe5, 0x60,
+ 'i', 4, 'l', 'o', 'n', 'g', 'D', 0x40, 0x3f, 0x21, 0x89, 0x37, 0x4b, 0xc6, 0xa8,
+ '}'
+ };
+ CHECK(json::to_ubjson(j) == v);
+ CHECK(json::from_ubjson(v) == j);
+ }
+
+ SECTION("Optimized with count")
+ {
+ // note the floats have been replaced by doubles
+ json const j = { {"lat", 29.976}, {"long", 31.131}, {"alt", 67.0} };
+ std::vector<uint8_t> const v = {'{', '#', 'i', 3,
+ 'i', 3, 'a', 'l', 't', 'D', 0x40, 0x50, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 'i', 3, 'l', 'a', 't', 'D', 0x40, 0x3d, 0xf9, 0xdb, 0x22, 0xd0, 0xe5, 0x60,
+ 'i', 4, 'l', 'o', 'n', 'g', 'D', 0x40, 0x3f, 0x21, 0x89, 0x37, 0x4b, 0xc6, 0xa8
+ };
+ CHECK(json::to_ubjson(j, true) == v);
+ CHECK(json::from_ubjson(v) == j);
+ }
+
+ SECTION("Optimized with type & count")
+ {
+ // note the floats have been replaced by doubles
+ json const j = { {"lat", 29.976}, {"long", 31.131}, {"alt", 67.0} };
+ std::vector<uint8_t> const v = {'{', '$', 'D', '#', 'i', 3,
+ 'i', 3, 'a', 'l', 't', 0x40, 0x50, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 'i', 3, 'l', 'a', 't', 0x40, 0x3d, 0xf9, 0xdb, 0x22, 0xd0, 0xe5, 0x60,
+ 'i', 4, 'l', 'o', 'n', 'g', 0x40, 0x3f, 0x21, 0x89, 0x37, 0x4b, 0xc6, 0xa8
+ };
+ CHECK(json::to_ubjson(j, true, true) == v);
+ CHECK(json::from_ubjson(v) == j);
+ }
+ }
+
+ SECTION("Special Cases (Null, No-Op and Boolean)")
+ {
+ SECTION("Array")
+ {
+ std::vector<uint8_t> const v = {'[', '$', 'N', '#', 'I', 0x02, 0x00};
+ CHECK(json::from_ubjson(v) == json::array());
+ }
+
+ SECTION("Object")
+ {
+ std::vector<uint8_t> const v = {'{', '$', 'Z', '#', 'i', 3, 'i', 4, 'n', 'a', 'm', 'e', 'i', 8, 'p', 'a', 's', 's', 'w', 'o', 'r', 'd', 'i', 5, 'e', 'm', 'a', 'i', 'l'};
+ CHECK(json::from_ubjson(v) == json({ {"name", nullptr}, {"password", nullptr}, {"email", nullptr} }));
+ }
+ }
+ }
+}
+
+#if !defined(JSON_NOEXCEPTION)
+TEST_CASE("all UBJSON first bytes")
+{
+ // these bytes will fail immediately with exception parse_error.112
+ std::set<uint8_t> supported =
+ {
+ 'T', 'F', 'Z', 'U', 'i', 'I', 'l', 'L', 'd', 'D', 'C', 'S', '[', '{', 'N', 'H'
+ };
+
+ for (auto i = 0; i < 256; ++i)
+ {
+ const auto byte = static_cast<uint8_t>(i);
+ CAPTURE(byte)
+
+ try
+ {
+ auto res = json::from_ubjson(std::vector<uint8_t>(1, byte));
+ }
+ catch (const json::parse_error& e)
+ {
+ // check that parse_error.112 is only thrown if the
+ // first byte is not in the supported set
+ INFO_WITH_TEMP(e.what());
+ if (supported.find(byte) == supported.end())
+ {
+ CHECK(e.id == 112);
+ }
+ else
+ {
+ CHECK(e.id != 112);
+ }
+ }
+ }
+}
+#endif
+
+TEST_CASE("UBJSON roundtrips" * doctest::skip())
+{
+ SECTION("input from self-generated UBJSON files")
+ {
+ for (std::string filename :
+ {
+ TEST_DATA_DIRECTORY "/json_nlohmann_tests/all_unicode.json",
+ TEST_DATA_DIRECTORY "/json.org/1.json",
+ TEST_DATA_DIRECTORY "/json.org/2.json",
+ TEST_DATA_DIRECTORY "/json.org/3.json",
+ TEST_DATA_DIRECTORY "/json.org/4.json",
+ TEST_DATA_DIRECTORY "/json.org/5.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip01.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip02.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip03.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip04.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip05.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip06.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip07.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip08.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip09.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip10.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip11.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip12.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip13.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip14.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip15.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip16.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip17.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip18.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip19.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip20.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip21.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip22.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip23.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip24.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip25.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip26.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip27.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip28.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip29.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip30.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip31.json",
+ TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip32.json",
+ TEST_DATA_DIRECTORY "/json_testsuite/sample.json",
+ TEST_DATA_DIRECTORY "/json_tests/pass1.json",
+ TEST_DATA_DIRECTORY "/json_tests/pass2.json",
+ TEST_DATA_DIRECTORY "/json_tests/pass3.json"
+ })
+ {
+ CAPTURE(filename)
+
+ {
+ INFO_WITH_TEMP(filename + ": std::vector<uint8_t>");
+ // parse JSON file
+ std::ifstream f_json(filename);
+ json const j1 = json::parse(f_json);
+
+ // parse UBJSON file
+ auto const packed = utils::read_binary_file(filename + ".ubjson");
+ json j2;
+ CHECK_NOTHROW(j2 = json::from_ubjson(packed));
+
+ // compare parsed JSON values
+ CHECK(j1 == j2);
+ }
+
+ {
+ INFO_WITH_TEMP(filename + ": std::ifstream");
+ // parse JSON file
+ std::ifstream f_json(filename);
+ json const j1 = json::parse(f_json);
+
+ // parse UBJSON file
+ std::ifstream f_ubjson(filename + ".ubjson", std::ios::binary);
+ json j2;
+ CHECK_NOTHROW(j2 = json::from_ubjson(f_ubjson));
+
+ // compare parsed JSON values
+ CHECK(j1 == j2);
+ }
+
+ {
+ INFO_WITH_TEMP(filename + ": uint8_t* and size");
+ // parse JSON file
+ std::ifstream f_json(filename);
+ const json j1 = json::parse(f_json);
+
+ // parse UBJSON file
+ auto const packed = utils::read_binary_file(filename + ".ubjson");
+ json j2;
+ CHECK_NOTHROW(j2 = json::from_ubjson({packed.data(), packed.size()}));
+
+ // compare parsed JSON values
+ CHECK(j1 == j2);
+ }
+
+ {
+ INFO_WITH_TEMP(filename + ": output to output adapters");
+ // parse JSON file
+ std::ifstream f_json(filename);
+ json const j1 = json::parse(f_json);
+
+ // parse UBJSON file
+ auto const packed = utils::read_binary_file(filename + ".ubjson");
+
+ {
+ INFO_WITH_TEMP(filename + ": output adapters: std::vector<uint8_t>");
+ std::vector<uint8_t> vec;
+ json::to_ubjson(j1, vec);
+ CHECK(vec == packed);
+ }
+ }
+ }
+ }
+}
diff --git a/json4cpp/tests/src/unit-udl.cpp b/json4cpp/tests/src/unit-udl.cpp
new file mode 100644
index 0000000000..405f4792d5
--- /dev/null
+++ b/json4cpp/tests/src/unit-udl.cpp
@@ -0,0 +1,57 @@
+// __ _____ _____ _____
+// __| | __| | | | 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>
+
+TEST_CASE("user-defined string literals")
+{
+ auto j_expected = nlohmann::json::parse(R"({"foo": "bar", "baz": 42})");
+ auto ptr_expected = nlohmann::json::json_pointer("/foo/bar");
+
+ SECTION("using namespace nlohmann::literals::json_literals")
+ {
+ using namespace nlohmann::literals::json_literals; // NOLINT(google-build-using-namespace)
+
+ CHECK(R"({"foo": "bar", "baz": 42})"_json == j_expected);
+ CHECK("/foo/bar"_json_pointer == ptr_expected);
+ }
+
+ SECTION("using namespace nlohmann::json_literals")
+ {
+ using namespace nlohmann::json_literals; // NOLINT(google-build-using-namespace)
+
+ CHECK(R"({"foo": "bar", "baz": 42})"_json == j_expected);
+ CHECK("/foo/bar"_json_pointer == ptr_expected);
+ }
+
+ SECTION("using namespace nlohmann::literals")
+ {
+ using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
+
+ CHECK(R"({"foo": "bar", "baz": 42})"_json == j_expected);
+ CHECK("/foo/bar"_json_pointer == ptr_expected);
+ }
+
+ SECTION("using namespace nlohmann")
+ {
+ using namespace nlohmann; // NOLINT(google-build-using-namespace)
+
+ CHECK(R"({"foo": "bar", "baz": 42})"_json == j_expected);
+ CHECK("/foo/bar"_json_pointer == ptr_expected);
+ }
+
+#ifndef JSON_TEST_NO_GLOBAL_UDLS
+ SECTION("global namespace")
+ {
+ CHECK(R"({"foo": "bar", "baz": 42})"_json == j_expected);
+ CHECK("/foo/bar"_json_pointer == ptr_expected);
+ }
+#endif
+}
diff --git a/json4cpp/tests/src/unit-udt.cpp b/json4cpp/tests/src/unit-udt.cpp
new file mode 100644
index 0000000000..24f763f96e
--- /dev/null
+++ b/json4cpp/tests/src/unit-udt.cpp
@@ -0,0 +1,908 @@
+// __ _____ _____ _____
+// __| | __| | | | 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"
+
+// disable -Wnoexcept due to class Evil
+DOCTEST_GCC_SUPPRESS_WARNING_PUSH
+DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept")
+
+// skip tests if JSON_DisableEnumSerialization=ON (#4384)
+#if defined(JSON_DISABLE_ENUM_SERIALIZATION) && (JSON_DISABLE_ENUM_SERIALIZATION == 1)
+ #define SKIP_TESTS_FOR_ENUM_SERIALIZATION
+#endif
+
+#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 <memory>
+#include <string>
+#include <utility>
+
+namespace udt
+{
+enum class country
+{
+ china,
+ france,
+ russia
+};
+
+struct age
+{
+ int m_val;
+ age(int rhs = 0) : m_val(rhs) {}
+};
+
+struct name
+{
+ std::string m_val;
+ name(std::string rhs = "") : m_val(std::move(rhs)) {}
+};
+
+struct address
+{
+ std::string m_val;
+ address(std::string rhs = "") : m_val(std::move(rhs)) {}
+};
+
+struct person
+{
+ age m_age{}; // NOLINT(readability-redundant-member-init)
+ name m_name{}; // NOLINT(readability-redundant-member-init)
+ country m_country{}; // NOLINT(readability-redundant-member-init)
+ person() = default;
+ person(const age& a, name n, const country& c) : m_age(a), m_name(std::move(n)), m_country(c) {}
+};
+
+struct contact
+{
+ person m_person{}; // NOLINT(readability-redundant-member-init)
+ address m_address{}; // NOLINT(readability-redundant-member-init)
+ contact() = default;
+ contact(person p, address a) : m_person(std::move(p)), m_address(std::move(a)) {}
+};
+
+enum class book_id : std::uint64_t;
+
+struct contact_book
+{
+ name m_book_name{}; // NOLINT(readability-redundant-member-init)
+ book_id m_book_id{};
+ std::vector<contact> m_contacts{}; // NOLINT(readability-redundant-member-init)
+ contact_book() = default;
+ contact_book(name n, book_id i, std::vector<contact> c) : m_book_name(std::move(n)), m_book_id(i), m_contacts(std::move(c)) {}
+};
+} // namespace udt
+
+// to_json methods
+namespace udt
+{
+// templates because of the custom_json tests (see below)
+template <typename BasicJsonType>
+static void to_json(BasicJsonType& j, age a)
+{
+ j = a.m_val;
+}
+
+template <typename BasicJsonType>
+static void to_json(BasicJsonType& j, const name& n)
+{
+ j = n.m_val;
+}
+
+template <typename BasicJsonType>
+static void to_json(BasicJsonType& j, country c)
+{
+ switch (c)
+ {
+ case country::china:
+ j = "中华人民共和国";
+ return;
+ case country::france:
+ j = "France";
+ return;
+ case country::russia:
+ j = "Российская Федерация";
+ return;
+ default:
+ break;
+ }
+}
+
+template <typename BasicJsonType>
+static void to_json(BasicJsonType& j, const person& p)
+{
+ j = BasicJsonType{{"age", p.m_age}, {"name", p.m_name}, {"country", p.m_country}};
+}
+
+static void to_json(nlohmann::json& j, const address& a)
+{
+ j = a.m_val;
+}
+
+static void to_json(nlohmann::json& j, const contact& c)
+{
+ j = json{{"person", c.m_person}, {"address", c.m_address}};
+}
+
+static void to_json(nlohmann::json& j, const contact_book& cb)
+{
+ j = json{{"name", cb.m_book_name},
+#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
+ {"id", cb.m_book_id},
+#endif
+ {"contacts", cb.m_contacts}};
+}
+
+// operators
+static bool operator==(age lhs, age rhs)
+{
+ return lhs.m_val == rhs.m_val;
+}
+
+static bool operator==(const address& lhs, const address& rhs)
+{
+ return lhs.m_val == rhs.m_val;
+}
+
+static bool operator==(const name& lhs, const name& rhs)
+{
+ return lhs.m_val == rhs.m_val;
+}
+
+static bool operator==(const person& lhs, const person& rhs)
+{
+ return std::tie(lhs.m_name, lhs.m_age) == std::tie(rhs.m_name, rhs.m_age);
+}
+
+static bool operator==(const contact& lhs, const contact& rhs)
+{
+ return std::tie(lhs.m_person, lhs.m_address) ==
+ std::tie(rhs.m_person, rhs.m_address);
+}
+
+static bool operator==(const contact_book& lhs, const contact_book& rhs)
+{
+ return std::tie(lhs.m_book_name, lhs.m_book_id, lhs.m_contacts) ==
+ std::tie(rhs.m_book_name, rhs.m_book_id, rhs.m_contacts);
+}
+} // namespace udt
+
+// from_json methods
+namespace udt
+{
+template <typename BasicJsonType>
+static void from_json(const BasicJsonType& j, age& a)
+{
+ a.m_val = j.template get<int>();
+}
+
+template <typename BasicJsonType>
+static void from_json(const BasicJsonType& j, name& n)
+{
+ n.m_val = j.template get<std::string>();
+}
+
+template <typename BasicJsonType>
+static void from_json(const BasicJsonType& j, country& c)
+{
+ const auto str = j.template get<std::string>();
+ const std::map<std::string, country> m =
+ {
+ {"中华人民共和国", country::china},
+ {"France", country::france},
+ {"Российская Федерация", country::russia}
+ };
+
+ const auto it = m.find(str);
+ // TODO(nlohmann) test exceptions
+ c = it->second;
+}
+
+template <typename BasicJsonType>
+static void from_json(const BasicJsonType& j, person& p)
+{
+ p.m_age = j["age"].template get<age>();
+ p.m_name = j["name"].template get<name>();
+ p.m_country = j["country"].template get<country>();
+}
+
+static void from_json(const nlohmann::json& j, address& a)
+{
+ a.m_val = j.get<std::string>();
+}
+
+static void from_json(const nlohmann::json& j, contact& c)
+{
+ c.m_person = j["person"].get<person>();
+ c.m_address = j["address"].get<address>();
+}
+
+static void from_json(const nlohmann::json& j, contact_book& cb)
+{
+ cb.m_book_name = j["name"].get<name>();
+#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
+ cb.m_book_id = j["id"].get<book_id>();
+#endif
+ cb.m_contacts = j["contacts"].get<std::vector<contact>>();
+}
+} // namespace udt
+
+TEST_CASE("basic usage" * doctest::test_suite("udt"))
+{
+
+ // a bit narcissistic maybe :) ?
+ const udt::age a
+ {
+ 23
+ };
+ const udt::name n{"theo"};
+ const udt::country c{udt::country::france};
+ const udt::person sfinae_addict{a, n, c};
+ const udt::person senior_programmer{{42}, {"王芳"}, udt::country::china};
+ const udt::address addr{"Paris"};
+ const udt::contact cpp_programmer{sfinae_addict, addr};
+ const udt::book_id large_id{static_cast<udt::book_id>(static_cast<std::uint64_t>(1) << 63)}; // verify large unsigned enums are handled correctly
+ const udt::contact_book book{{"C++"}, static_cast<udt::book_id>(42u), {cpp_programmer, {senior_programmer, addr}}};
+
+ SECTION("conversion to json via free-functions")
+ {
+ CHECK(json(a) == json(23));
+ CHECK(json(n) == json("theo"));
+ CHECK(json(c) == json("France"));
+ CHECK(json(sfinae_addict) == R"({"name":"theo", "age":23, "country":"France"})"_json);
+ CHECK(json("Paris") == json(addr));
+ CHECK(json(cpp_programmer) ==
+ R"({"person" : {"age":23, "name":"theo", "country":"France"}, "address":"Paris"})"_json);
+#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
+ CHECK(json(large_id) == json(static_cast<std::uint64_t>(1) << 63));
+ CHECK(json(large_id) > 0u);
+ CHECK(to_string(json(large_id)) == "9223372036854775808");
+ CHECK(json(large_id).is_number_unsigned());
+#endif
+
+#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
+ CHECK(
+ json(book) ==
+ R"({"name":"C++", "id":42, "contacts" : [{"person" : {"age":23, "name":"theo", "country":"France"}, "address":"Paris"}, {"person" : {"age":42, "country":"中华人民共和国", "name":"王芳"}, "address":"Paris"}]})"_json);
+#else
+ CHECK(
+ json(book) ==
+ R"({"name":"C++", "contacts" : [{"person" : {"age":23, "name":"theo", "country":"France"}, "address":"Paris"}, {"person" : {"age":42, "country":"中华人民共和国", "name":"王芳"}, "address":"Paris"}]})"_json);
+#endif
+
+ }
+
+ SECTION("conversion from json via free-functions")
+ {
+ const auto big_json =
+ R"({"name":"C++", "id":42, "contacts" : [{"person" : {"age":23, "name":"theo", "country":"France"}, "address":"Paris"}, {"person" : {"age":42, "country":"中华人民共和国", "name":"王芳"}, "address":"Paris"}]})"_json;
+ SECTION("via explicit calls to get")
+ {
+ const auto parsed_book = big_json.get<udt::contact_book>();
+ const auto book_name = big_json["name"].get<udt::name>();
+#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
+ const auto book_id = big_json["id"].get<udt::book_id>();
+#endif
+ const auto contacts =
+ big_json["contacts"].get<std::vector<udt::contact>>();
+ const auto contact_json = big_json["contacts"].at(0);
+ const auto contact = contact_json.get<udt::contact>();
+ const auto person = contact_json["person"].get<udt::person>();
+ const auto address = contact_json["address"].get<udt::address>();
+ const auto age = contact_json["person"]["age"].get<udt::age>();
+ const auto country =
+ contact_json["person"]["country"].get<udt::country>();
+ const auto name = contact_json["person"]["name"].get<udt::name>();
+
+ CHECK(age == a);
+ CHECK(name == n);
+ CHECK(country == c);
+ CHECK(address == addr);
+ CHECK(person == sfinae_addict);
+ CHECK(contact == cpp_programmer);
+ CHECK(contacts == book.m_contacts);
+ CHECK(book_name == udt::name{"C++"});
+#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
+ CHECK(book_id == book.m_book_id);
+ CHECK(book == parsed_book);
+#endif
+ }
+
+ SECTION("via explicit calls to get_to")
+ {
+ udt::person person;
+ udt::name name;
+
+ json person_json = big_json["contacts"][0]["person"];
+ CHECK(person_json.get_to(person) == sfinae_addict);
+
+ // correct reference gets returned
+ person_json["name"].get_to(name).m_val = "new name";
+ CHECK(name.m_val == "new name");
+ }
+
+#if JSON_USE_IMPLICIT_CONVERSIONS
+ SECTION("implicit conversions")
+ {
+ const udt::contact_book parsed_book = big_json;
+ const udt::name book_name = big_json["name"];
+#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
+ const udt::book_id book_id = big_json["id"];
+#endif
+ const std::vector<udt::contact> contacts = big_json["contacts"];
+ const auto contact_json = big_json["contacts"].at(0);
+ const udt::contact contact = contact_json;
+ const udt::person person = contact_json["person"];
+ const udt::address address = contact_json["address"];
+ const udt::age age = contact_json["person"]["age"];
+ const udt::country country = contact_json["person"]["country"];
+ const udt::name name = contact_json["person"]["name"];
+
+ CHECK(age == a);
+ CHECK(name == n);
+ CHECK(country == c);
+ CHECK(address == addr);
+ CHECK(person == sfinae_addict);
+ CHECK(contact == cpp_programmer);
+ CHECK(contacts == book.m_contacts);
+ CHECK(book_name == udt::name{"C++"});
+#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
+ CHECK(book_id == static_cast<udt::book_id>(42u));
+ CHECK(book == parsed_book);
+#endif
+ }
+#endif
+ }
+}
+
+namespace udt
+{
+struct legacy_type
+{
+ std::string number{}; // NOLINT(readability-redundant-member-init)
+ legacy_type() = default;
+ legacy_type(std::string n) : number(std::move(n)) {}
+};
+} // namespace udt
+
+namespace nlohmann
+{
+template <typename T>
+struct adl_serializer<std::shared_ptr<T>>
+{
+ static void to_json(json& j, const std::shared_ptr<T>& opt)
+ {
+ if (opt)
+ {
+ j = *opt;
+ }
+ else
+ {
+ j = nullptr;
+ }
+ }
+
+ static void from_json(const json& j, std::shared_ptr<T>& opt)
+ {
+ if (j.is_null())
+ {
+ opt = nullptr;
+ }
+ else
+ {
+ opt.reset(new T(j.get<T>())); // NOLINT(cppcoreguidelines-owning-memory)
+ }
+ }
+};
+
+template <>
+struct adl_serializer<udt::legacy_type>
+{
+ static void to_json(json& j, const udt::legacy_type& l)
+ {
+ j = std::stoi(l.number);
+ }
+
+ static void from_json(const json& j, udt::legacy_type& l)
+ {
+ l.number = std::to_string(j.get<int>());
+ }
+};
+} // namespace nlohmann
+
+TEST_CASE("adl_serializer specialization" * doctest::test_suite("udt"))
+{
+ SECTION("partial specialization")
+ {
+ SECTION("to_json")
+ {
+ std::shared_ptr<udt::person> optPerson;
+
+ json j = optPerson;
+ CHECK(j.is_null());
+
+ optPerson.reset(new udt::person{{42}, {"John Doe"}, udt::country::russia}); // NOLINT(cppcoreguidelines-owning-memory,modernize-make-shared)
+ j = optPerson;
+ CHECK_FALSE(j.is_null());
+
+ CHECK(j.get<udt::person>() == *optPerson);
+ }
+
+ SECTION("from_json")
+ {
+ auto person = udt::person{{42}, {"John Doe"}, udt::country::russia};
+ json j = person;
+
+ auto optPerson = j.get<std::shared_ptr<udt::person>>();
+ REQUIRE(optPerson);
+ CHECK(*optPerson == person);
+
+ j = nullptr;
+ optPerson = j.get<std::shared_ptr<udt::person>>();
+ CHECK(!optPerson);
+ }
+ }
+
+ SECTION("total specialization")
+ {
+ SECTION("to_json")
+ {
+ udt::legacy_type const lt{"4242"};
+
+ json const j = lt;
+ CHECK(j.get<int>() == 4242);
+ }
+
+ SECTION("from_json")
+ {
+ json const j = 4242;
+ auto lt = j.get<udt::legacy_type>();
+ CHECK(lt.number == "4242");
+ }
+ }
+}
+
+namespace nlohmann
+{
+template <>
+struct adl_serializer<std::vector<float>>
+{
+ using type = std::vector<float>;
+ static void to_json(json& j, const type& /*type*/)
+ {
+ j = "hijacked!";
+ }
+
+ static void from_json(const json& /*unnamed*/, type& opt)
+ {
+ opt = {42.0, 42.0, 42.0};
+ }
+
+ // preferred version
+ static type from_json(const json& /*unnamed*/)
+ {
+ return {4.0, 5.0, 6.0};
+ }
+};
+} // namespace nlohmann
+
+TEST_CASE("even supported types can be specialized" * doctest::test_suite("udt"))
+{
+ json const j = std::vector<float> {1.0, 2.0, 3.0};
+ CHECK(j.dump() == R"("hijacked!")");
+ auto f = j.get<std::vector<float>>();
+ // the single argument from_json method is preferred
+ CHECK((f == std::vector<float> {4.0, 5.0, 6.0}));
+}
+
+namespace nlohmann
+{
+template <typename T>
+struct adl_serializer<std::unique_ptr<T>>
+{
+ static void to_json(json& j, const std::unique_ptr<T>& opt)
+ {
+ if (opt)
+ {
+ j = *opt;
+ }
+ else
+ {
+ j = nullptr;
+ }
+ }
+
+ // this is the overload needed for non-copyable types,
+ static std::unique_ptr<T> from_json(const json& j)
+ {
+ if (j.is_null())
+ {
+ return nullptr;
+ }
+
+ return std::unique_ptr<T>(new T(j.get<T>()));
+ }
+};
+} // namespace nlohmann
+
+TEST_CASE("Non-copyable types" * doctest::test_suite("udt"))
+{
+ SECTION("to_json")
+ {
+ std::unique_ptr<udt::person> optPerson;
+
+ json j = optPerson;
+ CHECK(j.is_null());
+
+ optPerson.reset(new udt::person{{42}, {"John Doe"}, udt::country::russia}); // NOLINT(cppcoreguidelines-owning-memory,modernize-make-unique)
+ j = optPerson;
+ CHECK_FALSE(j.is_null());
+
+ CHECK(j.get<udt::person>() == *optPerson);
+ }
+
+ SECTION("from_json")
+ {
+ auto person = udt::person{{42}, {"John Doe"}, udt::country::russia};
+ json j = person;
+
+ auto optPerson = j.get<std::unique_ptr<udt::person>>();
+ REQUIRE(optPerson);
+ CHECK(*optPerson == person);
+
+ j = nullptr;
+ optPerson = j.get<std::unique_ptr<udt::person>>();
+ CHECK(!optPerson);
+ }
+}
+
+// custom serializer - advanced usage
+// pack structs that are pod-types (but not scalar types)
+// relies on adl for any other type
+template <typename T, typename = void>
+struct pod_serializer
+{
+ // use adl for non-pods, or scalar types
+ template <
+ typename BasicJsonType, typename U = T,
+ typename std::enable_if <
+ !(std::is_pod<U>::value && std::is_class<U>::value), int >::type = 0 >
+ static void from_json(const BasicJsonType& j, U& t)
+ {
+ using nlohmann::from_json;
+ from_json(j, t);
+ }
+
+ // special behaviour for pods
+ template < typename BasicJsonType, typename U = T,
+ typename std::enable_if <
+ std::is_pod<U>::value && std::is_class<U>::value, int >::type = 0 >
+ static void from_json(const BasicJsonType& j, U& t)
+ {
+ std::uint64_t value = 0;
+ // The following block is no longer relevant in this serializer, make another one that shows the issue
+ // the problem arises only when one from_json method is defined without any constraint
+ //
+ // Why cannot we simply use: j.get<std::uint64_t>() ?
+ // Well, with the current experiment, the get method looks for a from_json
+ // function, which we are currently defining!
+ // This would end up in a stack overflow. Calling nlohmann::from_json is a
+ // workaround (is it?).
+ // I shall find a good way to avoid this once all constructors are converted
+ // to free methods
+ //
+ // In short, constructing a json by constructor calls to_json
+ // calling get calls from_json, for now, we cannot do this in custom
+ // serializers
+ nlohmann::from_json(j, value);
+ auto* bytes = static_cast<char*>(static_cast<void*>(&value)); // NOLINT(bugprone-casting-through-void)
+ std::memcpy(&t, bytes, sizeof(value));
+ }
+
+ template <
+ typename BasicJsonType, typename U = T,
+ typename std::enable_if <
+ !(std::is_pod<U>::value && std::is_class<U>::value), int >::type = 0 >
+ static void to_json(BasicJsonType& j, const T& t)
+ {
+ using nlohmann::to_json;
+ to_json(j, t);
+ }
+
+ template < typename BasicJsonType, typename U = T,
+ typename std::enable_if <
+ std::is_pod<U>::value && std::is_class<U>::value, int >::type = 0 >
+ static void to_json(BasicJsonType& j, const T& t) noexcept
+ {
+ const auto* bytes = static_cast< const unsigned char*>(static_cast<const void*>(&t)); // NOLINT(bugprone-casting-through-void)
+ std::uint64_t value = 0;
+ std::memcpy(&value, bytes, sizeof(value));
+ nlohmann::to_json(j, value);
+ }
+};
+
+namespace udt
+{
+struct small_pod
+{
+ int begin;
+ char middle;
+ short end;
+};
+
+struct non_pod
+{
+ std::string s{}; // NOLINT(readability-redundant-member-init)
+ non_pod() = default;
+ non_pod(std::string S) : s(std::move(S)) {}
+};
+
+template <typename BasicJsonType>
+static void to_json(BasicJsonType& j, const non_pod& np)
+{
+ j = np.s;
+}
+
+template <typename BasicJsonType>
+static void from_json(const BasicJsonType& j, non_pod& np)
+{
+ np.s = j.template get<std::string>();
+}
+
+static bool operator==(small_pod lhs, small_pod rhs) noexcept
+{
+ return std::tie(lhs.begin, lhs.middle, lhs.end) ==
+ std::tie(rhs.begin, rhs.middle, rhs.end);
+}
+
+static bool operator==(const non_pod& lhs, const non_pod& rhs) noexcept
+{
+ return lhs.s == rhs.s;
+}
+
+static std::ostream& operator<<(std::ostream& os, small_pod l)
+{
+ return os << "begin: " << l.begin << ", middle: " << l.middle << ", end: " << l.end;
+}
+} // namespace udt
+
+TEST_CASE("custom serializer for pods" * doctest::test_suite("udt"))
+{
+ using custom_json =
+ nlohmann::basic_json<std::map, std::vector, std::string, bool,
+ std::int64_t, std::uint64_t, double, std::allocator, pod_serializer>;
+
+ auto p = udt::small_pod{42, '/', 42};
+ custom_json const j = p;
+
+ auto p2 = j.get<udt::small_pod>();
+
+ CHECK(p == p2);
+
+ auto np = udt::non_pod{{"non-pod"}};
+ custom_json const j2 = np;
+ auto np2 = j2.get<udt::non_pod>();
+ CHECK(np == np2);
+}
+
+template <typename T, typename>
+struct another_adl_serializer;
+
+using custom_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, double, std::allocator, another_adl_serializer>;
+
+template <typename T, typename>
+struct another_adl_serializer
+{
+ static void from_json(const custom_json& j, T& t)
+ {
+ using nlohmann::from_json;
+ from_json(j, t);
+ }
+
+ static void to_json(custom_json& j, const T& t)
+ {
+ using nlohmann::to_json;
+ to_json(j, t);
+ }
+};
+
+TEST_CASE("custom serializer that does adl by default" * doctest::test_suite("udt"))
+{
+ auto me = udt::person{{23}, {"theo"}, udt::country::france};
+
+ json const j = me;
+ custom_json const cj = me;
+
+ CHECK(j.dump() == cj.dump());
+
+ CHECK(me == j.get<udt::person>());
+ CHECK(me == cj.get<udt::person>());
+}
+
+TEST_CASE("different basic_json types conversions")
+{
+ SECTION("null")
+ {
+ json const j;
+ const custom_json cj = j;
+ CHECK(cj == nullptr);
+ }
+
+ SECTION("boolean")
+ {
+ json const j = true;
+ const custom_json cj = j;
+ CHECK(cj == true);
+ }
+
+ SECTION("discarded")
+ {
+ json const j(json::value_t::discarded);
+ custom_json cj;
+ CHECK_NOTHROW(cj = j);
+ CHECK(cj.type() == custom_json::value_t::discarded);
+ }
+
+ SECTION("array")
+ {
+ json const j = {1, 2, 3};
+ custom_json const cj = j;
+ CHECK((cj == std::vector<int> {1, 2, 3}));
+ }
+
+ SECTION("integer")
+ {
+ json const j = 42;
+ const custom_json cj = j;
+ CHECK(cj == 42);
+ }
+
+ SECTION("float")
+ {
+ json const j = 42.0;
+ const custom_json cj = j;
+ CHECK(cj == 42.0);
+ }
+
+ SECTION("unsigned")
+ {
+ json const j = 42u;
+ const custom_json cj = j;
+ CHECK(cj == 42u);
+ }
+
+ SECTION("string")
+ {
+ json const j = "forty-two";
+ const custom_json cj = j;
+ CHECK(cj == "forty-two");
+ }
+
+ SECTION("binary")
+ {
+ json j = json::binary({1, 2, 3}, 42);
+ custom_json cj = j;
+ CHECK(cj.get_binary().subtype() == 42);
+ const std::vector<std::uint8_t>& cv = cj.get_binary();
+ std::vector<std::uint8_t> v = j.get_binary();
+ CHECK(cv == v);
+ }
+
+ SECTION("object")
+ {
+ json const j = {{"forty", "two"}};
+ const custom_json cj = j;
+ auto m = j.get<std::map<std::string, std::string>>();
+ CHECK(cj == m);
+ }
+
+ SECTION("get<custom_json>")
+ {
+ json const j = 42;
+ const custom_json cj = j.get<custom_json>();
+ CHECK(cj == 42);
+ }
+}
+
+namespace
+{
+struct incomplete;
+
+// std::is_constructible is broken on macOS' libc++
+// use the cppreference implementation
+
+template <typename T, typename = void>
+struct is_constructible_patched : std::false_type {};
+
+template <typename T>
+struct is_constructible_patched<T, decltype(void(json(std::declval<T>())))> : std::true_type {};
+} // namespace
+
+TEST_CASE("an incomplete type does not trigger a compiler error in non-evaluated context" * doctest::test_suite("udt"))
+{
+ static_assert(!is_constructible_patched<json, incomplete>::value, "");
+}
+
+namespace
+{
+class Evil
+{
+ public:
+ Evil() = default;
+ template <typename T>
+ Evil(const T& t) : m_i(sizeof(t))
+ {
+ static_cast<void>(t); // fix MSVC's C4100 warning
+ }
+
+ int m_i = 0;
+};
+
+void from_json(const json& /*unused*/, Evil& /*unused*/) {}
+} // namespace
+
+TEST_CASE("Issue #924")
+{
+ // Prevent get<std::vector<Evil>>() to throw
+ auto j = json::array();
+
+ CHECK_NOTHROW(j.get<Evil>());
+ CHECK_NOTHROW(j.get<std::vector<Evil>>());
+
+ // silence Wunused-template warnings
+ const Evil e(1);
+ CHECK(e.m_i >= 0);
+
+ // suppress warning: function "<unnamed>::Evil::Evil(T) [with T=std::string]" was declared but never referenced [declared_but_not_referenced]
+ const Evil e2(std::string("foo"));
+ CHECK(e2.m_i >= 0);
+}
+
+TEST_CASE("Issue #1237")
+{
+ struct non_convertible_type {};
+ static_assert(!std::is_convertible<json, non_convertible_type>::value, "");
+}
+
+namespace
+{
+class no_iterator_type
+{
+ public:
+ no_iterator_type(std::initializer_list<int> l)
+ : _v(l)
+ {}
+
+ std::vector<int>::const_iterator begin() const
+ {
+ return _v.begin();
+ }
+
+ std::vector<int>::const_iterator end() const
+ {
+ return _v.end();
+ }
+
+ private:
+ std::vector<int> _v;
+};
+} // namespace
+
+TEST_CASE("compatible array type, without iterator type alias")
+{
+ no_iterator_type const vec{1, 2, 3};
+ json const j = vec;
+}
+
+DOCTEST_GCC_SUPPRESS_WARNING_POP
diff --git a/json4cpp/tests/src/unit-udt_macro.cpp b/json4cpp/tests/src/unit-udt_macro.cpp
new file mode 100644
index 0000000000..cd65134297
--- /dev/null
+++ b/json4cpp/tests/src/unit-udt_macro.cpp
@@ -0,0 +1,709 @@
+// __ _____ _____ _____
+// __| | __| | | | 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 <string>
+#include <vector>
+#include "doctest_compatibility.h"
+
+#include <nlohmann/json.hpp>
+using nlohmann::json;
+
+namespace persons
+{
+class person_with_private_data
+{
+ private:
+ std::string name{}; // NOLINT(readability-redundant-member-init)
+ int age = 0;
+ json metadata = nullptr;
+
+ public:
+ bool operator==(const person_with_private_data& rhs) const
+ {
+ return name == rhs.name && age == rhs.age && metadata == rhs.metadata;
+ }
+
+ person_with_private_data() = default;
+ person_with_private_data(std::string name_, int age_, json metadata_)
+ : name(std::move(name_))
+ , age(age_)
+ , metadata(std::move(metadata_))
+ {}
+
+ NLOHMANN_DEFINE_TYPE_INTRUSIVE(person_with_private_data, age, name, metadata)
+};
+
+class derived_person_with_private_data : public person_with_private_data
+{
+ private:
+ std::string hair_color{"blue"};
+
+ public:
+ bool operator==(const derived_person_with_private_data& rhs) const
+ {
+ return person_with_private_data::operator==(rhs) && hair_color == rhs.hair_color;
+ }
+
+ derived_person_with_private_data() = default;
+ derived_person_with_private_data(std::string name_, int age_, json metadata_, std::string hair_color_)
+ : person_with_private_data(std::move(name_), age_, std::move(metadata_))
+ , hair_color(std::move(hair_color_))
+ {}
+
+ NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(derived_person_with_private_data, person_with_private_data, hair_color)
+};
+
+class person_with_private_data_2
+{
+ private:
+ std::string name{}; // NOLINT(readability-redundant-member-init)
+ int age = 0;
+ json metadata = nullptr;
+
+ public:
+ bool operator==(const person_with_private_data_2& rhs) const
+ {
+ return name == rhs.name && age == rhs.age && metadata == rhs.metadata;
+ }
+
+ person_with_private_data_2() = default;
+ person_with_private_data_2(std::string name_, int age_, json metadata_)
+ : name(std::move(name_))
+ , age(age_)
+ , metadata(std::move(metadata_))
+ {}
+
+ std::string getName() const
+ {
+ return name;
+ }
+ int getAge() const
+ {
+ return age;
+ }
+ json getMetadata() const
+ {
+ return metadata;
+ }
+
+ NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(person_with_private_data_2, age, name, metadata)
+};
+
+class derived_person_with_private_data_2 : public person_with_private_data_2
+{
+ private:
+ std::string hair_color{"blue"};
+
+ public:
+ bool operator==(const derived_person_with_private_data_2& rhs) const
+ {
+ return person_with_private_data_2::operator==(rhs) && hair_color == rhs.hair_color;
+ }
+
+ derived_person_with_private_data_2() = default;
+ derived_person_with_private_data_2(std::string name_, int age_, json metadata_, std::string hair_color_)
+ : person_with_private_data_2(std::move(name_), age_, std::move(metadata_))
+ , hair_color(std::move(hair_color_))
+ {}
+
+ std::string getHairColor() const
+ {
+ return hair_color;
+ }
+
+ NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT(derived_person_with_private_data_2, person_with_private_data_2, hair_color)
+};
+
+class person_without_private_data_1
+{
+ public:
+ std::string name{}; // NOLINT(readability-redundant-member-init)
+ int age = 0;
+ json metadata = nullptr;
+
+ bool operator==(const person_without_private_data_1& rhs) const
+ {
+ return name == rhs.name && age == rhs.age && metadata == rhs.metadata;
+ }
+
+ person_without_private_data_1() = default;
+ person_without_private_data_1(std::string name_, int age_, json metadata_)
+ : name(std::move(name_))
+ , age(age_)
+ , metadata(std::move(metadata_))
+ {}
+
+ NLOHMANN_DEFINE_TYPE_INTRUSIVE(person_without_private_data_1, age, name, metadata)
+};
+
+class derived_person_without_private_data_1 : public person_without_private_data_1
+{
+ public:
+ std::string hair_color{"blue"};
+
+ public:
+ bool operator==(const derived_person_without_private_data_1& rhs) const
+ {
+ return person_without_private_data_1::operator==(rhs) && hair_color == rhs.hair_color;
+ }
+
+ derived_person_without_private_data_1() = default;
+ derived_person_without_private_data_1(std::string name_, int age_, json metadata_, std::string hair_color_)
+ : person_without_private_data_1(std::move(name_), age_, std::move(metadata_))
+ , hair_color(std::move(hair_color_))
+ {}
+
+ NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(derived_person_without_private_data_1, person_without_private_data_1, hair_color)
+};
+
+class person_without_private_data_2
+{
+ public:
+ std::string name{}; // NOLINT(readability-redundant-member-init)
+ int age = 0;
+ json metadata = nullptr;
+
+ bool operator==(const person_without_private_data_2& rhs) const
+ {
+ return name == rhs.name && age == rhs.age && metadata == rhs.metadata;
+ }
+
+ person_without_private_data_2() = default;
+ person_without_private_data_2(std::string name_, int age_, json metadata_)
+ : name(std::move(name_))
+ , age(age_)
+ , metadata(std::move(metadata_))
+ {}
+};
+
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(person_without_private_data_2, age, name, metadata)
+
+class derived_person_without_private_data_2 : public person_without_private_data_2
+{
+ public:
+ std::string hair_color{"blue"};
+
+ public:
+ bool operator==(const derived_person_without_private_data_2& rhs) const
+ {
+ return person_without_private_data_2::operator==(rhs) && hair_color == rhs.hair_color;
+ }
+
+ derived_person_without_private_data_2() = default;
+ derived_person_without_private_data_2(std::string name_, int age_, json metadata_, std::string hair_color_)
+ : person_without_private_data_2(std::move(name_), age_, std::move(metadata_))
+ , hair_color(std::move(hair_color_))
+ {}
+};
+
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(derived_person_without_private_data_2, person_without_private_data_2, hair_color)
+
+class person_without_private_data_3
+{
+ public:
+ std::string name{}; // NOLINT(readability-redundant-member-init)
+ int age = 0;
+ json metadata = nullptr;
+
+ bool operator==(const person_without_private_data_3& rhs) const
+ {
+ return name == rhs.name && age == rhs.age && metadata == rhs.metadata;
+ }
+
+ person_without_private_data_3() = default;
+ person_without_private_data_3(std::string name_, int age_, json metadata_)
+ : name(std::move(name_))
+ , age(age_)
+ , metadata(std::move(metadata_))
+ {}
+
+ std::string getName() const
+ {
+ return name;
+ }
+ int getAge() const
+ {
+ return age;
+ }
+ json getMetadata() const
+ {
+ return metadata;
+ }
+};
+
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(person_without_private_data_3, age, name, metadata)
+
+class derived_person_without_private_data_3 : public person_without_private_data_3
+{
+ public:
+ std::string hair_color{"blue"};
+
+ public:
+ bool operator==(const derived_person_without_private_data_3& rhs) const
+ {
+ return person_without_private_data_3::operator==(rhs) && hair_color == rhs.hair_color;
+ }
+
+ derived_person_without_private_data_3() = default;
+ derived_person_without_private_data_3(std::string name_, int age_, json metadata_, std::string hair_color_)
+ : person_without_private_data_3(std::move(name_), age_, std::move(metadata_))
+ , hair_color(std::move(hair_color_))
+ {}
+
+ std::string getHairColor() const
+ {
+ return hair_color;
+ }
+};
+
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT(derived_person_without_private_data_3, person_without_private_data_3, hair_color)
+
+class person_with_private_alphabet
+{
+ public:
+ bool operator==(const person_with_private_alphabet& other) const
+ {
+ return a == other.a &&
+ b == other.b &&
+ c == other.c &&
+ d == other.d &&
+ e == other.e &&
+ f == other.f &&
+ g == other.g &&
+ h == other.h &&
+ i == other.i &&
+ j == other.j &&
+ k == other.k &&
+ l == other.l &&
+ m == other.m &&
+ n == other.n &&
+ o == other.o &&
+ p == other.p &&
+ q == other.q &&
+ r == other.r &&
+ s == other.s &&
+ t == other.t &&
+ u == other.u &&
+ v == other.v &&
+ w == other.w &&
+ x == other.x &&
+ y == other.y &&
+ z == other.z;
+ }
+
+ private:
+ int a = 0;
+ int b = 0;
+ int c = 0;
+ int d = 0;
+ int e = 0;
+ int f = 0;
+ int g = 0;
+ int h = 0;
+ int i = 0;
+ int j = 0;
+ int k = 0;
+ int l = 0;
+ int m = 0;
+ int n = 0;
+ int o = 0;
+ int p = 0;
+ int q = 0;
+ int r = 0;
+ int s = 0;
+ int t = 0;
+ int u = 0;
+ int v = 0;
+ int w = 0;
+ int x = 0;
+ int y = 0;
+ int z = 0;
+ NLOHMANN_DEFINE_TYPE_INTRUSIVE(person_with_private_alphabet, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z)
+};
+
+class person_with_public_alphabet
+{
+ public:
+ bool operator==(const person_with_public_alphabet& other) const
+ {
+ return a == other.a &&
+ b == other.b &&
+ c == other.c &&
+ d == other.d &&
+ e == other.e &&
+ f == other.f &&
+ g == other.g &&
+ h == other.h &&
+ i == other.i &&
+ j == other.j &&
+ k == other.k &&
+ l == other.l &&
+ m == other.m &&
+ n == other.n &&
+ o == other.o &&
+ p == other.p &&
+ q == other.q &&
+ r == other.r &&
+ s == other.s &&
+ t == other.t &&
+ u == other.u &&
+ v == other.v &&
+ w == other.w &&
+ x == other.x &&
+ y == other.y &&
+ z == other.z;
+ }
+
+ int a = 0;
+ int b = 0;
+ int c = 0;
+ int d = 0;
+ int e = 0;
+ int f = 0;
+ int g = 0;
+ int h = 0;
+ int i = 0;
+ int j = 0;
+ int k = 0;
+ int l = 0;
+ int m = 0;
+ int n = 0;
+ int o = 0;
+ int p = 0;
+ int q = 0;
+ int r = 0;
+ int s = 0;
+ int t = 0;
+ int u = 0;
+ int v = 0;
+ int w = 0;
+ int x = 0;
+ int y = 0;
+ int z = 0;
+};
+
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(person_with_public_alphabet, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z)
+
+class person_without_default_constructor_1
+{
+ public:
+ std::string name;
+ int age;
+
+ bool operator==(const person_without_default_constructor_1& other) const
+ {
+ return name == other.name && age == other.age;
+ }
+
+ person_without_default_constructor_1(std::string name_, int age_)
+ : name{std::move(name_)}
+ , age{age_}
+ {}
+
+ NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(person_without_default_constructor_1, name, age)
+};
+
+class person_without_default_constructor_2
+{
+ public:
+ std::string name;
+ int age;
+
+ bool operator==(const person_without_default_constructor_2& other) const
+ {
+ return name == other.name && age == other.age;
+ }
+
+ person_without_default_constructor_2(std::string name_, int age_)
+ : name{std::move(name_)}
+ , age{age_}
+ {}
+};
+
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(person_without_default_constructor_2, name, age)
+
+class derived_person_only_serialize_public : public person_without_default_constructor_1
+{
+ public:
+ std::string hair_color;
+
+ derived_person_only_serialize_public(std::string name_, int age_, std::string hair_color_)
+ : person_without_default_constructor_1(std::move(name_), age_)
+ , hair_color(std::move(hair_color_))
+ {}
+};
+
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(derived_person_only_serialize_public, person_without_default_constructor_1, hair_color)
+
+class derived_person_only_serialize_private : person_without_default_constructor_1
+{
+ private:
+ std::string hair_color;
+ public:
+ derived_person_only_serialize_private(std::string name_, int age_, std::string hair_color_)
+ : person_without_default_constructor_1(std::move(name_), age_)
+ , hair_color(std::move(hair_color_))
+ {}
+
+ NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE(derived_person_only_serialize_private, person_without_default_constructor_1, hair_color)
+};
+
+} // namespace persons
+
+TEST_CASE_TEMPLATE("Serialization/deserialization via NLOHMANN_DEFINE_TYPE_INTRUSIVE and NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE", Pair, // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
+ std::pair<nlohmann::json, persons::person_with_private_data>,
+ std::pair<nlohmann::json, persons::person_without_private_data_1>,
+ std::pair<nlohmann::json, persons::person_without_private_data_2>,
+ std::pair<nlohmann::ordered_json, persons::person_with_private_data>,
+ std::pair<nlohmann::ordered_json, persons::person_without_private_data_1>,
+ std::pair<nlohmann::ordered_json, persons::person_without_private_data_2>)
+{
+ using Json = typename Pair::first_type;
+ using T = typename Pair::second_type;
+ constexpr bool is_ordered = std::is_same<Json, nlohmann::ordered_json>::value;
+
+ SECTION("person")
+ {
+ // serialization
+ T p1("Erik", 1, {{"haircuts", 2}});
+ CHECK(Json(p1).dump() == (is_ordered ?
+ R"({"age":1,"name":"Erik","metadata":{"haircuts":2}})" :
+ R"({"age":1,"metadata":{"haircuts":2},"name":"Erik"})"));
+
+ // deserialization
+ auto p2 = Json(p1).template get<T>();
+ CHECK(p2 == p1);
+
+ // roundtrip
+ CHECK(T(Json(p1)) == p1);
+ CHECK(Json(T(Json(p1))) == Json(p1));
+
+ // check exception in case of missing field
+ Json j = Json(p1);
+ j.erase("age");
+ CHECK_THROWS_WITH_AS(j.template get<T>(), "[json.exception.out_of_range.403] key 'age' not found", typename Json::out_of_range);
+ }
+}
+
+TEST_CASE_TEMPLATE("Serialization/deserialization via NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE and NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE", Pair, // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
+ std::pair<nlohmann::json, persons::derived_person_with_private_data>,
+ std::pair<nlohmann::json, persons::derived_person_without_private_data_1>,
+ std::pair<nlohmann::json, persons::derived_person_without_private_data_2>,
+ std::pair<nlohmann::ordered_json, persons::derived_person_with_private_data>,
+ std::pair<nlohmann::ordered_json, persons::derived_person_without_private_data_1>,
+ std::pair<nlohmann::ordered_json, persons::derived_person_without_private_data_2>)
+{
+ using Json = typename Pair::first_type;
+ using T = typename Pair::second_type;
+ constexpr bool is_ordered = std::is_same<Json, nlohmann::ordered_json>::value;
+
+ SECTION("person")
+ {
+ // serialization
+ T p1("Erik", 1, {{"haircuts", 2}}, "red");
+ CHECK(Json(p1).dump() == (is_ordered ?
+ R"({"age":1,"name":"Erik","metadata":{"haircuts":2},"hair_color":"red"})" :
+ R"({"age":1,"hair_color":"red","metadata":{"haircuts":2},"name":"Erik"})"));
+
+ // deserialization
+ auto p2 = Json(p1).template get<T>();
+ CHECK(p2 == p1);
+
+ // roundtrip
+ CHECK(T(Json(p1)) == p1);
+ CHECK(Json(T(Json(p1))) == Json(p1));
+
+ // check exception in case of missing field
+ Json j = Json(p1);
+ j.erase("age");
+ CHECK_THROWS_WITH_AS(j.template get<T>(), "[json.exception.out_of_range.403] key 'age' not found", typename Json::out_of_range);
+ }
+}
+
+TEST_CASE_TEMPLATE("Serialization/deserialization via NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT and NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT", Pair, // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
+ std::pair<nlohmann::json, persons::person_with_private_data_2>,
+ std::pair<nlohmann::json, persons::person_without_private_data_3>,
+ std::pair<nlohmann::ordered_json, persons::person_with_private_data_2>,
+ std::pair<nlohmann::ordered_json, persons::person_without_private_data_3>)
+{
+ using Json = typename Pair::first_type;
+ using T = typename Pair::second_type;
+ constexpr bool is_ordered = std::is_same<Json, nlohmann::ordered_json>::value;
+
+ SECTION("person with default values")
+ {
+ // serialization of default constructed object
+ const T p0{};
+ CHECK(Json(p0).dump() == (is_ordered ?
+ R"({"age":0,"name":"","metadata":null})" :
+ R"({"age":0,"metadata":null,"name":""})"));
+
+ // serialization
+ T p1("Erik", 1, {{"haircuts", 2}});
+ CHECK(Json(p1).dump() == (is_ordered ?
+ R"({"age":1,"name":"Erik","metadata":{"haircuts":2}})" :
+ R"({"age":1,"metadata":{"haircuts":2},"name":"Erik"})"));
+
+ // deserialization
+ auto p2 = Json(p1).template get<T>();
+ CHECK(p2 == p1);
+
+ // roundtrip
+ CHECK(T(Json(p1)) == p1);
+ CHECK(Json(T(Json(p1))) == Json(p1));
+
+ // check default value in case of missing field
+ Json j = Json(p1);
+ j.erase("name");
+ j.erase("age");
+ j.erase("metadata");
+ const T p3 = j.template get<T>();
+ CHECK(p3.getName() == "");
+ CHECK(p3.getAge() == 0);
+ CHECK(p3.getMetadata() == nullptr);
+
+ // check default value in case of empty json
+ const Json j4;
+ const T p4 = j4.template get<T>();
+ CHECK(p4.getName() == "");
+ CHECK(p4.getAge() == 0);
+ CHECK(p4.getMetadata() == nullptr);
+ }
+}
+
+TEST_CASE_TEMPLATE("Serialization/deserialization via NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT and NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT", Pair, // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
+ std::pair<nlohmann::json, persons::derived_person_with_private_data_2>,
+ std::pair<nlohmann::json, persons::derived_person_without_private_data_3>,
+ std::pair<nlohmann::ordered_json, persons::derived_person_with_private_data_2>,
+ std::pair<nlohmann::ordered_json, persons::derived_person_without_private_data_3>)
+{
+ using Json = typename Pair::first_type;
+ using T = typename Pair::second_type;
+ constexpr bool is_ordered = std::is_same<Json, nlohmann::ordered_json>::value;
+
+ SECTION("derived person with default values")
+ {
+ // serialization of default constructed object
+ const T p0{};
+ CHECK(Json(p0).dump() == (is_ordered ?
+ R"({"age":0,"name":"","metadata":null,"hair_color":"blue"})" :
+ R"({"age":0,"hair_color":"blue","metadata":null,"name":""})"));
+
+ // serialization
+ T p1("Erik", 1, {{"haircuts", 2}}, "red");
+ CHECK(Json(p1).dump() == (is_ordered ?
+ R"({"age":1,"name":"Erik","metadata":{"haircuts":2},"hair_color":"red"})" :
+ R"({"age":1,"hair_color":"red","metadata":{"haircuts":2},"name":"Erik"})"));
+
+ // deserialization
+ auto p2 = Json(p1).template get<T>();
+ CHECK(p2 == p1);
+
+ // roundtrip
+ CHECK(T(Json(p1)) == p1);
+ CHECK(Json(T(Json(p1))) == Json(p1));
+
+ // check default value in case of missing field
+ Json j = Json(p1);
+ j.erase("name");
+ j.erase("age");
+ j.erase("metadata");
+ j.erase("hair_color");
+ const T p3 = j.template get<T>();
+ CHECK(p3.getName() == "");
+ CHECK(p3.getAge() == 0);
+ CHECK(p3.getMetadata() == nullptr);
+ CHECK(p3.getHairColor() == "blue");
+ }
+}
+
+TEST_CASE_TEMPLATE("Serialization/deserialization of classes with 26 public/private member variables via NLOHMANN_DEFINE_TYPE_INTRUSIVE and NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE", Pair, // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
+ std::pair<nlohmann::json, persons::person_with_private_alphabet>,
+ std::pair<nlohmann::json, persons::person_with_public_alphabet>,
+ std::pair<nlohmann::ordered_json, persons::person_with_private_alphabet>,
+ std::pair<nlohmann::ordered_json, persons::person_with_public_alphabet>)
+{
+ using Json = typename Pair::first_type;
+ using T = typename Pair::second_type;
+
+ SECTION("alphabet")
+ {
+ T obj1; // NOLINT(misc-const-correctness)
+ Json const j = obj1;
+ T obj2;
+ j.get_to(obj2);
+ CHECK(obj1 == obj2);
+ }
+}
+
+TEST_CASE_TEMPLATE("Serialization of non-default-constructible classes via NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE and NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE", Pair, // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
+ std::pair<nlohmann::json, persons::person_without_default_constructor_1>,
+ std::pair<nlohmann::json, persons::person_without_default_constructor_2>,
+ std::pair<nlohmann::ordered_json, persons::person_without_default_constructor_1>,
+ std::pair<nlohmann::ordered_json, persons::person_without_default_constructor_2>)
+{
+ using Json = typename Pair::first_type;
+ using T = typename Pair::second_type;
+ constexpr bool is_ordered = std::is_same<Json, nlohmann::ordered_json>::value;
+
+ SECTION("person")
+ {
+ // serialization of a single object
+ const T person{"Erik", 1};
+ CHECK(Json(person).dump() == (is_ordered ?
+ R"({"name":"Erik","age":1})" :
+ R"({"age":1,"name":"Erik"})"));
+
+ // serialization of a container with objects
+ std::vector<T> const two_persons
+ {
+ {"Erik", 1},
+ {"Kyle", 2}
+ };
+ CHECK(Json(two_persons).dump() == (is_ordered ?
+ R"([{"name":"Erik","age":1},{"name":"Kyle","age":2}])" :
+ R"([{"age":1,"name":"Erik"},{"age":2,"name":"Kyle"}])"));
+ }
+}
+
+TEST_CASE_TEMPLATE("Serialization of non-default-constructible classes via NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE and NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE", Pair, // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
+ std::pair<nlohmann::json, persons::derived_person_only_serialize_public>,
+ std::pair<nlohmann::json, persons::derived_person_only_serialize_private>,
+ std::pair<nlohmann::ordered_json, persons::derived_person_only_serialize_public>,
+ std::pair<nlohmann::ordered_json, persons::derived_person_only_serialize_private>)
+{
+ using Json = typename Pair::first_type;
+ using T = typename Pair::second_type;
+ constexpr bool is_ordered = std::is_same<Json, nlohmann::ordered_json>::value;
+
+ SECTION("derived person only serialize")
+ {
+ // serialization of a single object
+ const T person{"Erik", 1, "brown"};
+ CHECK(Json(person).dump() == (is_ordered ?
+ R"({"name":"Erik","age":1,"hair_color":"brown"})" :
+ R"({"age":1,"hair_color":"brown","name":"Erik"})"));
+
+ // serialization of a container with objects
+ std::vector<T> const two_persons
+ {
+ {"Erik", 1, "brown"},
+ {"Kyle", 2, "black"}
+ };
+ CHECK(Json(two_persons).dump() == (is_ordered ?
+ R"([{"name":"Erik","age":1,"hair_color":"brown"},{"name":"Kyle","age":2,"hair_color":"black"}])" :
+ R"([{"age":1,"hair_color":"brown","name":"Erik"},{"age":2,"hair_color":"black","name":"Kyle"}])"));
+ }
+}
diff --git a/json4cpp/tests/src/unit-unicode1.cpp b/json4cpp/tests/src/unit-unicode1.cpp
new file mode 100644
index 0000000000..174ce13955
--- /dev/null
+++ b/json4cpp/tests/src/unit-unicode1.cpp
@@ -0,0 +1,620 @@
+// __ _____ _____ _____
+// __| | __| | | | 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"
+
+// for some reason including this after the json header leads to linker errors with VS 2017...
+#include <locale>
+#include <nlohmann/json.hpp>
+using nlohmann::json;
+
+#include <fstream>
+#include <sstream>
+#include <iomanip>
+#include "make_test_data_available.hpp"
+
+TEST_CASE("Unicode (1/5)" * doctest::skip())
+{
+ SECTION("\\uxxxx sequences")
+ {
+ // create an escaped string from a code point
+ const auto codepoint_to_unicode = [](std::size_t cp)
+ {
+ // code points are represented as a six-character sequence: a
+ // reverse solidus, followed by the lowercase letter u, followed
+ // by four hexadecimal digits that encode the character's code
+ // point
+ std::stringstream ss;
+ ss << "\\u" << std::setw(4) << std::setfill('0') << std::hex << cp;
+ return ss.str();
+ };
+
+ SECTION("correct sequences")
+ {
+ // generate all UTF-8 code points; in total, 1112064 code points are
+ // generated: 0x1FFFFF code points - 2048 invalid values between
+ // 0xD800 and 0xDFFF.
+ for (std::size_t cp = 0; cp <= 0x10FFFFu; ++cp)
+ {
+ // string to store the code point as in \uxxxx format
+ std::string json_text = "\"";
+
+ // decide whether to use one or two \uxxxx sequences
+ if (cp < 0x10000u)
+ {
+ // The Unicode standard permanently reserves these code point
+ // values for UTF-16 encoding of the high and low surrogates, and
+ // they will never be assigned a character, so there should be no
+ // reason to encode them. The official Unicode standard says that
+ // no UTF forms, including UTF-16, can encode these code points.
+ if (cp >= 0xD800u && cp <= 0xDFFFu)
+ {
+ // if we would not skip these code points, we would get a
+ // "missing low surrogate" exception
+ continue;
+ }
+
+ // code points in the Basic Multilingual Plane can be
+ // represented with one \uxxxx sequence
+ json_text += codepoint_to_unicode(cp);
+ }
+ else
+ {
+ // To escape an extended character that is not in the Basic
+ // Multilingual Plane, the character is represented as a
+ // 12-character sequence, encoding the UTF-16 surrogate pair
+ const auto codepoint1 = 0xd800u + (((cp - 0x10000u) >> 10) & 0x3ffu);
+ const auto codepoint2 = 0xdc00u + ((cp - 0x10000u) & 0x3ffu);
+ json_text += codepoint_to_unicode(codepoint1) + codepoint_to_unicode(codepoint2);
+ }
+
+ json_text += "\"";
+ CAPTURE(json_text)
+ json _;
+ CHECK_NOTHROW(_ = json::parse(json_text));
+ }
+ }
+
+ SECTION("incorrect sequences")
+ {
+ SECTION("incorrect surrogate values")
+ {
+ json _;
+
+ CHECK_THROWS_WITH_AS(_ = json::parse("\"\\uDC00\\uDC00\""), "[json.exception.parse_error.101] parse error at line 1, column 7: syntax error while parsing value - invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF; last read: '\"\\uDC00'", json::parse_error&);
+
+ CHECK_THROWS_WITH_AS(_ = json::parse("\"\\uD7FF\\uDC00\""), "[json.exception.parse_error.101] parse error at line 1, column 13: syntax error while parsing value - invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF; last read: '\"\\uD7FF\\uDC00'", json::parse_error&);
+
+ CHECK_THROWS_WITH_AS(_ = json::parse("\"\\uD800]\""), "[json.exception.parse_error.101] parse error at line 1, column 8: syntax error while parsing value - invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF; last read: '\"\\uD800]'", json::parse_error&);
+
+ CHECK_THROWS_WITH_AS(_ = json::parse("\"\\uD800\\v\""), "[json.exception.parse_error.101] parse error at line 1, column 9: syntax error while parsing value - invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF; last read: '\"\\uD800\\v'", json::parse_error&);
+
+ CHECK_THROWS_WITH_AS(_ = json::parse("\"\\uD800\\u123\""), "[json.exception.parse_error.101] parse error at line 1, column 13: syntax error while parsing value - invalid string: '\\u' must be followed by 4 hex digits; last read: '\"\\uD800\\u123\"'", json::parse_error&);
+
+ CHECK_THROWS_WITH_AS(_ = json::parse("\"\\uD800\\uDBFF\""), "[json.exception.parse_error.101] parse error at line 1, column 13: syntax error while parsing value - invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF; last read: '\"\\uD800\\uDBFF'", json::parse_error&);
+
+ CHECK_THROWS_WITH_AS(_ = json::parse("\"\\uD800\\uE000\""), "[json.exception.parse_error.101] parse error at line 1, column 13: syntax error while parsing value - invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF; last read: '\"\\uD800\\uE000'", json::parse_error&);
+ }
+ }
+
+#if 0 // NOLINT(readability-avoid-unconditional-preprocessor-if)
+ SECTION("incorrect sequences")
+ {
+ SECTION("high surrogate without low surrogate")
+ {
+ // D800..DBFF are high surrogates and must be followed by low
+ // surrogates DC00..DFFF; here, nothing follows
+ for (std::size_t cp = 0xD800u; cp <= 0xDBFFu; ++cp)
+ {
+ std::string json_text = "\"" + codepoint_to_unicode(cp) + "\"";
+ CAPTURE(json_text)
+ CHECK_THROWS_AS(json::parse(json_text), json::parse_error&);
+ }
+ }
+
+ SECTION("high surrogate with wrong low surrogate")
+ {
+ // D800..DBFF are high surrogates and must be followed by low
+ // surrogates DC00..DFFF; here a different sequence follows
+ for (std::size_t cp1 = 0xD800u; cp1 <= 0xDBFFu; ++cp1)
+ {
+ for (std::size_t cp2 = 0x0000u; cp2 <= 0xFFFFu; ++cp2)
+ {
+ if (0xDC00u <= cp2 && cp2 <= 0xDFFFu)
+ {
+ continue;
+ }
+
+ std::string json_text = "\"" + codepoint_to_unicode(cp1) + codepoint_to_unicode(cp2) + "\"";
+ CAPTURE(json_text)
+ CHECK_THROWS_AS(json::parse(json_text), json::parse_error&);
+ }
+ }
+ }
+
+ SECTION("low surrogate without high surrogate")
+ {
+ // low surrogates DC00..DFFF must follow high surrogates; here,
+ // they occur alone
+ for (std::size_t cp = 0xDC00u; cp <= 0xDFFFu; ++cp)
+ {
+ std::string json_text = "\"" + codepoint_to_unicode(cp) + "\"";
+ CAPTURE(json_text)
+ CHECK_THROWS_AS(json::parse(json_text), json::parse_error&);
+ }
+ }
+
+ }
+#endif
+ }
+
+ SECTION("read all unicode characters")
+ {
+ // read a file with all Unicode characters stored as single-character
+ // strings in a JSON array
+ std::ifstream f(TEST_DATA_DIRECTORY "/json_nlohmann_tests/all_unicode.json");
+ json j;
+ CHECK_NOTHROW(f >> j);
+
+ // the array has 1112064 + 1 elements (a terminating "null" value)
+ // Note: 1112064 = 0x1FFFFF code points - 2048 invalid values between
+ // 0xD800 and 0xDFFF.
+ CHECK(j.size() == 1112065);
+
+ SECTION("check JSON Pointers")
+ {
+ for (const auto& s : j)
+ {
+ // skip non-string JSON values
+ if (!s.is_string())
+ {
+ continue;
+ }
+
+ auto ptr = s.get<std::string>();
+
+ // tilde must be followed by 0 or 1
+ if (ptr == "~")
+ {
+ ptr += "0";
+ }
+
+ // JSON Pointers must begin with "/"
+ ptr.insert(0, "/");
+
+ CHECK_NOTHROW(json::json_pointer("/" + ptr));
+
+ // check escape/unescape roundtrip
+ auto escaped = nlohmann::detail::escape(ptr);
+ nlohmann::detail::unescape(escaped);
+ CHECK(escaped == ptr);
+ }
+ }
+ }
+
+ SECTION("ignore byte-order-mark")
+ {
+ SECTION("in a stream")
+ {
+ // read a file with a UTF-8 BOM
+ std::ifstream f(TEST_DATA_DIRECTORY "/json_nlohmann_tests/bom.json");
+ json j;
+ CHECK_NOTHROW(f >> j);
+ }
+
+ SECTION("with an iterator")
+ {
+ std::string i = "\xef\xbb\xbf{\n \"foo\": true\n}";
+ json _;
+ CHECK_NOTHROW(_ = json::parse(i.begin(), i.end()));
+ }
+ }
+
+ SECTION("error for incomplete/wrong BOM")
+ {
+ json _;
+ CHECK_THROWS_AS(_ = json::parse("\xef\xbb"), json::parse_error&);
+ CHECK_THROWS_AS(_ = json::parse("\xef\xbb\xbb"), json::parse_error&);
+ }
+}
+
+namespace
+{
+void roundtrip(bool success_expected, const std::string& s);
+
+void roundtrip(bool success_expected, const std::string& s)
+{
+ CAPTURE(s)
+ json _;
+
+ // create JSON string value
+ const json j = s;
+ // create JSON text
+ const std::string ps = std::string("\"") + s + "\"";
+
+ if (success_expected)
+ {
+ // serialization succeeds
+ CHECK_NOTHROW(j.dump());
+
+ // exclude parse test for U+0000
+ if (s[0] != '\0')
+ {
+ // parsing JSON text succeeds
+ CHECK_NOTHROW(_ = json::parse(ps));
+ }
+
+ // roundtrip succeeds
+ CHECK_NOTHROW(_ = json::parse(j.dump()));
+
+ // after roundtrip, the same string is stored
+ const json jr = json::parse(j.dump());
+ CHECK(jr.get<std::string>() == s);
+ }
+ else
+ {
+ // serialization fails
+ CHECK_THROWS_AS(j.dump(), json::type_error&);
+
+ // parsing JSON text fails
+ CHECK_THROWS_AS(_ = json::parse(ps), json::parse_error&);
+ }
+}
+} // namespace
+
+TEST_CASE("Markus Kuhn's UTF-8 decoder capability and stress test")
+{
+ // Markus Kuhn <http://www.cl.cam.ac.uk/~mgk25/> - 2015-08-28 - CC BY 4.0
+ // http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt
+
+ SECTION("1 Some correct UTF-8 text")
+ {
+ roundtrip(true, "κόσμε");
+ }
+
+ SECTION("2 Boundary condition test cases")
+ {
+ SECTION("2.1 First possible sequence of a certain length")
+ {
+ // 2.1.1 1 byte (U-00000000)
+ roundtrip(true, std::string("\0", 1));
+ // 2.1.2 2 bytes (U-00000080)
+ roundtrip(true, "\xc2\x80");
+ // 2.1.3 3 bytes (U-00000800)
+ roundtrip(true, "\xe0\xa0\x80");
+ // 2.1.4 4 bytes (U-00010000)
+ roundtrip(true, "\xf0\x90\x80\x80");
+
+ // 2.1.5 5 bytes (U-00200000)
+ roundtrip(false, "\xF8\x88\x80\x80\x80");
+ // 2.1.6 6 bytes (U-04000000)
+ roundtrip(false, "\xFC\x84\x80\x80\x80\x80");
+ }
+
+ SECTION("2.2 Last possible sequence of a certain length")
+ {
+ // 2.2.1 1 byte (U-0000007F)
+ roundtrip(true, "\x7f");
+ // 2.2.2 2 bytes (U-000007FF)
+ roundtrip(true, "\xdf\xbf");
+ // 2.2.3 3 bytes (U-0000FFFF)
+ roundtrip(true, "\xef\xbf\xbf");
+
+ // 2.2.4 4 bytes (U-001FFFFF)
+ roundtrip(false, "\xF7\xBF\xBF\xBF");
+ // 2.2.5 5 bytes (U-03FFFFFF)
+ roundtrip(false, "\xFB\xBF\xBF\xBF\xBF");
+ // 2.2.6 6 bytes (U-7FFFFFFF)
+ roundtrip(false, "\xFD\xBF\xBF\xBF\xBF\xBF");
+ }
+
+ SECTION("2.3 Other boundary conditions")
+ {
+ // 2.3.1 U-0000D7FF = ed 9f bf
+ roundtrip(true, "\xed\x9f\xbf");
+ // 2.3.2 U-0000E000 = ee 80 80
+ roundtrip(true, "\xee\x80\x80");
+ // 2.3.3 U-0000FFFD = ef bf bd
+ roundtrip(true, "\xef\xbf\xbd");
+ // 2.3.4 U-0010FFFF = f4 8f bf bf
+ roundtrip(true, "\xf4\x8f\xbf\xbf");
+
+ // 2.3.5 U-00110000 = f4 90 80 80
+ roundtrip(false, "\xf4\x90\x80\x80");
+ }
+ }
+
+ SECTION("3 Malformed sequences")
+ {
+ SECTION("3.1 Unexpected continuation bytes")
+ {
+ // Each unexpected continuation byte should be separately signalled as a
+ // malformed sequence of its own.
+
+ // 3.1.1 First continuation byte 0x80
+ roundtrip(false, "\x80");
+ // 3.1.2 Last continuation byte 0xbf
+ roundtrip(false, "\xbf");
+
+ // 3.1.3 2 continuation bytes
+ roundtrip(false, "\x80\xbf");
+ // 3.1.4 3 continuation bytes
+ roundtrip(false, "\x80\xbf\x80");
+ // 3.1.5 4 continuation bytes
+ roundtrip(false, "\x80\xbf\x80\xbf");
+ // 3.1.6 5 continuation bytes
+ roundtrip(false, "\x80\xbf\x80\xbf\x80");
+ // 3.1.7 6 continuation bytes
+ roundtrip(false, "\x80\xbf\x80\xbf\x80\xbf");
+ // 3.1.8 7 continuation bytes
+ roundtrip(false, "\x80\xbf\x80\xbf\x80\xbf\x80");
+
+ // 3.1.9 Sequence of all 64 possible continuation bytes (0x80-0xbf)
+ roundtrip(false, "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf");
+ }
+
+ SECTION("3.2 Lonely start characters")
+ {
+ // 3.2.1 All 32 first bytes of 2-byte sequences (0xc0-0xdf)
+ roundtrip(false, "\xc0 \xc1 \xc2 \xc3 \xc4 \xc5 \xc6 \xc7 \xc8 \xc9 \xca \xcb \xcc \xcd \xce \xcf \xd0 \xd1 \xd2 \xd3 \xd4 \xd5 \xd6 \xd7 \xd8 \xd9 \xda \xdb \xdc \xdd \xde \xdf");
+ // 3.2.2 All 16 first bytes of 3-byte sequences (0xe0-0xef)
+ roundtrip(false, "\xe0 \xe1 \xe2 \xe3 \xe4 \xe5 \xe6 \xe7 \xe8 \xe9 \xea \xeb \xec \xed \xee \xef");
+ // 3.2.3 All 8 first bytes of 4-byte sequences (0xf0-0xf7)
+ roundtrip(false, "\xf0 \xf1 \xf2 \xf3 \xf4 \xf5 \xf6 \xf7");
+ // 3.2.4 All 4 first bytes of 5-byte sequences (0xf8-0xfb)
+ roundtrip(false, "\xf8 \xf9 \xfa \xfb");
+ // 3.2.5 All 2 first bytes of 6-byte sequences (0xfc-0xfd)
+ roundtrip(false, "\xfc \xfd");
+ }
+
+ SECTION("3.3 Sequences with last continuation byte missing")
+ {
+ // All bytes of an incomplete sequence should be signalled as a single
+ // malformed sequence, i.e., you should see only a single replacement
+ // character in each of the next 10 tests. (Characters as in section 2)
+
+ // 3.3.1 2-byte sequence with last byte missing (U+0000)
+ roundtrip(false, "\xc0");
+ // 3.3.2 3-byte sequence with last byte missing (U+0000)
+ roundtrip(false, "\xe0\x80");
+ // 3.3.3 4-byte sequence with last byte missing (U+0000)
+ roundtrip(false, "\xf0\x80\x80");
+ // 3.3.4 5-byte sequence with last byte missing (U+0000)
+ roundtrip(false, "\xf8\x80\x80\x80");
+ // 3.3.5 6-byte sequence with last byte missing (U+0000)
+ roundtrip(false, "\xfc\x80\x80\x80\x80");
+ // 3.3.6 2-byte sequence with last byte missing (U-000007FF)
+ roundtrip(false, "\xdf");
+ // 3.3.7 3-byte sequence with last byte missing (U-0000FFFF)
+ roundtrip(false, "\xef\xbf");
+ // 3.3.8 4-byte sequence with last byte missing (U-001FFFFF)
+ roundtrip(false, "\xf7\xbf\xbf");
+ // 3.3.9 5-byte sequence with last byte missing (U-03FFFFFF)
+ roundtrip(false, "\xfb\xbf\xbf\xbf");
+ // 3.3.10 6-byte sequence with last byte missing (U-7FFFFFFF)
+ roundtrip(false, "\xfd\xbf\xbf\xbf\xbf");
+ }
+
+ SECTION("3.4 Concatenation of incomplete sequences")
+ {
+ // All the 10 sequences of 3.3 concatenated, you should see 10 malformed
+ // sequences being signalled:
+ roundtrip(false, "\xc0\xe0\x80\xf0\x80\x80\xf8\x80\x80\x80\xfc\x80\x80\x80\x80\xdf\xef\xbf\xf7\xbf\xbf\xfb\xbf\xbf\xbf\xfd\xbf\xbf\xbf\xbf");
+ }
+
+ SECTION("3.5 Impossible bytes")
+ {
+ // The following two bytes cannot appear in a correct UTF-8 string
+
+ // 3.5.1 fe
+ roundtrip(false, "\xfe");
+ // 3.5.2 ff
+ roundtrip(false, "\xff");
+ // 3.5.3 fe fe ff ff
+ roundtrip(false, "\xfe\xfe\xff\xff");
+ }
+ }
+
+ SECTION("4 Overlong sequences")
+ {
+ // The following sequences are not malformed according to the letter of
+ // the Unicode 2.0 standard. However, they are longer then necessary and
+ // a correct UTF-8 encoder is not allowed to produce them. A "safe UTF-8
+ // decoder" should reject them just like malformed sequences for two
+ // reasons: (1) It helps to debug applications if overlong sequences are
+ // not treated as valid representations of characters, because this helps
+ // to spot problems more quickly. (2) Overlong sequences provide
+ // alternative representations of characters, that could maliciously be
+ // used to bypass filters that check only for ASCII characters. For
+ // instance, a 2-byte encoded line feed (LF) would not be caught by a
+ // line counter that counts only 0x0a bytes, but it would still be
+ // processed as a line feed by an unsafe UTF-8 decoder later in the
+ // pipeline. From a security point of view, ASCII compatibility of UTF-8
+ // sequences means also, that ASCII characters are *only* allowed to be
+ // represented by ASCII bytes in the range 0x00-0x7f. To ensure this
+ // aspect of ASCII compatibility, use only "safe UTF-8 decoders" that
+ // reject overlong UTF-8 sequences for which a shorter encoding exists.
+
+ SECTION("4.1 Examples of an overlong ASCII character")
+ {
+ // With a safe UTF-8 decoder, all the following five overlong
+ // representations of the ASCII character slash ("/") should be rejected
+ // like a malformed UTF-8 sequence, for instance by substituting it with
+ // a replacement character. If you see a slash below, you do not have a
+ // safe UTF-8 decoder!
+
+ // 4.1.1 U+002F = c0 af
+ roundtrip(false, "\xc0\xaf");
+ // 4.1.2 U+002F = e0 80 af
+ roundtrip(false, "\xe0\x80\xaf");
+ // 4.1.3 U+002F = f0 80 80 af
+ roundtrip(false, "\xf0\x80\x80\xaf");
+ // 4.1.4 U+002F = f8 80 80 80 af
+ roundtrip(false, "\xf8\x80\x80\x80\xaf");
+ // 4.1.5 U+002F = fc 80 80 80 80 af
+ roundtrip(false, "\xfc\x80\x80\x80\x80\xaf");
+ }
+
+ SECTION("4.2 Maximum overlong sequences")
+ {
+ // Below you see the highest Unicode value that is still resulting in an
+ // overlong sequence if represented with the given number of bytes. This
+ // is a boundary test for safe UTF-8 decoders. All five characters should
+ // be rejected like malformed UTF-8 sequences.
+
+ // 4.2.1 U-0000007F = c1 bf
+ roundtrip(false, "\xc1\xbf");
+ // 4.2.2 U-000007FF = e0 9f bf
+ roundtrip(false, "\xe0\x9f\xbf");
+ // 4.2.3 U-0000FFFF = f0 8f bf bf
+ roundtrip(false, "\xf0\x8f\xbf\xbf");
+ // 4.2.4 U-001FFFFF = f8 87 bf bf bf
+ roundtrip(false, "\xf8\x87\xbf\xbf\xbf");
+ // 4.2.5 U-03FFFFFF = fc 83 bf bf bf bf
+ roundtrip(false, "\xfc\x83\xbf\xbf\xbf\xbf");
+ }
+
+ SECTION("4.3 Overlong representation of the NUL character")
+ {
+ // The following five sequences should also be rejected like malformed
+ // UTF-8 sequences and should not be treated like the ASCII NUL
+ // character.
+
+ // 4.3.1 U+0000 = c0 80
+ roundtrip(false, "\xc0\x80");
+ // 4.3.2 U+0000 = e0 80 80
+ roundtrip(false, "\xe0\x80\x80");
+ // 4.3.3 U+0000 = f0 80 80 80
+ roundtrip(false, "\xf0\x80\x80\x80");
+ // 4.3.4 U+0000 = f8 80 80 80 80
+ roundtrip(false, "\xf8\x80\x80\x80\x80");
+ // 4.3.5 U+0000 = fc 80 80 80 80 80
+ roundtrip(false, "\xfc\x80\x80\x80\x80\x80");
+ }
+ }
+
+ SECTION("5 Illegal code positions")
+ {
+ // The following UTF-8 sequences should be rejected like malformed
+ // sequences, because they never represent valid ISO 10646 characters and
+ // a UTF-8 decoder that accepts them might introduce security problems
+ // comparable to overlong UTF-8 sequences.
+
+ SECTION("5.1 Single UTF-16 surrogates")
+ {
+ // 5.1.1 U+D800 = ed a0 80
+ roundtrip(false, "\xed\xa0\x80");
+ // 5.1.2 U+DB7F = ed ad bf
+ roundtrip(false, "\xed\xad\xbf");
+ // 5.1.3 U+DB80 = ed ae 80
+ roundtrip(false, "\xed\xae\x80");
+ // 5.1.4 U+DBFF = ed af bf
+ roundtrip(false, "\xed\xaf\xbf");
+ // 5.1.5 U+DC00 = ed b0 80
+ roundtrip(false, "\xed\xb0\x80");
+ // 5.1.6 U+DF80 = ed be 80
+ roundtrip(false, "\xed\xbe\x80");
+ // 5.1.7 U+DFFF = ed bf bf
+ roundtrip(false, "\xed\xbf\xbf");
+ }
+
+ SECTION("5.2 Paired UTF-16 surrogates")
+ {
+ // 5.2.1 U+D800 U+DC00 = ed a0 80 ed b0 80
+ roundtrip(false, "\xed\xa0\x80\xed\xb0\x80");
+ // 5.2.2 U+D800 U+DFFF = ed a0 80 ed bf bf
+ roundtrip(false, "\xed\xa0\x80\xed\xbf\xbf");
+ // 5.2.3 U+DB7F U+DC00 = ed ad bf ed b0 80
+ roundtrip(false, "\xed\xad\xbf\xed\xb0\x80");
+ // 5.2.4 U+DB7F U+DFFF = ed ad bf ed bf bf
+ roundtrip(false, "\xed\xad\xbf\xed\xbf\xbf");
+ // 5.2.5 U+DB80 U+DC00 = ed ae 80 ed b0 80
+ roundtrip(false, "\xed\xae\x80\xed\xb0\x80");
+ // 5.2.6 U+DB80 U+DFFF = ed ae 80 ed bf bf
+ roundtrip(false, "\xed\xae\x80\xed\xbf\xbf");
+ // 5.2.7 U+DBFF U+DC00 = ed af bf ed b0 80
+ roundtrip(false, "\xed\xaf\xbf\xed\xb0\x80");
+ // 5.2.8 U+DBFF U+DFFF = ed af bf ed bf bf
+ roundtrip(false, "\xed\xaf\xbf\xed\xbf\xbf");
+ }
+
+ SECTION("5.3 Noncharacter code positions")
+ {
+ // The following "noncharacters" are "reserved for internal use" by
+ // applications, and according to older versions of the Unicode Standard
+ // "should never be interchanged". Unicode Corrigendum #9 dropped the
+ // latter restriction. Nevertheless, their presence in incoming UTF-8 data
+ // can remain a potential security risk, depending on what use is made of
+ // these codes subsequently. Examples of such internal use:
+ //
+ // - Some file APIs with 16-bit characters may use the integer value -1
+ // = U+FFFF to signal an end-of-file (EOF) or error condition.
+ //
+ // - In some UTF-16 receivers, code point U+FFFE might trigger a
+ // byte-swap operation (to convert between UTF-16LE and UTF-16BE).
+ //
+ // With such internal use of noncharacters, it may be desirable and safer
+ // to block those code points in UTF-8 decoders, as they should never
+ // occur legitimately in incoming UTF-8 data, and could trigger unsafe
+ // behaviour in subsequent processing.
+
+ // Particularly problematic noncharacters in 16-bit applications:
+
+ // 5.3.1 U+FFFE = ef bf be
+ roundtrip(true, "\xef\xbf\xbe");
+ // 5.3.2 U+FFFF = ef bf bf
+ roundtrip(true, "\xef\xbf\xbf");
+
+ // 5.3.3 U+FDD0 .. U+FDEF
+ roundtrip(true, "\xEF\xB7\x90");
+ roundtrip(true, "\xEF\xB7\x91");
+ roundtrip(true, "\xEF\xB7\x92");
+ roundtrip(true, "\xEF\xB7\x93");
+ roundtrip(true, "\xEF\xB7\x94");
+ roundtrip(true, "\xEF\xB7\x95");
+ roundtrip(true, "\xEF\xB7\x96");
+ roundtrip(true, "\xEF\xB7\x97");
+ roundtrip(true, "\xEF\xB7\x98");
+ roundtrip(true, "\xEF\xB7\x99");
+ roundtrip(true, "\xEF\xB7\x9A");
+ roundtrip(true, "\xEF\xB7\x9B");
+ roundtrip(true, "\xEF\xB7\x9C");
+ roundtrip(true, "\xEF\xB7\x9D");
+ roundtrip(true, "\xEF\xB7\x9E");
+ roundtrip(true, "\xEF\xB7\x9F");
+ roundtrip(true, "\xEF\xB7\xA0");
+ roundtrip(true, "\xEF\xB7\xA1");
+ roundtrip(true, "\xEF\xB7\xA2");
+ roundtrip(true, "\xEF\xB7\xA3");
+ roundtrip(true, "\xEF\xB7\xA4");
+ roundtrip(true, "\xEF\xB7\xA5");
+ roundtrip(true, "\xEF\xB7\xA6");
+ roundtrip(true, "\xEF\xB7\xA7");
+ roundtrip(true, "\xEF\xB7\xA8");
+ roundtrip(true, "\xEF\xB7\xA9");
+ roundtrip(true, "\xEF\xB7\xAA");
+ roundtrip(true, "\xEF\xB7\xAB");
+ roundtrip(true, "\xEF\xB7\xAC");
+ roundtrip(true, "\xEF\xB7\xAD");
+ roundtrip(true, "\xEF\xB7\xAE");
+ roundtrip(true, "\xEF\xB7\xAF");
+
+ // 5.3.4 U+nFFFE U+nFFFF (for n = 1..10)
+ roundtrip(true, "\xF0\x9F\xBF\xBF");
+ roundtrip(true, "\xF0\xAF\xBF\xBF");
+ roundtrip(true, "\xF0\xBF\xBF\xBF");
+ roundtrip(true, "\xF1\x8F\xBF\xBF");
+ roundtrip(true, "\xF1\x9F\xBF\xBF");
+ roundtrip(true, "\xF1\xAF\xBF\xBF");
+ roundtrip(true, "\xF1\xBF\xBF\xBF");
+ roundtrip(true, "\xF2\x8F\xBF\xBF");
+ roundtrip(true, "\xF2\x9F\xBF\xBF");
+ roundtrip(true, "\xF2\xAF\xBF\xBF");
+ }
+ }
+}
diff --git a/json4cpp/tests/src/unit-unicode2.cpp b/json4cpp/tests/src/unit-unicode2.cpp
new file mode 100644
index 0000000000..fb68815ba7
--- /dev/null
+++ b/json4cpp/tests/src/unit-unicode2.cpp
@@ -0,0 +1,610 @@
+// __ _____ _____ _____
+// __| | __| | | | 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"
+
+// for some reason including this after the json header leads to linker errors with VS 2017...
+#include <locale>
+
+#include <nlohmann/json.hpp>
+using nlohmann::json;
+
+#include <fstream>
+#include <sstream>
+#include <iostream>
+#include <iomanip>
+#include "make_test_data_available.hpp"
+
+// this test suite uses static variables with non-trivial destructors
+DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
+
+namespace
+{
+extern size_t calls;
+size_t calls = 0;
+
+void check_utf8dump(bool success_expected, int byte1, int byte2, int byte3, int byte4);
+
+void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3 = -1, int byte4 = -1)
+{
+ static std::string json_string;
+ json_string.clear();
+
+ CAPTURE(byte1)
+ CAPTURE(byte2)
+ CAPTURE(byte3)
+ CAPTURE(byte4)
+
+ json_string += std::string(1, static_cast<char>(byte1));
+
+ if (byte2 != -1)
+ {
+ json_string += std::string(1, static_cast<char>(byte2));
+ }
+
+ if (byte3 != -1)
+ {
+ json_string += std::string(1, static_cast<char>(byte3));
+ }
+
+ if (byte4 != -1)
+ {
+ json_string += std::string(1, static_cast<char>(byte4));
+ }
+
+ CAPTURE(json_string)
+
+ // store the string in a JSON value
+ static json j;
+ static json j2;
+ j = json_string;
+ j2 = "abc" + json_string + "xyz";
+
+ static std::string s_ignored;
+ static std::string s_ignored2;
+ static std::string s_ignored_ascii;
+ static std::string s_ignored2_ascii;
+ static std::string s_replaced;
+ static std::string s_replaced2;
+ static std::string s_replaced_ascii;
+ static std::string s_replaced2_ascii;
+
+ // dumping with ignore/replace must not throw in any case
+ s_ignored = j.dump(-1, ' ', false, json::error_handler_t::ignore);
+ s_ignored2 = j2.dump(-1, ' ', false, json::error_handler_t::ignore);
+ s_ignored_ascii = j.dump(-1, ' ', true, json::error_handler_t::ignore);
+ s_ignored2_ascii = j2.dump(-1, ' ', true, json::error_handler_t::ignore);
+ s_replaced = j.dump(-1, ' ', false, json::error_handler_t::replace);
+ s_replaced2 = j2.dump(-1, ' ', false, json::error_handler_t::replace);
+ s_replaced_ascii = j.dump(-1, ' ', true, json::error_handler_t::replace);
+ s_replaced2_ascii = j2.dump(-1, ' ', true, json::error_handler_t::replace);
+
+ if (success_expected)
+ {
+ static std::string s_strict;
+ // strict mode must not throw if success is expected
+ s_strict = j.dump();
+ // all dumps should agree on the string
+ CHECK(s_strict == s_ignored);
+ CHECK(s_strict == s_replaced);
+ }
+ else
+ {
+ // strict mode must throw if success is not expected
+ CHECK_THROWS_AS(j.dump(), json::type_error&);
+ // ignore and replace must create different dumps
+ CHECK(s_ignored != s_replaced);
+
+ // check that replace string contains a replacement character
+ CHECK(s_replaced.find("\xEF\xBF\xBD") != std::string::npos);
+ }
+
+ // check that prefix and suffix are preserved
+ CHECK(s_ignored2.substr(1, 3) == "abc");
+ CHECK(s_ignored2.substr(s_ignored2.size() - 4, 3) == "xyz");
+ CHECK(s_ignored2_ascii.substr(1, 3) == "abc");
+ CHECK(s_ignored2_ascii.substr(s_ignored2_ascii.size() - 4, 3) == "xyz");
+ CHECK(s_replaced2.substr(1, 3) == "abc");
+ CHECK(s_replaced2.substr(s_replaced2.size() - 4, 3) == "xyz");
+ CHECK(s_replaced2_ascii.substr(1, 3) == "abc");
+ CHECK(s_replaced2_ascii.substr(s_replaced2_ascii.size() - 4, 3) == "xyz");
+}
+
+void check_utf8string(bool success_expected, int byte1, int byte2, int byte3, int byte4);
+
+// create and check a JSON string with up to four UTF-8 bytes
+void check_utf8string(bool success_expected, int byte1, int byte2 = -1, int byte3 = -1, int byte4 = -1)
+{
+ if (++calls % 100000 == 0)
+ {
+ std::cout << calls << " of 455355 UTF-8 strings checked" << std::endl; // NOLINT(performance-avoid-endl)
+ }
+
+ static std::string json_string;
+ json_string = "\"";
+
+ CAPTURE(byte1)
+ json_string += std::string(1, static_cast<char>(byte1));
+
+ if (byte2 != -1)
+ {
+ CAPTURE(byte2)
+ json_string += std::string(1, static_cast<char>(byte2));
+ }
+
+ if (byte3 != -1)
+ {
+ CAPTURE(byte3)
+ json_string += std::string(1, static_cast<char>(byte3));
+ }
+
+ if (byte4 != -1)
+ {
+ CAPTURE(byte4)
+ json_string += std::string(1, static_cast<char>(byte4));
+ }
+
+ json_string += "\"";
+
+ CAPTURE(json_string)
+
+ json _;
+ if (success_expected)
+ {
+ CHECK_NOTHROW(_ = json::parse(json_string));
+ }
+ else
+ {
+ CHECK_THROWS_AS(_ = json::parse(json_string), json::parse_error&);
+ }
+}
+} // namespace
+
+TEST_CASE("Unicode (2/5)" * doctest::skip())
+{
+ SECTION("RFC 3629")
+ {
+ /*
+ RFC 3629 describes in Sect. 4 the syntax of UTF-8 byte sequences as
+ follows:
+
+ A UTF-8 string is a sequence of octets representing a sequence of UCS
+ characters. An octet sequence is valid UTF-8 only if it matches the
+ following syntax, which is derived from the rules for encoding UTF-8
+ and is expressed in the ABNF of [RFC2234].
+
+ UTF8-octets = *( UTF8-char )
+ UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
+ UTF8-1 = %x00-7F
+ UTF8-2 = %xC2-DF UTF8-tail
+ UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
+ %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
+ UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
+ %xF4 %x80-8F 2( UTF8-tail )
+ UTF8-tail = %x80-BF
+ */
+
+ SECTION("ill-formed first byte")
+ {
+ for (int byte1 = 0x80; byte1 <= 0xC1; ++byte1)
+ {
+ check_utf8string(false, byte1);
+ check_utf8dump(false, byte1);
+ }
+
+ for (int byte1 = 0xF5; byte1 <= 0xFF; ++byte1)
+ {
+ check_utf8string(false, byte1);
+ check_utf8dump(false, byte1);
+ }
+ }
+
+ SECTION("UTF8-1 (x00-x7F)")
+ {
+ SECTION("well-formed")
+ {
+ for (int byte1 = 0x00; byte1 <= 0x7F; ++byte1)
+ {
+ // unescaped control characters are parse errors in JSON
+ if (0x00 <= byte1 && byte1 <= 0x1F)
+ {
+ check_utf8string(false, byte1);
+ continue;
+ }
+
+ // a single quote is a parse error in JSON
+ if (byte1 == 0x22)
+ {
+ check_utf8string(false, byte1);
+ continue;
+ }
+
+ // a single backslash is a parse error in JSON
+ if (byte1 == 0x5C)
+ {
+ check_utf8string(false, byte1);
+ continue;
+ }
+
+ // all other characters are OK
+ check_utf8string(true, byte1);
+ check_utf8dump(true, byte1);
+ }
+ }
+ }
+
+ SECTION("UTF8-2 (xC2-xDF UTF8-tail)")
+ {
+ SECTION("well-formed")
+ {
+ for (int byte1 = 0xC2; byte1 <= 0xDF; ++byte1)
+ {
+ for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
+ {
+ check_utf8string(true, byte1, byte2);
+ check_utf8dump(true, byte1, byte2);
+ }
+ }
+ }
+
+ SECTION("ill-formed: missing second byte")
+ {
+ for (int byte1 = 0xC2; byte1 <= 0xDF; ++byte1)
+ {
+ check_utf8string(false, byte1);
+ check_utf8dump(false, byte1);
+ }
+ }
+
+ SECTION("ill-formed: wrong second byte")
+ {
+ for (int byte1 = 0xC2; byte1 <= 0xDF; ++byte1)
+ {
+ for (int byte2 = 0x00; byte2 <= 0xFF; ++byte2)
+ {
+ // skip correct second byte
+ if (0x80 <= byte2 && byte2 <= 0xBF)
+ {
+ continue;
+ }
+
+ check_utf8string(false, byte1, byte2);
+ check_utf8dump(false, byte1, byte2);
+ }
+ }
+ }
+ }
+
+ SECTION("UTF8-3 (xE0 xA0-BF UTF8-tail)")
+ {
+ SECTION("well-formed")
+ {
+ for (int byte1 = 0xE0; byte1 <= 0xE0; ++byte1)
+ {
+ for (int byte2 = 0xA0; byte2 <= 0xBF; ++byte2)
+ {
+ for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+ {
+ check_utf8string(true, byte1, byte2, byte3);
+ check_utf8dump(true, byte1, byte2, byte3);
+ }
+ }
+ }
+ }
+
+ SECTION("ill-formed: missing second byte")
+ {
+ for (int byte1 = 0xE0; byte1 <= 0xE0; ++byte1)
+ {
+ check_utf8string(false, byte1);
+ check_utf8dump(false, byte1);
+ }
+ }
+
+ SECTION("ill-formed: missing third byte")
+ {
+ for (int byte1 = 0xE0; byte1 <= 0xE0; ++byte1)
+ {
+ for (int byte2 = 0xA0; byte2 <= 0xBF; ++byte2)
+ {
+ check_utf8string(false, byte1, byte2);
+ check_utf8dump(false, byte1, byte2);
+ }
+ }
+ }
+
+ SECTION("ill-formed: wrong second byte")
+ {
+ for (int byte1 = 0xE0; byte1 <= 0xE0; ++byte1)
+ {
+ for (int byte2 = 0x00; byte2 <= 0xFF; ++byte2)
+ {
+ // skip correct second byte
+ if (0xA0 <= byte2 && byte2 <= 0xBF)
+ {
+ continue;
+ }
+
+ for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+ {
+ check_utf8string(false, byte1, byte2, byte3);
+ check_utf8dump(false, byte1, byte2, byte3);
+ }
+ }
+ }
+ }
+
+ SECTION("ill-formed: wrong third byte")
+ {
+ for (int byte1 = 0xE0; byte1 <= 0xE0; ++byte1)
+ {
+ for (int byte2 = 0xA0; byte2 <= 0xBF; ++byte2)
+ {
+ for (int byte3 = 0x00; byte3 <= 0xFF; ++byte3)
+ {
+ // skip correct third byte
+ if (0x80 <= byte3 && byte3 <= 0xBF)
+ {
+ continue;
+ }
+
+ check_utf8string(false, byte1, byte2, byte3);
+ check_utf8dump(false, byte1, byte2, byte3);
+ }
+ }
+ }
+ }
+ }
+
+ SECTION("UTF8-3 (xE1-xEC UTF8-tail UTF8-tail)")
+ {
+ SECTION("well-formed")
+ {
+ for (int byte1 = 0xE1; byte1 <= 0xEC; ++byte1)
+ {
+ for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
+ {
+ for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+ {
+ check_utf8string(true, byte1, byte2, byte3);
+ check_utf8dump(true, byte1, byte2, byte3);
+ }
+ }
+ }
+ }
+
+ SECTION("ill-formed: missing second byte")
+ {
+ for (int byte1 = 0xE1; byte1 <= 0xEC; ++byte1)
+ {
+ check_utf8string(false, byte1);
+ check_utf8dump(false, byte1);
+ }
+ }
+
+ SECTION("ill-formed: missing third byte")
+ {
+ for (int byte1 = 0xE1; byte1 <= 0xEC; ++byte1)
+ {
+ for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
+ {
+ check_utf8string(false, byte1, byte2);
+ check_utf8dump(false, byte1, byte2);
+ }
+ }
+ }
+
+ SECTION("ill-formed: wrong second byte")
+ {
+ for (int byte1 = 0xE1; byte1 <= 0xEC; ++byte1)
+ {
+ for (int byte2 = 0x00; byte2 <= 0xFF; ++byte2)
+ {
+ // skip correct second byte
+ if (0x80 <= byte2 && byte2 <= 0xBF)
+ {
+ continue;
+ }
+
+ for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+ {
+ check_utf8string(false, byte1, byte2, byte3);
+ check_utf8dump(false, byte1, byte2, byte3);
+ }
+ }
+ }
+ }
+
+ SECTION("ill-formed: wrong third byte")
+ {
+ for (int byte1 = 0xE1; byte1 <= 0xEC; ++byte1)
+ {
+ for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
+ {
+ for (int byte3 = 0x00; byte3 <= 0xFF; ++byte3)
+ {
+ // skip correct third byte
+ if (0x80 <= byte3 && byte3 <= 0xBF)
+ {
+ continue;
+ }
+
+ check_utf8string(false, byte1, byte2, byte3);
+ check_utf8dump(false, byte1, byte2, byte3);
+ }
+ }
+ }
+ }
+ }
+
+ SECTION("UTF8-3 (xED x80-9F UTF8-tail)")
+ {
+ SECTION("well-formed")
+ {
+ for (int byte1 = 0xED; byte1 <= 0xED; ++byte1)
+ {
+ for (int byte2 = 0x80; byte2 <= 0x9F; ++byte2)
+ {
+ for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+ {
+ check_utf8string(true, byte1, byte2, byte3);
+ check_utf8dump(true, byte1, byte2, byte3);
+ }
+ }
+ }
+ }
+
+ SECTION("ill-formed: missing second byte")
+ {
+ for (int byte1 = 0xED; byte1 <= 0xED; ++byte1)
+ {
+ check_utf8string(false, byte1);
+ check_utf8dump(false, byte1);
+ }
+ }
+
+ SECTION("ill-formed: missing third byte")
+ {
+ for (int byte1 = 0xED; byte1 <= 0xED; ++byte1)
+ {
+ for (int byte2 = 0x80; byte2 <= 0x9F; ++byte2)
+ {
+ check_utf8string(false, byte1, byte2);
+ check_utf8dump(false, byte1, byte2);
+ }
+ }
+ }
+
+ SECTION("ill-formed: wrong second byte")
+ {
+ for (int byte1 = 0xED; byte1 <= 0xED; ++byte1)
+ {
+ for (int byte2 = 0x00; byte2 <= 0xFF; ++byte2)
+ {
+ // skip correct second byte
+ if (0x80 <= byte2 && byte2 <= 0x9F)
+ {
+ continue;
+ }
+
+ for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+ {
+ check_utf8string(false, byte1, byte2, byte3);
+ check_utf8dump(false, byte1, byte2, byte3);
+ }
+ }
+ }
+ }
+
+ SECTION("ill-formed: wrong third byte")
+ {
+ for (int byte1 = 0xED; byte1 <= 0xED; ++byte1)
+ {
+ for (int byte2 = 0x80; byte2 <= 0x9F; ++byte2)
+ {
+ for (int byte3 = 0x00; byte3 <= 0xFF; ++byte3)
+ {
+ // skip correct third byte
+ if (0x80 <= byte3 && byte3 <= 0xBF)
+ {
+ continue;
+ }
+
+ check_utf8string(false, byte1, byte2, byte3);
+ check_utf8dump(false, byte1, byte2, byte3);
+ }
+ }
+ }
+ }
+ }
+
+ SECTION("UTF8-3 (xEE-xEF UTF8-tail UTF8-tail)")
+ {
+ SECTION("well-formed")
+ {
+ for (int byte1 = 0xEE; byte1 <= 0xEF; ++byte1)
+ {
+ for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
+ {
+ for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+ {
+ check_utf8string(true, byte1, byte2, byte3);
+ check_utf8dump(true, byte1, byte2, byte3);
+ }
+ }
+ }
+ }
+
+ SECTION("ill-formed: missing second byte")
+ {
+ for (int byte1 = 0xEE; byte1 <= 0xEF; ++byte1)
+ {
+ check_utf8string(false, byte1);
+ check_utf8dump(false, byte1);
+ }
+ }
+
+ SECTION("ill-formed: missing third byte")
+ {
+ for (int byte1 = 0xEE; byte1 <= 0xEF; ++byte1)
+ {
+ for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
+ {
+ check_utf8string(false, byte1, byte2);
+ check_utf8dump(false, byte1, byte2);
+ }
+ }
+ }
+
+ SECTION("ill-formed: wrong second byte")
+ {
+ for (int byte1 = 0xEE; byte1 <= 0xEF; ++byte1)
+ {
+ for (int byte2 = 0x00; byte2 <= 0xFF; ++byte2)
+ {
+ // skip correct second byte
+ if (0x80 <= byte2 && byte2 <= 0xBF)
+ {
+ continue;
+ }
+
+ for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+ {
+ check_utf8string(false, byte1, byte2, byte3);
+ check_utf8dump(false, byte1, byte2, byte3);
+ }
+ }
+ }
+ }
+
+ SECTION("ill-formed: wrong third byte")
+ {
+ for (int byte1 = 0xEE; byte1 <= 0xEF; ++byte1)
+ {
+ for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
+ {
+ for (int byte3 = 0x00; byte3 <= 0xFF; ++byte3)
+ {
+ // skip correct third byte
+ if (0x80 <= byte3 && byte3 <= 0xBF)
+ {
+ continue;
+ }
+
+ check_utf8string(false, byte1, byte2, byte3);
+ check_utf8dump(false, byte1, byte2, byte3);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+DOCTEST_CLANG_SUPPRESS_WARNING_POP
diff --git a/json4cpp/tests/src/unit-unicode3.cpp b/json4cpp/tests/src/unit-unicode3.cpp
new file mode 100644
index 0000000000..739a3dad3c
--- /dev/null
+++ b/json4cpp/tests/src/unit-unicode3.cpp
@@ -0,0 +1,324 @@
+// __ _____ _____ _____
+// __| | __| | | | 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"
+
+// for some reason including this after the json header leads to linker errors with VS 2017...
+#include <locale>
+
+#include <nlohmann/json.hpp>
+using nlohmann::json;
+
+#include <fstream>
+#include <sstream>
+#include <iostream>
+#include <iomanip>
+#include "make_test_data_available.hpp"
+
+// this test suite uses static variables with non-trivial destructors
+DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
+
+namespace
+{
+extern size_t calls;
+size_t calls = 0;
+
+void check_utf8dump(bool success_expected, int byte1, int byte2, int byte3, int byte4);
+
+void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3 = -1, int byte4 = -1)
+{
+ static std::string json_string;
+ json_string.clear();
+
+ CAPTURE(byte1)
+ CAPTURE(byte2)
+ CAPTURE(byte3)
+ CAPTURE(byte4)
+
+ json_string += std::string(1, static_cast<char>(byte1));
+
+ if (byte2 != -1)
+ {
+ json_string += std::string(1, static_cast<char>(byte2));
+ }
+
+ if (byte3 != -1)
+ {
+ json_string += std::string(1, static_cast<char>(byte3));
+ }
+
+ if (byte4 != -1)
+ {
+ json_string += std::string(1, static_cast<char>(byte4));
+ }
+
+ CAPTURE(json_string)
+
+ // store the string in a JSON value
+ static json j;
+ static json j2;
+ j = json_string;
+ j2 = "abc" + json_string + "xyz";
+
+ static std::string s_ignored;
+ static std::string s_ignored2;
+ static std::string s_ignored_ascii;
+ static std::string s_ignored2_ascii;
+ static std::string s_replaced;
+ static std::string s_replaced2;
+ static std::string s_replaced_ascii;
+ static std::string s_replaced2_ascii;
+
+ // dumping with ignore/replace must not throw in any case
+ s_ignored = j.dump(-1, ' ', false, json::error_handler_t::ignore);
+ s_ignored2 = j2.dump(-1, ' ', false, json::error_handler_t::ignore);
+ s_ignored_ascii = j.dump(-1, ' ', true, json::error_handler_t::ignore);
+ s_ignored2_ascii = j2.dump(-1, ' ', true, json::error_handler_t::ignore);
+ s_replaced = j.dump(-1, ' ', false, json::error_handler_t::replace);
+ s_replaced2 = j2.dump(-1, ' ', false, json::error_handler_t::replace);
+ s_replaced_ascii = j.dump(-1, ' ', true, json::error_handler_t::replace);
+ s_replaced2_ascii = j2.dump(-1, ' ', true, json::error_handler_t::replace);
+
+ if (success_expected)
+ {
+ static std::string s_strict;
+ // strict mode must not throw if success is expected
+ s_strict = j.dump();
+ // all dumps should agree on the string
+ CHECK(s_strict == s_ignored);
+ CHECK(s_strict == s_replaced);
+ }
+ else
+ {
+ // strict mode must throw if success is not expected
+ CHECK_THROWS_AS(j.dump(), json::type_error&);
+ // ignore and replace must create different dumps
+ CHECK(s_ignored != s_replaced);
+
+ // check that replace string contains a replacement character
+ CHECK(s_replaced.find("\xEF\xBF\xBD") != std::string::npos);
+ }
+
+ // check that prefix and suffix are preserved
+ CHECK(s_ignored2.substr(1, 3) == "abc");
+ CHECK(s_ignored2.substr(s_ignored2.size() - 4, 3) == "xyz");
+ CHECK(s_ignored2_ascii.substr(1, 3) == "abc");
+ CHECK(s_ignored2_ascii.substr(s_ignored2_ascii.size() - 4, 3) == "xyz");
+ CHECK(s_replaced2.substr(1, 3) == "abc");
+ CHECK(s_replaced2.substr(s_replaced2.size() - 4, 3) == "xyz");
+ CHECK(s_replaced2_ascii.substr(1, 3) == "abc");
+ CHECK(s_replaced2_ascii.substr(s_replaced2_ascii.size() - 4, 3) == "xyz");
+}
+
+void check_utf8string(bool success_expected, int byte1, int byte2, int byte3, int byte4);
+
+// create and check a JSON string with up to four UTF-8 bytes
+void check_utf8string(bool success_expected, int byte1, int byte2 = -1, int byte3 = -1, int byte4 = -1)
+{
+ if (++calls % 100000 == 0)
+ {
+ std::cout << calls << " of 1641521 UTF-8 strings checked" << std::endl; // NOLINT(performance-avoid-endl)
+ }
+
+ static std::string json_string;
+ json_string = "\"";
+
+ CAPTURE(byte1)
+ json_string += std::string(1, static_cast<char>(byte1));
+
+ if (byte2 != -1)
+ {
+ CAPTURE(byte2)
+ json_string += std::string(1, static_cast<char>(byte2));
+ }
+
+ if (byte3 != -1)
+ {
+ CAPTURE(byte3)
+ json_string += std::string(1, static_cast<char>(byte3));
+ }
+
+ if (byte4 != -1)
+ {
+ CAPTURE(byte4)
+ json_string += std::string(1, static_cast<char>(byte4));
+ }
+
+ json_string += "\"";
+
+ CAPTURE(json_string)
+
+ json _;
+ if (success_expected)
+ {
+ CHECK_NOTHROW(_ = json::parse(json_string));
+ }
+ else
+ {
+ CHECK_THROWS_AS(_ = json::parse(json_string), json::parse_error&);
+ }
+}
+} // namespace
+
+TEST_CASE("Unicode (3/5)" * doctest::skip())
+{
+ SECTION("RFC 3629")
+ {
+ /*
+ RFC 3629 describes in Sect. 4 the syntax of UTF-8 byte sequences as
+ follows:
+
+ A UTF-8 string is a sequence of octets representing a sequence of UCS
+ characters. An octet sequence is valid UTF-8 only if it matches the
+ following syntax, which is derived from the rules for encoding UTF-8
+ and is expressed in the ABNF of [RFC2234].
+
+ UTF8-octets = *( UTF8-char )
+ UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
+ UTF8-1 = %x00-7F
+ UTF8-2 = %xC2-DF UTF8-tail
+ UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
+ %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
+ UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
+ %xF4 %x80-8F 2( UTF8-tail )
+ UTF8-tail = %x80-BF
+ */
+
+ SECTION("UTF8-4 (xF0 x90-BF UTF8-tail UTF8-tail)")
+ {
+ SECTION("well-formed")
+ {
+ for (int byte1 = 0xF0; byte1 <= 0xF0; ++byte1)
+ {
+ for (int byte2 = 0x90; byte2 <= 0xBF; ++byte2)
+ {
+ for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+ {
+ for (int byte4 = 0x80; byte4 <= 0xBF; ++byte4)
+ {
+ check_utf8string(true, byte1, byte2, byte3, byte4);
+ check_utf8dump(true, byte1, byte2, byte3, byte4);
+ }
+ }
+ }
+ }
+ }
+
+ SECTION("ill-formed: missing second byte")
+ {
+ for (int byte1 = 0xF0; byte1 <= 0xF0; ++byte1)
+ {
+ check_utf8string(false, byte1);
+ check_utf8dump(false, byte1);
+ }
+ }
+
+ SECTION("ill-formed: missing third byte")
+ {
+ for (int byte1 = 0xF0; byte1 <= 0xF0; ++byte1)
+ {
+ for (int byte2 = 0x90; byte2 <= 0xBF; ++byte2)
+ {
+ check_utf8string(false, byte1, byte2);
+ check_utf8dump(false, byte1, byte2);
+ }
+ }
+ }
+
+ SECTION("ill-formed: missing fourth byte")
+ {
+ for (int byte1 = 0xF0; byte1 <= 0xF0; ++byte1)
+ {
+ for (int byte2 = 0x90; byte2 <= 0xBF; ++byte2)
+ {
+ for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+ {
+ check_utf8string(false, byte1, byte2, byte3);
+ check_utf8dump(false, byte1, byte2, byte3);
+ }
+ }
+ }
+ }
+
+ SECTION("ill-formed: wrong second byte")
+ {
+ for (int byte1 = 0xF0; byte1 <= 0xF0; ++byte1)
+ {
+ for (int byte2 = 0x00; byte2 <= 0xFF; ++byte2)
+ {
+ // skip correct second byte
+ if (0x90 <= byte2 && byte2 <= 0xBF)
+ {
+ continue;
+ }
+
+ for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+ {
+ for (int byte4 = 0x80; byte4 <= 0xBF; ++byte4)
+ {
+ check_utf8string(false, byte1, byte2, byte3, byte4);
+ check_utf8dump(false, byte1, byte2, byte3, byte4);
+ }
+ }
+ }
+ }
+ }
+
+ SECTION("ill-formed: wrong third byte")
+ {
+ for (int byte1 = 0xF0; byte1 <= 0xF0; ++byte1)
+ {
+ for (int byte2 = 0x90; byte2 <= 0xBF; ++byte2)
+ {
+ for (int byte3 = 0x00; byte3 <= 0xFF; ++byte3)
+ {
+ // skip correct third byte
+ if (0x80 <= byte3 && byte3 <= 0xBF)
+ {
+ continue;
+ }
+
+ for (int byte4 = 0x80; byte4 <= 0xBF; ++byte4)
+ {
+ check_utf8string(false, byte1, byte2, byte3, byte4);
+ check_utf8dump(false, byte1, byte2, byte3, byte4);
+ }
+ }
+ }
+ }
+ }
+
+ SECTION("ill-formed: wrong fourth byte")
+ {
+ for (int byte1 = 0xF0; byte1 <= 0xF0; ++byte1)
+ {
+ for (int byte2 = 0x90; byte2 <= 0xBF; ++byte2)
+ {
+ for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+ {
+ for (int byte4 = 0x00; byte4 <= 0xFF; ++byte4)
+ {
+ // skip fourth second byte
+ if (0x80 <= byte3 && byte3 <= 0xBF)
+ {
+ continue;
+ }
+
+ check_utf8string(false, byte1, byte2, byte3, byte4);
+ check_utf8dump(false, byte1, byte2, byte3, byte4);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+DOCTEST_CLANG_SUPPRESS_WARNING_POP
diff --git a/json4cpp/tests/src/unit-unicode4.cpp b/json4cpp/tests/src/unit-unicode4.cpp
new file mode 100644
index 0000000000..f7047201c6
--- /dev/null
+++ b/json4cpp/tests/src/unit-unicode4.cpp
@@ -0,0 +1,324 @@
+// __ _____ _____ _____
+// __| | __| | | | 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"
+
+// for some reason including this after the json header leads to linker errors with VS 2017...
+#include <locale>
+
+#include <nlohmann/json.hpp>
+using nlohmann::json;
+
+#include <fstream>
+#include <sstream>
+#include <iostream>
+#include <iomanip>
+#include "make_test_data_available.hpp"
+
+// this test suite uses static variables with non-trivial destructors
+DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
+
+namespace
+{
+extern size_t calls;
+size_t calls = 0;
+
+void check_utf8dump(bool success_expected, int byte1, int byte2, int byte3, int byte4);
+
+void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3 = -1, int byte4 = -1)
+{
+ static std::string json_string;
+ json_string.clear();
+
+ CAPTURE(byte1)
+ CAPTURE(byte2)
+ CAPTURE(byte3)
+ CAPTURE(byte4)
+
+ json_string += std::string(1, static_cast<char>(byte1));
+
+ if (byte2 != -1)
+ {
+ json_string += std::string(1, static_cast<char>(byte2));
+ }
+
+ if (byte3 != -1)
+ {
+ json_string += std::string(1, static_cast<char>(byte3));
+ }
+
+ if (byte4 != -1)
+ {
+ json_string += std::string(1, static_cast<char>(byte4));
+ }
+
+ CAPTURE(json_string)
+
+ // store the string in a JSON value
+ static json j;
+ static json j2;
+ j = json_string;
+ j2 = "abc" + json_string + "xyz";
+
+ static std::string s_ignored;
+ static std::string s_ignored2;
+ static std::string s_ignored_ascii;
+ static std::string s_ignored2_ascii;
+ static std::string s_replaced;
+ static std::string s_replaced2;
+ static std::string s_replaced_ascii;
+ static std::string s_replaced2_ascii;
+
+ // dumping with ignore/replace must not throw in any case
+ s_ignored = j.dump(-1, ' ', false, json::error_handler_t::ignore);
+ s_ignored2 = j2.dump(-1, ' ', false, json::error_handler_t::ignore);
+ s_ignored_ascii = j.dump(-1, ' ', true, json::error_handler_t::ignore);
+ s_ignored2_ascii = j2.dump(-1, ' ', true, json::error_handler_t::ignore);
+ s_replaced = j.dump(-1, ' ', false, json::error_handler_t::replace);
+ s_replaced2 = j2.dump(-1, ' ', false, json::error_handler_t::replace);
+ s_replaced_ascii = j.dump(-1, ' ', true, json::error_handler_t::replace);
+ s_replaced2_ascii = j2.dump(-1, ' ', true, json::error_handler_t::replace);
+
+ if (success_expected)
+ {
+ static std::string s_strict;
+ // strict mode must not throw if success is expected
+ s_strict = j.dump();
+ // all dumps should agree on the string
+ CHECK(s_strict == s_ignored);
+ CHECK(s_strict == s_replaced);
+ }
+ else
+ {
+ // strict mode must throw if success is not expected
+ CHECK_THROWS_AS(j.dump(), json::type_error&);
+ // ignore and replace must create different dumps
+ CHECK(s_ignored != s_replaced);
+
+ // check that replace string contains a replacement character
+ CHECK(s_replaced.find("\xEF\xBF\xBD") != std::string::npos);
+ }
+
+ // check that prefix and suffix are preserved
+ CHECK(s_ignored2.substr(1, 3) == "abc");
+ CHECK(s_ignored2.substr(s_ignored2.size() - 4, 3) == "xyz");
+ CHECK(s_ignored2_ascii.substr(1, 3) == "abc");
+ CHECK(s_ignored2_ascii.substr(s_ignored2_ascii.size() - 4, 3) == "xyz");
+ CHECK(s_replaced2.substr(1, 3) == "abc");
+ CHECK(s_replaced2.substr(s_replaced2.size() - 4, 3) == "xyz");
+ CHECK(s_replaced2_ascii.substr(1, 3) == "abc");
+ CHECK(s_replaced2_ascii.substr(s_replaced2_ascii.size() - 4, 3) == "xyz");
+}
+
+void check_utf8string(bool success_expected, int byte1, int byte2, int byte3, int byte4);
+
+// create and check a JSON string with up to four UTF-8 bytes
+void check_utf8string(bool success_expected, int byte1, int byte2 = -1, int byte3 = -1, int byte4 = -1)
+{
+ if (++calls % 100000 == 0)
+ {
+ std::cout << calls << " of 5517507 UTF-8 strings checked" << std::endl; // NOLINT(performance-avoid-endl)
+ }
+
+ static std::string json_string;
+ json_string = "\"";
+
+ CAPTURE(byte1)
+ json_string += std::string(1, static_cast<char>(byte1));
+
+ if (byte2 != -1)
+ {
+ CAPTURE(byte2)
+ json_string += std::string(1, static_cast<char>(byte2));
+ }
+
+ if (byte3 != -1)
+ {
+ CAPTURE(byte3)
+ json_string += std::string(1, static_cast<char>(byte3));
+ }
+
+ if (byte4 != -1)
+ {
+ CAPTURE(byte4)
+ json_string += std::string(1, static_cast<char>(byte4));
+ }
+
+ json_string += "\"";
+
+ CAPTURE(json_string)
+
+ json _;
+ if (success_expected)
+ {
+ CHECK_NOTHROW(_ = json::parse(json_string));
+ }
+ else
+ {
+ CHECK_THROWS_AS(_ = json::parse(json_string), json::parse_error&);
+ }
+}
+} // namespace
+
+TEST_CASE("Unicode (4/5)" * doctest::skip())
+{
+ SECTION("RFC 3629")
+ {
+ /*
+ RFC 3629 describes in Sect. 4 the syntax of UTF-8 byte sequences as
+ follows:
+
+ A UTF-8 string is a sequence of octets representing a sequence of UCS
+ characters. An octet sequence is valid UTF-8 only if it matches the
+ following syntax, which is derived from the rules for encoding UTF-8
+ and is expressed in the ABNF of [RFC2234].
+
+ UTF8-octets = *( UTF8-char )
+ UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
+ UTF8-1 = %x00-7F
+ UTF8-2 = %xC2-DF UTF8-tail
+ UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
+ %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
+ UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
+ %xF4 %x80-8F 2( UTF8-tail )
+ UTF8-tail = %x80-BF
+ */
+
+ SECTION("UTF8-4 (xF1-F3 UTF8-tail UTF8-tail UTF8-tail)")
+ {
+ SECTION("well-formed")
+ {
+ for (int byte1 = 0xF1; byte1 <= 0xF3; ++byte1)
+ {
+ for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
+ {
+ for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+ {
+ for (int byte4 = 0x80; byte4 <= 0xBF; ++byte4)
+ {
+ check_utf8string(true, byte1, byte2, byte3, byte4);
+ check_utf8dump(true, byte1, byte2, byte3, byte4);
+ }
+ }
+ }
+ }
+ }
+
+ SECTION("ill-formed: missing second byte")
+ {
+ for (int byte1 = 0xF1; byte1 <= 0xF3; ++byte1)
+ {
+ check_utf8string(false, byte1);
+ check_utf8dump(false, byte1);
+ }
+ }
+
+ SECTION("ill-formed: missing third byte")
+ {
+ for (int byte1 = 0xF1; byte1 <= 0xF3; ++byte1)
+ {
+ for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
+ {
+ check_utf8string(false, byte1, byte2);
+ check_utf8dump(false, byte1, byte2);
+ }
+ }
+ }
+
+ SECTION("ill-formed: missing fourth byte")
+ {
+ for (int byte1 = 0xF1; byte1 <= 0xF3; ++byte1)
+ {
+ for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
+ {
+ for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+ {
+ check_utf8string(false, byte1, byte2, byte3);
+ check_utf8dump(false, byte1, byte2, byte3);
+ }
+ }
+ }
+ }
+
+ SECTION("ill-formed: wrong second byte")
+ {
+ for (int byte1 = 0xF1; byte1 <= 0xF3; ++byte1)
+ {
+ for (int byte2 = 0x00; byte2 <= 0xFF; ++byte2)
+ {
+ // skip correct second byte
+ if (0x80 <= byte2 && byte2 <= 0xBF)
+ {
+ continue;
+ }
+
+ for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+ {
+ for (int byte4 = 0x80; byte4 <= 0xBF; ++byte4)
+ {
+ check_utf8string(false, byte1, byte2, byte3, byte4);
+ check_utf8dump(false, byte1, byte2, byte3, byte4);
+ }
+ }
+ }
+ }
+ }
+
+ SECTION("ill-formed: wrong third byte")
+ {
+ for (int byte1 = 0xF1; byte1 <= 0xF3; ++byte1)
+ {
+ for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
+ {
+ for (int byte3 = 0x00; byte3 <= 0xFF; ++byte3)
+ {
+ // skip correct third byte
+ if (0x80 <= byte3 && byte3 <= 0xBF)
+ {
+ continue;
+ }
+
+ for (int byte4 = 0x80; byte4 <= 0xBF; ++byte4)
+ {
+ check_utf8string(false, byte1, byte2, byte3, byte4);
+ check_utf8dump(false, byte1, byte2, byte3, byte4);
+ }
+ }
+ }
+ }
+ }
+
+ SECTION("ill-formed: wrong fourth byte")
+ {
+ for (int byte1 = 0xF1; byte1 <= 0xF3; ++byte1)
+ {
+ for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
+ {
+ for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+ {
+ for (int byte4 = 0x00; byte4 <= 0xFF; ++byte4)
+ {
+ // skip correct fourth byte
+ if (0x80 <= byte3 && byte3 <= 0xBF)
+ {
+ continue;
+ }
+
+ check_utf8string(false, byte1, byte2, byte3, byte4);
+ check_utf8dump(false, byte1, byte2, byte3, byte4);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+DOCTEST_CLANG_SUPPRESS_WARNING_POP
diff --git a/json4cpp/tests/src/unit-unicode5.cpp b/json4cpp/tests/src/unit-unicode5.cpp
new file mode 100644
index 0000000000..e4dcc21310
--- /dev/null
+++ b/json4cpp/tests/src/unit-unicode5.cpp
@@ -0,0 +1,324 @@
+// __ _____ _____ _____
+// __| | __| | | | 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"
+
+// for some reason including this after the json header leads to linker errors with VS 2017...
+#include <locale>
+
+#include <nlohmann/json.hpp>
+using nlohmann::json;
+
+#include <fstream>
+#include <sstream>
+#include <iostream>
+#include <iomanip>
+#include "make_test_data_available.hpp"
+
+// this test suite uses static variables with non-trivial destructors
+DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
+
+namespace
+{
+extern size_t calls;
+size_t calls = 0;
+
+void check_utf8dump(bool success_expected, int byte1, int byte2, int byte3, int byte4);
+
+void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3 = -1, int byte4 = -1)
+{
+ static std::string json_string;
+ json_string.clear();
+
+ CAPTURE(byte1)
+ CAPTURE(byte2)
+ CAPTURE(byte3)
+ CAPTURE(byte4)
+
+ json_string += std::string(1, static_cast<char>(byte1));
+
+ if (byte2 != -1)
+ {
+ json_string += std::string(1, static_cast<char>(byte2));
+ }
+
+ if (byte3 != -1)
+ {
+ json_string += std::string(1, static_cast<char>(byte3));
+ }
+
+ if (byte4 != -1)
+ {
+ json_string += std::string(1, static_cast<char>(byte4));
+ }
+
+ CAPTURE(json_string)
+
+ // store the string in a JSON value
+ static json j;
+ static json j2;
+ j = json_string;
+ j2 = "abc" + json_string + "xyz";
+
+ static std::string s_ignored;
+ static std::string s_ignored2;
+ static std::string s_ignored_ascii;
+ static std::string s_ignored2_ascii;
+ static std::string s_replaced;
+ static std::string s_replaced2;
+ static std::string s_replaced_ascii;
+ static std::string s_replaced2_ascii;
+
+ // dumping with ignore/replace must not throw in any case
+ s_ignored = j.dump(-1, ' ', false, json::error_handler_t::ignore);
+ s_ignored2 = j2.dump(-1, ' ', false, json::error_handler_t::ignore);
+ s_ignored_ascii = j.dump(-1, ' ', true, json::error_handler_t::ignore);
+ s_ignored2_ascii = j2.dump(-1, ' ', true, json::error_handler_t::ignore);
+ s_replaced = j.dump(-1, ' ', false, json::error_handler_t::replace);
+ s_replaced2 = j2.dump(-1, ' ', false, json::error_handler_t::replace);
+ s_replaced_ascii = j.dump(-1, ' ', true, json::error_handler_t::replace);
+ s_replaced2_ascii = j2.dump(-1, ' ', true, json::error_handler_t::replace);
+
+ if (success_expected)
+ {
+ static std::string s_strict;
+ // strict mode must not throw if success is expected
+ s_strict = j.dump();
+ // all dumps should agree on the string
+ CHECK(s_strict == s_ignored);
+ CHECK(s_strict == s_replaced);
+ }
+ else
+ {
+ // strict mode must throw if success is not expected
+ CHECK_THROWS_AS(j.dump(), json::type_error&);
+ // ignore and replace must create different dumps
+ CHECK(s_ignored != s_replaced);
+
+ // check that replace string contains a replacement character
+ CHECK(s_replaced.find("\xEF\xBF\xBD") != std::string::npos);
+ }
+
+ // check that prefix and suffix are preserved
+ CHECK(s_ignored2.substr(1, 3) == "abc");
+ CHECK(s_ignored2.substr(s_ignored2.size() - 4, 3) == "xyz");
+ CHECK(s_ignored2_ascii.substr(1, 3) == "abc");
+ CHECK(s_ignored2_ascii.substr(s_ignored2_ascii.size() - 4, 3) == "xyz");
+ CHECK(s_replaced2.substr(1, 3) == "abc");
+ CHECK(s_replaced2.substr(s_replaced2.size() - 4, 3) == "xyz");
+ CHECK(s_replaced2_ascii.substr(1, 3) == "abc");
+ CHECK(s_replaced2_ascii.substr(s_replaced2_ascii.size() - 4, 3) == "xyz");
+}
+
+void check_utf8string(bool success_expected, int byte1, int byte2, int byte3, int byte4);
+
+// create and check a JSON string with up to four UTF-8 bytes
+void check_utf8string(bool success_expected, int byte1, int byte2 = -1, int byte3 = -1, int byte4 = -1)
+{
+ if (++calls % 100000 == 0)
+ {
+ std::cout << calls << " of 1246225 UTF-8 strings checked" << std::endl; // NOLINT(performance-avoid-endl)
+ }
+
+ static std::string json_string;
+ json_string = "\"";
+
+ CAPTURE(byte1)
+ json_string += std::string(1, static_cast<char>(byte1));
+
+ if (byte2 != -1)
+ {
+ CAPTURE(byte2)
+ json_string += std::string(1, static_cast<char>(byte2));
+ }
+
+ if (byte3 != -1)
+ {
+ CAPTURE(byte3)
+ json_string += std::string(1, static_cast<char>(byte3));
+ }
+
+ if (byte4 != -1)
+ {
+ CAPTURE(byte4)
+ json_string += std::string(1, static_cast<char>(byte4));
+ }
+
+ json_string += "\"";
+
+ CAPTURE(json_string)
+
+ json _;
+ if (success_expected)
+ {
+ CHECK_NOTHROW(_ = json::parse(json_string));
+ }
+ else
+ {
+ CHECK_THROWS_AS(_ = json::parse(json_string), json::parse_error&);
+ }
+}
+} // namespace
+
+TEST_CASE("Unicode (5/5)" * doctest::skip())
+{
+ SECTION("RFC 3629")
+ {
+ /*
+ RFC 3629 describes in Sect. 4 the syntax of UTF-8 byte sequences as
+ follows:
+
+ A UTF-8 string is a sequence of octets representing a sequence of UCS
+ characters. An octet sequence is valid UTF-8 only if it matches the
+ following syntax, which is derived from the rules for encoding UTF-8
+ and is expressed in the ABNF of [RFC2234].
+
+ UTF8-octets = *( UTF8-char )
+ UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
+ UTF8-1 = %x00-7F
+ UTF8-2 = %xC2-DF UTF8-tail
+ UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
+ %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
+ UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
+ %xF4 %x80-8F 2( UTF8-tail )
+ UTF8-tail = %x80-BF
+ */
+
+ SECTION("UTF8-4 (xF4 x80-8F UTF8-tail UTF8-tail)")
+ {
+ SECTION("well-formed")
+ {
+ for (int byte1 = 0xF4; byte1 <= 0xF4; ++byte1)
+ {
+ for (int byte2 = 0x80; byte2 <= 0x8F; ++byte2)
+ {
+ for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+ {
+ for (int byte4 = 0x80; byte4 <= 0xBF; ++byte4)
+ {
+ check_utf8string(true, byte1, byte2, byte3, byte4);
+ check_utf8dump(true, byte1, byte2, byte3, byte4);
+ }
+ }
+ }
+ }
+ }
+
+ SECTION("ill-formed: missing second byte")
+ {
+ for (int byte1 = 0xF4; byte1 <= 0xF4; ++byte1)
+ {
+ check_utf8string(false, byte1);
+ check_utf8dump(false, byte1);
+ }
+ }
+
+ SECTION("ill-formed: missing third byte")
+ {
+ for (int byte1 = 0xF4; byte1 <= 0xF4; ++byte1)
+ {
+ for (int byte2 = 0x80; byte2 <= 0x8F; ++byte2)
+ {
+ check_utf8string(false, byte1, byte2);
+ check_utf8dump(false, byte1, byte2);
+ }
+ }
+ }
+
+ SECTION("ill-formed: missing fourth byte")
+ {
+ for (int byte1 = 0xF4; byte1 <= 0xF4; ++byte1)
+ {
+ for (int byte2 = 0x80; byte2 <= 0x8F; ++byte2)
+ {
+ for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+ {
+ check_utf8string(false, byte1, byte2, byte3);
+ check_utf8dump(false, byte1, byte2, byte3);
+ }
+ }
+ }
+ }
+
+ SECTION("ill-formed: wrong second byte")
+ {
+ for (int byte1 = 0xF4; byte1 <= 0xF4; ++byte1)
+ {
+ for (int byte2 = 0x00; byte2 <= 0xFF; ++byte2)
+ {
+ // skip correct second byte
+ if (0x80 <= byte2 && byte2 <= 0x8F)
+ {
+ continue;
+ }
+
+ for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+ {
+ for (int byte4 = 0x80; byte4 <= 0xBF; ++byte4)
+ {
+ check_utf8string(false, byte1, byte2, byte3, byte4);
+ check_utf8dump(false, byte1, byte2, byte3, byte4);
+ }
+ }
+ }
+ }
+ }
+
+ SECTION("ill-formed: wrong third byte")
+ {
+ for (int byte1 = 0xF4; byte1 <= 0xF4; ++byte1)
+ {
+ for (int byte2 = 0x80; byte2 <= 0x8F; ++byte2)
+ {
+ for (int byte3 = 0x00; byte3 <= 0xFF; ++byte3)
+ {
+ // skip correct third byte
+ if (0x80 <= byte3 && byte3 <= 0xBF)
+ {
+ continue;
+ }
+
+ for (int byte4 = 0x80; byte4 <= 0xBF; ++byte4)
+ {
+ check_utf8string(false, byte1, byte2, byte3, byte4);
+ check_utf8dump(false, byte1, byte2, byte3, byte4);
+ }
+ }
+ }
+ }
+ }
+
+ SECTION("ill-formed: wrong fourth byte")
+ {
+ for (int byte1 = 0xF4; byte1 <= 0xF4; ++byte1)
+ {
+ for (int byte2 = 0x80; byte2 <= 0x8F; ++byte2)
+ {
+ for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
+ {
+ for (int byte4 = 0x00; byte4 <= 0xFF; ++byte4)
+ {
+ // skip correct fourth byte
+ if (0x80 <= byte3 && byte3 <= 0xBF)
+ {
+ continue;
+ }
+
+ check_utf8string(false, byte1, byte2, byte3, byte4);
+ check_utf8dump(false, byte1, byte2, byte3, byte4);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+DOCTEST_CLANG_SUPPRESS_WARNING_POP
diff --git a/json4cpp/tests/src/unit-user_defined_input.cpp b/json4cpp/tests/src/unit-user_defined_input.cpp
new file mode 100644
index 0000000000..56446ea94c
--- /dev/null
+++ b/json4cpp/tests/src/unit-user_defined_input.cpp
@@ -0,0 +1,130 @@
+// __ _____ _____ _____
+// __| | __| | | | 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;
+
+#include <list>
+
+namespace
+{
+TEST_CASE("Use arbitrary stdlib container")
+{
+ std::string raw_data = "[1,2,3,4]";
+ std::list<char> data(raw_data.begin(), raw_data.end());
+
+ json as_json = json::parse(data.begin(), data.end());
+ CHECK(as_json.at(0) == 1);
+ CHECK(as_json.at(1) == 2);
+ CHECK(as_json.at(2) == 3);
+ CHECK(as_json.at(3) == 4);
+}
+
+struct MyContainer
+{
+ const char* data;
+};
+
+const char* begin(const MyContainer& c)
+{
+ return c.data;
+}
+
+const char* end(const MyContainer& c)
+{
+ return c.data + strlen(c.data); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+}
+
+TEST_CASE("Custom container non-member begin/end")
+{
+
+ const MyContainer data{"[1,2,3,4]"};
+ json as_json = json::parse(data);
+ CHECK(as_json.at(0) == 1);
+ CHECK(as_json.at(1) == 2);
+ CHECK(as_json.at(2) == 3);
+ CHECK(as_json.at(3) == 4);
+
+}
+
+TEST_CASE("Custom container member begin/end")
+{
+ struct MyContainer2
+ {
+ const char* data;
+
+ const char* begin() const noexcept
+ {
+ return data;
+ }
+
+ const char* end() const noexcept
+ {
+ return data + strlen(data); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ }
+ };
+
+ const MyContainer2 data{"[1,2,3,4]"};
+ json as_json = json::parse(data);
+ CHECK(as_json.at(0) == 1);
+ CHECK(as_json.at(1) == 2);
+ CHECK(as_json.at(2) == 3);
+ CHECK(as_json.at(3) == 4);
+}
+
+TEST_CASE("Custom iterator")
+{
+ const char* raw_data = "[1,2,3,4]";
+
+ struct MyIterator
+ {
+ using difference_type = std::size_t;
+ using value_type = char;
+ using pointer = const char*;
+ using reference = const char&;
+ using iterator_category = std::input_iterator_tag;
+
+ MyIterator& operator++()
+ {
+ ++ptr; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ return *this;
+ }
+
+ reference operator*() const
+ {
+ return *ptr;
+ }
+
+ bool operator!=(const MyIterator& rhs) const
+ {
+ return ptr != rhs.ptr;
+ }
+
+ const char* ptr;
+ };
+
+ // avoid -Wunused-local-typedefs
+ CHECK(std::is_same<MyIterator::difference_type, std::size_t>::value);
+ CHECK(std::is_same<MyIterator::value_type, char>::value);
+ CHECK(std::is_same<MyIterator::pointer, const char*>::value);
+ CHECK(std::is_same<MyIterator::reference, const char&>::value);
+ CHECK(std::is_same<MyIterator::iterator_category, std::input_iterator_tag>::value);
+
+ const MyIterator begin{raw_data};
+ const MyIterator end{raw_data + strlen(raw_data)}; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+
+ json as_json = json::parse(begin, end);
+ CHECK(as_json.at(0) == 1);
+ CHECK(as_json.at(1) == 2);
+ CHECK(as_json.at(2) == 3);
+ CHECK(as_json.at(3) == 4);
+}
+
+} // namespace
diff --git a/json4cpp/tests/src/unit-windows_h.cpp b/json4cpp/tests/src/unit-windows_h.cpp
new file mode 100644
index 0000000000..a5c5603410
--- /dev/null
+++ b/json4cpp/tests/src/unit-windows_h.cpp
@@ -0,0 +1,23 @@
+// __ _____ _____ _____
+// __| | __| | | | 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"
+#undef WIN32_LEAN_AND_MEAN
+#undef NOMINMAX
+
+#ifdef _WIN32
+ #include <windows.h>
+#endif
+
+#include <nlohmann/json.hpp>
+using nlohmann::json;
+
+TEST_CASE("include windows.h")
+{
+ CHECK(true);
+}
diff --git a/json4cpp/tests/src/unit-wstring.cpp b/json4cpp/tests/src/unit-wstring.cpp
new file mode 100644
index 0000000000..dec9c1f9a7
--- /dev/null
+++ b/json4cpp/tests/src/unit-wstring.cpp
@@ -0,0 +1,99 @@
+// __ _____ _____ _____
+// __| | __| | | | 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;
+
+// ICPC errors out on multibyte character sequences in source files
+#ifndef __INTEL_COMPILER
+namespace
+{
+bool wstring_is_utf16();
+bool wstring_is_utf16()
+{
+ return (std::wstring(L"💩") == std::wstring(L"\U0001F4A9"));
+}
+
+bool u16string_is_utf16();
+bool u16string_is_utf16()
+{
+ return (std::u16string(u"💩") == std::u16string(u"\U0001F4A9"));
+}
+
+bool u32string_is_utf32();
+bool u32string_is_utf32()
+{
+ return (std::u32string(U"💩") == std::u32string(U"\U0001F4A9"));
+}
+} // namespace
+
+TEST_CASE("wide strings")
+{
+ SECTION("std::wstring")
+ {
+ if (wstring_is_utf16())
+ {
+ std::wstring const w = L"[12.2,\"Ⴥaäö💤🧢\"]";
+ json const j = json::parse(w);
+ CHECK(j.dump() == "[12.2,\"Ⴥaäö💤🧢\"]");
+ }
+ }
+
+ SECTION("invalid std::wstring")
+ {
+ if (wstring_is_utf16())
+ {
+ std::wstring const w = L"\"\xDBFF";
+ json _;
+ CHECK_THROWS_AS(_ = json::parse(w), json::parse_error&);
+ }
+ }
+
+ SECTION("std::u16string")
+ {
+ if (u16string_is_utf16())
+ {
+ std::u16string const w = u"[12.2,\"Ⴥaäö💤🧢\"]";
+ json const j = json::parse(w);
+ CHECK(j.dump() == "[12.2,\"Ⴥaäö💤🧢\"]");
+ }
+ }
+
+ SECTION("invalid std::u16string")
+ {
+ if (wstring_is_utf16())
+ {
+ std::u16string const w = u"\"\xDBFF";
+ json _;
+ CHECK_THROWS_AS(_ = json::parse(w), json::parse_error&);
+ }
+ }
+
+ SECTION("std::u32string")
+ {
+ if (u32string_is_utf32())
+ {
+ std::u32string const w = U"[12.2,\"Ⴥaäö💤🧢\"]";
+ json const j = json::parse(w);
+ CHECK(j.dump() == "[12.2,\"Ⴥaäö💤🧢\"]");
+ }
+ }
+
+ SECTION("invalid std::u32string")
+ {
+ if (u32string_is_utf32())
+ {
+ std::u32string const w = U"\"\x110000";
+ json _;
+ CHECK_THROWS_AS(_ = json::parse(w), json::parse_error&);
+ }
+ }
+}
+#endif
diff --git a/json4cpp/tests/src/unit.cpp b/json4cpp/tests/src/unit.cpp
new file mode 100644
index 0000000000..93a934005b
--- /dev/null
+++ b/json4cpp/tests/src/unit.cpp
@@ -0,0 +1,10 @@
+// __ _____ _____ _____
+// __| | __| | | | 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
+
+#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
+#include "doctest_compatibility.h"