json_view: accept() checks the text without writing an index

json_document::accept() ran the builder that writes the node index and
discarded the index. For input without comments and trailing commas it now
runs a second instantiation of the builder, with a template parameter Store
that is false: the grammar, number, string, UTF-8 and nesting checks are the
same code, but no node is written, the container nodes are not patched when
they close, the node array is not reserved, and no list of large objects is
kept. What only builds the arguments of a node (integer conversion, the digit
counts of floats) is not run either. accept() with comments or trailing commas
and parse() are not changed; the builder that stores is the one parse() always
used. A flag of the document passes the request from accept() to build().

accept() on twitter, citm_catalog, canada and the two minified files, A/B in
one binary, 6 code layouts, time after / before (geometric mean of the five;
the other 14 files of the set are faster too, none is slower):

- Apple M1 Max, Apple clang 21: 0.89
- x86-64 Haswell, GCC 16.2: 0.75
- x86-64 Haswell, Clang 23: 0.78

(semanticscholar-corpus.json: 0.52 on both x86 compilers.) parse() runs the same
instructions as before; its timings moved by up to 3 % on a few small files with
unchanged instruction counts (code placement).

Tests: accept() against json::accept() for the same inputs with all four
option combinations and with and without a terminating NUL: 2,000 generated
documents and damaged copies of them, nesting from 63 to 1000 levels (closed and
not), objects with 300 members, a null byte in the text, and float overflow for
a float document. Checked against a deliberately broken validator.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann committed 2026-10-30 22:18:14 +01:00
1 parent 755bb16d7a
commit dea2a4434a
6 files changed
+179 -27

No files matched your search

+43 -12
View File
@@ -366,6 +366,7 @@ struct document_data
const char* src = nullptr;
std::size_t size = 0;
node* nodes = nullptr; ///< the node index (see node.hpp)
bool validate_only = false; ///< accept(): build() checks the text and stores no nodes
std::size_t node_count = 0;
std::size_t node_capacity = 0;
node* inline_nodes = nullptr; ///< node array allocated together with this header
@@ -1311,7 +1312,8 @@ struct parse_failure
};
/// FloatType: the number_float_t of the document, whose overflow parse() rejects
template<typename FloatType, bool Comments, bool TrailingCommas, bool NulIsEnd, bool Sentinel>
/// Store: false for accept(): the grammar, number, string and UTF-8 checks run as for parse(), but no node is written
template<typename FloatType, bool Comments, bool TrailingCommas, bool NulIsEnd, bool Sentinel, bool Store>
class builder
{
public:
@@ -1852,10 +1854,13 @@ run_found:
NLOHMANN_VIEW_ALWAYS_INLINE bool run()
{
cold.m_doc.reserve(estimate_nodes(reinterpret_cast<const char*>(b), static_cast<std::size_t>(e - b)));
base = cold.m_doc.nodes;
out = base;
cap = base + cold.m_doc.node_capacity;
if (enabled(Store))
{
cold.m_doc.reserve(estimate_nodes(reinterpret_cast<const char*>(b), static_cast<std::size_t>(e - b)));
base = cold.m_doc.nodes;
out = base;
cap = base + cold.m_doc.node_capacity;
}
if (e - p >= 3 && p[0] == 0xEF && p[1] == 0xBB && p[2] == 0xBF)
{
@@ -2039,7 +2044,10 @@ close_object:
// an array pays nothing for this)
if (NLOHMANN_VIEW_UNLIKELY(cur_count >= document_data::index_min_members))
{
cold.note_large_object(cur_idx);
if (enabled(Store))
{
cold.note_large_object(cur_idx);
}
}
close_container:
@@ -2199,6 +2207,17 @@ indent_done:
/// an integer's value); two stores on little-endian targets
NLOHMANN_VIEW_ALWAYS_INLINE node* push_node(node_kind k, std::uint8_t flags, std::uint16_t extra, std::size_t off, std::uint64_t second)
{
if (!enabled(Store))
{
// accept(): nothing is stored, so what computes the arguments is not run either. base and out stay
// null: their difference, which open() takes, is 0.
static_cast<void>(k);
static_cast<void>(flags);
static_cast<void>(extra);
static_cast<void>(off);
static_cast<void>(second);
return out;
}
if (NLOHMANN_VIEW_UNLIKELY(out == cap))
{
const auto n = static_cast<std::size_t>(out - base);
@@ -2257,9 +2276,12 @@ indent_done:
NLOHMANN_VIEW_ALWAYS_INLINE void close()
{
node& n = base[cur_idx];
n.len = cur_count;
n.next = static_cast<std::uint32_t>(out - base) - cur_idx;
if (enabled(Store))
{
node& n = base[cur_idx];
n.len = cur_count;
n.next = static_cast<std::uint32_t>(out - base) - cur_idx;
}
if (--depth != 0)
{
if (NLOHMANN_VIEW_LIKELY(depth <= 64))
@@ -2434,17 +2456,17 @@ indent_done:
};
/// run the builder with compile-time options
template<typename FloatType, bool Comments, bool TrailingCommas, bool NulIsEnd>
template<typename FloatType, bool Comments, bool TrailingCommas, bool NulIsEnd, bool Store = true>
inline bool build_with(document_data& d, const char* src, std::size_t size, bool sentinel, parse_failure& failure)
{
if (sentinel)
{
builder<FloatType, Comments, TrailingCommas, NulIsEnd, true> bld(d, src, size);
builder<FloatType, Comments, TrailingCommas, NulIsEnd, true, Store> bld(d, src, size);
const bool ok = bld.run();
failure = bld.failure();
return ok;
}
builder<FloatType, Comments, TrailingCommas, NulIsEnd, false> bld(d, src, size);
builder<FloatType, Comments, TrailingCommas, NulIsEnd, false, Store> bld(d, src, size);
const bool ok = bld.run();
failure = bld.failure();
return ok;
@@ -2455,6 +2477,11 @@ inline bool build_with(document_data& d, const char* src, std::size_t size, bool
template<typename FloatType, bool NulIsEnd>
inline bool build(document_data& d, const char* src, std::size_t size, bool comments, bool trailing_commas, bool sentinel, parse_failure& failure)
{
if (d.validate_only && !comments && !trailing_commas)
{
// accept() of plain JSON: nothing is stored (the other option sets keep the builder that stores)
return build_with<FloatType, false, false, NulIsEnd, false>(d, src, size, sentinel, failure);
}
if (comments)
{
return trailing_commas ? build_with<FloatType, true, true, NulIsEnd>(d, src, size, sentinel, failure)
@@ -10129,6 +10156,7 @@ class basic_json_document
static bool accept(InputType&& input, const bool ignore_comments = false, const bool ignore_trailing_commas = false)
{
basic_json_document d;
d.m_validate_only = true; // (nothing is stored: only the result is used)
d.read(std::forward<InputType>(input), false, ignore_comments, ignore_trailing_commas);
return !d.is_discarded();
}
@@ -10573,7 +10601,9 @@ class basic_json_document
}
else
{
d.validate_only = m_validate_only;
ok = detail::view::build < typename BasicJsonType::number_float_t, !detail::abi_config::strict_nul_handling > (d, src, size, comments, trailing_commas, sentinel, failure);
d.validate_only = false;
}
if (NLOHMANN_VIEW_LIKELY(ok))
{
@@ -10734,6 +10764,7 @@ class basic_json_document
}
std::unique_ptr<document_data, document_data::deleter> m_data{}; // NOLINT(readability-redundant-member-init)
bool m_validate_only = false; ///< set by accept(): build() stores no nodes
};
/*!