| // __ _____ _____ _____ | |
| // __| | __| | | | 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 | |
| // capture whether JSON_DELETE_DEPRECATED_FUNCTIONS was enabled on the command | |
| // line *before* including json.hpp, since the library #undefs it once the header | |
| // has been fully processed (see include/nlohmann/detail/macro_unscope.hpp); the | |
| // tests of deprecated functions are skipped if these functions are deleted | |
| #if defined(JSON_DELETE_DEPRECATED_FUNCTIONS) && (JSON_DELETE_DEPRECATED_FUNCTIONS == 1) | |
| #define JSON_TEST_DEPRECATED_FUNCTIONS_DELETED | |
| #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> | |
| #include <vector> | |
| #include "sax_event_loggers.hpp" | |
| using utils::SaxEventLogger; | |
| using utils::SaxEventLoggerExitAfterKey; | |
| using utils::SaxEventLoggerExitAfterStartArray; | |
| using utils::SaxEventLoggerExitAfterStartObject; | |
| namespace | |
| { | |
| 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()" | |
| })); | |
| } | |
| #ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED | |
| 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}}})); | |
| } | |
| #endif | |
| 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&); | |
| } | |
| #ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED | |
| 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&); | |
| } | |
| #endif | |
| 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; | |
| s >> j; | |
| CHECK(j == 123); | |
| s >> j; | |
| 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); | |
| } | |
| namespace | |
| { | |
| // records the position and message of a parse error; reporting the error via | |
| // SAX does not throw, so the test also runs with JSON_NOEXCEPTION | |
| struct SaxErrorRecorder : public SaxEventLogger | |
| { | |
| bool parse_error(std::size_t position, const std::string& /*last_token*/, const json::exception& ex) override | |
| { | |
| error = std::to_string(position) + " " + ex.what(); | |
| return false; | |
| } | |
| std::string error {}; // NOLINT(readability-redundant-member-init) | |
| }; | |
| } // namespace | |
| TEST_CASE("parse error positions are the same for contiguous and stream input") | |
| { | |
| // contiguous input derives the position from the input on error, stream | |
| // input counts it while reading (see lexer::lazy_position); the cases | |
| // cover newlines before the error, ungetting a newline after a number, | |
| // newlines inside a string, a comment, and reads at the end of input | |
| struct test_case | |
| { | |
| std::string input; | |
| std::size_t byte; | |
| std::string message; | |
| }; | |
| const std::vector<test_case> cases = | |
| { | |
| {"[\n 1,\n 2\n", 12, "[json.exception.parse_error.101] parse error at line 4, column 1: syntax error while parsing array - unexpected end of input; expected ']'"}, | |
| {"[1\n,2,\n]", 8, "[json.exception.parse_error.101] parse error at line 3, column 1: syntax error while parsing value - unexpected ']'; expected '[', '{', or a literal"}, | |
| {"[1\n x]", 5, "[json.exception.parse_error.101] parse error at line 2, column 2: syntax error while parsing array - invalid literal; last read: '1<U+000A> x'; expected ']'"}, | |
| {"1\n2", 3, "[json.exception.parse_error.101] parse error at line 2, column 1: syntax error while parsing value - unexpected number literal; expected end of input"}, | |
| { | |
| "{\"a\":\n\"b\nc\"}", 9, R"([json.exception.parse_error.101] parse error at line 3, column 0: syntax error while parsing value - invalid string: control character U+000A (LF) must be escaped to \u000A or \n; last read: '"b<U+000A>')" | |
| }, | |
| {"/* a\n b */\n x", 13, "[json.exception.parse_error.101] parse error at line 3, column 2: syntax error while parsing value - invalid literal; last read: '/* a<U+000A> b */<U+000A> x'"}, | |
| {"\n\n", 3, "[json.exception.parse_error.101] parse error at line 3, column 1: syntax error while parsing value - unexpected end of input; expected '[', '{', or a literal"}, | |
| {"[\"a\",\n 1e\n]", 10, "[json.exception.parse_error.101] parse error at line 3, column 0: syntax error while parsing value - invalid number; expected '+', '-', or digit after exponent; last read: '1e<U+000A>'"}, | |
| {"[tru\n]", 5, "[json.exception.parse_error.101] parse error at line 2, column 0: syntax error while parsing value - invalid literal; last read: '[tru<U+000A>'"}, | |
| }; | |
| for (const auto& c : cases) | |
| { | |
| CAPTURE(c.input) | |
| const std::string expected = std::to_string(c.byte) + " " + c.message; | |
| SaxErrorRecorder string_sax; | |
| CHECK_FALSE(json::sax_parse(c.input, &string_sax, json::input_format_t::json, true, true)); | |
| CHECK(string_sax.error == expected); | |
| SaxErrorRecorder pointer_sax; | |
| CHECK_FALSE(json::sax_parse(c.input.data(), c.input.data() + c.input.size(), &pointer_sax, json::input_format_t::json, true, true)); | |
| CHECK(pointer_sax.error == expected); | |
| std::istringstream is(c.input); | |
| SaxErrorRecorder stream_sax; | |
| CHECK_FALSE(json::sax_parse(is, &stream_sax, json::input_format_t::json, true, true)); | |
| CHECK(stream_sax.error == expected); | |
| } | |
| } | |
| #if !defined(JSON_NOEXCEPTION) | |
| TEST_CASE("indentation runs of every length around the 8-byte skip") | |
| { | |
| // contiguous input skips the spaces after a newline 8 bytes at a time (see | |
| // lexer::skip_indentation), stream input reads them one by one; results, | |
| // byte offsets, and messages must be the same for runs that end anywhere | |
| // relative to a word boundary, including at the end of the input | |
| const auto parse_result = [](const std::string & input, bool contiguous) | |
| { | |
| try | |
| { | |
| if (contiguous) | |
| { | |
| return json::parse(input.data(), input.data() + input.size(), nullptr, true, true).dump(); | |
| } | |
| std::istringstream is(input); | |
| return json::parse(is, nullptr, true, true).dump(); | |
| } | |
| catch (const json::parse_error& e) | |
| { | |
| return std::to_string(e.byte).append(" ").append(e.what()); | |
| } | |
| }; | |
| // replace every '~' in a pattern by an indentation run | |
| const auto indented = [](const std::string & pattern, const std::string & indent) | |
| { | |
| std::string result; | |
| for (const char c : pattern) | |
| { | |
| if (c == '~') | |
| { | |
| result += indent; | |
| } | |
| else | |
| { | |
| result += c; | |
| } | |
| } | |
| return result; | |
| }; | |
| const std::vector<std::string> patterns = | |
| { | |
| "[\n~1,\n~2\n]", | |
| "[\n~1,\n~x]", | |
| "[\n~1,\n~", | |
| "1\n~", | |
| "[\n~\t1,\n~\r\n~2]", | |
| "[\n~/* c */ 1]", | |
| "[\n~1\n~2]", | |
| }; | |
| for (std::size_t n = 0; n <= 33; ++n) | |
| { | |
| const std::string indent(n, ' '); | |
| for (const auto& pattern : patterns) | |
| { | |
| const std::string input = indented(pattern, indent); | |
| CAPTURE(n) | |
| CAPTURE(input) | |
| const std::string expected = parse_result(input, false); | |
| CHECK(parse_result(input, true) == expected); | |
| CHECK(json::accept(input.data(), input.data() + input.size(), true) == json::accept(input, true)); | |
| } | |
| } | |
| // and against fixed expectations, in case both paths broke the same way | |
| CHECK(parse_result(indented("[\n~x]", std::string(16, ' ')), true) == "19 [json.exception.parse_error.101] parse error at line 2, column 17: syntax error while parsing value - invalid literal; last read: '[<U+000A> x'"); | |
| CHECK(parse_result(indented("[\n~1]", std::string(17, ' ')), true) == "[1]"); | |
| } | |
| #endif |