mirror of
https://github.com/simdjson/simdjson
synced 2026-06-08 17:27:07 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f4807d63a3 | |||
| 72ca766152 | |||
| e886511471 | |||
| 9eaeb6d314 | |||
| 0ac0107742 |
@@ -143,6 +143,24 @@ concept container_but_not_string =
|
||||
std::ranges::input_range<T> && !string_like<T> && !concepts::string_view_keyed_map<T>;
|
||||
|
||||
|
||||
// Concept: Indexable container that is not a string or associative container
|
||||
// Accepts: std::vector, std::array, std::deque (have operator[], value_type, not string_like)
|
||||
// Rejects: std::string (string_like), std::list (no operator[]), std::map (has key_type)
|
||||
template<typename Container>
|
||||
concept indexable_container = requires {
|
||||
typename Container::value_type;
|
||||
requires !concepts::string_like<Container>;
|
||||
requires !requires { typename Container::key_type; }; // Reject maps/sets
|
||||
requires requires(Container& c, std::size_t i) {
|
||||
{ c[i] } -> std::convertible_to<typename Container::value_type>;
|
||||
};
|
||||
};
|
||||
|
||||
// Variable template to use with std::meta::substitute
|
||||
template<typename Container>
|
||||
constexpr bool indexable_container_v = indexable_container<Container>;
|
||||
|
||||
|
||||
} // namespace concepts
|
||||
|
||||
|
||||
|
||||
@@ -52,3 +52,6 @@
|
||||
#include "simdjson/generic/ondemand/json_string_builder-inl.h"
|
||||
#include "simdjson/generic/ondemand/json_builder.h"
|
||||
|
||||
// JSON path accessor (compile-time) - must be after inline definitions
|
||||
#include "simdjson/generic/ondemand/compile_time_accessors.h"
|
||||
|
||||
|
||||
@@ -0,0 +1,731 @@
|
||||
/**
|
||||
* Compile-time JSONPath and JSON Pointer accessors using C++26 reflection
|
||||
* This file provides functionality to pre-compile JSON paths and pointers at compile time
|
||||
* and generate optimized accessor code using reflection.
|
||||
*/
|
||||
#ifndef SIMDJSON_GENERIC_ONDEMAND_COMPILE_TIME_ACCESSORS_H
|
||||
|
||||
#ifndef SIMDJSON_CONDITIONAL_INCLUDE
|
||||
#define SIMDJSON_GENERIC_ONDEMAND_COMPILE_TIME_ACCESSORS_H
|
||||
|
||||
#endif // SIMDJSON_CONDITIONAL_INCLUDE
|
||||
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS && SIMDJSON_STATIC_REFLECTION
|
||||
|
||||
#include <string_view>
|
||||
#include <cstddef>
|
||||
#include <array>
|
||||
|
||||
namespace simdjson {
|
||||
namespace SIMDJSON_IMPLEMENTATION {
|
||||
namespace ondemand {
|
||||
/***
|
||||
* JSONPath implementation for compile-time access
|
||||
* RFC 9535 JSONPath: Query Expressions for JSON, https://www.rfc-editor.org/rfc/rfc9535
|
||||
*/
|
||||
namespace json_path {
|
||||
|
||||
// Note: value type must be fully defined before this header is included
|
||||
// This is ensured by including this in amalgamated.h after value-inl.h
|
||||
|
||||
// Path step types
|
||||
enum class step_type {
|
||||
field, // .field_name or ["field_name"]
|
||||
array_index // [index]
|
||||
};
|
||||
|
||||
// Represents a single step in a JSONPath expression
|
||||
template<std::size_t N>
|
||||
struct path_step {
|
||||
step_type type;
|
||||
char key[N]; // Field name (empty for array indices)
|
||||
std::size_t index; // Array index (0 for field access)
|
||||
|
||||
constexpr path_step(step_type t, const char (&k)[N], std::size_t idx = 0)
|
||||
: type(t), index(idx) {
|
||||
for (std::size_t i = 0; i < N; ++i) {
|
||||
key[i] = k[i];
|
||||
}
|
||||
}
|
||||
|
||||
constexpr std::string_view key_view() const {
|
||||
return {key, N - 1};
|
||||
}
|
||||
};
|
||||
|
||||
// Helper to create field step
|
||||
template<std::size_t N>
|
||||
consteval auto make_field_step(const char (&name)[N]) {
|
||||
return path_step<N>(step_type::field, name, 0);
|
||||
}
|
||||
|
||||
// Helper to create array index step
|
||||
consteval auto make_index_step(std::size_t idx) {
|
||||
return path_step<1>(step_type::array_index, "", idx);
|
||||
}
|
||||
|
||||
// Parse state for compile-time JSONPath parsing
|
||||
struct parse_result {
|
||||
bool success;
|
||||
std::size_t pos;
|
||||
std::string_view error_msg;
|
||||
};
|
||||
|
||||
// Compile-time JSONPath parser
|
||||
// Supports subset: .field, ["field"], [index], nested combinations
|
||||
template<constevalutil::fixed_string Path>
|
||||
struct json_path_parser {
|
||||
static constexpr std::string_view path_str = Path.view();
|
||||
|
||||
// Skip leading $ if present
|
||||
static consteval std::size_t skip_root() {
|
||||
if (!path_str.empty() && path_str[0] == '$') {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Count the number of steps in the path at compile time
|
||||
static consteval std::size_t count_steps() {
|
||||
std::size_t count = 0;
|
||||
std::size_t i = skip_root();
|
||||
|
||||
while (i < path_str.size()) {
|
||||
if (path_str[i] == '.') {
|
||||
// Field access: .field
|
||||
++i;
|
||||
if (i >= path_str.size()) break;
|
||||
|
||||
// Skip field name
|
||||
while (i < path_str.size() && path_str[i] != '.' && path_str[i] != '[') {
|
||||
++i;
|
||||
}
|
||||
++count;
|
||||
} else if (path_str[i] == '[') {
|
||||
// Array or bracket notation
|
||||
++i;
|
||||
if (i >= path_str.size()) break;
|
||||
|
||||
if (path_str[i] == '"' || path_str[i] == '\'') {
|
||||
// Field access: ["field"] or ['field']
|
||||
char quote = path_str[i];
|
||||
++i;
|
||||
while (i < path_str.size() && path_str[i] != quote) {
|
||||
++i;
|
||||
}
|
||||
if (i < path_str.size()) ++i; // skip closing quote
|
||||
if (i < path_str.size() && path_str[i] == ']') ++i;
|
||||
} else {
|
||||
// Array index: [0], [123]
|
||||
while (i < path_str.size() && path_str[i] != ']') {
|
||||
++i;
|
||||
}
|
||||
if (i < path_str.size()) ++i; // skip ]
|
||||
}
|
||||
++count;
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
// Parse a field name at compile time
|
||||
static consteval std::size_t parse_field_name(std::size_t start, char* out, std::size_t max_len) {
|
||||
std::size_t len = 0;
|
||||
std::size_t i = start;
|
||||
|
||||
while (i < path_str.size() && path_str[i] != '.' && path_str[i] != '[' && len < max_len - 1) {
|
||||
out[len++] = path_str[i++];
|
||||
}
|
||||
out[len] = '\0';
|
||||
return i;
|
||||
}
|
||||
|
||||
// Parse an array index at compile time
|
||||
static consteval std::pair<std::size_t, std::size_t> parse_array_index(std::size_t start) {
|
||||
std::size_t index = 0;
|
||||
std::size_t i = start;
|
||||
|
||||
while (i < path_str.size() && path_str[i] >= '0' && path_str[i] <= '9') {
|
||||
index = index * 10 + (path_str[i] - '0');
|
||||
++i;
|
||||
}
|
||||
|
||||
return {i, index};
|
||||
}
|
||||
};
|
||||
|
||||
// Compile-time path accessor generator
|
||||
template<typename T, constevalutil::fixed_string Path>
|
||||
struct path_accessor {
|
||||
using value = ::simdjson::SIMDJSON_IMPLEMENTATION::ondemand::value;
|
||||
|
||||
static constexpr auto parser = json_path_parser<Path>();
|
||||
static constexpr std::size_t num_steps = parser.count_steps();
|
||||
static constexpr std::string_view path_view = Path.view();
|
||||
|
||||
// Compile-time accessor generation
|
||||
// If T is a struct, validates the path at compile time
|
||||
// If T is void, skips validation
|
||||
template<typename DocOrValue>
|
||||
static inline simdjson_result<value> access(DocOrValue& doc_or_val) noexcept {
|
||||
// Validate path at compile time if T is a struct
|
||||
if constexpr (std::is_class_v<T>) {
|
||||
constexpr bool path_valid = validate_path();
|
||||
static_assert(path_valid, "JSONPath does not match struct definition");
|
||||
}
|
||||
|
||||
// Parse the path at compile time to build access steps
|
||||
return access_impl<parser.skip_root()>(doc_or_val.get_value());
|
||||
}
|
||||
|
||||
private:
|
||||
// Recursive template to generate compile-time accessor code
|
||||
// PathPos parameter is the position in the path string (compile-time constant)
|
||||
template<std::size_t PathPos>
|
||||
static inline simdjson_result<value> access_impl(simdjson_result<value> current) noexcept {
|
||||
if (current.error()) return current;
|
||||
|
||||
// Base case: if we've consumed the entire path, return current value
|
||||
if constexpr (PathPos >= path_view.size()) {
|
||||
return current;
|
||||
} else if constexpr (path_view[PathPos] == '.') {
|
||||
// Field access - extract field name at compile time
|
||||
constexpr auto field_info = parse_next_field(PathPos);
|
||||
constexpr std::string_view field_name = std::get<0>(field_info);
|
||||
constexpr std::size_t next_pos = std::get<1>(field_info);
|
||||
|
||||
// Generate field access code
|
||||
auto obj_result = current.get_object();
|
||||
if (obj_result.error()) return obj_result.error();
|
||||
|
||||
auto obj = obj_result.value_unsafe();
|
||||
auto next_value = obj.find_field_unordered(field_name);
|
||||
|
||||
// Recursively process next step at compile time
|
||||
return access_impl<next_pos>(next_value);
|
||||
|
||||
} else if constexpr (path_view[PathPos] == '[') {
|
||||
// Array or bracket notation
|
||||
constexpr auto bracket_info = parse_bracket(PathPos);
|
||||
constexpr bool is_field = std::get<0>(bracket_info);
|
||||
constexpr std::size_t next_pos = std::get<2>(bracket_info);
|
||||
|
||||
if constexpr (is_field) {
|
||||
// Field access with bracket notation
|
||||
constexpr std::string_view field_name = std::get<1>(bracket_info);
|
||||
|
||||
auto obj_result = current.get_object();
|
||||
if (obj_result.error()) return obj_result.error();
|
||||
|
||||
auto obj = obj_result.value_unsafe();
|
||||
auto next_value = obj.find_field_unordered(field_name);
|
||||
|
||||
return access_impl<next_pos>(next_value);
|
||||
|
||||
} else {
|
||||
// Array index access
|
||||
constexpr std::size_t index = std::get<3>(bracket_info);
|
||||
|
||||
auto arr_result = current.get_array();
|
||||
if (arr_result.error()) return arr_result.error();
|
||||
|
||||
auto arr = arr_result.value_unsafe();
|
||||
auto next_value = arr.at(index);
|
||||
|
||||
return access_impl<next_pos>(next_value);
|
||||
}
|
||||
} else {
|
||||
// Skip unexpected characters and continue
|
||||
return access_impl<PathPos + 1>(current);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: Parse next field name at compile time
|
||||
static consteval auto parse_next_field(std::size_t start) {
|
||||
std::size_t i = start + 1; // skip '.'
|
||||
std::size_t field_start = i;
|
||||
while (i < path_view.size() && path_view[i] != '.' && path_view[i] != '[') {
|
||||
++i;
|
||||
}
|
||||
std::string_view field_name = path_view.substr(field_start, i - field_start);
|
||||
return std::make_tuple(field_name, i);
|
||||
}
|
||||
|
||||
// Helper: Parse bracket notation at compile time
|
||||
// Returns: (is_field, field_name, next_pos, index)
|
||||
static consteval auto parse_bracket(std::size_t start) {
|
||||
std::size_t i = start + 1; // skip '['
|
||||
|
||||
if (i < path_view.size() && (path_view[i] == '"' || path_view[i] == '\'')) {
|
||||
// Field access
|
||||
char quote = path_view[i];
|
||||
++i;
|
||||
std::size_t field_start = i;
|
||||
while (i < path_view.size() && path_view[i] != quote) {
|
||||
++i;
|
||||
}
|
||||
std::string_view field_name = path_view.substr(field_start, i - field_start);
|
||||
if (i < path_view.size()) ++i; // skip closing quote
|
||||
if (i < path_view.size() && path_view[i] == ']') ++i;
|
||||
|
||||
return std::make_tuple(true, field_name, i, std::size_t(0));
|
||||
} else {
|
||||
// Array index
|
||||
std::size_t index = 0;
|
||||
while (i < path_view.size() && path_view[i] >= '0' && path_view[i] <= '9') {
|
||||
index = index * 10 + (path_view[i] - '0');
|
||||
++i;
|
||||
}
|
||||
if (i < path_view.size() && path_view[i] == ']') ++i;
|
||||
|
||||
return std::make_tuple(false, std::string_view{}, i, index);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: Check if a type has a member with given name using reflection
|
||||
template<typename Type>
|
||||
static consteval bool has_member(std::string_view member_name) {
|
||||
constexpr auto members = std::meta::nonstatic_data_members_of(
|
||||
^^Type, std::meta::access_context::unchecked()
|
||||
);
|
||||
|
||||
for (auto mem : members) {
|
||||
if (std::meta::identifier_of(mem) == member_name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Helper: Get type of member by name using reflection
|
||||
template<typename Type>
|
||||
static consteval auto get_member_type(std::string_view member_name) {
|
||||
constexpr auto members = std::meta::nonstatic_data_members_of(
|
||||
^^Type, std::meta::access_context::unchecked()
|
||||
);
|
||||
|
||||
for (auto mem : members) {
|
||||
if (std::meta::identifier_of(mem) == member_name) {
|
||||
return std::meta::type_of(mem);
|
||||
}
|
||||
}
|
||||
return ^^void; // Return void if not found
|
||||
}
|
||||
|
||||
public:
|
||||
// Helper: Check if type represents a JSON array (indexable sequence container)
|
||||
//
|
||||
// Rationale:
|
||||
// - We're validating JSONPath semantics: path[index] requires subscript access
|
||||
// - JSON arrays are ordered sequences with numeric indexed access
|
||||
// - Runtime JSON parsing uses operator[] for array element access
|
||||
//
|
||||
// Requirements:
|
||||
// 1. Must support operator[](size_t) for indexed access
|
||||
// 2. Must represent a sequence (have value_type)
|
||||
// 3. Must NOT be a string (strings are JSON strings, not arrays)
|
||||
// 4. Must NOT be associative (maps/sets have different JSON semantics)
|
||||
//
|
||||
// Helper to check if a reflected type satisfies the indexable_container concept
|
||||
// We use std::meta::substitute to evaluate the concept against a reflected type
|
||||
static consteval bool is_array_like_reflected(std::meta::info type_reflection) {
|
||||
// C-style arrays
|
||||
if (std::meta::is_array_type(type_reflection)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Test if the reflected type satisfies our indexable_container concept
|
||||
// substitute evaluates indexable_container_v<T> where T is the reflected type
|
||||
if (std::meta::can_substitute(^^concepts::indexable_container_v, {type_reflection})) {
|
||||
return std::meta::extract<bool>(std::meta::substitute(^^concepts::indexable_container_v, {type_reflection}));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Helper: Get element type from reflected array-like type
|
||||
static consteval std::meta::info get_element_type_reflected(std::meta::info type_reflection) {
|
||||
// Check for C-style arrays first using reflection predicates
|
||||
if (std::meta::is_array_type(type_reflection)) {
|
||||
// For C-style arrays (e.g., int[10]), extract element type using std::meta::remove_extent
|
||||
return std::meta::remove_extent(type_reflection);
|
||||
}
|
||||
|
||||
// Look for value_type member in the reflected type (standard containers)
|
||||
auto members = std::meta::members_of(type_reflection, std::meta::access_context::unchecked());
|
||||
for (auto mem : members) {
|
||||
if (std::meta::is_type(mem)) {
|
||||
auto name = std::meta::identifier_of(mem);
|
||||
if (name == "value_type") {
|
||||
// Return the reflected type of value_type
|
||||
return mem;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ^^void;
|
||||
}
|
||||
|
||||
// Helper: Check if a non-reflected type is array-like (for template metaprogramming)
|
||||
template<typename Type>
|
||||
static consteval bool is_container_type() {
|
||||
using BaseType = std::remove_cvref_t<Type>;
|
||||
|
||||
// Has value_type (std::vector, std::array, std::list, etc.)
|
||||
if constexpr (requires { typename BaseType::value_type; }) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// C-style array
|
||||
if constexpr (std::is_array_v<BaseType>) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Helper: Extract element type from non-reflected container
|
||||
template<typename Type>
|
||||
using extract_element_type = std::conditional_t<
|
||||
requires { typename std::remove_cvref_t<Type>::value_type; },
|
||||
typename std::remove_cvref_t<Type>::value_type,
|
||||
std::conditional_t<
|
||||
std::is_array_v<std::remove_cvref_t<Type>>,
|
||||
std::remove_extent_t<std::remove_cvref_t<Type>>,
|
||||
void
|
||||
>
|
||||
>;
|
||||
|
||||
public:
|
||||
// Validate that the path matches the struct definition using reflection
|
||||
static consteval bool validate_path() {
|
||||
if constexpr (!std::is_class_v<T>) {
|
||||
// If T is void or not a class, we can't validate - allow it
|
||||
return true;
|
||||
}
|
||||
|
||||
auto current_type = ^^T;
|
||||
std::size_t i = parser.skip_root();
|
||||
|
||||
while (i < path_view.size()) {
|
||||
if (path_view[i] == '.') {
|
||||
// Field access - validate member exists
|
||||
++i;
|
||||
std::size_t field_start = i;
|
||||
while (i < path_view.size() && path_view[i] != '.' && path_view[i] != '[') {
|
||||
++i;
|
||||
}
|
||||
|
||||
std::string_view field_name = path_view.substr(field_start, i - field_start);
|
||||
|
||||
// Check if current type has this member
|
||||
bool found = false;
|
||||
auto members = std::meta::nonstatic_data_members_of(
|
||||
current_type, std::meta::access_context::unchecked()
|
||||
);
|
||||
|
||||
for (auto mem : members) {
|
||||
if (std::meta::identifier_of(mem) == field_name) {
|
||||
current_type = std::meta::type_of(mem);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return false; // Member not found
|
||||
}
|
||||
|
||||
} else if (path_view[i] == '[') {
|
||||
++i;
|
||||
if (i >= path_view.size()) return false;
|
||||
|
||||
if (path_view[i] == '"' || path_view[i] == '\'') {
|
||||
// Field access with bracket notation
|
||||
char quote = path_view[i];
|
||||
++i;
|
||||
std::size_t field_start = i;
|
||||
while (i < path_view.size() && path_view[i] != quote) {
|
||||
++i;
|
||||
}
|
||||
|
||||
std::string_view field_name = path_view.substr(field_start, i - field_start);
|
||||
if (i < path_view.size()) ++i; // skip closing quote
|
||||
if (i < path_view.size() && path_view[i] == ']') ++i;
|
||||
|
||||
// Check if current type has this member
|
||||
bool found = false;
|
||||
auto members = std::meta::nonstatic_data_members_of(
|
||||
current_type, std::meta::access_context::unchecked()
|
||||
);
|
||||
|
||||
for (auto mem : members) {
|
||||
if (std::meta::identifier_of(mem) == field_name) {
|
||||
current_type = std::meta::type_of(mem);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return false; // Member not found
|
||||
}
|
||||
|
||||
} else {
|
||||
// Array index - verify current type is array-like and extract element type
|
||||
while (i < path_view.size() && path_view[i] >= '0' && path_view[i] <= '9') {
|
||||
++i;
|
||||
}
|
||||
|
||||
if (i < path_view.size() && path_view[i] == ']') ++i;
|
||||
|
||||
// Check if current type is array-like
|
||||
if (!is_array_like_reflected(current_type)) {
|
||||
return false; // Not an array/container type
|
||||
}
|
||||
|
||||
// Extract element type and continue validation
|
||||
auto new_type = get_element_type_reflected(current_type);
|
||||
|
||||
// If we couldn't extract element type (returns ^^void), fail validation
|
||||
if (new_type == ^^void) {
|
||||
return false; // Could not determine element type
|
||||
}
|
||||
|
||||
current_type = new_type;
|
||||
}
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
return true; // Path validated successfully
|
||||
}
|
||||
};
|
||||
|
||||
// User-facing API: compile-time path accessor
|
||||
// When used with a struct type T, validates the path at compile time
|
||||
// Example: at_path_compiled<User, ".name">(doc)
|
||||
template<typename T, constevalutil::fixed_string Path, typename DocOrValue>
|
||||
inline simdjson_result<::simdjson::SIMDJSON_IMPLEMENTATION::ondemand::value> at_path_compiled(DocOrValue& doc_or_val) noexcept {
|
||||
using accessor = path_accessor<T, Path>;
|
||||
return accessor::access(doc_or_val);
|
||||
}
|
||||
|
||||
// Convenience overload without type parameter (no validation, just compile-time parsing)
|
||||
// Example: at_path_compiled<".name">(doc)
|
||||
template<constevalutil::fixed_string Path, typename DocOrValue>
|
||||
inline simdjson_result<::simdjson::SIMDJSON_IMPLEMENTATION::ondemand::value> at_path_compiled(DocOrValue& doc_or_val) noexcept {
|
||||
using accessor = path_accessor<void, Path>;
|
||||
return accessor::access(doc_or_val);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// JSON Pointer Compile-Time Support (RFC 6901)
|
||||
// ============================================================================
|
||||
|
||||
// JSON Pointer parser - simpler syntax than JSONPath
|
||||
// Format: /field/0/nested (slash-separated, numeric for arrays)
|
||||
template<constevalutil::fixed_string Pointer>
|
||||
struct json_pointer_parser {
|
||||
static constexpr std::string_view pointer_str = Pointer.view();
|
||||
|
||||
// Unescape JSON Pointer token: ~0 -> ~, ~1 -> /
|
||||
static consteval void unescape_token(std::string_view src, char* dest, std::size_t& out_len) {
|
||||
out_len = 0;
|
||||
for (std::size_t i = 0; i < src.size(); ++i) {
|
||||
if (src[i] == '~' && i + 1 < src.size()) {
|
||||
if (src[i + 1] == '0') {
|
||||
dest[out_len++] = '~';
|
||||
++i;
|
||||
} else if (src[i + 1] == '1') {
|
||||
dest[out_len++] = '/';
|
||||
++i;
|
||||
} else {
|
||||
dest[out_len++] = src[i];
|
||||
}
|
||||
} else {
|
||||
dest[out_len++] = src[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if token is numeric (array index)
|
||||
static consteval bool is_numeric(std::string_view token) {
|
||||
if (token.empty()) return false;
|
||||
if (token[0] == '0' && token.size() > 1) return false; // Leading zeros not allowed
|
||||
for (char c : token) {
|
||||
if (c < '0' || c > '9') return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parse numeric token to index
|
||||
static consteval std::size_t parse_index(std::string_view token) {
|
||||
std::size_t result = 0;
|
||||
for (char c : token) {
|
||||
result = result * 10 + (c - '0');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Count number of tokens (path segments)
|
||||
static consteval std::size_t count_tokens() {
|
||||
if (pointer_str.empty() || pointer_str == "/") return 0;
|
||||
|
||||
std::size_t count = 0;
|
||||
std::size_t pos = pointer_str[0] == '/' ? 1 : 0;
|
||||
|
||||
while (pos < pointer_str.size()) {
|
||||
++count;
|
||||
std::size_t next_slash = pointer_str.find('/', pos);
|
||||
if (next_slash == std::string_view::npos) break;
|
||||
pos = next_slash + 1;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
// Get the Nth token at compile time
|
||||
static consteval std::string_view get_token(std::size_t token_index) {
|
||||
std::size_t pos = pointer_str[0] == '/' ? 1 : 0;
|
||||
std::size_t current_token = 0;
|
||||
|
||||
while (current_token < token_index) {
|
||||
std::size_t next_slash = pointer_str.find('/', pos);
|
||||
pos = next_slash + 1;
|
||||
++current_token;
|
||||
}
|
||||
|
||||
std::size_t token_end = pointer_str.find('/', pos);
|
||||
if (token_end == std::string_view::npos) token_end = pointer_str.size();
|
||||
|
||||
return pointer_str.substr(pos, token_end - pos);
|
||||
}
|
||||
};
|
||||
|
||||
// JSON Pointer accessor - similar to path_accessor but for JSON Pointer syntax
|
||||
template<typename T, constevalutil::fixed_string Pointer>
|
||||
struct pointer_accessor {
|
||||
using parser = json_pointer_parser<Pointer>;
|
||||
static constexpr std::string_view pointer_view = Pointer.view();
|
||||
static constexpr std::size_t token_count = parser::count_tokens();
|
||||
|
||||
// Validate JSON Pointer against struct definition
|
||||
static consteval bool validate_pointer() {
|
||||
if constexpr (!std::is_class_v<T>) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto current_type = ^^T;
|
||||
std::size_t pos = pointer_view[0] == '/' ? 1 : 0;
|
||||
|
||||
while (pos < pointer_view.size()) {
|
||||
// Extract token up to next /
|
||||
std::size_t token_end = pointer_view.find('/', pos);
|
||||
if (token_end == std::string_view::npos) token_end = pointer_view.size();
|
||||
|
||||
std::string_view token = pointer_view.substr(pos, token_end - pos);
|
||||
|
||||
// Check if it's an array index
|
||||
if (parser::is_numeric(token)) {
|
||||
// Validate current type is array-like
|
||||
if (!path_accessor<T, Pointer>::is_array_like_reflected(current_type)) {
|
||||
return false;
|
||||
}
|
||||
current_type = path_accessor<T, Pointer>::get_element_type_reflected(current_type);
|
||||
} else {
|
||||
// Field access - validate member exists
|
||||
bool found = false;
|
||||
auto members = std::meta::nonstatic_data_members_of(
|
||||
current_type, std::meta::access_context::unchecked()
|
||||
);
|
||||
|
||||
for (auto mem : members) {
|
||||
if (std::meta::identifier_of(mem) == token) {
|
||||
current_type = std::meta::type_of(mem);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) return false;
|
||||
}
|
||||
|
||||
pos = token_end + 1;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Recursive accessor implementation
|
||||
template<std::size_t TokenIndex>
|
||||
static inline simdjson_result<value> access_impl(simdjson_result<value> current) noexcept {
|
||||
if constexpr (TokenIndex >= token_count) {
|
||||
return current;
|
||||
} else {
|
||||
// Get token at compile time
|
||||
constexpr std::string_view token = parser::get_token(TokenIndex);
|
||||
|
||||
if constexpr (parser::is_numeric(token)) {
|
||||
// Array index access
|
||||
constexpr std::size_t index = parser::parse_index(token);
|
||||
auto arr = current.get_array().value_unsafe();
|
||||
auto next_value = arr.at(index);
|
||||
return access_impl<TokenIndex + 1>(next_value);
|
||||
} else {
|
||||
// Field access
|
||||
auto obj = current.get_object().value_unsafe();
|
||||
auto next_value = obj.find_field_unordered(token);
|
||||
return access_impl<TokenIndex + 1>(next_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Main entry point
|
||||
template<typename DocOrValue>
|
||||
static inline simdjson_result<value> access(DocOrValue& doc_or_val) noexcept {
|
||||
if constexpr (std::is_class_v<T>) {
|
||||
constexpr bool pointer_valid = validate_pointer();
|
||||
static_assert(pointer_valid, "JSON Pointer does not match struct definition");
|
||||
}
|
||||
|
||||
if (pointer_view.empty() || pointer_view == "/") {
|
||||
// Root pointer
|
||||
if constexpr (requires { doc_or_val.get_value(); }) {
|
||||
return doc_or_val.get_value();
|
||||
} else {
|
||||
return doc_or_val;
|
||||
}
|
||||
}
|
||||
|
||||
simdjson_result<value> current = doc_or_val.get_value();
|
||||
return access_impl<0>(current);
|
||||
}
|
||||
};
|
||||
|
||||
// User-facing API: compile-time JSON Pointer accessor with validation
|
||||
// Example: at_pointer_compiled<User, "/name">(doc)
|
||||
template<typename T, constevalutil::fixed_string Pointer, typename DocOrValue>
|
||||
inline simdjson_result<::simdjson::SIMDJSON_IMPLEMENTATION::ondemand::value> at_pointer_compiled(DocOrValue& doc_or_val) noexcept {
|
||||
using accessor = pointer_accessor<T, Pointer>;
|
||||
return accessor::access(doc_or_val);
|
||||
}
|
||||
|
||||
// Convenience overload without type parameter (no validation)
|
||||
// Example: at_pointer_compiled<"/name">(doc)
|
||||
template<constevalutil::fixed_string Pointer, typename DocOrValue>
|
||||
inline simdjson_result<::simdjson::SIMDJSON_IMPLEMENTATION::ondemand::value> at_pointer_compiled(DocOrValue& doc_or_val) noexcept {
|
||||
using accessor = pointer_accessor<void, Pointer>;
|
||||
return accessor::access(doc_or_val);
|
||||
}
|
||||
|
||||
} // namespace json_path
|
||||
} // namespace ondemand
|
||||
} // namespace SIMDJSON_IMPLEMENTATION
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_SUPPORTS_CONCEPTS && SIMDJSON_STATIC_REFLECTION
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_COMPILE_TIME_ACCESSORS_H
|
||||
|
||||
@@ -16,6 +16,8 @@ add_cpp_test(ondemand_error_tests LABELS ondemand acceptance
|
||||
add_cpp_test(ondemand_error_location_tests LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(ondemand_json_pointer_tests LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(ondemand_json_path_tests LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(compile_time_json_path_tests LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(compile_time_json_pointer_tests LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(ondemand_key_string_tests LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(ondemand_misc_tests LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(ondemand_number_tests LABELS ondemand acceptance per_implementation)
|
||||
|
||||
@@ -0,0 +1,374 @@
|
||||
#include "simdjson.h"
|
||||
#include "test_ondemand.h"
|
||||
#include <string>
|
||||
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS && SIMDJSON_STATIC_REFLECTION
|
||||
|
||||
using namespace simdjson;
|
||||
|
||||
namespace compile_time_json_path_tests {
|
||||
|
||||
// Test structures
|
||||
struct User {
|
||||
std::string name;
|
||||
int age;
|
||||
std::string email;
|
||||
};
|
||||
|
||||
struct TirePressure {
|
||||
std::vector<double> values;
|
||||
};
|
||||
|
||||
struct Car {
|
||||
std::string make;
|
||||
std::string model;
|
||||
int64_t year;
|
||||
std::vector<double> tire_pressure;
|
||||
};
|
||||
|
||||
const padded_string TEST_USER_JSON = R"(
|
||||
{
|
||||
"name": "John Doe",
|
||||
"age": 30,
|
||||
"email": "john@example.com"
|
||||
}
|
||||
)"_padded;
|
||||
|
||||
const padded_string TEST_CAR_JSON = R"(
|
||||
{
|
||||
"make": "Toyota",
|
||||
"model": "Camry",
|
||||
"year": 2018,
|
||||
"tire_pressure": [40.1, 39.9, 37.7, 40.4]
|
||||
}
|
||||
)"_padded;
|
||||
|
||||
const padded_string TEST_NESTED_JSON = R"(
|
||||
{
|
||||
"users": [
|
||||
{
|
||||
"name": "Alice",
|
||||
"age": 25,
|
||||
"email": "alice@example.com"
|
||||
},
|
||||
{
|
||||
"name": "Bob",
|
||||
"age": 35,
|
||||
"email": "bob@example.com"
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"count": 2,
|
||||
"version": "1.0"
|
||||
}
|
||||
}
|
||||
)"_padded;
|
||||
|
||||
const padded_string TEST_ARRAY_JSON = R"(
|
||||
[
|
||||
{"make": "Toyota", "model": "Camry", "year": 2018, "tire_pressure": [40.1, 39.9, 37.7, 40.4]},
|
||||
{"make": "Kia", "model": "Soul", "year": 2012, "tire_pressure": [30.1, 31.0, 28.6, 28.7]},
|
||||
{"make": "Toyota", "model": "Tercel", "year": 1999, "tire_pressure": [29.8, 30.0, 30.2, 30.5]}
|
||||
]
|
||||
)"_padded;
|
||||
|
||||
// Test 1: Simple field access with dot notation
|
||||
bool test_simple_field_access() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_USER_JSON).get(doc));
|
||||
|
||||
// Test compile-time accessor with validation
|
||||
auto result = ondemand::json_path::at_path_compiled<".name">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
std::string_view name;
|
||||
ASSERT_SUCCESS(result.get_string().get(name));
|
||||
ASSERT_EQUAL(name, "John Doe");
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 2: Field access with bracket notation
|
||||
bool test_bracket_field_access() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_USER_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_path_compiled<R"(["email"])">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
std::string_view email;
|
||||
ASSERT_SUCCESS(result.get_string().get(email));
|
||||
ASSERT_EQUAL(email, "john@example.com");
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 3: Integer field access
|
||||
bool test_integer_field() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_USER_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_path_compiled<".age">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
int64_t age;
|
||||
ASSERT_SUCCESS(result.get_int64().get(age));
|
||||
ASSERT_EQUAL(age, 30);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 4: Array index access
|
||||
bool test_array_index_access() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_CAR_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_path_compiled<".tire_pressure[1]">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
double pressure;
|
||||
ASSERT_SUCCESS(result.get_double().get(pressure));
|
||||
ASSERT_EQUAL(pressure, 39.9);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 5: Nested field access
|
||||
bool test_nested_field_access() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_NESTED_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_path_compiled<".metadata.version">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
std::string_view version;
|
||||
ASSERT_SUCCESS(result.get_string().get(version));
|
||||
ASSERT_EQUAL(version, "1.0");
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 6: Array of objects with nested path
|
||||
bool test_array_object_nested_path() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_NESTED_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_path_compiled<".users[0].name">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
std::string_view name;
|
||||
ASSERT_SUCCESS(result.get_string().get(name));
|
||||
ASSERT_EQUAL(name, "Alice");
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 7: Root array access
|
||||
bool test_root_array_access() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_ARRAY_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_path_compiled<"[1].make">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
std::string_view make;
|
||||
ASSERT_SUCCESS(result.get_string().get(make));
|
||||
ASSERT_EQUAL(make, "Kia");
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 8: Deep nested array access
|
||||
bool test_deep_nested_array() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_ARRAY_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_path_compiled<"[0].tire_pressure[2]">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
double pressure;
|
||||
ASSERT_SUCCESS(result.get_double().get(pressure));
|
||||
ASSERT_EQUAL(pressure, 37.7);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 9: Path with $ prefix
|
||||
bool test_path_with_dollar_prefix() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_USER_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_path_compiled<"$.name">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
std::string_view name;
|
||||
ASSERT_SUCCESS(result.get_string().get(name));
|
||||
ASSERT_EQUAL(name, "John Doe");
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 10: Multiple array indices in path
|
||||
bool test_multiple_indices() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_NESTED_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_path_compiled<".users[1].age">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
int64_t age;
|
||||
ASSERT_SUCCESS(result.get_int64().get(age));
|
||||
ASSERT_EQUAL(age, 35);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 11: Compare compile-time vs runtime path
|
||||
bool test_compile_vs_runtime() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_USER_JSON).get(doc));
|
||||
|
||||
// Compile-time version
|
||||
auto compile_result = ondemand::json_path::at_path_compiled<".name">(doc);
|
||||
ASSERT_SUCCESS(compile_result.error());
|
||||
std::string_view compile_name;
|
||||
ASSERT_SUCCESS(compile_result.get_string().get(compile_name));
|
||||
|
||||
// Runtime version for comparison
|
||||
ondemand::parser parser2;
|
||||
ondemand::document doc2;
|
||||
ASSERT_SUCCESS(parser2.iterate(TEST_USER_JSON).get(doc2));
|
||||
auto runtime_result = doc2.at_path(".name");
|
||||
ASSERT_SUCCESS(runtime_result.error());
|
||||
std::string_view runtime_name;
|
||||
ASSERT_SUCCESS(runtime_result.get_string().get(runtime_name));
|
||||
|
||||
// Should produce same result
|
||||
ASSERT_EQUAL(compile_name, runtime_name);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 12: Bracket notation with single quotes
|
||||
bool test_bracket_single_quotes() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_CAR_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_path_compiled<"['model']">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
std::string_view model;
|
||||
ASSERT_SUCCESS(result.get_string().get(model));
|
||||
ASSERT_EQUAL(model, "Camry");
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 13: Access first array element
|
||||
bool test_first_array_element() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_CAR_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_path_compiled<".tire_pressure[0]">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
double pressure;
|
||||
ASSERT_SUCCESS(result.get_double().get(pressure));
|
||||
ASSERT_EQUAL(pressure, 40.1);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 14: Mixed bracket and dot notation
|
||||
bool test_mixed_notation() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_NESTED_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_path_compiled<R"(.users[0]["email"])">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
std::string_view email;
|
||||
ASSERT_SUCCESS(result.get_string().get(email));
|
||||
ASSERT_EQUAL(email, "alice@example.com");
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 15: Integer field in nested object
|
||||
bool test_nested_integer() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_NESTED_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_path_compiled<".metadata.count">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
int64_t count;
|
||||
ASSERT_SUCCESS(result.get_int64().get(count));
|
||||
ASSERT_EQUAL(count, 2);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
} // namespace compile_time_json_path_tests
|
||||
|
||||
#endif // SIMDJSON_SUPPORTS_CONCEPTS && SIMDJSON_STATIC_REFLECTION
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS && SIMDJSON_STATIC_REFLECTION
|
||||
std::cout << "Running compile-time JSON path tests" << std::endl;
|
||||
|
||||
if (!compile_time_json_path_tests::test_simple_field_access()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_path_tests::test_bracket_field_access()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_path_tests::test_integer_field()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_path_tests::test_array_index_access()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_path_tests::test_nested_field_access()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_path_tests::test_array_object_nested_path()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_path_tests::test_root_array_access()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_path_tests::test_deep_nested_array()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_path_tests::test_path_with_dollar_prefix()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_path_tests::test_multiple_indices()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_path_tests::test_compile_vs_runtime()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_path_tests::test_bracket_single_quotes()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_path_tests::test_first_array_element()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_path_tests::test_mixed_notation()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_path_tests::test_nested_integer()) { return EXIT_FAILURE; }
|
||||
|
||||
std::cout << "All compile-time JSON path tests passed!" << std::endl;
|
||||
return EXIT_SUCCESS;
|
||||
#else
|
||||
std::cout << "Compile-time JSON path tests require C++26 reflection support" << std::endl;
|
||||
return EXIT_SUCCESS;
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
#include "simdjson.h"
|
||||
#include "test_ondemand.h"
|
||||
#include <string>
|
||||
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS && SIMDJSON_STATIC_REFLECTION
|
||||
|
||||
using namespace simdjson;
|
||||
|
||||
namespace compile_time_json_pointer_tests {
|
||||
|
||||
// Test structures
|
||||
struct User {
|
||||
std::string name;
|
||||
int age;
|
||||
std::string email;
|
||||
};
|
||||
|
||||
struct Car {
|
||||
std::string make;
|
||||
std::string model;
|
||||
int64_t year;
|
||||
std::vector<double> tire_pressure;
|
||||
};
|
||||
|
||||
const padded_string TEST_USER_JSON = R"(
|
||||
{
|
||||
"name": "John Doe",
|
||||
"age": 30,
|
||||
"email": "john@example.com"
|
||||
}
|
||||
)"_padded;
|
||||
|
||||
const padded_string TEST_CAR_JSON = R"(
|
||||
{
|
||||
"make": "Toyota",
|
||||
"model": "Camry",
|
||||
"year": 2018,
|
||||
"tire_pressure": [40.1, 39.9, 37.7, 40.4]
|
||||
}
|
||||
)"_padded;
|
||||
|
||||
const padded_string TEST_NESTED_JSON = R"(
|
||||
{
|
||||
"users": [
|
||||
{
|
||||
"name": "Alice",
|
||||
"age": 25,
|
||||
"email": "alice@example.com"
|
||||
},
|
||||
{
|
||||
"name": "Bob",
|
||||
"age": 35,
|
||||
"email": "bob@example.com"
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"count": 2,
|
||||
"version": "1.0"
|
||||
}
|
||||
}
|
||||
)"_padded;
|
||||
|
||||
const padded_string TEST_ARRAY_JSON = R"(
|
||||
[
|
||||
{"make": "Toyota", "model": "Camry", "year": 2018, "tire_pressure": [40.1, 39.9, 37.7, 40.4]},
|
||||
{"make": "Kia", "model": "Soul", "year": 2012, "tire_pressure": [30.1, 31.0, 28.6, 28.7]},
|
||||
{"make": "Toyota", "model": "Tercel", "year": 1999, "tire_pressure": [29.8, 30.0, 30.2, 30.5]}
|
||||
]
|
||||
)"_padded;
|
||||
|
||||
// Test 1: Simple field access
|
||||
bool test_simple_field() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_USER_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_pointer_compiled<"/name">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
std::string_view name;
|
||||
ASSERT_SUCCESS(result.get_string().get(name));
|
||||
ASSERT_EQUAL(name, "John Doe");
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 2: Integer field access
|
||||
bool test_integer_field() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_USER_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_pointer_compiled<"/age">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
int64_t age;
|
||||
ASSERT_SUCCESS(result.get_int64().get(age));
|
||||
ASSERT_EQUAL(age, 30);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 3: Array index access
|
||||
bool test_array_index() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_CAR_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_pointer_compiled<"/tire_pressure/1">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
double pressure;
|
||||
ASSERT_SUCCESS(result.get_double().get(pressure));
|
||||
ASSERT_EQUAL(pressure, 39.9);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 4: Nested field access
|
||||
bool test_nested_field() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_NESTED_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_pointer_compiled<"/metadata/version">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
std::string_view version;
|
||||
ASSERT_SUCCESS(result.get_string().get(version));
|
||||
ASSERT_EQUAL(version, "1.0");
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 5: Array of objects with nested path
|
||||
bool test_array_object_nested() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_NESTED_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_pointer_compiled<"/users/0/name">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
std::string_view name;
|
||||
ASSERT_SUCCESS(result.get_string().get(name));
|
||||
ASSERT_EQUAL(name, "Alice");
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 6: Root array access
|
||||
bool test_root_array() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_ARRAY_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_pointer_compiled<"/1/make">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
std::string_view make;
|
||||
ASSERT_SUCCESS(result.get_string().get(make));
|
||||
ASSERT_EQUAL(make, "Kia");
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 7: Deep nested array
|
||||
bool test_deep_nested_array() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_ARRAY_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_pointer_compiled<"/0/tire_pressure/2">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
double pressure;
|
||||
ASSERT_SUCCESS(result.get_double().get(pressure));
|
||||
ASSERT_EQUAL(pressure, 37.7);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 8: Root pointer (empty or "/")
|
||||
bool test_root_pointer() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_USER_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_pointer_compiled<"">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
auto obj = result.get_object();
|
||||
ASSERT_SUCCESS(obj.error());
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 9: First array element
|
||||
bool test_first_array_element() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_CAR_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_pointer_compiled<"/tire_pressure/0">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
double pressure;
|
||||
ASSERT_SUCCESS(result.get_double().get(pressure));
|
||||
ASSERT_EQUAL(pressure, 40.1);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 10: Multiple indices in path
|
||||
bool test_multiple_indices() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_NESTED_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_pointer_compiled<"/users/1/age">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
int64_t age;
|
||||
ASSERT_SUCCESS(result.get_int64().get(age));
|
||||
ASSERT_EQUAL(age, 35);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 11: Compare compile-time vs runtime pointer
|
||||
bool test_compile_vs_runtime() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_USER_JSON).get(doc));
|
||||
|
||||
// Compile-time version
|
||||
auto compile_result = ondemand::json_path::at_pointer_compiled<"/name">(doc);
|
||||
ASSERT_SUCCESS(compile_result.error());
|
||||
std::string_view compile_name;
|
||||
ASSERT_SUCCESS(compile_result.get_string().get(compile_name));
|
||||
|
||||
// Runtime version for comparison
|
||||
ondemand::parser parser2;
|
||||
ondemand::document doc2;
|
||||
ASSERT_SUCCESS(parser2.iterate(TEST_USER_JSON).get(doc2));
|
||||
auto runtime_result = doc2.at_pointer("/name");
|
||||
ASSERT_SUCCESS(runtime_result.error());
|
||||
std::string_view runtime_name;
|
||||
ASSERT_SUCCESS(runtime_result.get_string().get(runtime_name));
|
||||
|
||||
// Should produce same result
|
||||
ASSERT_EQUAL(compile_name, runtime_name);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 12: Nested integer field
|
||||
bool test_nested_integer() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_NESTED_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_pointer_compiled<"/metadata/count">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
int64_t count;
|
||||
ASSERT_SUCCESS(result.get_int64().get(count));
|
||||
ASSERT_EQUAL(count, 2);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 13: Last array element
|
||||
bool test_last_array_element() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_CAR_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_pointer_compiled<"/tire_pressure/3">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
double pressure;
|
||||
ASSERT_SUCCESS(result.get_double().get(pressure));
|
||||
ASSERT_EQUAL(pressure, 40.4);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 14: Access second user's email
|
||||
bool test_second_user_email() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_NESTED_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_pointer_compiled<"/users/1/email">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
std::string_view email;
|
||||
ASSERT_SUCCESS(result.get_string().get(email));
|
||||
ASSERT_EQUAL(email, "bob@example.com");
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 15: Root array first element field
|
||||
bool test_root_array_first_field() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_ARRAY_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_pointer_compiled<"/0/model">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
std::string_view model;
|
||||
ASSERT_SUCCESS(result.get_string().get(model));
|
||||
ASSERT_EQUAL(model, "Camry");
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
} // namespace compile_time_json_pointer_tests
|
||||
|
||||
#endif // SIMDJSON_SUPPORTS_CONCEPTS && SIMDJSON_STATIC_REFLECTION
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS && SIMDJSON_STATIC_REFLECTION
|
||||
std::cout << "Running compile-time JSON Pointer tests" << std::endl;
|
||||
|
||||
if (!compile_time_json_pointer_tests::test_simple_field()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_pointer_tests::test_integer_field()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_pointer_tests::test_array_index()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_pointer_tests::test_nested_field()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_pointer_tests::test_array_object_nested()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_pointer_tests::test_root_array()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_pointer_tests::test_deep_nested_array()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_pointer_tests::test_root_pointer()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_pointer_tests::test_first_array_element()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_pointer_tests::test_multiple_indices()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_pointer_tests::test_compile_vs_runtime()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_pointer_tests::test_nested_integer()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_pointer_tests::test_last_array_element()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_pointer_tests::test_second_user_email()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_pointer_tests::test_root_array_first_field()) { return EXIT_FAILURE; }
|
||||
|
||||
std::cout << "All compile-time JSON Pointer tests passed!" << std::endl;
|
||||
return EXIT_SUCCESS;
|
||||
#else
|
||||
std::cout << "Compile-time JSON Pointer tests require C++26 reflection support" << std::endl;
|
||||
return EXIT_SUCCESS;
|
||||
#endif
|
||||
}
|
||||
Reference in New Issue
Block a user