mirror of
https://github.com/nlohmann/json.git
synced 2026-10-23 06:18:56 +00:00
Address review comments: explain why a closed container erases only the top of the shared stack, set the payload before the type, rename the helper that makes the enclosing container current again, and declare the parser's state stack right before reserving it. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
612 lines
22 KiB
C++
612 lines
22 KiB
C++
// __ _____ _____ _____
|
|
// __| | __| | | | JSON for Modern C++
|
|
// | | |__ | | | | | | version 3.12.0
|
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
//
|
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#pragma once
|
|
|
|
#include <cmath> // isfinite
|
|
#include <cstdint> // uint8_t
|
|
#include <functional> // function
|
|
#include <string> // string
|
|
#include <utility> // move
|
|
#include <vector> // vector
|
|
|
|
#include <nlohmann/detail/exceptions.hpp>
|
|
#include <nlohmann/detail/input/input_adapters.hpp>
|
|
#include <nlohmann/detail/input/json_sax.hpp>
|
|
#include <nlohmann/detail/input/lexer.hpp>
|
|
#include <nlohmann/detail/macro_scope.hpp>
|
|
#include <nlohmann/detail/meta/is_sax.hpp>
|
|
#include <nlohmann/detail/string_concat.hpp>
|
|
#include <nlohmann/detail/value_t.hpp>
|
|
|
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
|
namespace detail
|
|
{
|
|
////////////
|
|
// parser //
|
|
////////////
|
|
|
|
enum class parse_event_t : std::uint8_t
|
|
{
|
|
/// the parser read `{` and started to process a JSON object
|
|
object_start,
|
|
/// the parser read `}` and finished processing a JSON object
|
|
object_end,
|
|
/// the parser read `[` and started to process a JSON array
|
|
array_start,
|
|
/// the parser read `]` and finished processing a JSON array
|
|
array_end,
|
|
/// the parser read a key of a value in an object
|
|
key,
|
|
/// the parser finished reading a JSON value
|
|
value
|
|
};
|
|
|
|
template<typename BasicJsonType>
|
|
using parser_callback_t =
|
|
std::function<bool(int /*depth*/, parse_event_t /*event*/, BasicJsonType& /*parsed*/)>;
|
|
|
|
/// whether a SAX handler ignores the text of float tokens (the library's own
|
|
/// handlers do), so that the parser need not materialize it
|
|
template<typename SAX>
|
|
struct sax_ignores_number_text : std::false_type {};
|
|
|
|
template<typename BasicJsonType, typename InputAdapterType, bool Collect>
|
|
struct sax_ignores_number_text<json_sax_dom_parser<BasicJsonType, InputAdapterType, Collect>> : std::true_type {};
|
|
|
|
template<typename BasicJsonType, typename InputAdapterType>
|
|
struct sax_ignores_number_text<json_sax_dom_callback_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
|
|
|
|
template<typename BasicJsonType>
|
|
struct sax_ignores_number_text<json_sax_acceptor<BasicJsonType>> : std::true_type {};
|
|
|
|
/*!
|
|
@brief syntax analysis
|
|
|
|
This class implements a parser for JSON text. Nested arrays and objects are tracked with an explicit
|
|
stack instead of recursion, so deeply nested input does not exhaust the call stack, and what is read
|
|
is reported as SAX events.
|
|
*/
|
|
template<typename BasicJsonType, typename InputAdapterType>
|
|
class parser
|
|
{
|
|
using number_integer_t = typename BasicJsonType::number_integer_t;
|
|
using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
|
|
using number_float_t = typename BasicJsonType::number_float_t;
|
|
using string_t = typename BasicJsonType::string_t;
|
|
using lexer_t = lexer<BasicJsonType, InputAdapterType>;
|
|
using token_type = typename lexer_t::token_type;
|
|
|
|
public:
|
|
/// a parser reading from an input adapter
|
|
explicit parser(InputAdapterType&& adapter,
|
|
parser_callback_t<BasicJsonType> cb = nullptr,
|
|
const bool allow_exceptions_ = true,
|
|
const bool ignore_comments = false,
|
|
const bool ignore_trailing_commas_ = false,
|
|
const bool discard_number_values_ = false)
|
|
: callback(std::move(cb))
|
|
, m_lexer(std::move(adapter), ignore_comments, discard_number_values_)
|
|
, allow_exceptions(allow_exceptions_)
|
|
, ignore_trailing_commas(ignore_trailing_commas_)
|
|
{
|
|
// read first token
|
|
get_token();
|
|
}
|
|
|
|
/*!
|
|
@brief public parser interface
|
|
|
|
@param[in] strict whether to expect the last token to be EOF
|
|
@param[in,out] result parsed JSON value
|
|
|
|
@throw parse_error.101 in case of an unexpected token (including invalid
|
|
unicode escapes and surrogate errors, which are reported with a
|
|
detailed message)
|
|
*/
|
|
void parse(const bool strict, BasicJsonType& result)
|
|
{
|
|
if (callback)
|
|
{
|
|
json_sax_dom_callback_parser<BasicJsonType, InputAdapterType> sdp(result, callback, allow_exceptions, &m_lexer);
|
|
|
|
// in case of an error, return a discarded value
|
|
if (!parse_dom(sdp, strict))
|
|
{
|
|
result = value_t::discarded;
|
|
return;
|
|
}
|
|
|
|
// set top-level value to null if it was discarded by the callback
|
|
// function
|
|
if (result.is_discarded())
|
|
{
|
|
result = nullptr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
json_sax_dom_parser<BasicJsonType, InputAdapterType, true> sdp(result, allow_exceptions, &m_lexer);
|
|
|
|
// in case of an error, return a discarded value
|
|
if (!parse_dom(sdp, strict))
|
|
{
|
|
result = value_t::discarded;
|
|
return;
|
|
}
|
|
}
|
|
|
|
result.assert_invariant();
|
|
}
|
|
|
|
/*!
|
|
@brief public accept interface
|
|
|
|
@param[in] strict whether to expect the last token to be EOF
|
|
@return whether the input is a proper JSON text
|
|
*/
|
|
bool accept(const bool strict = true)
|
|
{
|
|
json_sax_acceptor<BasicJsonType> sax_acceptor;
|
|
return sax_parse(&sax_acceptor, strict);
|
|
}
|
|
|
|
template<typename SAX>
|
|
JSON_HEDLEY_NON_NULL(2)
|
|
bool sax_parse(SAX* sax, const bool strict = true)
|
|
{
|
|
(void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
|
|
const bool result = sax_parse_internal(sax);
|
|
|
|
if (result)
|
|
{
|
|
if (strict)
|
|
{
|
|
// strict mode: next byte must be EOF
|
|
if (get_token() != token_type::end_of_input)
|
|
{
|
|
return syntax_error(*sax, exception_message(token_type::end_of_input, "value"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// the caller keeps using the input: position it right after
|
|
// the value by leaving the character that terminated it
|
|
m_lexer.release_lookahead();
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private:
|
|
/// the text of the current float token for @a sax (see sax_ignores_number_text)
|
|
template<typename SAX>
|
|
const string_t& number_float_text(SAX* /*sax*/)
|
|
{
|
|
return number_float_text(std::integral_constant<bool, sax_ignores_number_text<SAX>::value> {});
|
|
}
|
|
|
|
const string_t& number_float_text(std::true_type /*ignored*/) const noexcept
|
|
{
|
|
return m_lexer.get_ignored_number_text();
|
|
}
|
|
|
|
string_t& number_float_text(std::false_type /*ignored*/)
|
|
{
|
|
return m_lexer.get_string();
|
|
}
|
|
|
|
/*!
|
|
@brief run a DOM SAX parser to completion and position the lexer
|
|
|
|
Shared by both branches of @ref parse(): builds no SAX parser itself,
|
|
but drives an already-constructed @a json_sax_dom_parser or
|
|
@ref json_sax_dom_callback_parser through @ref sax_parse_internal(),
|
|
then applies the strict-EOF check (reporting parse_error.101 through
|
|
@a sdp on failure) or, in non-strict mode, releases the lookahead so
|
|
the caller can keep reading the input right after the parsed value.
|
|
|
|
@param[in,out] sdp the DOM SAX parser to run
|
|
@param[in] strict whether to expect the last token to be EOF
|
|
@return whether @a sdp did not report an error
|
|
*/
|
|
template<typename DomSax>
|
|
bool parse_dom(DomSax& sdp, const bool strict)
|
|
{
|
|
sax_parse_internal(&sdp);
|
|
|
|
if (strict)
|
|
{
|
|
// in strict mode, input must be completely read
|
|
if (get_token() != token_type::end_of_input)
|
|
{
|
|
syntax_error(sdp, exception_message(token_type::end_of_input, "value"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// the caller keeps using the input: position it right after
|
|
// the value by leaving the character that terminated it
|
|
m_lexer.release_lookahead();
|
|
}
|
|
|
|
return !sdp.is_errored();
|
|
}
|
|
|
|
template<typename SAX>
|
|
JSON_HEDLEY_NON_NULL(2)
|
|
bool sax_parse_internal(SAX* sax)
|
|
{
|
|
// value to avoid a goto (see comment where set to true)
|
|
bool skip_to_state_evaluation = false;
|
|
|
|
// stack to remember the hierarchy of structured values we are parsing
|
|
// true = array; false = object (bytes, not std::vector<bool>, whose
|
|
// push_back() is a call with GCC)
|
|
std::vector<std::uint8_t> states;
|
|
// a document that starts with a container pushes onto the stack at
|
|
// once: allocate it in one step instead of growing it from 1 byte
|
|
if (last_token == token_type::begin_object || last_token == token_type::begin_array)
|
|
{
|
|
states.reserve(32);
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
if (!skip_to_state_evaluation)
|
|
{
|
|
// invariant: get_token() was called before each iteration
|
|
switch (last_token)
|
|
{
|
|
case token_type::begin_object:
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size())))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// closing } -> we are done
|
|
if (get_token() == token_type::end_object)
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(!sax->end_object()))
|
|
{
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// parse key
|
|
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
|
|
{
|
|
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
|
|
}
|
|
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// parse separator (:)
|
|
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
|
{
|
|
return syntax_error(*sax, exception_message(token_type::name_separator, "object separator"));
|
|
}
|
|
|
|
// remember we are now inside an object
|
|
states.push_back(0);
|
|
|
|
// parse values
|
|
get_token();
|
|
continue;
|
|
}
|
|
|
|
case token_type::begin_array:
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size())))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// closing ] -> we are done
|
|
if (get_token() == token_type::end_array)
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(!sax->end_array()))
|
|
{
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// remember we are now inside an array
|
|
states.push_back(1);
|
|
|
|
// parse values (no need to call get_token)
|
|
continue;
|
|
}
|
|
|
|
case token_type::value_float:
|
|
{
|
|
const auto res = m_lexer.get_number_float();
|
|
|
|
if (JSON_HEDLEY_UNLIKELY(!std::isfinite(res)))
|
|
{
|
|
return sax->parse_error(m_lexer.get_position(),
|
|
m_lexer.get_token_string(),
|
|
out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr));
|
|
}
|
|
|
|
if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, number_float_text(sax))))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case token_type::literal_false:
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(!sax->boolean(false)))
|
|
{
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case token_type::literal_null:
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(!sax->null()))
|
|
{
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case token_type::literal_true:
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(!sax->boolean(true)))
|
|
{
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case token_type::value_integer:
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(!sax->number_integer(m_lexer.get_number_integer())))
|
|
{
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case token_type::value_string:
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(!sax->string(m_lexer.get_string_token())))
|
|
{
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case token_type::value_unsigned:
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(!sax->number_unsigned(m_lexer.get_number_unsigned())))
|
|
{
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case token_type::parse_error:
|
|
{
|
|
// using "uninitialized" to avoid an "expected" message
|
|
return syntax_error(*sax, exception_message(token_type::uninitialized, "value"));
|
|
}
|
|
case token_type::end_of_input:
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(m_lexer.get_chars_read_total() == 1))
|
|
{
|
|
return syntax_error(*sax, "attempting to parse an empty input; check that your input string or stream contains the expected JSON");
|
|
}
|
|
|
|
return syntax_error(*sax, exception_message(token_type::literal_or_value, "value"));
|
|
}
|
|
case token_type::uninitialized:
|
|
case token_type::end_array:
|
|
case token_type::end_object:
|
|
case token_type::name_separator:
|
|
case token_type::value_separator:
|
|
case token_type::literal_or_value:
|
|
default: // the last token was unexpected
|
|
{
|
|
return syntax_error(*sax, exception_message(token_type::literal_or_value, "value"));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
skip_to_state_evaluation = false;
|
|
}
|
|
|
|
// we reached this line after we successfully parsed a value
|
|
if (states.empty())
|
|
{
|
|
// empty stack: we reached the end of the hierarchy: done
|
|
return true;
|
|
}
|
|
|
|
if (states.back() != 0) // array
|
|
{
|
|
// comma -> next value
|
|
// or end of array (ignore_trailing_commas = true)
|
|
if (get_token_expecting(token_type::value_separator))
|
|
{
|
|
// parse a new value
|
|
get_token();
|
|
|
|
// if ignore_trailing_commas and last_token is ], we can continue to "closing ]"
|
|
if (!(ignore_trailing_commas && last_token == token_type::end_array))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// closing ]
|
|
if (JSON_HEDLEY_LIKELY(last_token == token_type::end_array))
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(!sax->end_array()))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// We are done with this array. Before we can parse a
|
|
// new value, we need to evaluate the new state first.
|
|
// By setting skip_to_state_evaluation to true, the next
|
|
// iteration skips parsing a value and evaluates the
|
|
// enclosing state directly.
|
|
JSON_ASSERT(!states.empty());
|
|
states.pop_back();
|
|
skip_to_state_evaluation = true;
|
|
continue;
|
|
}
|
|
|
|
return syntax_error(*sax, exception_message(token_type::end_array, "array"));
|
|
}
|
|
|
|
// states.back() is false -> object
|
|
|
|
// comma -> next value
|
|
// or end of object (ignore_trailing_commas = true)
|
|
if (get_token_expecting(token_type::value_separator))
|
|
{
|
|
get_token();
|
|
|
|
// if ignore_trailing_commas and last_token is }, we can continue to "closing }"
|
|
if (!(ignore_trailing_commas && last_token == token_type::end_object))
|
|
{
|
|
// parse key
|
|
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
|
|
{
|
|
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
|
|
}
|
|
|
|
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// parse separator (:)
|
|
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
|
{
|
|
return syntax_error(*sax, exception_message(token_type::name_separator, "object separator"));
|
|
}
|
|
|
|
// parse values
|
|
get_token();
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// closing }
|
|
if (JSON_HEDLEY_LIKELY(last_token == token_type::end_object))
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(!sax->end_object()))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// We are done with this object. Before we can parse a
|
|
// new value, we need to evaluate the new state first.
|
|
// By setting skip_to_state_evaluation to true, the next
|
|
// iteration skips parsing a value and evaluates the
|
|
// enclosing state directly.
|
|
JSON_ASSERT(!states.empty());
|
|
states.pop_back();
|
|
skip_to_state_evaluation = true;
|
|
continue;
|
|
}
|
|
|
|
return syntax_error(*sax, exception_message(token_type::end_object, "object"));
|
|
}
|
|
}
|
|
|
|
/// get next token from lexer
|
|
token_type get_token()
|
|
{
|
|
return last_token = m_lexer.scan();
|
|
}
|
|
|
|
/// get next token from lexer; true if it is the separator @a expected_type
|
|
/// (name_separator or value_separator), which it usually is
|
|
bool get_token_expecting(token_type expected_type)
|
|
{
|
|
return (last_token = m_lexer.scan_expecting(expected_type)) == expected_type;
|
|
}
|
|
|
|
/*!
|
|
@brief reports a syntax error at the current token (parse_error.101)
|
|
@param[in] sax the SAX parser to report the error to
|
|
@param[in] message the error message
|
|
@return the result of the SAX parser's parse_error()
|
|
*/
|
|
template<typename SAX>
|
|
bool syntax_error(SAX& sax, const std::string& message)
|
|
{
|
|
// MSVC 2015 reports C4100 (unreferenced parameter) when SAX::parse_error is static
|
|
static_cast<void>(sax);
|
|
// computing the position is O(input read so far) for seekable input
|
|
const position_t position = m_lexer.get_position();
|
|
return sax.parse_error(position, m_lexer.get_token_string(),
|
|
parse_error::create(exception_id::syntax_error, position, message, nullptr));
|
|
}
|
|
|
|
std::string exception_message(const token_type expected, const std::string& context)
|
|
{
|
|
std::string error_msg = "syntax error ";
|
|
|
|
if (!context.empty())
|
|
{
|
|
error_msg += concat("while parsing ", context, ' ');
|
|
}
|
|
|
|
error_msg += "- ";
|
|
|
|
if (last_token == token_type::parse_error)
|
|
{
|
|
error_msg += concat(m_lexer.get_error_message(), "; last read: '",
|
|
m_lexer.get_token_string(), '\'');
|
|
}
|
|
else
|
|
{
|
|
error_msg += concat("unexpected ", lexer_t::token_type_name(last_token));
|
|
}
|
|
|
|
if (expected != token_type::uninitialized)
|
|
{
|
|
error_msg += concat("; expected ", lexer_t::token_type_name(expected));
|
|
}
|
|
|
|
return error_msg;
|
|
}
|
|
|
|
private:
|
|
/// callback function
|
|
const parser_callback_t<BasicJsonType> callback = nullptr;
|
|
/// the type of the last read token
|
|
token_type last_token = token_type::uninitialized;
|
|
/// the lexer
|
|
lexer_t m_lexer;
|
|
/// whether to throw exceptions in case of errors
|
|
const bool allow_exceptions = true;
|
|
/// whether trailing commas in objects and arrays should be ignored (true) or signaled as errors (false)
|
|
const bool ignore_trailing_commas = false;
|
|
};
|
|
|
|
} // namespace detail
|
|
NLOHMANN_JSON_NAMESPACE_END
|