Merge pull request #2212 from nlohmann/comments

Add option to ignore comments in parse/accept functions
This commit is contained in:
Niels Lohmann 2020-06-27 12:44:51 +02:00 committed by GitHub
commit 3948b5b091
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 416 additions and 66 deletions

View file

@ -1508,7 +1508,7 @@ The library supports **Unicode input** as follows:
### Comments in JSON ### 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. 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: 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. 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: 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.
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).
### Order of object keys ### Order of object keys

View file

@ -112,8 +112,11 @@ class lexer : public lexer_base<BasicJsonType>
public: public:
using token_type = typename lexer_base<BasicJsonType>::token_type; using token_type = typename lexer_base<BasicJsonType>::token_type;
explicit lexer(InputAdapterType&& adapter) explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false)
: ia(std::move(adapter)), decimal_point_char(static_cast<char_int_type>(get_decimal_point())) {} : ia(std::move(adapter))
, ignore_comments(ignore_comments_)
, decimal_point_char(static_cast<char_int_type>(get_decimal_point()))
{}
// delete because of pointer members // delete because of pointer members
lexer(const lexer&) = delete; 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) JSON_HEDLEY_NON_NULL(2)
static void strtof(float& f, const char* str, char** endptr) noexcept static void strtof(float& f, const char* str, char** endptr) noexcept
{ {
@ -1415,6 +1489,15 @@ scan_number_done:
return true; return true;
} }
void skip_whitespace()
{
do
{
get();
}
while (current == ' ' or current == '\t' or current == '\n' or current == '\r');
}
token_type scan() token_type scan()
{ {
// initially, skip the BOM // initially, skip the BOM
@ -1425,11 +1508,19 @@ scan_number_done:
} }
// read next character and ignore whitespace // 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) switch (current)
{ {
@ -1499,6 +1590,9 @@ scan_number_done:
/// input adapter /// input adapter
InputAdapterType ia; InputAdapterType ia;
/// whether comments should be ignored (true) or signaled as errors (false)
const bool ignore_comments = false;
/// the current character /// the current character
char_int_type current = std::char_traits<char_type>::eof(); char_int_type current = std::char_traits<char_type>::eof();

View file

@ -63,8 +63,11 @@ class parser
/// a parser reading from an input adapter /// a parser reading from an input adapter
explicit parser(InputAdapterType&& adapter, explicit parser(InputAdapterType&& adapter,
const parser_callback_t<BasicJsonType> cb = nullptr, const parser_callback_t<BasicJsonType> cb = nullptr,
const bool allow_exceptions_ = true) const bool allow_exceptions_ = true,
: callback(cb), m_lexer(std::move(adapter)), allow_exceptions(allow_exceptions_) const bool skip_comments = false)
: callback(cb)
, m_lexer(std::move(adapter), skip_comments)
, allow_exceptions(allow_exceptions_)
{ {
// read first token // read first token
get_token(); get_token();

View file

@ -196,10 +196,12 @@ class basic_json
static ::nlohmann::detail::parser<basic_json, InputAdapterType> parser( static ::nlohmann::detail::parser<basic_json, InputAdapterType> parser(
InputAdapterType adapter, InputAdapterType adapter,
detail::parser_callback_t<basic_json>cb = nullptr, 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; using primitive_iterator_t = ::nlohmann::detail::primitive_iterator_t;
@ -6591,6 +6593,9 @@ class basic_json
(optional) (optional)
@param[in] allow_exceptions whether to throw exceptions in case of a @param[in] allow_exceptions whether to throw exceptions in case of a
parse error (optional, true by default) 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 @return deserialized JSON value; in case of a parse error and
@a allow_exceptions set to `false`, the return value will be @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 @liveexample{The example below demonstrates the `parse()` function reading
from a contiguous container.,parse__contiguouscontainer__parser_callback_t} 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> template<typename InputType>
JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_WARN_UNUSED_RESULT
static basic_json parse(InputType&& i, static basic_json parse(InputType&& i,
const parser_callback_t cb = nullptr, const parser_callback_t cb = nullptr,
const bool allow_exceptions = true) const bool allow_exceptions = true,
const bool ignore_comments = false)
{ {
basic_json result; 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; return result;
} }
@ -6645,6 +6652,9 @@ class basic_json
(optional) (optional)
@param[in] allow_exceptions whether to throw exceptions in case of a @param[in] allow_exceptions whether to throw exceptions in case of a
parse error (optional, true by default) 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 @return deserialized JSON value; in case of a parse error and
@a allow_exceptions set to `false`, the return value will be @a allow_exceptions set to `false`, the return value will be
@ -6660,10 +6670,11 @@ class basic_json
static basic_json parse(IteratorType first, static basic_json parse(IteratorType first,
IteratorType last, IteratorType last,
const parser_callback_t cb = nullptr, const parser_callback_t cb = nullptr,
const bool allow_exceptions = true) const bool allow_exceptions = true,
const bool ignore_comments = false)
{ {
basic_json result; 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; return result;
} }
@ -6671,10 +6682,11 @@ class basic_json
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, parse(ptr, ptr + len)) JSON_HEDLEY_DEPRECATED_FOR(3.8.0, parse(ptr, ptr + len))
static basic_json parse(detail::span_input_adapter&& i, static basic_json parse(detail::span_input_adapter&& i,
const parser_callback_t cb = nullptr, const parser_callback_t cb = nullptr,
const bool allow_exceptions = true) const bool allow_exceptions = true,
const bool ignore_comments = false)
{ {
basic_json result; 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; return result;
} }
@ -6694,6 +6706,9 @@ class basic_json
iterators. iterators.
@param[in] i input to read from @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. @return Whether the input read from @a i is valid JSON.
@ -6706,22 +6721,25 @@ class basic_json
from a string.,accept__string} from a string.,accept__string}
*/ */
template<typename InputType> 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> 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_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, accept(ptr, ptr + len)) 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,out] sax SAX event listener
@param[in] format the format to parse (JSON, CBOR, MessagePack, or UBJSON) @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] 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 @return return value of the last processed SAX event
@ -6765,11 +6786,12 @@ class basic_json
JSON_HEDLEY_NON_NULL(2) JSON_HEDLEY_NON_NULL(2)
static bool sax_parse(InputType&& i, SAX* sax, static bool sax_parse(InputType&& i, SAX* sax,
input_format_t format = input_format_t::json, 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)); auto ia = detail::input_adapter(std::forward<InputType>(i));
return format == input_format_t::json 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); : 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) JSON_HEDLEY_NON_NULL(3)
static bool sax_parse(IteratorType first, IteratorType last, SAX* sax, static bool sax_parse(IteratorType first, IteratorType last, SAX* sax,
input_format_t format = input_format_t::json, 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)); auto ia = detail::input_adapter(std::move(first), std::move(last));
return format == input_format_t::json 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); : 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) JSON_HEDLEY_NON_NULL(2)
static bool sax_parse(detail::span_input_adapter&& i, SAX* sax, static bool sax_parse(detail::span_input_adapter&& i, SAX* sax,
input_format_t format = input_format_t::json, 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(); auto ia = i.get();
return format == input_format_t::json 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); : detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia)).sax_parse(format, sax, strict);
} }

View file

@ -8183,8 +8183,11 @@ class lexer : public lexer_base<BasicJsonType>
public: public:
using token_type = typename lexer_base<BasicJsonType>::token_type; using token_type = typename lexer_base<BasicJsonType>::token_type;
explicit lexer(InputAdapterType&& adapter) explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false)
: ia(std::move(adapter)), decimal_point_char(static_cast<char_int_type>(get_decimal_point())) {} : ia(std::move(adapter))
, ignore_comments(ignore_comments_)
, decimal_point_char(static_cast<char_int_type>(get_decimal_point()))
{}
// delete because of pointer members // delete because of pointer members
lexer(const lexer&) = delete; 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) JSON_HEDLEY_NON_NULL(2)
static void strtof(float& f, const char* str, char** endptr) noexcept static void strtof(float& f, const char* str, char** endptr) noexcept
{ {
@ -9486,6 +9560,15 @@ scan_number_done:
return true; return true;
} }
void skip_whitespace()
{
do
{
get();
}
while (current == ' ' or current == '\t' or current == '\n' or current == '\r');
}
token_type scan() token_type scan()
{ {
// initially, skip the BOM // initially, skip the BOM
@ -9496,11 +9579,19 @@ scan_number_done:
} }
// read next character and ignore whitespace // 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) switch (current)
{ {
@ -9570,6 +9661,9 @@ scan_number_done:
/// input adapter /// input adapter
InputAdapterType ia; InputAdapterType ia;
/// whether comments should be ignored (true) or signaled as errors (false)
const bool ignore_comments = false;
/// the current character /// the current character
char_int_type current = std::char_traits<char_type>::eof(); char_int_type current = std::char_traits<char_type>::eof();
@ -9672,8 +9766,11 @@ class parser
/// a parser reading from an input adapter /// a parser reading from an input adapter
explicit parser(InputAdapterType&& adapter, explicit parser(InputAdapterType&& adapter,
const parser_callback_t<BasicJsonType> cb = nullptr, const parser_callback_t<BasicJsonType> cb = nullptr,
const bool allow_exceptions_ = true) const bool allow_exceptions_ = true,
: callback(cb), m_lexer(std::move(adapter)), allow_exceptions(allow_exceptions_) const bool skip_comments = false)
: callback(cb)
, m_lexer(std::move(adapter), skip_comments)
, allow_exceptions(allow_exceptions_)
{ {
// read first token // read first token
get_token(); get_token();
@ -15994,10 +16091,12 @@ class basic_json
static ::nlohmann::detail::parser<basic_json, InputAdapterType> parser( static ::nlohmann::detail::parser<basic_json, InputAdapterType> parser(
InputAdapterType adapter, InputAdapterType adapter,
detail::parser_callback_t<basic_json>cb = nullptr, 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; using primitive_iterator_t = ::nlohmann::detail::primitive_iterator_t;
@ -22389,6 +22488,9 @@ class basic_json
(optional) (optional)
@param[in] allow_exceptions whether to throw exceptions in case of a @param[in] allow_exceptions whether to throw exceptions in case of a
parse error (optional, true by default) 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 @return deserialized JSON value; in case of a parse error and
@a allow_exceptions set to `false`, the return value will be @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 @liveexample{The example below demonstrates the `parse()` function reading
from a contiguous container.,parse__contiguouscontainer__parser_callback_t} 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> template<typename InputType>
JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_WARN_UNUSED_RESULT
static basic_json parse(InputType&& i, static basic_json parse(InputType&& i,
const parser_callback_t cb = nullptr, const parser_callback_t cb = nullptr,
const bool allow_exceptions = true) const bool allow_exceptions = true,
const bool ignore_comments = false)
{ {
basic_json result; 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; return result;
} }
@ -22443,6 +22547,9 @@ class basic_json
(optional) (optional)
@param[in] allow_exceptions whether to throw exceptions in case of a @param[in] allow_exceptions whether to throw exceptions in case of a
parse error (optional, true by default) 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 @return deserialized JSON value; in case of a parse error and
@a allow_exceptions set to `false`, the return value will be @a allow_exceptions set to `false`, the return value will be
@ -22458,10 +22565,11 @@ class basic_json
static basic_json parse(IteratorType first, static basic_json parse(IteratorType first,
IteratorType last, IteratorType last,
const parser_callback_t cb = nullptr, const parser_callback_t cb = nullptr,
const bool allow_exceptions = true) const bool allow_exceptions = true,
const bool ignore_comments = false)
{ {
basic_json result; 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; return result;
} }
@ -22469,10 +22577,11 @@ class basic_json
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, parse(ptr, ptr + len)) JSON_HEDLEY_DEPRECATED_FOR(3.8.0, parse(ptr, ptr + len))
static basic_json parse(detail::span_input_adapter&& i, static basic_json parse(detail::span_input_adapter&& i,
const parser_callback_t cb = nullptr, const parser_callback_t cb = nullptr,
const bool allow_exceptions = true) const bool allow_exceptions = true,
const bool ignore_comments = false)
{ {
basic_json result; 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; return result;
} }
@ -22492,6 +22601,9 @@ class basic_json
iterators. iterators.
@param[in] i input to read from @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. @return Whether the input read from @a i is valid JSON.
@ -22504,22 +22616,25 @@ class basic_json
from a string.,accept__string} from a string.,accept__string}
*/ */
template<typename InputType> 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> 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_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, accept(ptr, ptr + len)) 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,out] sax SAX event listener
@param[in] format the format to parse (JSON, CBOR, MessagePack, or UBJSON) @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] 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 @return return value of the last processed SAX event
@ -22563,11 +22681,12 @@ class basic_json
JSON_HEDLEY_NON_NULL(2) JSON_HEDLEY_NON_NULL(2)
static bool sax_parse(InputType&& i, SAX* sax, static bool sax_parse(InputType&& i, SAX* sax,
input_format_t format = input_format_t::json, 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)); auto ia = detail::input_adapter(std::forward<InputType>(i));
return format == input_format_t::json 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); : 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) JSON_HEDLEY_NON_NULL(3)
static bool sax_parse(IteratorType first, IteratorType last, SAX* sax, static bool sax_parse(IteratorType first, IteratorType last, SAX* sax,
input_format_t format = input_format_t::json, 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)); auto ia = detail::input_adapter(std::move(first), std::move(last));
return format == input_format_t::json 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); : 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) JSON_HEDLEY_NON_NULL(2)
static bool sax_parse(detail::span_input_adapter&& i, SAX* sax, static bool sax_parse(detail::span_input_adapter&& i, SAX* sax,
input_format_t format = input_format_t::json, 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(); auto ia = i.get();
return format == input_format_t::json 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); : detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia)).sax_parse(format, sax, strict);
} }

View file

@ -37,14 +37,23 @@ using nlohmann::json;
namespace namespace
{ {
// shortcut to scan a string literal // shortcut to scan a string literal
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) json::lexer::token_type scan_string(const char* s, const bool ignore_comments)
{ {
auto ia = nlohmann::detail::input_adapter(s); 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") TEST_CASE("lexer class")
{ {
SECTION("scan") SECTION("scan")
@ -127,6 +136,8 @@ TEST_CASE("lexer class")
// store scan() result // store scan() result
const auto res = scan_string(s.c_str()); const auto res = scan_string(s.c_str());
CAPTURE(s);
switch (c) switch (c)
{ {
// single characters that are valid tokens // single characters that are valid tokens
@ -179,4 +190,56 @@ TEST_CASE("lexer class")
s += "\""; s += "\"";
CHECK((scan_string(s.c_str()) == json::lexer::token_type::value_string)); 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));
}
} }

View file

@ -224,6 +224,7 @@ class SaxCountdown : public nlohmann::json::json_sax_t
json parser_helper(const std::string& s); json parser_helper(const std::string& s);
bool accept_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) json parser_helper(const std::string& s)
{ {
@ -241,6 +242,8 @@ json parser_helper(const std::string& s)
json::sax_parse(s, &sdp); json::sax_parse(s, &sdp);
CHECK(j_sax == j); CHECK(j_sax == j);
comments_helper(s);
return j; return j;
} }
@ -275,10 +278,50 @@ bool accept_helper(const std::string& s)
// 6. check if this approach came to the same result // 6. check if this approach came to the same result
CHECK(ok_noexcept == ok_noexcept_cb); 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; 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") TEST_CASE("parser class")
{ {
@ -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);
}
} }