mirror of
https://github.com/simdjson/simdjson
synced 2026-06-08 17:27:07 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 12062bc5bc | |||
| 6e0abb7f9c | |||
| 686c9bff54 | |||
| e1654be9b3 |
@@ -12,7 +12,6 @@
|
||||
#include <string_view>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#if defined(__aarch64__) || defined(__ARM_NEON)
|
||||
#include <arm_neon.h>
|
||||
@@ -37,10 +36,7 @@ namespace key_selector_detail {
|
||||
|
||||
// ============================================================================
|
||||
// Compile-time perfect-hash generator.
|
||||
//
|
||||
// This is a port of the ConstexprCore perfect-hash generator
|
||||
// (https://github.com/ConstexprCore/perfect_hash). It scales to ~100 keys at
|
||||
// compile time by determining association values one (position, character)
|
||||
// It scales to ~100 keys at compile time by determining association values one (position, character)
|
||||
// symbol at a time (gperf-style) instead of an exhaustive offset search, and
|
||||
// falls back to a Hash-and-Displace construction for large/awkward key sets.
|
||||
//
|
||||
|
||||
@@ -65,37 +65,44 @@ simdjson_inline simdjson_result<value> object::find_field(const std::string_view
|
||||
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS
|
||||
template <typename Selector, typename Func>
|
||||
simdjson_inline error_code object::for_each(Func&& on_match) noexcept {
|
||||
auto first = this->begin();
|
||||
if (first.error()) { return first.error(); }
|
||||
object_iterator it = first.value_unsafe();
|
||||
object_iterator last{};
|
||||
simdjson_flatten simdjson_inline for_each_result object::for_each(Func&& on_match) noexcept {
|
||||
// Single pass driven directly by the value_iterator, mirroring
|
||||
// find_field_unordered_raw + value(iter.child()). Compared to walking via
|
||||
// object_iterator/field, this avoids constructing a simdjson_result<field> and
|
||||
// a field (key + value) for every field -- and the development-check bookkeeping
|
||||
// in object_iterator -- building a value only for the fields that actually match.
|
||||
// We operate on a copy of the iterator, as object::begin() would.
|
||||
value_iterator it = iter;
|
||||
std::array<bool, Selector::size()> seen{};
|
||||
std::size_t matched = 0;
|
||||
while (it != last) {
|
||||
auto field_res = *it;
|
||||
if (field_res.error()) { return field_res.error(); }
|
||||
field f = field_res.value_unsafe();
|
||||
std::size_t idx = Selector::match_raw(f.key());
|
||||
while (it.is_open()) {
|
||||
raw_json_string key;
|
||||
error_code error = it.field_key().get(key);
|
||||
if (error) { it.abandon(); return {error, matched}; }
|
||||
// Advance past the ':' and descend onto the value.
|
||||
if ((error = it.field_value())) { it.abandon(); return {error, matched}; }
|
||||
std::size_t idx = Selector::match_raw(key);
|
||||
if (idx < Selector::size() && !seen[idx]) {
|
||||
seen[idx] = true;
|
||||
value matched_value = f.value();
|
||||
value matched_value(it.child());
|
||||
// The callback may return either void or an error_code. When it returns an
|
||||
// error_code, we stop at the first non-SUCCESS result and propagate it so
|
||||
// the caller can surface value-parse errors (for example, a type mismatch
|
||||
// on a matched field). A void-returning callback is responsible for
|
||||
// handling its own errors.
|
||||
if constexpr (std::is_same_v<decltype(on_match(idx, matched_value)), error_code>) {
|
||||
error_code e = on_match(idx, matched_value);
|
||||
if (e) { return e; }
|
||||
if ((error = on_match(idx, matched_value))) { return {error, matched}; }
|
||||
} else {
|
||||
on_match(idx, matched_value);
|
||||
}
|
||||
if (++matched >= Selector::size()) { break; }
|
||||
}
|
||||
++it;
|
||||
// Skip the value (a no-op if the callback consumed it) and step to the next
|
||||
// field; has_next_field() ends the container on '}', which closes the loop.
|
||||
if ((error = it.skip_child())) { it.abandon(); return {error, matched}; }
|
||||
if ((error = it.has_next_field().error())) { return {error, matched}; }
|
||||
}
|
||||
return SUCCESS;
|
||||
return {SUCCESS, matched};
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -16,6 +16,22 @@ namespace simdjson {
|
||||
namespace SIMDJSON_IMPLEMENTATION {
|
||||
namespace ondemand {
|
||||
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS
|
||||
/**
|
||||
* Result of object::for_each: the first error encountered (SUCCESS if none) and
|
||||
* the number of distinct selector keys that matched during the walk. A
|
||||
* matched_count equal to Selector::size() means every selected key was present
|
||||
* in the object. Implicitly converts to error_code so existing callers that only
|
||||
* care about the error (including SIMDJSON_TRY and the test ASSERT_* macros) keep
|
||||
* working unchanged.
|
||||
*/
|
||||
struct for_each_result {
|
||||
error_code error{SUCCESS};
|
||||
std::size_t matched_count{0};
|
||||
constexpr operator error_code() const noexcept { return error; }
|
||||
};
|
||||
#endif
|
||||
|
||||
/**
|
||||
* A forward-only JSON object field iterator.
|
||||
*/
|
||||
@@ -148,11 +164,14 @@ public:
|
||||
* error_code, the walk stops at the first non-SUCCESS result and that error is
|
||||
* returned, which lets the callback surface value-parse errors.
|
||||
*
|
||||
* @returns SUCCESS, or the first error encountered while walking the object
|
||||
* (including any error returned by the callback).
|
||||
* @returns a for_each_result holding the first error encountered while walking
|
||||
* the object (including any error returned by the callback, SUCCESS if
|
||||
* none) and the number of distinct selector keys that matched. The
|
||||
* result converts implicitly to error_code, so callers that only need
|
||||
* the error can ignore the count.
|
||||
*/
|
||||
template <typename Selector, typename Func>
|
||||
simdjson_inline error_code for_each(Func&& on_match) noexcept;
|
||||
simdjson_inline for_each_result for_each(Func&& on_match) noexcept;
|
||||
#endif
|
||||
|
||||
/**
|
||||
|
||||
@@ -309,6 +309,22 @@ template <typename T>
|
||||
using selector_for = typename [: std::meta::substitute(
|
||||
^^SIMDJSON_IMPLEMENTATION::ondemand::key_selector, selector_key_args<T>()) :];
|
||||
|
||||
// True when none of T's eligible members is an optional type, i.e. every member
|
||||
// is required. In that case presence can be checked with a single match count
|
||||
// instead of a per-member "seen" array.
|
||||
template <typename T>
|
||||
consteval bool all_eligible_members_required() {
|
||||
bool all_required = true;
|
||||
template for (constexpr auto mem : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
|
||||
if constexpr (is_eligible_member(mem)) {
|
||||
if constexpr (concepts::optional_type<typename [: std::meta::type_of(mem) :]>) {
|
||||
all_required = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return all_required;
|
||||
}
|
||||
|
||||
} // namespace key_selector_reflection_detail
|
||||
|
||||
template <typename T, typename ValT>
|
||||
@@ -321,40 +337,63 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept {
|
||||
SIMDJSON_TRY(val.get_object().get(obj));
|
||||
}
|
||||
using selector = key_selector_reflection_detail::selector_for<T>;
|
||||
std::array<bool, selector::size()> seen_member{};
|
||||
// Single pass over the object: each field whose key matches a member yields its
|
||||
// selector index, which we map back to the corresponding member. The callback
|
||||
// returns an error_code so that a value-parse error (e.g. a type mismatch on a
|
||||
// matched field) is propagated by for_each instead of being silently dropped.
|
||||
error_code walk_error = obj.template for_each<selector>(
|
||||
[&](std::size_t matched_index, SIMDJSON_IMPLEMENTATION::ondemand::value field_value) -> error_code {
|
||||
std::size_t counter = 0;
|
||||
error_code field_error = SUCCESS;
|
||||
if constexpr (key_selector_reflection_detail::all_eligible_members_required<T>()) {
|
||||
// Fast path: every member is required. A single for_each pass parses each
|
||||
// matched field; the returned match count then tells us whether every member
|
||||
// was present (matched_count == selector::size()) without a per-member "seen"
|
||||
// array. A value-parse error (e.g. a type mismatch) is propagated by for_each.
|
||||
auto walk = obj.template for_each<selector>(
|
||||
[&](std::size_t matched_index, SIMDJSON_IMPLEMENTATION::ondemand::value field_value) -> error_code {
|
||||
std::size_t counter = 0;
|
||||
template for (constexpr auto mem : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
|
||||
if constexpr (key_selector_reflection_detail::is_eligible_member(mem)) {
|
||||
if (matched_index == counter) { return field_value.get(out.[:mem:]); }
|
||||
++counter;
|
||||
}
|
||||
}
|
||||
return SUCCESS;
|
||||
});
|
||||
if (walk.error) { return walk.error; }
|
||||
// A missing required member shows up as a short match count and is reported as
|
||||
// NO_SUCH_FIELD, mirroring the ordered obj[key] path.
|
||||
if (walk.matched_count != selector::size()) { return NO_SUCH_FIELD; }
|
||||
return SUCCESS;
|
||||
} else {
|
||||
std::array<bool, selector::size()> seen_member{};
|
||||
// Single pass over the object: each field whose key matches a member yields its
|
||||
// selector index, which we map back to the corresponding member. The callback
|
||||
// returns an error_code so that a value-parse error (e.g. a type mismatch on a
|
||||
// matched field) is propagated by for_each instead of being silently dropped.
|
||||
error_code walk_error = obj.template for_each<selector>(
|
||||
[&](std::size_t matched_index, SIMDJSON_IMPLEMENTATION::ondemand::value field_value) -> error_code {
|
||||
std::size_t counter = 0;
|
||||
error_code field_error = SUCCESS;
|
||||
template for (constexpr auto mem : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
|
||||
if constexpr (key_selector_reflection_detail::is_eligible_member(mem)) {
|
||||
if (matched_index == counter) {
|
||||
seen_member[counter] = true;
|
||||
field_error = field_value.get(out.[:mem:]);
|
||||
}
|
||||
++counter;
|
||||
}
|
||||
}
|
||||
return field_error;
|
||||
});
|
||||
if (walk_error) { return walk_error; }
|
||||
// Required (non-optional) members must be present: a missing one is reported as
|
||||
// NO_SUCH_FIELD, mirroring the ordered obj[key] path. Optional members may be
|
||||
// absent.
|
||||
std::size_t check_counter = 0;
|
||||
template for (constexpr auto mem : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
|
||||
if constexpr (key_selector_reflection_detail::is_eligible_member(mem)) {
|
||||
if (matched_index == counter) {
|
||||
seen_member[counter] = true;
|
||||
field_error = field_value.get(out.[:mem:]);
|
||||
if constexpr (!concepts::optional_type<decltype(out.[:mem:])>) {
|
||||
if (!seen_member[check_counter]) { return NO_SUCH_FIELD; }
|
||||
}
|
||||
++counter;
|
||||
++check_counter;
|
||||
}
|
||||
}
|
||||
return field_error;
|
||||
});
|
||||
if (walk_error) { return walk_error; }
|
||||
// Required (non-optional) members must be present: a missing one is reported as
|
||||
// NO_SUCH_FIELD, mirroring the ordered obj[key] path. Optional members may be
|
||||
// absent.
|
||||
std::size_t check_counter = 0;
|
||||
template for (constexpr auto mem : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
|
||||
if constexpr (key_selector_reflection_detail::is_eligible_member(mem)) {
|
||||
if constexpr (!concepts::optional_type<decltype(out.[:mem:])>) {
|
||||
if (!seen_member[check_counter]) { return NO_SUCH_FIELD; }
|
||||
}
|
||||
++check_counter;
|
||||
}
|
||||
return SUCCESS;
|
||||
}
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
@@ -344,6 +344,47 @@ namespace object_tests {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS
|
||||
// The key_selector's matcher (match_raw, the perfect hash) must return the
|
||||
// right selector index for every key, including tricky cases: keys that are
|
||||
// prefixes of one another, keys that extend a real key, a long key, and misses.
|
||||
// This guards the prefix/length disambiguation against the hash.
|
||||
bool key_selector_matchers_agree() {
|
||||
TEST_START();
|
||||
// Probe builds "<key>\"" in a padded buffer and checks match_raw returns the
|
||||
// expected selector index (or N for a miss).
|
||||
auto probe = [](auto sel_tag, std::string_view key, std::size_t expected) -> bool {
|
||||
using sel = decltype(sel_tag);
|
||||
char buf[64] = {};
|
||||
for (size_t i = 0; i < key.size(); ++i) { buf[i] = key[i]; }
|
||||
buf[key.size()] = '"';
|
||||
ondemand::raw_json_string r(reinterpret_cast<const uint8_t*>(buf));
|
||||
ASSERT_EQUAL(sel::match_raw(r), expected);
|
||||
return true;
|
||||
};
|
||||
// Selector with prefix keys and a 30-character key.
|
||||
using small_sel = ondemand::key_selector<"a", "ab", "abc", "id", "name",
|
||||
"abcdefghijklmnopqrstuvwxyz1234">;
|
||||
std::size_t i = 0;
|
||||
for (auto k : {"a", "ab", "abc", "id", "name", "abcdefghijklmnopqrstuvwxyz1234"}) {
|
||||
if (!probe(small_sel{}, k, i++)) { return false; }
|
||||
}
|
||||
for (auto k : {"x", "abcd", "nam", "names", "i", "ids", "zzzzz", ""}) {
|
||||
if (!probe(small_sel{}, k, small_sel::size())) { return false; }
|
||||
}
|
||||
// Larger selector exercising the same matcher.
|
||||
using big_sel = ondemand::key_selector<"k00","k01","k02","k03","k04","k05",
|
||||
"k06","k07","k08","k09","k10","k11">;
|
||||
if (!probe(big_sel{}, "k00", 0)) { return false; }
|
||||
if (!probe(big_sel{}, "k05", 5)) { return false; }
|
||||
if (!probe(big_sel{}, "k11", 11)) { return false; }
|
||||
for (auto k : {"k12","nope",""}) {
|
||||
if (!probe(big_sel{}, k, big_sel::size())) { return false; }
|
||||
}
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
#endif
|
||||
|
||||
bool run() {
|
||||
return
|
||||
object_find_field_unordered() &&
|
||||
@@ -355,6 +396,7 @@ namespace object_tests {
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS
|
||||
object_find_field_key_selector() &&
|
||||
object_for_each_callback_error() &&
|
||||
key_selector_matchers_agree() &&
|
||||
#endif
|
||||
#if SIMDJSON_EXCEPTIONS && SIMDJSON_SUPPORTS_CONCEPTS
|
||||
key_selector_example_toplevel() &&
|
||||
|
||||
Reference in New Issue
Block a user