mirror of
https://github.com/nlohmann/json.git
synced 2026-10-24 06:48:55 +00:00
* Reserve the parser's nesting stack once The stack of open arrays and objects was a std::vector<std::uint8_t> that allocated at the first push_back and then reallocated at 1, 2, 4, 8 bytes, so a document nested four levels deep made four allocations before it parsed a value. A document that starts with a container now reserves 32 levels in one allocation; deeper documents grow as before. The loop that reads the tokens is the code it was, so large documents are not affected (a stack object with its own storage, and a reserve at every container, both cost the token loop instructions or cycles). Fixed cost of parsing a 124-byte object (macOS instructions): accept -7.6 %, DOM -2.5 %; no change for a single number. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Build arrays of the DOM parser in one exact-size allocation Reading an array of unknown length appended its elements to a std::vector one at a time, so a small array reallocated (and moved its elements) at sizes 1, 2, 4, ... The DOM SAX parser now collects the elements of arrays whose length is unknown on one stack shared by all open arrays and moves them into an array of exactly the right size when the closing bracket arrives. An array that grows past 16 elements switches to appending to its own vector, so large arrays pay no second copy. Binary formats, which announce the length, and builds with JSON_DIAGNOSTICS or JSON_DIAGNOSTIC_POSITIONS (whose values need their final place from the start) keep storing into the array directly. The stacks of open containers are reserved on first use instead of growing from one element. The binary readers get a DOM parser of their own, json_sax_dom_binary_parser: it is the plain parser that stores every value directly in its container (the code the readers used before). Their containers announce their size, so the bookkeeping of the collecting parser (frame entries, the destination of a finished container) was pure overhead for them: with the collecting parser FromBinaryShape nested was 3-12 % and containers up to 5 % more instructions (GCC) than before. With the plain parser Linux Ir of FromBinaryShape and FromBinaryBuffer is within 0.8 % of develop (Clang: identical). canada.json has 56 000 two-element arrays that took three allocations each and now take two. DOM parse Ir on Linux against develop (GCC / Clang): canada -7.0 % / -7.7 %, citm -0.8 % / -1.6 %, 124-byte object -5.4 % / -5.5 %; accept and SAX are untouched. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Reuse one value for the key events of the callback parser parse() with a callback built a new string value for every key it passed to the callback (a heap allocation and release per key), and copied the key a second time into the object. The callback parser now keeps one string value for key events, which it assigns, and moves the key into the object. Callback parse, Linux Ir (GCC / Clang): twitter -13.3 % / -11.7 %, citm -9.5 % / -7.6 %. (Earlier macOS measurement: twitter -19.5 % instructions.) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Remember where the DOM parser stores the next value The DOM SAX parser looked up the innermost open container for every value: the end of its frame stack, then the object or array pointer in the frame. Compilers have to reload all of that after each value is stored, since a store of a value may alias the frame. The parser now keeps the innermost container's kind and, for an array that stores directly, the array itself in members, updated when a container opens or closes. Flat arrays of numbers are the case that mattered: parsing floats.json (1 000 000 doubles) into a DOM took 2 % more cycles than before the previous commit on macOS and now takes 0.4 % fewer than the code before it (signed_ints.json -0.8 %). Linux Ir is unchanged (canada -6.1 % GCC / -6.9 % Clang, citm -0.3 % / -0.8 %, twitter +0.2 % / 0.0 %). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Look up the keys of big ordered_json objects in a hash table ordered_map keeps its elements in a vector and searches it linearly for every key it adds, so parsing an object with n keys into ordered_json took O(n^2) key comparisons: 33 billion instructions for one object with 30 000 keys, where json needs 245 million. From 64 keys on, the DOM parser now keeps a hash table of the keys of the open object (hashes and positions only; the elements stay in the vector) and finds duplicate keys there. The result is the same, including the position of a key that occurs twice. Smaller objects, std::map based objects and key types without std::hash are untouched. ordered_json parse, macOS instructions: object with 1 000 keys 64.8 M -> 14.6 M, 10 000 keys 5 104 M -> 38.7 M, 30 000 keys 33 358 M -> 89 M. Linux Ir of an object with 2 000 keys: 43.0 M -> 1.9 M (GCC), 37.9 M -> 1.8 M (Clang). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Trim the work the DOM parser does per container and per member Opening and closing an array or object cost the DOM parser more than before the exact-size array change, which showed on documents of many small objects (jeopardy.json: +1.4 % Ir with GCC). This takes it back: - the stack of open containers keeps its first eight entries in the parser object instead of in a vector, so it needs no capacity check beyond the push itself and no allocation for typical documents; - a container is created in the place it ends up in, instead of as a temporary that is then moved there; - a new object member, which is null, is constructed over the null instead of being swapped with it (only with the default base class of basic_json); - the key index that big ordered_json objects use is only a part of the stack entries of objects that can have one; - an array that outgrows the stack reserves twice what it has so far, as a vector that grew by itself would have. DOM parse Ir of the series up to here against develop, Linux GCC / Clang: canada -6.6 % / -7.3 %, citm +1.2 % / +0.2 %, jeopardy +0.4 % / -0.1 %, 124-byte object -6.5 % / -6.2 %. (twitter measures +6.8 % / +6.6 % at this point in steady state, almost all of it in glibc's malloc/consolidate for the new allocation sizes; with the last commit of the series it is -0.1 % / 0.0 %.) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Test parsing containers of the sizes where the DOM parser changes storage The DOM parser collects the elements of an array on a stack (up to 16) and the members of an ordered_json object (up to 32) before it moves them into the container, and uses a hash table for keys from 64 members on. Parse arrays and objects of the sizes around these limits, nested into each other, with repeated keys, and as random trees that are built next to their text, and compare json and ordered_json against the value built through the API. The tests pass on the code from before these changes as well. The random trees are what finds the mistakes that are easy to make here: while the collecting change was being written, a collected array that outgrew the stack at the moment its parent had just reached the limit left its elements in the parent, and only these tests (142 failed assertions) showed it. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Do not convert floats that cannot overflow in accept() accept() only has to find out whether a float token is finite: json_sax_acceptor ignores the value, and the parser rejects only a float that overflows. A float whose digits fit into 19 digits and whose decimal exponent is at most max_exponent10 - 19 is below 10^max_exponent10 and cannot overflow, so the lexer, told by accept() that the value is not needed, skips the conversion for those. A token that may overflow, with more digits, or in another float format is converted as before, so 1e309 and its neighbours are rejected as they were (the new test compares accept() with parse() on tokens around the limit). json::accept(): canada.json Ir -8.6 % (GCC) / -10.9 % (Clang), 124-byte object -3.2 %; cycles on the M1 (release benchmarks, LTO): canada -7.8 %, floats.json -9.0 %. The check tests the accept() flag first, so the parser that needs the value pays one load and branch: SaxParse canada +0.35 % Ir (GCC and Clang), DOM canada +0.4 %. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Collect the members of ordered_json objects on a stack until they are known An ordered_json object keeps its members in a vector and appends every new member to it, so reading an object of unknown size reallocated the vector at 1, 2, 4, 8, ... members, and ordered_map copies all keys on every reallocation. The DOM SAX parser now collects the members of such objects on one stack shared by all open objects, as it does the elements of arrays, and moves them into a vector of exactly the right size when the closing brace arrives. An object that gets past 32 members switches to appending to its own vector (and, from 64, to the key index of the earlier commit). The members are the same, in the same order, and a key that occurs twice keeps the place of its first occurrence. std::map based objects, objects whose size the input announces (binary formats), and builds with JSON_DIAGNOSTICS or JSON_DIAGNOSTIC_POSITIONS are untouched. What only ordered objects need (the collected members, the key indexes) is a member of the parser only for them, and the code that stores a value tries the array that takes its elements directly first. An array that outgrows the stack now reserves 32 elements, the capacity a vector that grew by itself has at that point; with the larger reserve before, parsing floats.json (1 000 000 numbers) was 2 % slower on Apple M1 than before this series. ordered_json parse, Linux Ir against develop (GCC / Clang): citm -11.6 % / -11.7 %, twitter -8.4 % / -8.4 %, jeopardy (12 000 objects of 7 keys) -10.5 % / -8.0 %, canada -3.9 % / -5.2 %, 124-byte object -20.9 % / -16.5 %. macOS time (M1, earlier base): citm -19 %, twitter -13 %, jeopardy -13 %, canada -14 %, 124-byte object -26 %. json parse: steady-state twitter -0.1 % (see the previous commits), citm -1.2 %. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Rename the key index's match callback so Flawfinder doesn't read it as std::equal Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Bind the parser test's strings by reference in the range-for loop GCC and Clang 14 (-Wrange-loop-construct) reported the copy. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Collect container values only in basic_json's own parse functions A json_sax_dom_parser that a user constructs (as in the sax_no_exception example) must hold the values read before a parse error, as on develop: its value is used after the error. The values of containers of unknown size are now collected on a stack only with the new template parameter Collect (default false), which json::parse() and the json_view materialization set; without it, every value is stored in place as it arrives. A test checks the values kept after an error for json and ordered_json. Also fixes the clang-tidy, cpplint and Infer findings: the frame stack keeps its first frames in a std::array (left uninitialized) and the others in a std::unique_ptr, auto for create<>(), NOLINT for member initializers -Weffc++ needs, and an @infer-ignore for the nullptr that place_container() does not use. json::parse() and ordered_json::parse() instructions are unchanged (Apple clang; twitter, citm, canada, jeopardy: -0.03 % to +0.25 %). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Clarify the DOM parser's collection stacks and rename reenter_enclosing_container() 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> * Export json_sax_dom_parser from the C++20 module for MSVC MSVC needs the detail classes that basic_json befriends to be visible in the module (#3970, as json_sax_dom_callback_parser); with its new template parameter, json_sax_dom_parser failed in ci_module_cpp20 (C2039, not a member of nlohmann::detail). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Keep the overflow-limit numbers of the parser test in a std::vector clang-tidy (cppcoreguidelines-avoid-c-arrays) rejected the C array. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix the clang-tidy findings of the ordered_json parser tests The container sizes are a std::array, the expected text uses raw string literals, the generator's arithmetic is in parentheses, auto for the result of make<>(), and single characters are appended as characters. clang-tidy now reports nothing on the lines the branch adds to unit-ordered_json.cpp and unit-class_parser.cpp (apart from checks CI's clang-tidy does not have). Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- 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
|