Merge pull request #2212 from nlohmann/comments
Add option to ignore comments in parse/accept functions
This commit is contained in:
		
						commit
						3948b5b091
					
				
					 7 changed files with 416 additions and 66 deletions
				
			
		|  | @ -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 | ||||
| 
 | ||||
|  |  | |||
|  | @ -112,8 +112,11 @@ class lexer : public lexer_base<BasicJsonType> | |||
|   public: | ||||
|     using token_type = typename lexer_base<BasicJsonType>::token_type; | ||||
| 
 | ||||
|     explicit lexer(InputAdapterType&& adapter) | ||||
|         : ia(std::move(adapter)), decimal_point_char(static_cast<char_int_type>(get_decimal_point())) {} | ||||
|     explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false) | ||||
|         : ia(std::move(adapter)) | ||||
|         , ignore_comments(ignore_comments_) | ||||
|         , decimal_point_char(static_cast<char_int_type>(get_decimal_point())) | ||||
|     {} | ||||
| 
 | ||||
|     // delete because of pointer members
 | ||||
|     lexer(const lexer&) = delete; | ||||
|  | @ -826,6 +829,77 @@ class lexer : public lexer_base<BasicJsonType> | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /*!
 | ||||
|      * @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<char_type>::eof(): | ||||
|                         case '\0': | ||||
|                             return true; | ||||
| 
 | ||||
|                         default: | ||||
|                             break; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             // multi-line comments skip input until */ is read
 | ||||
|             case '*': | ||||
|             { | ||||
|                 while (true) | ||||
|                 { | ||||
|                     switch (get()) | ||||
|                     { | ||||
|                         case std::char_traits<char_type>::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<char_type>::eof(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -63,8 +63,11 @@ class parser | |||
|     /// a parser reading from an input adapter
 | ||||
|     explicit parser(InputAdapterType&& adapter, | ||||
|                     const parser_callback_t<BasicJsonType> 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(); | ||||
|  |  | |||
|  | @ -196,10 +196,12 @@ class basic_json | |||
|     static ::nlohmann::detail::parser<basic_json, InputAdapterType> parser( | ||||
|         InputAdapterType adapter, | ||||
|         detail::parser_callback_t<basic_json>cb = nullptr, | ||||
|         bool allow_exceptions = true | ||||
|         const bool allow_exceptions = true, | ||||
|         const bool ignore_comments = false | ||||
|     ) | ||||
|     { | ||||
|         return ::nlohmann::detail::parser<basic_json, InputAdapterType>(std::move(adapter), std::move(cb), allow_exceptions); | ||||
|         return ::nlohmann::detail::parser<basic_json, InputAdapterType>(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<typename InputType> | ||||
|     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<InputType>(i)), cb, allow_exceptions).parse(true, result); | ||||
|         parser(detail::input_adapter(std::forward<InputType>(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<typename InputType> | ||||
|     static bool accept(InputType&& i) | ||||
|     static bool accept(InputType&& i, | ||||
|                        const bool ignore_comments = false) | ||||
|     { | ||||
|         return parser(detail::input_adapter(std::forward<InputType>(i))).accept(true); | ||||
|         return parser(detail::input_adapter(std::forward<InputType>(i)), nullptr, false, ignore_comments).accept(true); | ||||
|     } | ||||
| 
 | ||||
|     template<typename IteratorType> | ||||
|     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<InputType>(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<basic_json, decltype(ia), SAX>(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<basic_json, decltype(ia), SAX>(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<basic_json, decltype(ia), SAX>(std::move(ia)).sax_parse(format, sax, strict); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -8183,8 +8183,11 @@ class lexer : public lexer_base<BasicJsonType> | |||
|   public: | ||||
|     using token_type = typename lexer_base<BasicJsonType>::token_type; | ||||
| 
 | ||||
|     explicit lexer(InputAdapterType&& adapter) | ||||
|         : ia(std::move(adapter)), decimal_point_char(static_cast<char_int_type>(get_decimal_point())) {} | ||||
|     explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false) | ||||
|         : ia(std::move(adapter)) | ||||
|         , ignore_comments(ignore_comments_) | ||||
|         , decimal_point_char(static_cast<char_int_type>(get_decimal_point())) | ||||
|     {} | ||||
| 
 | ||||
|     // delete because of pointer members
 | ||||
|     lexer(const lexer&) = delete; | ||||
|  | @ -8897,6 +8900,77 @@ class lexer : public lexer_base<BasicJsonType> | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /*!
 | ||||
|      * @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<char_type>::eof(): | ||||
|                         case '\0': | ||||
|                             return true; | ||||
| 
 | ||||
|                         default: | ||||
|                             break; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             // multi-line comments skip input until */ is read
 | ||||
|             case '*': | ||||
|             { | ||||
|                 while (true) | ||||
|                 { | ||||
|                     switch (get()) | ||||
|                     { | ||||
|                         case std::char_traits<char_type>::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<char_type>::eof(); | ||||
| 
 | ||||
|  | @ -9672,8 +9766,11 @@ class parser | |||
|     /// a parser reading from an input adapter
 | ||||
|     explicit parser(InputAdapterType&& adapter, | ||||
|                     const parser_callback_t<BasicJsonType> 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<basic_json, InputAdapterType> parser( | ||||
|         InputAdapterType adapter, | ||||
|         detail::parser_callback_t<basic_json>cb = nullptr, | ||||
|         bool allow_exceptions = true | ||||
|         const bool allow_exceptions = true, | ||||
|         const bool ignore_comments = false | ||||
|     ) | ||||
|     { | ||||
|         return ::nlohmann::detail::parser<basic_json, InputAdapterType>(std::move(adapter), std::move(cb), allow_exceptions); | ||||
|         return ::nlohmann::detail::parser<basic_json, InputAdapterType>(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<typename InputType> | ||||
|     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<InputType>(i)), cb, allow_exceptions).parse(true, result); | ||||
|         parser(detail::input_adapter(std::forward<InputType>(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<typename InputType> | ||||
|     static bool accept(InputType&& i) | ||||
|     static bool accept(InputType&& i, | ||||
|                        const bool ignore_comments = false) | ||||
|     { | ||||
|         return parser(detail::input_adapter(std::forward<InputType>(i))).accept(true); | ||||
|         return parser(detail::input_adapter(std::forward<InputType>(i)), nullptr, false, ignore_comments).accept(true); | ||||
|     } | ||||
| 
 | ||||
|     template<typename IteratorType> | ||||
|     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<InputType>(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<basic_json, decltype(ia), SAX>(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<basic_json, decltype(ia), SAX>(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<basic_json, decltype(ia), SAX>(std::move(ia)).sax_parse(format, sax, strict); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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<json, decltype(ia)>(std::move(ia)).scan(); | ||||
|     return nlohmann::detail::lexer<json, decltype(ia)>(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<json, decltype(ia)>(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)); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -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<std::string> 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: '/*<U+0000>'", json::parse_error); | ||||
|     } | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue