Compare commits

..
Author SHA1 Message Date
Claude d479d34677 Un-nest the run-length ternary in the string escaper
clang-tidy's readability-avoid-nested-conditional-operator (newly enforced
by the ci_clang_tidy job's clang) rejected the nested ?: that picked the
run length: EnsureAscii ? (is_ascii_copyable(...) ? scan : 0) : bulk.

Compute it in an immediately-invoked lambda instead. The value stays const
and the laziness is unchanged - the ascii scan still runs only on the
ensure_ascii path when the first byte is copyable, and string_bulk_run only
on the other path (EnsureAscii is a template bool, so the dead branch is
folded away). Output is byte-for-byte identical; single_include regenerated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L1oJ2ggRHS37zeVe94QTA1
Signed-off-by: Claude <noreply@anthropic.com>
2026-09-02 22:42:18 +00:00
5 changed files with 42 additions and 266 deletions
+11 -83
View File
@@ -222,9 +222,7 @@ class json_sax_dom_parser
bool string(string_t& val) bool string(string_t& val)
{ {
// the interface allows moving the value (see json_sax::string), which handle_value(val);
// hands the lexer's buffer to the new value instead of copying it
handle_value(std::move(val));
return true; return true;
} }
@@ -534,8 +532,7 @@ class json_sax_dom_callback_parser
bool string(string_t& val) bool string(string_t& val)
{ {
// see json_sax_dom_parser::string() handle_value(val);
handle_value(std::move(val));
return true; return true;
} }
@@ -551,11 +548,6 @@ class json_sax_dom_callback_parser
const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::object_start, discarded); const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::object_start, discarded);
keep_stack.push_back(keep); keep_stack.push_back(keep);
// the key this object will be stored under, read before handle_value()
// may consume it; kept in lockstep with ref_stack so end_object() can
// find the object in its parent again
container_key_stack.push_back(current_key());
auto val = handle_value(BasicJsonType::value_t::object, true); auto val = handle_value(BasicJsonType::value_t::object, true);
ref_stack.push_back(val.second); ref_stack.push_back(val.second);
@@ -589,9 +581,6 @@ class json_sax_dom_callback_parser
// check callback for the key // check callback for the key
const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::key, k); const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::key, k);
key_keep_stack.push_back(keep); key_keep_stack.push_back(keep);
// remember the key so a rejected value can be erased without searching
// the object for it (kept in lockstep with key_keep_stack)
key_stack.push_back(val);
// add discarded value at the given key and store the reference for later // add discarded value at the given key and store the reference for later
if (keep && ref_stack.back()) if (keep && ref_stack.back())
@@ -633,16 +622,13 @@ class json_sax_dom_callback_parser
JSON_ASSERT(!ref_stack.empty()); JSON_ASSERT(!ref_stack.empty());
JSON_ASSERT(!keep_stack.empty()); JSON_ASSERT(!keep_stack.empty());
JSON_ASSERT(!container_key_stack.empty());
ref_stack.pop_back(); ref_stack.pop_back();
keep_stack.pop_back(); keep_stack.pop_back();
const string_t object_key = std::move(container_key_stack.back());
container_key_stack.pop_back();
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured()) if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured())
{ {
// remove discarded value // remove discarded value
remove_discarded_value(*ref_stack.back(), object_key); remove_discarded_value(*ref_stack.back());
} }
return true; return true;
@@ -653,9 +639,6 @@ class json_sax_dom_callback_parser
const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::array_start, discarded); const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::array_start, discarded);
keep_stack.push_back(keep); keep_stack.push_back(keep);
// see start_object()
container_key_stack.push_back(current_key());
auto val = handle_value(BasicJsonType::value_t::array, true); auto val = handle_value(BasicJsonType::value_t::array, true);
ref_stack.push_back(val.second); ref_stack.push_back(val.second);
@@ -718,11 +701,8 @@ class json_sax_dom_callback_parser
JSON_ASSERT(!ref_stack.empty()); JSON_ASSERT(!ref_stack.empty());
JSON_ASSERT(!keep_stack.empty()); JSON_ASSERT(!keep_stack.empty());
JSON_ASSERT(!container_key_stack.empty());
ref_stack.pop_back(); ref_stack.pop_back();
keep_stack.pop_back(); keep_stack.pop_back();
const string_t object_key = std::move(container_key_stack.back());
container_key_stack.pop_back();
// remove discarded value // remove discarded value
if (!ref_stack.empty() && ref_stack.back()) if (!ref_stack.empty() && ref_stack.back())
@@ -736,7 +716,7 @@ class json_sax_dom_callback_parser
// the array is either still stored under its key or was never // the array is either still stored under its key or was never
// stored, leaving the placeholder key() wrote; both show up as // stored, leaving the placeholder key() wrote; both show up as
// a discarded member of the parent object // a discarded member of the parent object
remove_discarded_value(*ref_stack.back(), object_key); remove_discarded_value(*ref_stack.back());
} }
} }
@@ -829,56 +809,15 @@ class json_sax_dom_callback_parser
} }
#endif #endif
/*! /// remove the discarded value the callback rejected from its parent
@brief the key the value now being handled will be stored under static void remove_discarded_value(BasicJsonType& parent)
Empty unless the enclosing container is an object, in which case it is the
key of the pending key() event. Read before handle_value() consumes that
key, so it is also correct when the value never reaches its parent.
*/
string_t current_key() const
{ {
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_object() for (auto it = parent.begin(); it != parent.end(); ++it)
&& !key_stack.empty())
{ {
return key_stack.back(); if (it->is_discarded())
}
return string_t{};
}
/*!
@brief remove the discarded value the callback rejected from its parent
A rejected value can only ever be the one most recently added to @a parent:
the last element of an array, or the placeholder key() stored under @a key
in an object. Looking there directly makes this O(1) resp. O(log n), where
searching @a parent for it made a filtering parse quadratic in the number of
members of a single container.
Finding no discarded value there means none was stored in the first place -
the callback rejected the value before it reached its parent - so there is
nothing to remove.
@param[in,out] parent the container to remove the rejected value from
@param[in] key the key the value was stored under; unused for arrays
*/
static void remove_discarded_value(BasicJsonType& parent, const string_t& key)
{ {
if (parent.is_array()) parent.erase(it);
{ break;
auto& array = *parent.m_data.m_value.array;
if (!array.empty() && array.back().is_discarded())
{
array.pop_back();
}
}
else if (parent.is_object())
{
auto& object = *parent.m_data.m_value.object;
const auto it = object.find(key);
if (it != object.end() && it->second.is_discarded())
{
object.erase(it);
} }
} }
} }
@@ -928,14 +867,11 @@ class json_sax_dom_callback_parser
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_object()) if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_object())
{ {
JSON_ASSERT(!key_keep_stack.empty()); JSON_ASSERT(!key_keep_stack.empty());
JSON_ASSERT(!key_stack.empty());
const bool placeholder_stored = key_keep_stack.back(); const bool placeholder_stored = key_keep_stack.back();
key_keep_stack.pop_back(); key_keep_stack.pop_back();
const string_t key = std::move(key_stack.back());
key_stack.pop_back();
if (placeholder_stored) if (placeholder_stored)
{ {
remove_discarded_value(*ref_stack.back(), key); remove_discarded_value(*ref_stack.back());
} }
} }
return {false, nullptr}; return {false, nullptr};
@@ -968,10 +904,8 @@ class json_sax_dom_callback_parser
JSON_ASSERT(ref_stack.back()->is_object()); JSON_ASSERT(ref_stack.back()->is_object());
// check if we should store an element for the current key // check if we should store an element for the current key
JSON_ASSERT(!key_keep_stack.empty()); JSON_ASSERT(!key_keep_stack.empty());
JSON_ASSERT(!key_stack.empty());
const bool store_element = key_keep_stack.back(); const bool store_element = key_keep_stack.back();
key_keep_stack.pop_back(); key_keep_stack.pop_back();
key_stack.pop_back();
if (!store_element) if (!store_element)
{ {
@@ -991,12 +925,6 @@ class json_sax_dom_callback_parser
std::vector<bool> keep_stack {}; // NOLINT(readability-redundant-member-init) std::vector<bool> keep_stack {}; // NOLINT(readability-redundant-member-init)
/// stack to manage which object keys to keep /// stack to manage which object keys to keep
std::vector<bool> key_keep_stack {}; // NOLINT(readability-redundant-member-init) std::vector<bool> key_keep_stack {}; // NOLINT(readability-redundant-member-init)
/// the keys key() stored a placeholder for, in lockstep with key_keep_stack
std::vector<string_t> key_stack {}; // NOLINT(readability-redundant-member-init)
/// for each open container, the key it is stored under in its parent
/// object, in lockstep with ref_stack; unused where the parent is not an
/// object
std::vector<string_t> container_key_stack {}; // NOLINT(readability-redundant-member-init)
/// helper to hold the reference for the next object element /// helper to hold the reference for the next object element
BasicJsonType* object_element = nullptr; BasicJsonType* object_element = nullptr;
/// whether a syntax error occurred /// whether a syntax error occurred
@@ -850,11 +850,16 @@ class serializer
// for the scan. Without it, text whose characters all have to be // for the scan. Without it, text whose characters all have to be
// escaped - CJK under ensure_ascii, where every byte is >= 0x80 - // escaped - CJK under ensure_ascii, where every byte is >= 0x80 -
// runs the scanner once per character only to be told zero. // runs the scanner once per character only to be told zero.
const std::size_t run = EnsureAscii const std::size_t run = [&]() -> std::size_t
? (is_ascii_copyable(data[i]) {
if (EnsureAscii)
{
return is_ascii_copyable(data[i])
? find_ascii_copyable_run(data + i, s.size() - i) ? find_ascii_copyable_run(data + i, s.size() - i)
: 0) : 0;
: string_bulk_run(data + i, s.size() - i); }
return string_bulk_run(data + i, s.size() - i);
}();
if (run != 0) if (run != 0)
{ {
// emit any bytes still pending in string_buffer first to // emit any bytes still pending in string_buffer first to
+20 -87
View File
@@ -10739,9 +10739,7 @@ class json_sax_dom_parser
bool string(string_t& val) bool string(string_t& val)
{ {
// the interface allows moving the value (see json_sax::string), which handle_value(val);
// hands the lexer's buffer to the new value instead of copying it
handle_value(std::move(val));
return true; return true;
} }
@@ -11051,8 +11049,7 @@ class json_sax_dom_callback_parser
bool string(string_t& val) bool string(string_t& val)
{ {
// see json_sax_dom_parser::string() handle_value(val);
handle_value(std::move(val));
return true; return true;
} }
@@ -11068,11 +11065,6 @@ class json_sax_dom_callback_parser
const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::object_start, discarded); const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::object_start, discarded);
keep_stack.push_back(keep); keep_stack.push_back(keep);
// the key this object will be stored under, read before handle_value()
// may consume it; kept in lockstep with ref_stack so end_object() can
// find the object in its parent again
container_key_stack.push_back(current_key());
auto val = handle_value(BasicJsonType::value_t::object, true); auto val = handle_value(BasicJsonType::value_t::object, true);
ref_stack.push_back(val.second); ref_stack.push_back(val.second);
@@ -11106,9 +11098,6 @@ class json_sax_dom_callback_parser
// check callback for the key // check callback for the key
const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::key, k); const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::key, k);
key_keep_stack.push_back(keep); key_keep_stack.push_back(keep);
// remember the key so a rejected value can be erased without searching
// the object for it (kept in lockstep with key_keep_stack)
key_stack.push_back(val);
// add discarded value at the given key and store the reference for later // add discarded value at the given key and store the reference for later
if (keep && ref_stack.back()) if (keep && ref_stack.back())
@@ -11150,16 +11139,13 @@ class json_sax_dom_callback_parser
JSON_ASSERT(!ref_stack.empty()); JSON_ASSERT(!ref_stack.empty());
JSON_ASSERT(!keep_stack.empty()); JSON_ASSERT(!keep_stack.empty());
JSON_ASSERT(!container_key_stack.empty());
ref_stack.pop_back(); ref_stack.pop_back();
keep_stack.pop_back(); keep_stack.pop_back();
const string_t object_key = std::move(container_key_stack.back());
container_key_stack.pop_back();
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured()) if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured())
{ {
// remove discarded value // remove discarded value
remove_discarded_value(*ref_stack.back(), object_key); remove_discarded_value(*ref_stack.back());
} }
return true; return true;
@@ -11170,9 +11156,6 @@ class json_sax_dom_callback_parser
const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::array_start, discarded); const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::array_start, discarded);
keep_stack.push_back(keep); keep_stack.push_back(keep);
// see start_object()
container_key_stack.push_back(current_key());
auto val = handle_value(BasicJsonType::value_t::array, true); auto val = handle_value(BasicJsonType::value_t::array, true);
ref_stack.push_back(val.second); ref_stack.push_back(val.second);
@@ -11235,11 +11218,8 @@ class json_sax_dom_callback_parser
JSON_ASSERT(!ref_stack.empty()); JSON_ASSERT(!ref_stack.empty());
JSON_ASSERT(!keep_stack.empty()); JSON_ASSERT(!keep_stack.empty());
JSON_ASSERT(!container_key_stack.empty());
ref_stack.pop_back(); ref_stack.pop_back();
keep_stack.pop_back(); keep_stack.pop_back();
const string_t object_key = std::move(container_key_stack.back());
container_key_stack.pop_back();
// remove discarded value // remove discarded value
if (!ref_stack.empty() && ref_stack.back()) if (!ref_stack.empty() && ref_stack.back())
@@ -11253,7 +11233,7 @@ class json_sax_dom_callback_parser
// the array is either still stored under its key or was never // the array is either still stored under its key or was never
// stored, leaving the placeholder key() wrote; both show up as // stored, leaving the placeholder key() wrote; both show up as
// a discarded member of the parent object // a discarded member of the parent object
remove_discarded_value(*ref_stack.back(), object_key); remove_discarded_value(*ref_stack.back());
} }
} }
@@ -11346,56 +11326,15 @@ class json_sax_dom_callback_parser
} }
#endif #endif
/*! /// remove the discarded value the callback rejected from its parent
@brief the key the value now being handled will be stored under static void remove_discarded_value(BasicJsonType& parent)
Empty unless the enclosing container is an object, in which case it is the
key of the pending key() event. Read before handle_value() consumes that
key, so it is also correct when the value never reaches its parent.
*/
string_t current_key() const
{ {
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_object() for (auto it = parent.begin(); it != parent.end(); ++it)
&& !key_stack.empty())
{ {
return key_stack.back(); if (it->is_discarded())
}
return string_t{};
}
/*!
@brief remove the discarded value the callback rejected from its parent
A rejected value can only ever be the one most recently added to @a parent:
the last element of an array, or the placeholder key() stored under @a key
in an object. Looking there directly makes this O(1) resp. O(log n), where
searching @a parent for it made a filtering parse quadratic in the number of
members of a single container.
Finding no discarded value there means none was stored in the first place -
the callback rejected the value before it reached its parent - so there is
nothing to remove.
@param[in,out] parent the container to remove the rejected value from
@param[in] key the key the value was stored under; unused for arrays
*/
static void remove_discarded_value(BasicJsonType& parent, const string_t& key)
{ {
if (parent.is_array()) parent.erase(it);
{ break;
auto& array = *parent.m_data.m_value.array;
if (!array.empty() && array.back().is_discarded())
{
array.pop_back();
}
}
else if (parent.is_object())
{
auto& object = *parent.m_data.m_value.object;
const auto it = object.find(key);
if (it != object.end() && it->second.is_discarded())
{
object.erase(it);
} }
} }
} }
@@ -11445,14 +11384,11 @@ class json_sax_dom_callback_parser
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_object()) if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_object())
{ {
JSON_ASSERT(!key_keep_stack.empty()); JSON_ASSERT(!key_keep_stack.empty());
JSON_ASSERT(!key_stack.empty());
const bool placeholder_stored = key_keep_stack.back(); const bool placeholder_stored = key_keep_stack.back();
key_keep_stack.pop_back(); key_keep_stack.pop_back();
const string_t key = std::move(key_stack.back());
key_stack.pop_back();
if (placeholder_stored) if (placeholder_stored)
{ {
remove_discarded_value(*ref_stack.back(), key); remove_discarded_value(*ref_stack.back());
} }
} }
return {false, nullptr}; return {false, nullptr};
@@ -11485,10 +11421,8 @@ class json_sax_dom_callback_parser
JSON_ASSERT(ref_stack.back()->is_object()); JSON_ASSERT(ref_stack.back()->is_object());
// check if we should store an element for the current key // check if we should store an element for the current key
JSON_ASSERT(!key_keep_stack.empty()); JSON_ASSERT(!key_keep_stack.empty());
JSON_ASSERT(!key_stack.empty());
const bool store_element = key_keep_stack.back(); const bool store_element = key_keep_stack.back();
key_keep_stack.pop_back(); key_keep_stack.pop_back();
key_stack.pop_back();
if (!store_element) if (!store_element)
{ {
@@ -11508,12 +11442,6 @@ class json_sax_dom_callback_parser
std::vector<bool> keep_stack {}; // NOLINT(readability-redundant-member-init) std::vector<bool> keep_stack {}; // NOLINT(readability-redundant-member-init)
/// stack to manage which object keys to keep /// stack to manage which object keys to keep
std::vector<bool> key_keep_stack {}; // NOLINT(readability-redundant-member-init) std::vector<bool> key_keep_stack {}; // NOLINT(readability-redundant-member-init)
/// the keys key() stored a placeholder for, in lockstep with key_keep_stack
std::vector<string_t> key_stack {}; // NOLINT(readability-redundant-member-init)
/// for each open container, the key it is stored under in its parent
/// object, in lockstep with ref_stack; unused where the parent is not an
/// object
std::vector<string_t> container_key_stack {}; // NOLINT(readability-redundant-member-init)
/// helper to hold the reference for the next object element /// helper to hold the reference for the next object element
BasicJsonType* object_element = nullptr; BasicJsonType* object_element = nullptr;
/// whether a syntax error occurred /// whether a syntax error occurred
@@ -21982,11 +21910,16 @@ class serializer
// for the scan. Without it, text whose characters all have to be // for the scan. Without it, text whose characters all have to be
// escaped - CJK under ensure_ascii, where every byte is >= 0x80 - // escaped - CJK under ensure_ascii, where every byte is >= 0x80 -
// runs the scanner once per character only to be told zero. // runs the scanner once per character only to be told zero.
const std::size_t run = EnsureAscii const std::size_t run = [&]() -> std::size_t
? (is_ascii_copyable(data[i]) {
if (EnsureAscii)
{
return is_ascii_copyable(data[i])
? find_ascii_copyable_run(data + i, s.size() - i) ? find_ascii_copyable_run(data + i, s.size() - i)
: 0) : 0;
: string_bulk_run(data + i, s.size() - i); }
return string_bulk_run(data + i, s.size() - i);
}();
if (run != 0) if (run != 0)
{ {
// emit any bytes still pending in string_buffer first to // emit any bytes still pending in string_buffer first to
-38
View File
@@ -81,44 +81,6 @@ BENCHMARK_CAPTURE(ParseString, signed_ints, TEST_DATA_DIRECTORY "/regressi
BENCHMARK_CAPTURE(ParseString, unsigned_ints, TEST_DATA_DIRECTORY "/regression/unsigned_ints.json"); BENCHMARK_CAPTURE(ParseString, unsigned_ints, TEST_DATA_DIRECTORY "/regression/unsigned_ints.json");
BENCHMARK_CAPTURE(ParseString, small_signed_ints, TEST_DATA_DIRECTORY "/regression/small_signed_ints.json"); BENCHMARK_CAPTURE(ParseString, small_signed_ints, TEST_DATA_DIRECTORY "/regression/small_signed_ints.json");
//////////////////////////////////////////////////////////////////////////////
// parse pretty-printed JSON from string
//
// Every file in the corpus above is minified or only lightly spaced, so none of
// them exercise the lexer's whitespace handling. Real-world JSON is frequently
// indented - configuration files, pretty-printed API responses, anything kept
// under version control - where insignificant whitespace can outweigh the data.
// Re-serializing a document with an indentation and parsing that keeps the
// content identical to the ParseString row above, so the pair isolates the cost
// of the whitespace alone.
//////////////////////////////////////////////////////////////////////////////
static void ParseIndented(benchmark::State& state, const char* filename, int indent)
{
std::ifstream f(filename);
std::string str((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
const std::string indented = json::parse(str).dump(indent);
while (state.KeepRunning())
{
state.PauseTiming();
auto* j = new json();
state.ResumeTiming();
*j = json::parse(indented);
state.PauseTiming();
delete j;
state.ResumeTiming();
}
state.SetBytesProcessed(state.iterations() * indented.size());
}
BENCHMARK_CAPTURE(ParseIndented, jeopardy / 4, TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json", 4);
BENCHMARK_CAPTURE(ParseIndented, canada / 4, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", 4);
BENCHMARK_CAPTURE(ParseIndented, citm_catalog / 4, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json", 4);
BENCHMARK_CAPTURE(ParseIndented, twitter / 4, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", 4);
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
// serialize JSON // serialize JSON
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
-52
View File
@@ -1564,58 +1564,6 @@ TEST_CASE("parser class")
CHECK (j_filtered2 == json({{"foo", {1, 2}}})); CHECK (j_filtered2 == json({{"foo", {1, 2}}}));
} }
SECTION("filter many members of one container")
{
// Rejecting a value makes the parser remove the placeholder its key
// event stored. Locating that placeholder used to be a scan of the
// whole parent, which made filtering a large container quadratic:
// 128k members took ~25 s. These cases keep many members alive
// while discarding many others, so the removal cost is the whole
// point; they run in milliseconds when the placeholder is erased
// directly.
constexpr int count = 20000;
std::string s = "{";
for (int i = 0; i < count; ++i)
{
// "a<i>" is kept, "z<i>" is discarded
s += "\"a" + std::to_string(i) + "\":" + std::to_string(i) + ",";
s += "\"z" + std::to_string(i) + "\":-1,";
}
s.back() = '}';
const json j_values = json::parse(s, [](int /*unused*/, json::parse_event_t e, const json & parsed)
{
return !(e == json::parse_event_t::value && parsed == json(-1));
});
CHECK(j_values.size() == count);
CHECK(j_values.at("a0") == json(0));
CHECK(j_values.at("a" + std::to_string(count - 1)) == json(count - 1));
CHECK_FALSE(j_values.contains("z0"));
CHECK_FALSE(j_values.contains("z" + std::to_string(count - 1)));
// the same, but discarding whole containers rather than values,
// which takes the end_object()/end_array() removal path
std::string s_nested = "{";
for (int i = 0; i < count; ++i)
{
s_nested += "\"a" + std::to_string(i) + "\":" + std::to_string(i) + ",";
s_nested += "\"z" + std::to_string(i) + "\":[1,2],";
}
s_nested.back() = '}';
const json j_arrays = json::parse(s_nested, [](int /*unused*/, json::parse_event_t e, const json& /*unused*/) noexcept
{
return e != json::parse_event_t::array_end;
});
CHECK(j_arrays.size() == count);
CHECK(j_arrays.at("a0") == json(0));
CHECK_FALSE(j_arrays.contains("z0"));
CHECK_FALSE(j_arrays.contains("z" + std::to_string(count - 1)));
}
SECTION("filter specific events") SECTION("filter specific events")
{ {
SECTION("first closing event") SECTION("first closing event")