diff options
Diffstat (limited to 'json4cpp/tests/src/unit-deserialization.cpp')
| -rw-r--r-- | json4cpp/tests/src/unit-deserialization.cpp | 1236 |
1 files changed, 1236 insertions, 0 deletions
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); +} |
