mirror of
https://github.com/nlohmann/json.git
synced 2026-10-01 22:45:17 +00:00
* Run the README test case in JSON_FastTests jobs The "README" test case was marked doctest::skip() when the tests moved from Catch to doctest in 2019, where it replaced Catch's hidden tag. It is not slow (17 assertions, about 0.00 s), but cmake/test.cmake only passes --no-skip when JSON_FastTests is off, so the per-compiler ci_test_*_cxxNN matrix, macOS, Windows Release/ARM, icpc, icpx and nvhpc compiled the README examples without running them. Drop the skip decorator so every job runs the case. Test-only change. Part of #5713 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Remove stale clang ranges guards in unit-iterators2.cpp The "algorithms" and "views" sections were guarded by clang/libstdc++ checks written for a clang 15 (04/2022) bug. The first guard's condition contradicts its own comment: it skips clang+libc++ and keeps clang+libstdc++. Both sections already sit inside `#if JSON_HAS_RANGES`, which macro_scope.hpp excludes for the toolchains these guards targeted, so the inner guards never let the sections run on the platforms they meant to protect and are redundant on the rest. Verified locally with Apple clang 21/libc++ and clang 16.0.6/libstdc++ 12 (Docker): both pass all 1355 assertions with the guards removed. Part of #5713 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix copy-pasted CBOR half-float checks; enable stale encode checks In the RFC 8949 Appendix A test case, the decode checks for 5.960464477539063e-8 (0xf9 0x00 0x01) and 0.00006103515625 (0xf9 0x04 0x00) were copy-pasted from the neighboring -4.0 example, so those two half-float byte sequences were never actually decoded and checked, and -4.0 was checked three times instead. The two float32 encode checks for 100000.0 and 3.4028234663852886e+38 were commented out before the writer supported emitting float32 and are now verified to match byte for byte, so they are enabled. The remaining commented-out half-precision to_cbor checks are collapsed into a single explanatory comment, since the writer never emits half-precision floats. Part of #5713 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Assert on the result of STL container conversions in tests The "object-like STL containers" and "array-like STL containers" sections converted json values into std::map, unordered_map, multimap, unordered_multimap, list, forward_list, array, valarray, vector, deque, set and unordered_set and discarded the result, so these ~60 conversions only proved that the code compiles and does not throw; a conversion that dropped or reordered elements would still pass. Bind each result and compare it against the expected container. Also fix a copy-paste slip in the deque section (`j2.get<std::deque<double>>()` instead of j3, so j3's doubles were never converted to a deque), and remove the dead `// CHECK(m5["one"] == "eins")` comments that referred to a variable that did not exist by asserting the equivalent through the bound result. Part of #5713 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Deduplicate SaxCountdown and other test helpers across formats SaxCountdown was copied byte-for-byte into six binary-format test files (unit-cbor.cpp, unit-msgpack.cpp, unit-ubjson.cpp, unit-bjdata.cpp, unit-bon8.cpp, unit-bson.cpp), about 370 redundant lines. Move it into tests/src/sax_countdown.hpp (namespace utils, alongside test_utils.hpp and round_trip_corpus.hpp) and include it from all six. trait_test_arg and the "value_in_range_of trait" TEST_CASE_TEMPLATE_DEFINE were duplicated between unit-32bit.cpp and unit-bjdata.cpp; the trait is a detail/meta trait, not specific to either file. Move it into tests/src/value_in_range_of_test.hpp; unit-32bit.cpp keeps its own include, since JSON_32bitTest=ONLY builds only that file. Each file keeps its own TEST_CASE_TEMPLATE_INVOKE list. sax_no_exception and the "issue #2824" section were duplicated in unit-regression2.cpp and unit-disabled_exceptions.cpp. Drop the copy from unit-regression2.cpp; unit-disabled_exceptions.cpp already covers the no-exceptions case that #2824 was about, and ci_test_noexceptions reruns it. No behavior change. Verified by building and running unit-cbor, unit-msgpack, unit-ubjson, unit-bjdata, unit-bon8, unit-bson, unit-32bit, unit-regression2 and unit-disabled_exceptions against include/ (clang++ -std=c++11, ASan/UBSan where applicable); assertion counts are unchanged from before the refactor. Part of #5714 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Remove the unreferenced vendored libFuzzer tests/thirdparty/Fuzzer (155 files, ~776 KB of vendored Apache-2.0 LLVM code from the 2016 OSS-Fuzz import) is not referenced by any CMakeLists, Makefile or workflow: the fuzz drivers link against -fsanitize=fuzzer or the repo's own tests/src/fuzzer-driver_afl.cpp. Its vendored README only points at llvm.org's own libFuzzer docs. Being dead code, it also adds noise to the flawfinder code-scanning workflow, which scans the whole tree. Remove the directory and its .reuse/dep5 entry. Part of #5714 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Remove unreferenced 2016 benchmark and fuzz reports tests/reports (1.6 MB) holds AFL status pages and plots from 2016-08-29 and 2016-10-02, and a nativejson-benchmark snapshot from 2016 with links to rawgit.com, which shut down in 2019. Nothing references this directory: no doc, README section, script or workflow points at it, and it describes a ten-years-old, pre-2.0 snapshot of the library. Part of #5714 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Run the CBOR, MessagePack, BSON and BON8 round-trip invariants in CI tests/src/round_trip_corpus.hpp exists so that the byte-stability invariant the fuzzer drivers check also runs on a fixed corpus in CI, instead of only at OSS-Fuzz. So far only the UBJSON and BJData drivers had a matching unit test; the CBOR, MessagePack, BSON and BON8 drivers assert the same invariant (assert(to_X(j2) == vec)) but nothing ran it outside OSS-Fuzz. Add "<FORMAT> round-trip invariants" test cases to unit-cbor.cpp, unit-msgpack.cpp, unit-bson.cpp and unit-bon8.cpp, modeled on the UBJSON case: seed j1 from the corpus (skipping values that do not survive the format's own round trip, as the fuzzer drivers only ever see values from_X() actually produced), then require from_X(to_X(j1)) not to throw and check to_X(j2) == to_X(j1). BSON only serializes objects, so non-object corpus values are skipped. Update the comments in round_trip_corpus.hpp and tests/fuzzing.md to name all six formats. The stream-versus-contiguous check in the BON8 driver is left out, as #5601 reworks it. A local probe confirms no violations on the current corpus (CBOR 3849 checked, MessagePack 3909, BSON 2958, BON8 3841 - matching the counts already recorded for this probe in the issue). Closes #5714 item 1. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix fuzzer driver step lists to match the checks the code performs The header comment of six of the seven binary-format fuzzer drivers listed an invariant the code does not check: CBOR, MessagePack, BSON and BON8 said "assert(j1 == j2)", but the code checks byte stability, assert(to_X(j2) == vec). UBJSON and BJData still described the old "assert(j1 == j2/j3/j4)" byte-exact check from before PR #5494 replaced it with a use_size/use_type-aware round trip (UBJSON) and a value-stability check (BJData); BJData's added paragraph already explained the new check, but the step list above it did not. Also remove a dead branch in fuzzer-parse_bson.cpp: from_bson() is called with allow_exceptions = true, so it throws instead of returning a discarded value, and the "if (j1.is_discarded()) return 0;" guard could never trigger. Drop the unused <iostream> include from all seven drivers and <sstream> from all but fuzzer-parse_bon8.cpp, which is the only one that uses std::istringstream. Overlaps #5601, which edits all seven drivers in the same hunks. Closes #5714 item 4. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Silence the CMP0169 deprecation in cmake_fetch_content, fix stale guards tests/cmake_fetch_content/project calls the single-argument FetchContent_Populate(json) after FetchContent_Declare(), which CMake 3.30 deprecated as CMP0169. Since the project declares cmake_minimum_required(VERSION 3.11...3.14), the policy stays unset, so every configure with a current CMake prints the deprecation warning. The test is kept on purpose: it is the only coverage of the FetchContent_Populate + add_subdirectory pattern for CMake 3.11-3.13 users, which the docs still describe as supported. Explicitly set CMP0169 to OLD, with a comment explaining why. Also fix two stale version guards: - tests/cmake_fetch_content/CMakeLists.txt guarded the test with VERSION_GREATER "3.11.0", which is dead now that tests/CMakeLists.txt requires CMake 3.13. - tests/cmake_fetch_content2/CMakeLists.txt guarded with VERSION_GREATER "3.14.0", which skips exactly 3.14.0, the first version with FetchContent_MakeAvailable. Change it to VERSION_GREATER_EQUAL "3.14". Verified locally: `ctest -R cmake_fetch_content` passes with CMake 4.1, and the CMP0169 deprecation warning that appeared before this change is gone. Closes #5714 item 5. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Make the CMake integration-test wrappers consistent The six tests/cmake_* integration-test wrappers had drifted: - Only cmake_import and cmake_import_minver forwarded -A "${CMAKE_GENERATOR_PLATFORM}" to the inner configure, and none forwarded -T "${CMAKE_GENERATOR_TOOLSET}". The Windows workflow configures the outer build with -A Win32 -T ClangCL, so without forwarding, the inner projects of cmake_add_subdirectory, cmake_fetch_content, cmake_fetch_content2 and cmake_target_include_directories built with the generator defaults instead of matching the outer build's platform and toolset. Forward both consistently from all six wrappers. - cmake_fetch_content and cmake_fetch_content2 passed -Dnlohmann_json_source to their inner projects, which never read it (CMake warns "manually-specified variables were not used"); the inner projects fetch their own copy of the library instead. Drop it. - tests/CMakeLists.txt set JSON_FORCED_GLOBAL_COMPILE_OPTIONS from the matching environment variable but never read the cache variable again; the lines right below it read $ENV{JSON_FORCED_GLOBAL_COMPILE_OPTIONS} directly, like the LINK_OPTIONS counterpart already does. Remove the dead set(). This changes which platform and toolset the Win32 and ClangCL CI jobs build the four newly-forwarding wrappers' inner projects with, which may surface new failures there; CI has to confirm those jobs. Verified locally with Ninja (empty -A ""/-T "" is accepted): all 12 cmake_* tests still pass, and the inner fetch_content configures no longer warn about the unused variable. Closes #5714 item 6. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Turn the #972 fifo_map regression test into a real test The #972 regression test in unit-regression1.cpp only built a my_json array from a string literal (the original crash) and had no CHECK, so the fifo_map object type it exists to demonstrate was never exercised. Meanwhile the docs recommend fifo_map for keeping object keys in insertion order (object_order.md, template_parameters.md), and nothing tested that recommendation. Extend the section: after the original array assignment, parse an object with my_json::parse() (not via the "..."_json UDL, which returns a plain nlohmann::json and would exercise the cross-basic_json conversion constructor instead of the parser's own key insertion - and, as tried locally, does not keep fifo order for this stateful comparator) and check that dump() keeps insertion order, and that it survives erase() and inserting a new key. Also narrow thirdparty/fifo_map off the include path of every other test-* target: it was a PUBLIC include directory of test_main, even though unit-regression1.cpp is its only user. Add a small fifo_map_include INTERFACE library with that include directory and attach it to test-regression1 only via json_test_set_test_options(). Verified locally (test-regression1_cpp11, default build and -fsanitize=address,undefined): the new checks pass; `git grep fifo_map tests` still only finds unit-regression1.cpp and the vendored header. Closes #5714 item 7. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Document the vendored doctest.h patch; fix stale doctest_compatibility.h comments tests/thirdparty/doctest/doctest.h is doctest 2.4.12, imported in #4771. Two weeks later, #4801 hand-edited translateActiveException() to declare "String res;" inside the translator loop instead of before it, so a translator that does not match does not leave a previous translator's result in "res" for the next iteration to see. Nothing recorded this, so re-vendoring doctest.h from upstream would silently drop the fix. Add a comment at the patched site naming the version, the PR and the reason, so a future re-vendor knows to re-apply it. Also fix two stale comments in doctest_compatibility.h: - The DOCTEST_THREAD_LOCAL comment referenced Xcode 6/7, which is no longer supported; reword it to explain why the define must stay regardless (it keeps doctest's own thread_local usage out of the way of the same Clang/MinGW crash that JSON_NO_THREAD_LOCAL works around in the library, see ci_test_no_thread_local). - The <iosfwd> include's comment justified it with tests that define "private" as "public"; no test under tests/src does that any more (removed by #2352). Reword the comment instead of dropping the include, since confirming it is safe to drop needs the full CI matrix including MSVC 2015+. Verified locally that tests/src/unit-readme.cpp still builds and passes 17/17 with these headers. Closes #5714 item 9. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Stop compiling unit-wstring.cpp out entirely on classic ICC tests/src/unit-wstring.cpp wrapped the whole file in #ifndef __INTEL_COMPILER, with the comment "ICPC errors out on multibyte character sequences in source files". The ci_icpc job (intel/oneapi-hpckit:2023.2.1) still exists, so that job ran none of the wstring/u16string/u32string input adapter tests, including the malformed-input checks #5704 (open) extends. Only 9 lines contained non-ASCII bytes: the three *_is_utf16()/ *_is_utf32() probe functions, and three std::wstring/u16string/ u32string literals plus their narrow-string dump() expectations. Rewrite all of them with \u/\U escapes in the wide/u16/u32 literals and \x escapes (split into separate string-literal tokens so a following byte is never read as part of the same hex escape, e.g. "\xE1\x83\x85" "a") in the narrow ones. Remove the #ifndef __INTEL_COMPILER/#endif guard along with it. The *_is_utf16()/*_is_utf32() probes compared a raw multibyte literal against an escape-based one to detect a compiler that misreads the source file's encoding; with no raw literals left to misread, the comparison is now tautological, so drop the probes and the "if" guards around each SECTION's body instead of leaving them in as dead checks. The same non-ASCII-in-source-and-in-a-narrow-comparison pattern existed once more in unit-deserialization.cpp's "Using _json with char8_t literals #4945" test: a raw emoji character in a u8R"(...)" literal, guarded by a check_utf8() that returned false for ICC (same reason) and for Windows without the active UTF-8 code page. Rewrite the literal with a \U escape and compare it against a \x-escaped expectation instead of a second raw literal, and drop check_utf8() and the now-unused <windows.h> include along with the guard. Verified locally (clang, -std=c++11 and -std=c++20, -fsanitize=address,undefined, and a plain build): test-wstring keeps 18/18 assertions and unit-deserialization keeps 466/466 (c++11) and 477/477 (c++20) assertions, matching this branch before the change exactly - no coverage was gained or lost, only the source-encoding dependency was removed. ci_icpc has to confirm classic ICC actually builds and passes test-wstring now; if it does not, that is a real finding, not a reason to restore the guard. Overlaps #5704 (open), which edits unit-wstring.cpp inside the previously-guarded region (an include near the top, checks in the invalid-string sections, and a new section at the end). Closes #5713 item 5. Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
1381 lines
53 KiB
C++
1381 lines
53 KiB
C++
// __ _____ _____ _____
|
|
// __| | __| | | | JSON for Modern C++ (supporting code)
|
|
// | | |__ | | | | | | version 3.12.0
|
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
//
|
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#include "doctest_compatibility.h"
|
|
|
|
// capture whether JSON_STRICT_NUL_HANDLING was enabled on the command line
|
|
// (e.g. -DJSON_STRICT_NUL_HANDLING=1) *before* including json.hpp, since the
|
|
// library #undefs JSON_STRICT_NUL_HANDLING itself once the header has been
|
|
// fully processed (see include/nlohmann/detail/macro_unscope.hpp)
|
|
#if defined(JSON_STRICT_NUL_HANDLING) && (JSON_STRICT_NUL_HANDLING == 1)
|
|
#define JSON_TEST_STRICT_NUL_HANDLING_ENABLED 1
|
|
#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 <iostream>
|
|
#include <iterator>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <valarray>
|
|
|
|
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;
|
|
};
|
|
|
|
} // 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("operator>> with a NUL byte after the value (issue #5530)")
|
|
{
|
|
// operator>> parses non-strictly (it does not require the whole
|
|
// stream to be consumed), so a NUL byte following a complete
|
|
// value is simply left unread on the stream and never reaches
|
|
// the "expected end of input" check that JSON_STRICT_NUL_HANDLING
|
|
// affects; this holds regardless of the macro (verified below for
|
|
// the opt-in state as well)
|
|
std::string data = "123";
|
|
data.push_back('\0');
|
|
std::istringstream ss(data);
|
|
json j;
|
|
ss >> j;
|
|
CHECK(j == json(123));
|
|
CHECK(ss.good());
|
|
}
|
|
|
|
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("stream with eofbit in its exception mask (issue #5646)")
|
|
{
|
|
// reaching EOF while parsing a value that fills the whole input
|
|
// (e.g., a number, or any value under strict parsing) makes
|
|
// get_character() call std::istream::clear() to record eofbit;
|
|
// with eofbit in the exception mask, that clear() itself throws
|
|
// std::ios_base::failure - it must propagate to the caller instead
|
|
// of ~input_stream_adapter() throwing a second exception while the
|
|
// first is still unwinding, which would call std::terminate
|
|
json _;
|
|
|
|
std::istringstream is1("1");
|
|
is1.exceptions(std::ios::eofbit);
|
|
CHECK_THROWS_AS(_ = json::parse(is1), std::ios_base::failure&);
|
|
|
|
// the same holds for the common std::ifstream::exceptions(failbit |
|
|
// badbit | eofbit) pattern, because only eofbit ends up set
|
|
std::istringstream is2("1");
|
|
is2.exceptions(std::ios::failbit | std::ios::badbit | std::ios::eofbit);
|
|
CHECK_THROWS_AS(_ = json::parse(is2), std::ios_base::failure&);
|
|
}
|
|
|
|
SECTION("stream without a streambuf (issue #5646)")
|
|
{
|
|
// std::istream(nullptr) has badbit set and rdbuf() == nullptr;
|
|
// get_character() must not dereference that null streambuf
|
|
std::istream is(nullptr);
|
|
json _;
|
|
CHECK_THROWS_WITH_AS(_ = json::parse(is), "[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("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&);
|
|
}
|
|
|
|
#if defined(JSON_TEST_STRICT_NUL_HANDLING_ENABLED)
|
|
SECTION("operator>> with a NUL byte where a value is expected (JSON_STRICT_NUL_HANDLING == 1, issue #5530)")
|
|
{
|
|
// a trailing NUL byte *after* a complete value is unaffected by the
|
|
// macro (see the successful-deserialization "operator>> with a NUL
|
|
// byte after the value" section above): operator>> parses
|
|
// non-strictly and never reaches the "expected end of input" check
|
|
// that the macro changes. A NUL byte where a *value* is expected,
|
|
// however, goes through the same token dispatch as any other input
|
|
// and is affected: with the macro enabled it now raises
|
|
// parse_error.101 (like any other unrecognized byte) instead of
|
|
// being silently treated the same as an empty stream.
|
|
std::string const data(1, '\0');
|
|
std::istringstream ss(data);
|
|
json j;
|
|
CHECK_THROWS_WITH_AS(ss >> j,
|
|
"[json.exception.parse_error.101] parse error at line 1, column 1: syntax error while parsing value - invalid literal; last read: '<U+0000>'",
|
|
json::parse_error&);
|
|
}
|
|
#endif
|
|
|
|
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::vector<signed char>")
|
|
{
|
|
std::vector<signed char> 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)"}));
|
|
|
|
// bytes outside ASCII are negative here and must not be sign-extended;
|
|
// 0xC3 and 0xA9 do not fit in signed char (MSVC C4309), so spell them as negative values
|
|
std::vector<signed char> const umlaut = {'"', static_cast<signed char>(0xC3 - 0x100), static_cast<signed char>(0xA9 - 0x100), '"'};
|
|
CHECK(json::parse(umlaut) == json("\xC3\xA9"));
|
|
CHECK(json::accept(umlaut));
|
|
|
|
// 0xFF (spelled as -1 to stay in range) must not be reported as end of input
|
|
std::vector<signed char> const trailing = {'t', 'r', 'u', 'e', static_cast<signed char>(0xFF - 0x100)};
|
|
json _;
|
|
CHECK_THROWS_WITH_AS(_ = json::parse(trailing), "[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - invalid literal; last read: 'true\xFF'; expected end of input", json::parse_error&);
|
|
CHECK(!json::accept(trailing));
|
|
}
|
|
|
|
SECTION("from std::array")
|
|
{
|
|
// sized to exactly the length of "true": a size of 5 would leave
|
|
// a value-initialized trailing 0x00 element that is only
|
|
// silently accepted as end-of-input by default and would fail
|
|
// under JSON_STRICT_NUL_HANDLING
|
|
std::array<uint8_t, 4> 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")
|
|
{
|
|
// sized to exactly the length of "true", see the analogous
|
|
// "from std::array" section above for why
|
|
std::array<uint8_t, 4> 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);
|
|
}
|
|
}
|
|
|
|
SECTION("stream position after extraction without JSON_PRECISE_STREAM_POSITION (#5340)")
|
|
{
|
|
// By default, the character that terminates a number is consumed, so
|
|
// the stream is left one byte too far after a number (and only after a
|
|
// number). JSON_PRECISE_STREAM_POSITION changes this; see
|
|
// unit-precise-stream-position.cpp. These checks pin the default.
|
|
const auto remaining = [](std::istream & is) -> std::string
|
|
{
|
|
return {std::istreambuf_iterator<char>(is), std::istreambuf_iterator<char>()};
|
|
};
|
|
|
|
SECTION("the character after a number is consumed")
|
|
{
|
|
std::istringstream ss("1true");
|
|
json j;
|
|
ss >> j;
|
|
CHECK(j == 1);
|
|
CHECK(remaining(ss) == "rue");
|
|
}
|
|
|
|
SECTION("the character after other values is not consumed")
|
|
{
|
|
std::istringstream ss("[1]true");
|
|
json j;
|
|
ss >> j;
|
|
CHECK(j == json::parse("[1]"));
|
|
CHECK(remaining(ss) == "true");
|
|
}
|
|
|
|
SECTION("comma-separated numbers can be read one by one")
|
|
{
|
|
std::istringstream ss("1,2,3");
|
|
json j1;
|
|
json j2;
|
|
json j3;
|
|
ss >> j1 >> j2 >> j3;
|
|
CHECK(j1 == 1);
|
|
CHECK(j2 == 2);
|
|
CHECK(j3 == 3);
|
|
}
|
|
|
|
SECTION("std::getline after a number skips the line break")
|
|
{
|
|
std::istringstream ss("42\nfoo");
|
|
json j;
|
|
std::string line;
|
|
ss >> j;
|
|
std::getline(ss, line);
|
|
CHECK(j == 42);
|
|
CHECK(line == "foo");
|
|
}
|
|
}
|
|
|
|
// 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); the emoji is written as a
|
|
// \U escape rather than a raw multibyte character so this does not
|
|
// depend on the compiler's source-file encoding (e.g., MSVC without
|
|
// /utf-8, or classic ICC, which does not encode non-ASCII narrow
|
|
// string literals as UTF-8 - compare against a \x-escaped expectation
|
|
// for the same reason)
|
|
const auto j2 = u8"{\"emoji\": \"\U0001F600\", \"msg\": \"hello\"}"_json;
|
|
CHECK(j2["emoji"] == "\xF0\x9F\x98\x80");
|
|
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);
|
|
}
|