diff --git a/README.md b/README.md index 42d057a3..0699bdc3 100644 --- a/README.md +++ b/README.md @@ -1508,7 +1508,7 @@ The library supports **Unicode input** as follows: ### Comments in JSON -This library does not support comments. It does so for three reasons: +This library does not support comments by default. It does so for three reasons: 1. Comments are not part of the [JSON specification](https://tools.ietf.org/html/rfc8259). You may argue that `//` or `/* */` are allowed in JavaScript, but JSON is not JavaScript. 2. This was not an oversight: Douglas Crockford [wrote on this](https://plus.google.com/118095276221607585885/posts/RK8qyGVaGSr) in May 2012: @@ -1519,11 +1519,7 @@ This library does not support comments. It does so for three reasons: 3. It is dangerous for interoperability if some libraries would add comment support while others don't. Please check [The Harmful Consequences of the Robustness Principle](https://tools.ietf.org/html/draft-iab-protocol-maintenance-01) on this. -This library will not support comments in the future. If you wish to use comments, I see three options: - -1. Strip comments before using this library. -2. Use a different JSON library with comment support. -3. Use a format that natively supports comments (e.g., YAML or JSON5). +However, you can pass set parameter `ignore_comments` to true in the `parse` function to ignore `//` or `/* */` comments. Comments will then be treated as whitespace. ### Order of object keys diff --git a/include/nlohmann/detail/input/lexer.hpp b/include/nlohmann/detail/input/lexer.hpp index 6d188e47..8042f3c4 100644 --- a/include/nlohmann/detail/input/lexer.hpp +++ b/include/nlohmann/detail/input/lexer.hpp @@ -112,8 +112,11 @@ class lexer : public lexer_base public: using token_type = typename lexer_base::token_type; - explicit lexer(InputAdapterType&& adapter) - : ia(std::move(adapter)), decimal_point_char(static_cast(get_decimal_point())) {} + explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false) + : ia(std::move(adapter)) + , ignore_comments(ignore_comments_) + , decimal_point_char(static_cast(get_decimal_point())) + {} // delete because of pointer members lexer(const lexer&) = delete; @@ -826,6 +829,77 @@ class lexer : public lexer_base } } + /*! + * @brief scan a comment + * @return whether comment could be scanned successfully + */ + bool scan_comment() + { + switch (get()) + { + // single-line comments skip input until a newline or EOF is read + case '/': + { + while (true) + { + switch (get()) + { + case '\n': + case '\r': + case std::char_traits::eof(): + case '\0': + return true; + + default: + break; + } + } + } + + // multi-line comments skip input until */ is read + case '*': + { + while (true) + { + switch (get()) + { + case std::char_traits::eof(): + case '\0': + { + error_message = "invalid comment; missing closing '*/'"; + return false; + } + + case '*': + { + switch (get()) + { + case '/': + return true; + + default: + { + unget(); + break; + } + } + } + + default: + break; + } + } + } + + // unexpected character after reading '/' + default: + { + error_message = "invalid comment; expecting '/' or '*' after '/'"; + return false; + } + } + } + JSON_HEDLEY_NON_NULL(2) static void strtof(float& f, const char* str, char** endptr) noexcept { @@ -1415,6 +1489,15 @@ scan_number_done: return true; } + void skip_whitespace() + { + do + { + get(); + } + while (current == ' ' or current == '\t' or current == '\n' or current == '\r'); + } + token_type scan() { // initially, skip the BOM @@ -1425,11 +1508,19 @@ scan_number_done: } // read next character and ignore whitespace - do + skip_whitespace(); + + // ignore comments + if (ignore_comments and current == '/') { - get(); + if (not scan_comment()) + { + return token_type::parse_error; + } + + // skip following whitespace + skip_whitespace(); } - while (current == ' ' or current == '\t' or current == '\n' or current == '\r'); switch (current) { @@ -1499,6 +1590,9 @@ scan_number_done: /// input adapter InputAdapterType ia; + /// whether comments should be ignored (true) or signaled as errors (false) + const bool ignore_comments = false; + /// the current character char_int_type current = std::char_traits::eof(); diff --git a/include/nlohmann/detail/input/parser.hpp b/include/nlohmann/detail/input/parser.hpp index 0546b88c..c79b492a 100644 --- a/include/nlohmann/detail/input/parser.hpp +++ b/include/nlohmann/detail/input/parser.hpp @@ -63,8 +63,11 @@ class parser /// a parser reading from an input adapter explicit parser(InputAdapterType&& adapter, const parser_callback_t cb = nullptr, - const bool allow_exceptions_ = true) - : callback(cb), m_lexer(std::move(adapter)), allow_exceptions(allow_exceptions_) + const bool allow_exceptions_ = true, + const bool skip_comments = false) + : callback(cb) + , m_lexer(std::move(adapter), skip_comments) + , allow_exceptions(allow_exceptions_) { // read first token get_token(); diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 8f95a797..ace649b6 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -196,10 +196,12 @@ class basic_json static ::nlohmann::detail::parser parser( InputAdapterType adapter, detail::parser_callback_tcb = nullptr, - bool allow_exceptions = true + const bool allow_exceptions = true, + const bool ignore_comments = false ) { - return ::nlohmann::detail::parser(std::move(adapter), std::move(cb), allow_exceptions); + return ::nlohmann::detail::parser(std::move(adapter), + std::move(cb), allow_exceptions, ignore_comments); } using primitive_iterator_t = ::nlohmann::detail::primitive_iterator_t; @@ -6591,6 +6593,9 @@ class basic_json (optional) @param[in] allow_exceptions whether to throw exceptions in case of a parse error (optional, true by default) + @param[in] ignore_comments whether comments should be ignored and treated + like whitespace (true) or yield a parse error (true); (optional, false by + default) @return deserialized JSON value; in case of a parse error and @a allow_exceptions set to `false`, the return value will be @@ -6619,16 +6624,18 @@ class basic_json @liveexample{The example below demonstrates the `parse()` function reading from a contiguous container.,parse__contiguouscontainer__parser_callback_t} - @since version 2.0.3 (contiguous containers) + @since version 2.0.3 (contiguous containers); version 3.9.0 allowed to + ignore comments. */ template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json parse(InputType&& i, const parser_callback_t cb = nullptr, - const bool allow_exceptions = true) + const bool allow_exceptions = true, + const bool ignore_comments = false) { basic_json result; - parser(detail::input_adapter(std::forward(i)), cb, allow_exceptions).parse(true, result); + parser(detail::input_adapter(std::forward(i)), cb, allow_exceptions, ignore_comments).parse(true, result); return result; } @@ -6645,6 +6652,9 @@ class basic_json (optional) @param[in] allow_exceptions whether to throw exceptions in case of a parse error (optional, true by default) + @param[in] ignore_comments whether comments should be ignored and treated + like whitespace (true) or yield a parse error (true); (optional, false by + default) @return deserialized JSON value; in case of a parse error and @a allow_exceptions set to `false`, the return value will be @@ -6660,10 +6670,11 @@ class basic_json static basic_json parse(IteratorType first, IteratorType last, const parser_callback_t cb = nullptr, - const bool allow_exceptions = true) + const bool allow_exceptions = true, + const bool ignore_comments = false) { basic_json result; - parser(detail::input_adapter(std::move(first), std::move(last)), cb, allow_exceptions).parse(true, result); + parser(detail::input_adapter(std::move(first), std::move(last)), cb, allow_exceptions, ignore_comments).parse(true, result); return result; } @@ -6671,10 +6682,11 @@ class basic_json JSON_HEDLEY_DEPRECATED_FOR(3.8.0, parse(ptr, ptr + len)) static basic_json parse(detail::span_input_adapter&& i, const parser_callback_t cb = nullptr, - const bool allow_exceptions = true) + const bool allow_exceptions = true, + const bool ignore_comments = false) { basic_json result; - parser(i.get(), cb, allow_exceptions).parse(true, result); + parser(i.get(), cb, allow_exceptions, ignore_comments).parse(true, result); return result; } @@ -6694,6 +6706,9 @@ class basic_json iterators. @param[in] i input to read from + @param[in] ignore_comments whether comments should be ignored and treated + like whitespace (true) or yield a parse error (true); (optional, false by + default) @return Whether the input read from @a i is valid JSON. @@ -6706,22 +6721,25 @@ class basic_json from a string.,accept__string} */ template - static bool accept(InputType&& i) + static bool accept(InputType&& i, + const bool ignore_comments = false) { - return parser(detail::input_adapter(std::forward(i))).accept(true); + return parser(detail::input_adapter(std::forward(i)), nullptr, false, ignore_comments).accept(true); } template - static bool accept(IteratorType first, IteratorType last) + static bool accept(IteratorType first, IteratorType last, + const bool ignore_comments = false) { - return parser(detail::input_adapter(std::move(first), std::move(last))).accept(true); + return parser(detail::input_adapter(std::move(first), std::move(last)), nullptr, false, ignore_comments).accept(true); } JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, accept(ptr, ptr + len)) - static bool accept(detail::span_input_adapter&& i) + static bool accept(detail::span_input_adapter&& i, + const bool ignore_comments = false) { - return parser(i.get()).accept(true); + return parser(i.get(), nullptr, false, ignore_comments).accept(true); } /*! @@ -6741,6 +6759,9 @@ class basic_json @param[in,out] sax SAX event listener @param[in] format the format to parse (JSON, CBOR, MessagePack, or UBJSON) @param[in] strict whether the input has to be consumed completely + @param[in] ignore_comments whether comments should be ignored and treated + like whitespace (true) or yield a parse error (true); (optional, false by + default); only applies to the JSON file format. @return return value of the last processed SAX event @@ -6765,11 +6786,12 @@ class basic_json JSON_HEDLEY_NON_NULL(2) static bool sax_parse(InputType&& i, SAX* sax, input_format_t format = input_format_t::json, - const bool strict = true) + const bool strict = true, + const bool ignore_comments = false) { auto ia = detail::input_adapter(std::forward(i)); return format == input_format_t::json - ? parser(std::move(ia)).sax_parse(sax, strict) + ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); } @@ -6777,11 +6799,12 @@ class basic_json JSON_HEDLEY_NON_NULL(3) static bool sax_parse(IteratorType first, IteratorType last, SAX* sax, input_format_t format = input_format_t::json, - const bool strict = true) + const bool strict = true, + const bool ignore_comments = false) { auto ia = detail::input_adapter(std::move(first), std::move(last)); return format == input_format_t::json - ? parser(std::move(ia)).sax_parse(sax, strict) + ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); } @@ -6790,11 +6813,12 @@ class basic_json JSON_HEDLEY_NON_NULL(2) static bool sax_parse(detail::span_input_adapter&& i, SAX* sax, input_format_t format = input_format_t::json, - const bool strict = true) + const bool strict = true, + const bool ignore_comments = false) { auto ia = i.get(); return format == input_format_t::json - ? parser(std::move(ia)).sax_parse(sax, strict) + ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 06c897d2..770418f4 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -8183,8 +8183,11 @@ class lexer : public lexer_base public: using token_type = typename lexer_base::token_type; - explicit lexer(InputAdapterType&& adapter) - : ia(std::move(adapter)), decimal_point_char(static_cast(get_decimal_point())) {} + explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false) + : ia(std::move(adapter)) + , ignore_comments(ignore_comments_) + , decimal_point_char(static_cast(get_decimal_point())) + {} // delete because of pointer members lexer(const lexer&) = delete; @@ -8897,6 +8900,77 @@ class lexer : public lexer_base } } + /*! + * @brief scan a comment + * @return whether comment could be scanned successfully + */ + bool scan_comment() + { + switch (get()) + { + // single-line comments skip input until a newline or EOF is read + case '/': + { + while (true) + { + switch (get()) + { + case '\n': + case '\r': + case std::char_traits::eof(): + case '\0': + return true; + + default: + break; + } + } + } + + // multi-line comments skip input until */ is read + case '*': + { + while (true) + { + switch (get()) + { + case std::char_traits::eof(): + case '\0': + { + error_message = "invalid comment; missing closing '*/'"; + return false; + } + + case '*': + { + switch (get()) + { + case '/': + return true; + + default: + { + unget(); + break; + } + } + } + + default: + break; + } + } + } + + // unexpected character after reading '/' + default: + { + error_message = "invalid comment; expecting '/' or '*' after '/'"; + return false; + } + } + } + JSON_HEDLEY_NON_NULL(2) static void strtof(float& f, const char* str, char** endptr) noexcept { @@ -9486,6 +9560,15 @@ scan_number_done: return true; } + void skip_whitespace() + { + do + { + get(); + } + while (current == ' ' or current == '\t' or current == '\n' or current == '\r'); + } + token_type scan() { // initially, skip the BOM @@ -9496,11 +9579,19 @@ scan_number_done: } // read next character and ignore whitespace - do + skip_whitespace(); + + // ignore comments + if (ignore_comments and current == '/') { - get(); + if (not scan_comment()) + { + return token_type::parse_error; + } + + // skip following whitespace + skip_whitespace(); } - while (current == ' ' or current == '\t' or current == '\n' or current == '\r'); switch (current) { @@ -9570,6 +9661,9 @@ scan_number_done: /// input adapter InputAdapterType ia; + /// whether comments should be ignored (true) or signaled as errors (false) + const bool ignore_comments = false; + /// the current character char_int_type current = std::char_traits::eof(); @@ -9672,8 +9766,11 @@ class parser /// a parser reading from an input adapter explicit parser(InputAdapterType&& adapter, const parser_callback_t cb = nullptr, - const bool allow_exceptions_ = true) - : callback(cb), m_lexer(std::move(adapter)), allow_exceptions(allow_exceptions_) + const bool allow_exceptions_ = true, + const bool skip_comments = false) + : callback(cb) + , m_lexer(std::move(adapter), skip_comments) + , allow_exceptions(allow_exceptions_) { // read first token get_token(); @@ -15994,10 +16091,12 @@ class basic_json static ::nlohmann::detail::parser parser( InputAdapterType adapter, detail::parser_callback_tcb = nullptr, - bool allow_exceptions = true + const bool allow_exceptions = true, + const bool ignore_comments = false ) { - return ::nlohmann::detail::parser(std::move(adapter), std::move(cb), allow_exceptions); + return ::nlohmann::detail::parser(std::move(adapter), + std::move(cb), allow_exceptions, ignore_comments); } using primitive_iterator_t = ::nlohmann::detail::primitive_iterator_t; @@ -22389,6 +22488,9 @@ class basic_json (optional) @param[in] allow_exceptions whether to throw exceptions in case of a parse error (optional, true by default) + @param[in] ignore_comments whether comments should be ignored and treated + like whitespace (true) or yield a parse error (true); (optional, false by + default) @return deserialized JSON value; in case of a parse error and @a allow_exceptions set to `false`, the return value will be @@ -22417,16 +22519,18 @@ class basic_json @liveexample{The example below demonstrates the `parse()` function reading from a contiguous container.,parse__contiguouscontainer__parser_callback_t} - @since version 2.0.3 (contiguous containers) + @since version 2.0.3 (contiguous containers); version 3.9.0 allowed to + ignore comments. */ template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json parse(InputType&& i, const parser_callback_t cb = nullptr, - const bool allow_exceptions = true) + const bool allow_exceptions = true, + const bool ignore_comments = false) { basic_json result; - parser(detail::input_adapter(std::forward(i)), cb, allow_exceptions).parse(true, result); + parser(detail::input_adapter(std::forward(i)), cb, allow_exceptions, ignore_comments).parse(true, result); return result; } @@ -22443,6 +22547,9 @@ class basic_json (optional) @param[in] allow_exceptions whether to throw exceptions in case of a parse error (optional, true by default) + @param[in] ignore_comments whether comments should be ignored and treated + like whitespace (true) or yield a parse error (true); (optional, false by + default) @return deserialized JSON value; in case of a parse error and @a allow_exceptions set to `false`, the return value will be @@ -22458,10 +22565,11 @@ class basic_json static basic_json parse(IteratorType first, IteratorType last, const parser_callback_t cb = nullptr, - const bool allow_exceptions = true) + const bool allow_exceptions = true, + const bool ignore_comments = false) { basic_json result; - parser(detail::input_adapter(std::move(first), std::move(last)), cb, allow_exceptions).parse(true, result); + parser(detail::input_adapter(std::move(first), std::move(last)), cb, allow_exceptions, ignore_comments).parse(true, result); return result; } @@ -22469,10 +22577,11 @@ class basic_json JSON_HEDLEY_DEPRECATED_FOR(3.8.0, parse(ptr, ptr + len)) static basic_json parse(detail::span_input_adapter&& i, const parser_callback_t cb = nullptr, - const bool allow_exceptions = true) + const bool allow_exceptions = true, + const bool ignore_comments = false) { basic_json result; - parser(i.get(), cb, allow_exceptions).parse(true, result); + parser(i.get(), cb, allow_exceptions, ignore_comments).parse(true, result); return result; } @@ -22492,6 +22601,9 @@ class basic_json iterators. @param[in] i input to read from + @param[in] ignore_comments whether comments should be ignored and treated + like whitespace (true) or yield a parse error (true); (optional, false by + default) @return Whether the input read from @a i is valid JSON. @@ -22504,22 +22616,25 @@ class basic_json from a string.,accept__string} */ template - static bool accept(InputType&& i) + static bool accept(InputType&& i, + const bool ignore_comments = false) { - return parser(detail::input_adapter(std::forward(i))).accept(true); + return parser(detail::input_adapter(std::forward(i)), nullptr, false, ignore_comments).accept(true); } template - static bool accept(IteratorType first, IteratorType last) + static bool accept(IteratorType first, IteratorType last, + const bool ignore_comments = false) { - return parser(detail::input_adapter(std::move(first), std::move(last))).accept(true); + return parser(detail::input_adapter(std::move(first), std::move(last)), nullptr, false, ignore_comments).accept(true); } JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, accept(ptr, ptr + len)) - static bool accept(detail::span_input_adapter&& i) + static bool accept(detail::span_input_adapter&& i, + const bool ignore_comments = false) { - return parser(i.get()).accept(true); + return parser(i.get(), nullptr, false, ignore_comments).accept(true); } /*! @@ -22539,6 +22654,9 @@ class basic_json @param[in,out] sax SAX event listener @param[in] format the format to parse (JSON, CBOR, MessagePack, or UBJSON) @param[in] strict whether the input has to be consumed completely + @param[in] ignore_comments whether comments should be ignored and treated + like whitespace (true) or yield a parse error (true); (optional, false by + default); only applies to the JSON file format. @return return value of the last processed SAX event @@ -22563,11 +22681,12 @@ class basic_json JSON_HEDLEY_NON_NULL(2) static bool sax_parse(InputType&& i, SAX* sax, input_format_t format = input_format_t::json, - const bool strict = true) + const bool strict = true, + const bool ignore_comments = false) { auto ia = detail::input_adapter(std::forward(i)); return format == input_format_t::json - ? parser(std::move(ia)).sax_parse(sax, strict) + ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); } @@ -22575,11 +22694,12 @@ class basic_json JSON_HEDLEY_NON_NULL(3) static bool sax_parse(IteratorType first, IteratorType last, SAX* sax, input_format_t format = input_format_t::json, - const bool strict = true) + const bool strict = true, + const bool ignore_comments = false) { auto ia = detail::input_adapter(std::move(first), std::move(last)); return format == input_format_t::json - ? parser(std::move(ia)).sax_parse(sax, strict) + ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); } @@ -22588,11 +22708,12 @@ class basic_json JSON_HEDLEY_NON_NULL(2) static bool sax_parse(detail::span_input_adapter&& i, SAX* sax, input_format_t format = input_format_t::json, - const bool strict = true) + const bool strict = true, + const bool ignore_comments = false) { auto ia = i.get(); return format == input_format_t::json - ? parser(std::move(ia)).sax_parse(sax, strict) + ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); } diff --git a/test/src/unit-class_lexer.cpp b/test/src/unit-class_lexer.cpp index 0e1b5378..d8304ccf 100644 --- a/test/src/unit-class_lexer.cpp +++ b/test/src/unit-class_lexer.cpp @@ -37,14 +37,23 @@ using nlohmann::json; namespace { // shortcut to scan a string literal -json::lexer::token_type scan_string(const char* s); -json::lexer::token_type scan_string(const char* s) +json::lexer::token_type scan_string(const char* s, const bool ignore_comments = false); +json::lexer::token_type scan_string(const char* s, const bool ignore_comments) { auto ia = nlohmann::detail::input_adapter(s); - return nlohmann::detail::lexer(std::move(ia)).scan(); + return nlohmann::detail::lexer(std::move(ia), ignore_comments).scan(); } } +std::string get_error_message(const char* s, const bool ignore_comments = false); +std::string get_error_message(const char* s, const bool ignore_comments) +{ + auto ia = nlohmann::detail::input_adapter(s); + auto lexer = nlohmann::detail::lexer(std::move(ia), ignore_comments); + lexer.scan(); + return lexer.get_error_message(); +} + TEST_CASE("lexer class") { SECTION("scan") @@ -127,6 +136,8 @@ TEST_CASE("lexer class") // store scan() result const auto res = scan_string(s.c_str()); + CAPTURE(s); + switch (c) { // single characters that are valid tokens @@ -179,4 +190,56 @@ TEST_CASE("lexer class") s += "\""; CHECK((scan_string(s.c_str()) == json::lexer::token_type::value_string)); } + + SECTION("fail on comments") + { + CHECK((scan_string("/", false) == json::lexer::token_type::parse_error)); + CHECK(get_error_message("/", false) == "invalid literal"); + + CHECK((scan_string("/!", false) == json::lexer::token_type::parse_error)); + CHECK(get_error_message("/!", false) == "invalid literal"); + CHECK((scan_string("/*", false) == json::lexer::token_type::parse_error)); + CHECK(get_error_message("/*", false) == "invalid literal"); + CHECK((scan_string("/**", false) == json::lexer::token_type::parse_error)); + CHECK(get_error_message("/**", false) == "invalid literal"); + + CHECK((scan_string("//", false) == json::lexer::token_type::parse_error)); + CHECK(get_error_message("//", false) == "invalid literal"); + CHECK((scan_string("/**/", false) == json::lexer::token_type::parse_error)); + CHECK(get_error_message("/**/", false) == "invalid literal"); + CHECK((scan_string("/** /", false) == json::lexer::token_type::parse_error)); + CHECK(get_error_message("/** /", false) == "invalid literal"); + + CHECK((scan_string("/***/", false) == json::lexer::token_type::parse_error)); + CHECK(get_error_message("/***/", false) == "invalid literal"); + CHECK((scan_string("/* true */", false) == json::lexer::token_type::parse_error)); + CHECK(get_error_message("/* true */", false) == "invalid literal"); + CHECK((scan_string("/*/**/", false) == json::lexer::token_type::parse_error)); + CHECK(get_error_message("/*/**/", false) == "invalid literal"); + CHECK((scan_string("/*/* */", false) == json::lexer::token_type::parse_error)); + CHECK(get_error_message("/*/* */", false) == "invalid literal"); + } + + SECTION("ignore comments") + { + CHECK((scan_string("/", true) == json::lexer::token_type::parse_error)); + CHECK(get_error_message("/", true) == "invalid comment; expecting '/' or '*' after '/'"); + + CHECK((scan_string("/!", true) == json::lexer::token_type::parse_error)); + CHECK(get_error_message("/!", true) == "invalid comment; expecting '/' or '*' after '/'"); + CHECK((scan_string("/*", true) == json::lexer::token_type::parse_error)); + CHECK(get_error_message("/*", true) == "invalid comment; missing closing '*/'"); + CHECK((scan_string("/**", true) == json::lexer::token_type::parse_error)); + CHECK(get_error_message("/**", true) == "invalid comment; missing closing '*/'"); + + CHECK((scan_string("//", true) == json::lexer::token_type::end_of_input)); + CHECK((scan_string("/**/", true) == json::lexer::token_type::end_of_input)); + CHECK((scan_string("/** /", true) == json::lexer::token_type::parse_error)); + CHECK(get_error_message("/** /", true) == "invalid comment; missing closing '*/'"); + + CHECK((scan_string("/***/", true) == json::lexer::token_type::end_of_input)); + CHECK((scan_string("/* true */", true) == json::lexer::token_type::end_of_input)); + CHECK((scan_string("/*/**/", true) == json::lexer::token_type::end_of_input)); + CHECK((scan_string("/*/* */", true) == json::lexer::token_type::end_of_input)); + } } diff --git a/test/src/unit-class_parser.cpp b/test/src/unit-class_parser.cpp index 1912094a..da16ffca 100644 --- a/test/src/unit-class_parser.cpp +++ b/test/src/unit-class_parser.cpp @@ -224,6 +224,7 @@ class SaxCountdown : public nlohmann::json::json_sax_t json parser_helper(const std::string& s); bool accept_helper(const std::string& s); +void comments_helper(const std::string& s); json parser_helper(const std::string& s) { @@ -241,6 +242,8 @@ json parser_helper(const std::string& s) json::sax_parse(s, &sdp); CHECK(j_sax == j); + comments_helper(s); + return j; } @@ -275,11 +278,51 @@ bool accept_helper(const std::string& s) // 6. check if this approach came to the same result CHECK(ok_noexcept == ok_noexcept_cb); - // 7. return result + // 7. check if comments are properly ignored + if (ok_accept) + { + comments_helper(s); + } + + // 8. return result return ok_accept; } + +void comments_helper(const std::string& s) +{ + json _; + + // parse/accept with default parser + CHECK_NOTHROW(_ = json::parse(s)); + CHECK(json::accept(s)); + + // parse/accept while skipping comments + CHECK_NOTHROW(_ = json::parse(s, nullptr, false, true)); + CHECK(json::accept(s, true)); + + std::vector json_with_comments; + + // start with a comment + json_with_comments.push_back(std::string("// this is a comment\n") + s); + json_with_comments.push_back(std::string("/* this is a comment */") + s); + // end with a comment + json_with_comments.push_back(s + "// this is a comment"); + json_with_comments.push_back(s + "/* this is a comment */"); + + // check all strings + for (const auto& json_with_comment : json_with_comments) + { + CAPTURE(json_with_comment) + CHECK_THROWS_AS(_ = json::parse(json_with_comment), json::parse_error); + CHECK(not json::accept(json_with_comment)); + + CHECK_NOTHROW(_ = json::parse(json_with_comment, nullptr, true, true)); + CHECK(json::accept(json_with_comment, true)); + } } +} // namespace + TEST_CASE("parser class") { SECTION("parse") @@ -1834,4 +1877,10 @@ TEST_CASE("parser class") } } } + + SECTION("error messages for comments") + { + CHECK_THROWS_WITH_AS(json::parse("/a", nullptr, true, true), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid comment; expecting '/' or '*' after '/'; last read: '/a'", json::parse_error); + CHECK_THROWS_WITH_AS(json::parse("/*", nullptr, true, true), "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid comment; missing closing '*/'; last read: '/*'", json::parse_error); + } }