Compare commits

...

5 Commits

Author SHA1 Message Date
Daniel Lemire f4807d63a3 minor suggested changes. 2025-10-16 16:01:52 -04:00
Francisco Geiman Thiesen 72ca766152 Removing unnecessary comment 2025-10-03 09:20:07 -07:00
Francisco Geiman Thiesen e886511471 Adding support for compile-time json pointer as well. 2025-10-02 13:20:09 -07:00
Francisco Geiman Thiesen 9eaeb6d314 using string_view 2025-10-01 22:24:45 -07:00
Francisco Geiman Thiesen 0ac0107742 Adding compile time json path 2025-10-01 22:09:36 -07:00
6 changed files with 1496 additions and 0 deletions
+18
View File
@@ -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
+2
View File
@@ -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
}