/* __ _____ _____ _____ __| | __| | | | JSON for Modern C++ (test suite) | | |__ | | | | | | version 3.7.0 |_____|_____|_____|_|___| https://github.com/nlohmann/json Licensed under the MIT License . SPDX-License-Identifier: MIT Copyright (c) 2013-2019 Niels Lohmann . Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "doctest_compatibility.h" #define private public #include using nlohmann::json; #undef private #include namespace { class SaxEventLogger { public: bool null() { events.push_back("null()"); return true; } bool boolean(bool val) { events.push_back(val ? "boolean(true)" : "boolean(false)"); return true; } bool number_integer(json::number_integer_t val) { events.push_back("number_integer(" + std::to_string(val) + ")"); return true; } bool number_unsigned(json::number_unsigned_t val) { events.push_back("number_unsigned(" + std::to_string(val) + ")"); return true; } bool number_float(json::number_float_t, const std::string& s) { events.push_back("number_float(" + s + ")"); return true; } bool string(std::string& val) { events.push_back("string(" + val + ")"); return true; } bool start_object(std::size_t elements) { if (elements == std::size_t(-1)) { events.push_back("start_object()"); } else { events.push_back("start_object(" + std::to_string(elements) + ")"); } return true; } bool key(std::string& val) { events.push_back("key(" + val + ")"); return true; } bool end_object() { events.push_back("end_object()"); return true; } bool start_array(std::size_t elements) { if (elements == std::size_t(-1)) { events.push_back("start_array()"); } else { events.push_back("start_array(" + std::to_string(elements) + ")"); } return true; } bool end_array() { events.push_back("end_array()"); return true; } bool parse_error(std::size_t position, const std::string&, const json::exception&) { errored = true; events.push_back("parse_error(" + std::to_string(position) + ")"); return false; } std::vector events {}; bool errored = false; }; class SaxCountdown : public nlohmann::json::json_sax_t { public: explicit SaxCountdown(const int count) : events_left(count) {} bool null() override { return events_left-- > 0; } bool boolean(bool) override { return events_left-- > 0; } bool number_integer(json::number_integer_t) override { return events_left-- > 0; } bool number_unsigned(json::number_unsigned_t) override { return events_left-- > 0; } bool number_float(json::number_float_t, const std::string&) override { return events_left-- > 0; } bool string(std::string&) override { return events_left-- > 0; } bool start_object(std::size_t) override { return events_left-- > 0; } bool key(std::string&) override { return events_left-- > 0; } bool end_object() override { return events_left-- > 0; } bool start_array(std::size_t) override { return events_left-- > 0; } bool end_array() override { return events_left-- > 0; } bool parse_error(std::size_t, const std::string&, const json::exception&) override { return false; } private: int events_left = 0; }; json parser_helper(const std::string& s); bool accept_helper(const std::string& s); json parser_helper(const std::string& s) { json j; json::parser(nlohmann::detail::input_adapter(s)).parse(true, j); // if this line was reached, no exception ocurred // -> check if result is the same without exceptions json j_nothrow; CHECK_NOTHROW(json::parser(nlohmann::detail::input_adapter(s), nullptr, false).parse(true, j_nothrow)); CHECK(j_nothrow == j); json j_sax; nlohmann::detail::json_sax_dom_parser sdp(j_sax); json::sax_parse(s, &sdp); CHECK(j_sax == j); return j; } bool accept_helper(const std::string& s) { CAPTURE(s) // 1. parse s without exceptions json j; CHECK_NOTHROW(json::parser(nlohmann::detail::input_adapter(s), nullptr, false).parse(true, j)); const bool ok_noexcept = not j.is_discarded(); // 2. accept s const bool ok_accept = json::parser(nlohmann::detail::input_adapter(s)).accept(true); // 3. check if both approaches come to the same result CHECK(ok_noexcept == ok_accept); // 4. parse with SAX (compare with relaxed accept result) SaxEventLogger el; CHECK_NOTHROW(json::sax_parse(s, &el, json::input_format_t::json, false)); CHECK(json::parser(nlohmann::detail::input_adapter(s)).accept(false) == not el.errored); // 5. parse with simple callback json::parser_callback_t cb = [](int, json::parse_event_t, json&) { return true; }; json j_cb = json::parse(s, cb, false); const bool ok_noexcept_cb = not j_cb.is_discarded(); // 6. check if this approach came to the same result CHECK(ok_noexcept == ok_noexcept_cb); // 7. return result return ok_accept; } } TEST_CASE("parser class") { SECTION("parse") { SECTION("null") { CHECK(parser_helper("null") == json(nullptr)); } SECTION("true") { CHECK(parser_helper("true") == json(true)); } SECTION("false") { CHECK(parser_helper("false") == json(false)); } SECTION("array") { SECTION("empty array") { CHECK(parser_helper("[]") == json(json::value_t::array)); CHECK(parser_helper("[ ]") == json(json::value_t::array)); } SECTION("nonempty array") { CHECK(parser_helper("[true, false, null]") == json({true, false, nullptr})); } } SECTION("object") { SECTION("empty object") { CHECK(parser_helper("{}") == json(json::value_t::object)); CHECK(parser_helper("{ }") == json(json::value_t::object)); } SECTION("nonempty object") { CHECK(parser_helper("{\"\": true, \"one\": 1, \"two\": null}") == json({{"", true}, {"one", 1}, {"two", nullptr}})); } } SECTION("string") { // empty string CHECK(parser_helper("\"\"") == json(json::value_t::string)); SECTION("errors") { // error: tab in string CHECK_THROWS_AS(parser_helper("\"\t\""), json::parse_error&); CHECK_THROWS_WITH(parser_helper("\"\t\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t; last read: '\"'"); // error: newline in string CHECK_THROWS_AS(parser_helper("\"\n\""), json::parse_error&); CHECK_THROWS_AS(parser_helper("\"\r\""), json::parse_error&); CHECK_THROWS_WITH(parser_helper("\"\n\""), "[json.exception.parse_error.101] parse error at line 2, column 0: syntax error while parsing value - invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n; last read: '\"'"); CHECK_THROWS_WITH(parser_helper("\"\r\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r; last read: '\"'"); // error: backspace in string CHECK_THROWS_AS(parser_helper("\"\b\""), json::parse_error&); CHECK_THROWS_WITH(parser_helper("\"\b\""), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b; last read: '\"'");