Compare commits

..

5 Commits

Author SHA1 Message Date
Francisco Geiman Thiesen d326f2ce9f Working! 2025-10-10 21:32:10 -07:00
Francisco Geiman Thiesen ca42a49fba Compile-time support for parsing json objects! 2025-10-10 18:40:09 -07:00
0xflotus a7f8fb71c5 chore: fix small error in docs (#2497) 2025-10-03 11:03:17 -04:00
Howard Guo a553db4c67 Update workflow name to Ubuntu aarch64 (GCC 13) (#2484) 2025-10-03 09:56:07 -04:00
Jaël Champagne Gareau 1fa1af8c15 Fix yyjson leaks when running ./bench_ondemand (#2485) 2025-10-03 09:55:32 -04:00
20 changed files with 1056 additions and 1511 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
name: Ubuntu ppc64le (GCC 11)
name: Ubuntu aarch64 (GCC 13)
on:
push:
+9 -2
View File
@@ -44,7 +44,10 @@ struct yyjson_base {
struct yyjson : yyjson_base {
bool run(simdjson::padded_string &json, std::vector<uint64_t> &result) {
return yyjson_base::run(yyjson_read(json.data(), json.size(), 0), result);
yyjson_doc *doc = yyjson_read(json.data(), json.size(), 0);
bool b = yyjson_base::run(doc, result);
yyjson_doc_free(doc);
return b;
}
};
BENCHMARK_TEMPLATE(distinct_user_id, yyjson)->UseManualTime();
@@ -52,11 +55,15 @@ BENCHMARK_TEMPLATE(distinct_user_id, yyjson)->UseManualTime();
#if SIMDJSON_COMPETITION_ONDEMAND_INSITU
struct yyjson_insitu : yyjson_base {
bool run(simdjson::padded_string &json, std::vector<uint64_t> &result) {
return yyjson_base::run(yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0), result);
yyjson_doc *doc = yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0);
bool b = yyjson_base::run(doc, result);
yyjson_doc_free(doc);
return b;
}
};
BENCHMARK_TEMPLATE(distinct_user_id, yyjson_insitu)->UseManualTime();
#endif // SIMDJSON_COMPETITION_ONDEMAND_INSITU
} // namespace distinct_user_id
#endif // SIMDJSON_COMPETITION_YYJSON
+10 -2
View File
@@ -36,18 +36,26 @@ struct yyjson_base {
struct yyjson : yyjson_base {
bool run(simdjson::padded_string &json, uint64_t find_id, std::string_view &result) {
return yyjson_base::run(yyjson_read(json.data(), json.size(), 0), find_id, result);
yyjson_doc *doc = yyjson_read(json.data(), json.size(), 0);
bool b = yyjson_base::run(doc, find_id, result);
yyjson_doc_free(doc);
return b;
}
};
BENCHMARK_TEMPLATE(find_tweet, yyjson)->UseManualTime();
#if SIMDJSON_COMPETITION_ONDEMAND_INSITU
struct yyjson_insitu : yyjson_base {
bool run(simdjson::padded_string &json, uint64_t find_id, std::string_view &result) {
return yyjson_base::run(yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0), find_id, result);
yyjson_doc *doc = yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0);
bool b = yyjson_base::run(doc, find_id, result);
yyjson_doc_free(doc);
return b;
}
};
BENCHMARK_TEMPLATE(find_tweet, yyjson_insitu)->UseManualTime();
#endif // SIMDJSON_COMPETITION_ONDEMAND_INSITU
} // namespace find_tweet
#endif // SIMDJSON_COMPETITION_YYJSON
+3 -1
View File
@@ -100,6 +100,7 @@ struct yyjson : yyjson2msgpack {
std::string_view &result) {
yyjson_doc *doc = yyjson_read(json.data(), json.size(), 0);
result = to_msgpack(doc, reinterpret_cast<uint8_t*>(buffer));
yyjson_doc_free(doc);
return true;
}
};
@@ -113,6 +114,7 @@ struct yyjson_insitu : yyjson2msgpack {
yyjson_doc *doc =
yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0);
result = to_msgpack(doc, reinterpret_cast<uint8_t*>(buffer));
yyjson_doc_free(doc);
return true;
}
};
@@ -120,4 +122,4 @@ BENCHMARK_TEMPLATE(json2msgpack, yyjson_insitu)->UseManualTime();
#endif // SIMDJSON_COMPETITION_ONDEMAND_INSITU
} // namespace json2msgpack
#endif // SIMDJSON_COMPETITION_YYJSON
#endif // SIMDJSON_COMPETITION_YYJSON
+10 -2
View File
@@ -49,18 +49,26 @@ struct yyjson_base {
struct yyjson : yyjson_base {
bool run(simdjson::padded_string &json, std::vector<point> &result) {
return yyjson_base::run(yyjson_read(json.data(), json.size(), 0), result);
yyjson_doc *doc = yyjson_read(json.data(), json.size(), 0);
bool b = yyjson_base::run(doc, result);
yyjson_doc_free(doc);
return b;
}
};
BENCHMARK_TEMPLATE(kostya, yyjson)->UseManualTime();
#if SIMDJSON_COMPETITION_ONDEMAND_INSITU
struct yyjson_insitu : yyjson_base {
bool run(simdjson::padded_string &json, std::vector<point> &result) {
return yyjson_base::run(yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0), result);
yyjson_doc *doc = yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0);
bool b = yyjson_base::run(doc, result);
yyjson_doc_free(doc);
return b;
}
};
BENCHMARK_TEMPLATE(kostya, yyjson_insitu)->UseManualTime();
#endif // SIMDJSON_COMPETITION_ONDEMAND_INSITU
} // namespace kostya
#endif // SIMDJSON_COMPETITION_YYJSON
+10 -2
View File
@@ -47,18 +47,26 @@ struct yyjson_base {
struct yyjson : yyjson_base {
bool run(simdjson::padded_string &json, std::vector<point> &result) {
return yyjson_base::run(yyjson_read(json.data(), json.size(), 0), result);
yyjson_doc *doc = yyjson_read(json.data(), json.size(), 0);
bool b = yyjson_base::run(doc, result);
yyjson_doc_free(doc);
return b;
}
};
BENCHMARK_TEMPLATE(large_random, yyjson)->UseManualTime();
#if SIMDJSON_COMPETITION_ONDEMAND_INSITU
struct yyjson_insitu : yyjson_base {
bool run(simdjson::padded_string &json, std::vector<point> &result) {
return yyjson_base::run(yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0), result);
yyjson_doc *doc = yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0);
bool b = yyjson_base::run(doc, result);
yyjson_doc_free(doc);
return b;
}
};
BENCHMARK_TEMPLATE(large_random, yyjson_insitu)->UseManualTime();
#endif // SIMDJSON_COMPETITION_ONDEMAND_INSITU
} // namespace large_random
#endif // SIMDJSON_COMPETITION_YYJSON
+10 -3
View File
@@ -62,19 +62,26 @@ struct yyjson_base {
struct yyjson : yyjson_base {
bool run(simdjson::padded_string &json, std::vector<tweet<std::string_view>> &result) {
return yyjson_base::run(yyjson_read(json.data(), json.size(), 0), result);
yyjson_doc *doc = yyjson_read(json.data(), json.size(), 0);
bool b = yyjson_base::run(doc, result);
yyjson_doc_free(doc);
return b;
}
};
BENCHMARK_TEMPLATE(partial_tweets, yyjson)->UseManualTime();
#if SIMDJSON_COMPETITION_ONDEMAND_INSITU
struct yyjson_insitu : yyjson_base {
bool run(simdjson::padded_string &json, std::vector<tweet<std::string_view>> &result) {
return yyjson_base::run(yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0), result);
yyjson_doc *doc = yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0);
bool b = yyjson_base::run(doc, result);
yyjson_doc_free(doc);
return b;
}
};
BENCHMARK_TEMPLATE(partial_tweets, yyjson_insitu)->UseManualTime();
#endif // SIMDJSON_COMPETITION_ONDEMAND_INSITU
} // namespace partial_tweets
#endif // SIMDJSON_COMPETITION_YYJSON
+10 -2
View File
@@ -51,18 +51,26 @@ struct yyjson_base {
struct yyjson : yyjson_base {
bool run(simdjson::padded_string &json, int64_t max_retweet_count, top_tweet_result<StringType> &result) {
return yyjson_base::run(yyjson_read(json.data(), json.size(), 0), max_retweet_count, result);
yyjson_doc *doc = yyjson_read(json.data(), json.size(), 0);
bool b = yyjson_base::run(doc, max_retweet_count, result);
yyjson_doc_free(doc);
return b;
}
};
BENCHMARK_TEMPLATE(top_tweet, yyjson)->UseManualTime();
#if SIMDJSON_COMPETITION_ONDEMAND_INSITU
struct yyjson_insitu : yyjson_base {
bool run(simdjson::padded_string &json, int64_t max_retweet_count, top_tweet_result<StringType> &result) {
return yyjson_base::run(yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0), max_retweet_count, result);
yyjson_doc *doc = yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0);
bool b = yyjson_base::run(doc, max_retweet_count, result);
yyjson_doc_free(doc);
return b;
}
};
BENCHMARK_TEMPLATE(top_tweet, yyjson_insitu)->UseManualTime();
#endif // SIMDJSON_COMPETITION_ONDEMAND_INSITU
} // namespace top_tweet
#endif // SIMDJSON_COMPETITION_YYJSON
+1 -1
View File
@@ -452,7 +452,7 @@ support for users who avoid exceptions. See [the simdjson error handling documen
* **Field Access:** To get the value of the "foo" field in an object, use `object["foo"]`. This will
scan through the object looking for the field with the matching string, doing a character-by-character
comparison. It may generate the error `simdjson::NO_SUCH_FIELD` if there is no such key in the object, it may throw an exception (see [Error handling](#error-handling)). For efficiency reason, you should avoid looking up the same field repeatedly: e.g., do
not do `object["foo"]` followed by `object["foo"]` with the same `object` instance. Generally, you should not mix and match iterating through an object (`for(auto field : object) {...}`) and key accesses (`object["foo"]`): if you need to iterate through an object after a key access, you need to call `reset()` on the object. Whenever you call `reset()`, you need to keep in mind that though you can iterate over the array repeatedly, values should be consumedonly once (e.g., repeatedly calling `unescaped_key()` on the same key is forbidden). Keep in mind that On-Demand does not buffer or save the result of the parsing: if you repeatedly access `object["foo"]`, then it must repeatedly seek the key and parse the content. The library does not provide a distinct function to check if a key is present, instead we recommend you attempt to access the key: e.g., by doing `ondemand::value val{}; if (!object["foo"].get(val)) {...}`, you have that `val` contains the requested value inside the if clause. It is your responsibility as a user to temporarily keep a reference to the value (`auto v = object["foo"]`), or to consume the content and store it in your own data structures. If you consume an
not do `object["foo"]` followed by `object["foo"]` with the same `object` instance. Generally, you should not mix and match iterating through an object (`for(auto field : object) {...}`) and key accesses (`object["foo"]`): if you need to iterate through an object after a key access, you need to call `reset()` on the object. Whenever you call `reset()`, you need to keep in mind that though you can iterate over the array repeatedly, values should be consumed only once (e.g., repeatedly calling `unescaped_key()` on the same key is forbidden). Keep in mind that On-Demand does not buffer or save the result of the parsing: if you repeatedly access `object["foo"]`, then it must repeatedly seek the key and parse the content. The library does not provide a distinct function to check if a key is present, instead we recommend you attempt to access the key: e.g., by doing `ondemand::value val{}; if (!object["foo"].get(val)) {...}`, you have that `val` contains the requested value inside the if clause. It is your responsibility as a user to temporarily keep a reference to the value (`auto v = object["foo"]`), or to consume the content and store it in your own data structures. If you consume an
object twice: `std::string_view(object["foo"]` followed by `std::string_view(object["foo"]` then your code
is in error. Furthermore, you can only consume one field at a time, on the same object. The
value instance you get from `content["bids"]` becomes invalid when you call `content["asks"]`.
-18
View File
@@ -143,24 +143,6 @@ 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
+1
View File
@@ -53,6 +53,7 @@ consteval std::string consteval_to_quoted_escaped(std::string_view input) {
#if SIMDJSON_SUPPORTS_CONCEPTS
template <std::size_t N>
struct fixed_string {
constexpr fixed_string() : data{} {} // Default constructor for buffers
constexpr fixed_string(const char (&str)[N]) {
for (std::size_t i = 0; i < N; ++i) {
data[i] = str[i];
@@ -52,6 +52,7 @@
#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"
// Compile-time JSON parsing (C++26 P2996 reflection)
#include "simdjson/generic/ondemand/compile_time_json.h"
#include "simdjson/generic/ondemand/compile_time_json-inl.h"
@@ -1,731 +0,0 @@
/**
* 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
@@ -0,0 +1,98 @@
/**
* @file compile_time_json-inl.h
* @brief Implementation details for compile-time JSON parsing
*
* This file contains inline implementations and helper utilities for compile-time
* JSON parsing. Currently, the main implementation is self-contained in the header.
*/
#ifndef SIMDJSON_GENERIC_COMPILE_TIME_JSON_INL_H
#ifndef SIMDJSON_CONDITIONAL_INCLUDE
#define SIMDJSON_GENERIC_COMPILE_TIME_JSON_INL_H
#include "simdjson/generic/ondemand/compile_time_json.h"
#endif // SIMDJSON_CONDITIONAL_INCLUDE
#if SIMDJSON_STATIC_REFLECTION
#include <meta>
#include <array>
#include <string_view>
#include <cstdint>
namespace simdjson {
namespace SIMDJSON_IMPLEMENTATION {
namespace compile_time {
/**
* @brief Optimized constexpr string to integer conversion
*
* This can be used for more efficient integer parsing in the future.
* Currently, we parse all numbers as doubles for simplicity.
*/
constexpr int64_t parse_int_fast(std::string_view str) {
int64_t result = 0;
bool negative = false;
std::size_t i = 0;
if (i < str.size() && str[i] == '-') {
negative = true;
++i;
}
while (i < str.size() && str[i] >= '0' && str[i] <= '9') {
result = result * 10 + (str[i] - '0');
++i;
}
return negative ? -result : result;
}
/**
* @brief Unescape JSON string at compile-time
*
* Currently, strings are returned as views into the original JSON.
* This function can be used in the future for proper escape handling.
*/
template<std::size_t MaxLen = 1024>
constexpr auto unescape_json_string(std::string_view escaped) {
std::array<char, MaxLen> result{};
std::size_t out_pos = 0;
std::size_t i = 0;
while (i < escaped.size() && out_pos < MaxLen) {
if (escaped[i] == '\\' && i + 1 < escaped.size()) {
++i;
switch (escaped[i]) {
case '"': result[out_pos++] = '"'; break;
case '\\': result[out_pos++] = '\\'; break;
case '/': result[out_pos++] = '/'; break;
case 'b': result[out_pos++] = '\b'; break;
case 'f': result[out_pos++] = '\f'; break;
case 'n': result[out_pos++] = '\n'; break;
case 'r': result[out_pos++] = '\r'; break;
case 't': result[out_pos++] = '\t'; break;
case 'u':
// Unicode escape - would need proper implementation
// For now, skip the escape sequence
i += 4; // Skip 4 hex digits
break;
default:
result[out_pos++] = escaped[i];
}
++i;
} else {
result[out_pos++] = escaped[i];
++i;
}
}
return std::pair{result, out_pos};
}
} // namespace compile_time
} // namespace SIMDJSON_IMPLEMENTATION
} // namespace simdjson
#endif // SIMDJSON_STATIC_REFLECTION
#endif // SIMDJSON_GENERIC_COMPILE_TIME_JSON_INL_H
@@ -0,0 +1,464 @@
/**
* @file compile_time_json.h
* @brief Compile-time JSON parsing using C++26 reflection with std::meta::substitute()
*
* Based on the godbolt example: https://godbolt.org/z/Kn5b46T8j
* Uses the Outer<Ms...>::Inner + substitute() pattern for recursive type generation.
*/
#ifndef SIMDJSON_GENERIC_COMPILE_TIME_JSON_H
#ifndef SIMDJSON_CONDITIONAL_INCLUDE
#define SIMDJSON_GENERIC_COMPILE_TIME_JSON_H
#include "simdjson/generic/ondemand/base.h"
#endif // SIMDJSON_CONDITIONAL_INCLUDE
#if SIMDJSON_STATIC_REFLECTION
#include <meta>
#include <string_view>
#include <array>
#include <vector>
#include <cstdint>
#include <string>
#include <algorithm>
namespace simdjson {
namespace SIMDJSON_IMPLEMENTATION {
namespace compile_time {
/**
* @brief Helper struct for substitute() pattern
* The consteval block can use define_aggregate because it's in a template context
*/
template <std::meta::info ...Ms>
struct Outer {
struct Inner;
consteval {
std::meta::define_aggregate(^^Inner, {Ms...});
}
};
/**
* @brief Type alias for the generated struct
*/
template <std::meta::info ...Ms>
using Cls = Outer<Ms...>::Inner;
/**
* @brief Variable template for constructing instances with values
*/
template <typename T, auto ... Vs>
constexpr auto construct_from = T{Vs...};
// Forward declaration
consteval std::meta::info parse_json_impl(std::string_view json);
/**
* @brief Parse JSON array and return std::meta::info for the generated array
*/
consteval std::meta::info parse_json_array_impl(std::string_view json) {
auto cursor = json.begin();
auto end = json.end();
auto is_whitespace = [](char c) {
return c == ' ' || c == '\n' || c == '\t' || c == '\r';
};
auto skip_whitespace = [&]() -> void {
while (cursor != end && is_whitespace(*cursor)) cursor++;
};
auto expect_consume = [&](char c) -> void {
skip_whitespace();
if (cursor == end || *(cursor++) != c) throw "unexpected character";
};
auto parse_value = [&](std::string &out) -> void {
skip_whitespace();
bool quoted = false;
unsigned depth = 0;
while (true) {
if (cursor == end) throw "unexpected end of stream";
if (is_whitespace(*cursor) && !quoted && depth == 0)
break;
if (depth == 0 && (*cursor == ',' || *cursor == ']'))
break;
out += *(cursor++);
if (out.back() == '{')
++depth;
else if (out.back() == '}')
--depth;
else if (out.back() == '[')
++depth;
else if (out.back() == ']')
--depth;
else if (out.back() == '"') {
if (quoted && depth == 0)
break;
quoted = true;
}
}
};
skip_whitespace();
expect_consume('[');
std::vector<std::meta::info> values = {^^void};
std::meta::info element_type = ^^void;
bool first = true;
using std::meta::reflect_constant, std::meta::reflect_constant_string;
skip_whitespace();
if (cursor != end && *cursor == ']') {
expect_consume(']');
// Empty array - use int as placeholder type since void doesn't work
auto array_type = std::meta::substitute(^^std::array, {^^int, reflect_constant(0uz)});
values[0] = array_type;
return std::meta::substitute(^^construct_from, values);
}
while (cursor != end && *cursor != ']') {
std::string value;
parse_value(value);
if (value.empty()) throw "expected value";
if (value[0] == '"') {
if (value.back() != '"') throw "expected end of string";
std::string_view contents(&value[1], value.size() - 2);
if (first) element_type = ^^char const*;
values.push_back(reflect_constant_string(contents));
} else if (value == "true") {
if (first) element_type = ^^bool;
values.push_back(reflect_constant(true));
} else if (value == "false") {
if (first) element_type = ^^bool;
values.push_back(reflect_constant(false));
} else if (value == "null") {
if (first) element_type = ^^std::nullptr_t;
values.push_back(reflect_constant(nullptr));
} else if ((value[0] >= '0' && value[0] <= '9') || value[0] == '-') {
// Try to parse as integer first
bool is_int = true;
for (char c : value) {
if (c == '.' || c == 'e' || c == 'E') {
is_int = false;
break;
}
}
if (is_int) {
int contents = [](std::string_view in) {
int out = 0;
bool negative = false;
std::size_t i = 0;
if (in[0] == '-') {
negative = true;
i = 1;
}
for (; i < in.size(); ++i) {
out = out * 10 + (in[i] - '0');
}
return negative ? -out : out;
}(value);
if (first) element_type = ^^int;
values.push_back(reflect_constant(contents));
} else {
// Parse as double
double contents = [](std::string_view in) {
double result = 0.0;
double sign = 1.0;
std::size_t i = 0;
if (in[0] == '-') {
sign = -1.0;
i = 1;
}
while (i < in.size() && in[i] >= '0' && in[i] <= '9') {
result = result * 10.0 + (in[i] - '0');
++i;
}
if (i < in.size() && in[i] == '.') {
++i;
double fraction = 0.0;
double divisor = 1.0;
while (i < in.size() && in[i] >= '0' && in[i] <= '9') {
fraction = fraction * 10.0 + (in[i] - '0');
divisor *= 10.0;
++i;
}
result += fraction / divisor;
}
return result * sign;
}(value);
if (first) element_type = ^^double;
values.push_back(reflect_constant(contents));
}
} else if (value[0] == '{') {
// Nested object in array
std::meta::info parsed = parse_json_impl(value);
if (first) element_type = std::meta::type_of(parsed);
values.push_back(parsed);
} else if (value[0] == '[') {
// Nested array
std::meta::info parsed = parse_json_array_impl(value);
if (first) element_type = std::meta::type_of(parsed);
values.push_back(parsed);
}
first = false;
skip_whitespace();
if (cursor != end && *cursor == ',')
++cursor;
}
if (cursor == end) throw "unexpected end";
expect_consume(']');
// Create std::array<ElementType, Count> type
std::size_t count = values.size() - 1; // -1 because first element is ^^void placeholder
auto array_type = std::meta::substitute(^^std::array, {element_type, reflect_constant(count)});
// Create array instance with values
values[0] = array_type;
return std::meta::substitute(^^construct_from, values);
}
/**
* @brief Parse JSON and return std::meta::info for the generated type + instance
*/
consteval std::meta::info parse_json_impl(std::string_view json) {
auto cursor = json.begin();
auto end = json.end();
auto is_whitespace = [](char c) {
return c == ' ' || c == '\n' || c == '\t' || c == '\r';
};
auto skip_whitespace = [&]() -> void {
while (cursor != end && is_whitespace(*cursor)) cursor++;
};
auto expect_consume = [&](char c) -> void {
skip_whitespace();
if (cursor == end || *(cursor++) != c) throw "unexpected character";
};
auto parse_until = [&](std::vector<char> delims, std::string &out) -> void {
skip_whitespace();
while (cursor != end &&
!std::ranges::any_of(delims, [&](char c) { return c == *cursor; }))
out += *(cursor++);
};
auto parse_delimited = [&](char lhs, std::string &out, char rhs) -> void {
skip_whitespace();
expect_consume(lhs);
parse_until({rhs}, out);
expect_consume(rhs);
};
auto parse_value = [&](std::string &out) -> void {
skip_whitespace();
bool quoted = false;
unsigned depth = 0;
bool in_array = false;
while (true) {
if (cursor == end) throw "unexpected end of stream";
if (is_whitespace(*cursor) && !quoted && depth == 0)
break;
if (depth == 0 && (*cursor == ',' || *cursor == '}' || *cursor == ']'))
break;
out += *(cursor++);
if (out.back() == '{')
++depth;
else if (out.back() == '}')
--depth;
else if (out.back() == '[') {
in_array = true;
++depth;
} else if (out.back() == ']')
--depth;
else if (out.back() == '"') {
if (quoted && depth == 0)
break;
quoted = true;
}
}
};
skip_whitespace();
expect_consume('{');
std::vector<std::meta::info> members;
std::vector<std::meta::info> values = {^^void};
using std::meta::reflect_constant, std::meta::reflect_constant_string;
while (cursor != end && *cursor != '}') {
std::string field_name;
std::string value;
parse_delimited('"', field_name, '"');
expect_consume(':');
parse_value(value);
if (value.empty()) throw "expected value";
if (cursor == end) throw "unexpected end of stream";
if (value[0] == '"') {
if (value.back() != '"') throw "expected end of string";
std::string_view contents(&value[1], value.size() - 2);
auto dms = std::meta::data_member_spec(^^char const*, {.name=field_name});
members.push_back(reflect_constant(dms));
values.push_back(reflect_constant_string(contents));
} else if (value == "true") {
auto dms = std::meta::data_member_spec(^^bool, {.name=field_name});
members.push_back(reflect_constant(dms));
values.push_back(reflect_constant(true));
} else if (value == "false") {
auto dms = std::meta::data_member_spec(^^bool, {.name=field_name});
members.push_back(reflect_constant(dms));
values.push_back(reflect_constant(false));
} else if (value == "null") {
auto dms = std::meta::data_member_spec(^^std::nullptr_t, {.name=field_name});
members.push_back(reflect_constant(dms));
values.push_back(reflect_constant(nullptr));
} else if ((value[0] >= '0' && value[0] <= '9') || value[0] == '-') {
// Try to parse as integer first
bool is_int = true;
for (char c : value) {
if (c == '.' || c == 'e' || c == 'E') {
is_int = false;
break;
}
}
if (is_int) {
int contents = [](std::string_view in) {
int out = 0;
bool negative = false;
std::size_t i = 0;
if (in[0] == '-') {
negative = true;
i = 1;
}
for (; i < in.size(); ++i) {
out = out * 10 + (in[i] - '0');
}
return negative ? -out : out;
}(value);
auto dms = std::meta::data_member_spec(^^int, {.name=field_name});
members.push_back(reflect_constant(dms));
values.push_back(reflect_constant(contents));
} else {
// Parse as double
double contents = [](std::string_view in) {
double result = 0.0;
double sign = 1.0;
std::size_t i = 0;
if (in[0] == '-') {
sign = -1.0;
i = 1;
}
while (i < in.size() && in[i] >= '0' && in[i] <= '9') {
result = result * 10.0 + (in[i] - '0');
++i;
}
if (i < in.size() && in[i] == '.') {
++i;
double fraction = 0.0;
double divisor = 1.0;
while (i < in.size() && in[i] >= '0' && in[i] <= '9') {
fraction = fraction * 10.0 + (in[i] - '0');
divisor *= 10.0;
++i;
}
result += fraction / divisor;
}
return result * sign;
}(value);
auto dms = std::meta::data_member_spec(^^double, {.name=field_name});
members.push_back(reflect_constant(dms));
values.push_back(reflect_constant(contents));
}
} else if (value[0] == '{') {
// Nested object
std::meta::info parsed = parse_json_impl(value);
auto dms = std::meta::data_member_spec(std::meta::type_of(parsed), {.name=field_name});
members.push_back(reflect_constant(dms));
values.push_back(parsed);
} else if (value[0] == '[') {
// Array
std::meta::info parsed = parse_json_array_impl(value);
auto dms = std::meta::data_member_spec(std::meta::type_of(parsed), {.name=field_name});
members.push_back(reflect_constant(dms));
values.push_back(parsed);
}
skip_whitespace();
if (cursor != end && *cursor == ',')
++cursor;
}
if (cursor == end) throw "unexpected end";
expect_consume('}');
// The substitute() trick:
// 1. Create the type: Cls<member_specs...>
values[0] = std::meta::substitute(^^Cls, members);
// 2. Create instance: construct_from<Type, values...>
return std::meta::substitute(^^construct_from, values);
}
/**
* @brief Main parse_json function - template wrapper
*/
template<constevalutil::fixed_string json_str>
consteval auto parse_json() {
constexpr std::meta::info result = parse_json_impl(json_str.view());
return [:result:];
}
/**
* @brief JSON validation
*/
template<constevalutil::fixed_string json_str>
consteval bool validate_json() {
try {
parse_json_impl(json_str.view());
return true;
} catch (...) {
return false;
}
}
} // namespace compile_time
} // namespace SIMDJSON_IMPLEMENTATION
} // namespace simdjson
#endif // SIMDJSON_STATIC_REFLECTION
#endif // SIMDJSON_GENERIC_COMPILE_TIME_JSON_H
-2
View File
@@ -16,8 +16,6 @@ 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)
@@ -1,374 +0,0 @@
#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
}
@@ -1,368 +0,0 @@
#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
}
+418
View File
@@ -0,0 +1,418 @@
/**
* @file compile_time_json_tests.cpp
* @brief Comprehensive tests for compile-time JSON parsing using C++26 P2996 reflection
*/
#include "simdjson.h"
#include "test_ondemand.h"
using namespace simdjson;
using namespace std::string_view_literals;
namespace compile_time_json_tests {
#if SIMDJSON_STATIC_REFLECTION
using namespace arm64::compile_time;
#endif
/**
* Test 1: Basic object with primitives
*/
bool test_basic_object() {
TEST_START();
constexpr auto config = parse_json<R"({
"port": 8080,
"host": "localhost",
"debug": true,
"timeout": 30.5
})">();
static_assert(config.port == 8080);
static_assert(std::string_view(config.host) == "localhost");
static_assert(config.debug == true);
static_assert(config.timeout == 30.5);
ASSERT_EQUAL(config.port, 8080);
ASSERT_EQUAL(std::string_view(config.host), "localhost"sv);
ASSERT_TRUE(config.debug);
ASSERT_EQUAL(config.timeout, 30.5);
TEST_SUCCEED();
}
/**
* Test 2: Nested objects
*/
bool test_nested_objects() {
TEST_START();
constexpr auto config = parse_json<R"({
"server_port": 3000,
"enable_ssl": true,
"database": {
"host": "db.example.com",
"port": 5432,
"timeout_sec": 30.0
}
})">();
static_assert(config.server_port == 3000);
static_assert(config.enable_ssl == true);
static_assert(std::string_view(config.database.host) == "db.example.com");
static_assert(config.database.port == 5432);
static_assert(config.database.timeout_sec == 30.0);
ASSERT_EQUAL(config.server_port, 3000);
ASSERT_TRUE(config.enable_ssl);
ASSERT_EQUAL(std::string_view(config.database.host), "db.example.com"sv);
ASSERT_EQUAL(config.database.port, 5432);
ASSERT_EQUAL(config.database.timeout_sec, 30.0);
TEST_SUCCEED();
}
/**
* Test 3: Deeply nested objects (3+ levels)
*/
bool test_deeply_nested_objects() {
TEST_START();
constexpr auto config = parse_json<R"({
"app_name": "MyApp",
"version": 1.5,
"server": {
"host": "api.example.com",
"port": 443,
"tls": {
"enabled": true,
"cert_path": "/etc/ssl/cert.pem",
"min_version": 1.3
}
}
})">();
static_assert(std::string_view(config.app_name) == "MyApp");
static_assert(config.version == 1.5);
static_assert(std::string_view(config.server.host) == "api.example.com");
static_assert(config.server.port == 443);
static_assert(config.server.tls.enabled == true);
static_assert(std::string_view(config.server.tls.cert_path) == "/etc/ssl/cert.pem");
static_assert(config.server.tls.min_version == 1.3);
ASSERT_EQUAL(std::string_view(config.app_name), "MyApp"sv);
ASSERT_EQUAL(config.server.tls.enabled, true);
ASSERT_EQUAL(config.server.tls.min_version, 1.3);
TEST_SUCCEED();
}
/**
* Test 4: Empty object
*/
bool test_empty_object() {
TEST_START();
constexpr auto config = parse_json<"{}">();
(void)config; // Suppress unused warning
TEST_SUCCEED();
}
/**
* Test 5: Negative numbers
*/
bool test_negative_numbers() {
TEST_START();
constexpr auto data = parse_json<R"({
"temperature": -273.15,
"count": -42
})">();
static_assert(data.temperature == -273.15);
static_assert(data.count == -42);
ASSERT_EQUAL(data.temperature, -273.15);
ASSERT_EQUAL(data.count, -42);
TEST_SUCCEED();
}
/**
* Test 6: Whitespace handling
*/
bool test_whitespace() {
TEST_START();
constexpr auto data = parse_json<R"(
{
"key1" : "value1" ,
"key2" : 42
}
)">();
static_assert(std::string_view(data.key1) == "value1");
static_assert(data.key2 == 42);
ASSERT_EQUAL(std::string_view(data.key1), "value1"sv);
ASSERT_EQUAL(data.key2, 42);
TEST_SUCCEED();
}
/**
* Test 7: Real-time system configuration
*/
bool test_realtime_config() {
TEST_START();
constexpr auto config = parse_json<R"({
"control_loop_hz": 1000,
"max_acceleration": 9.8,
"min_velocity": -50.0,
"max_velocity": 50.0,
"enable_safety_checks": true,
"log_level": "INFO"
})">();
static_assert(config.control_loop_hz == 1000);
static_assert(config.max_acceleration == 9.8);
static_assert(config.enable_safety_checks == true);
ASSERT_EQUAL(config.control_loop_hz, 1000);
ASSERT_EQUAL(config.max_acceleration, 9.8);
ASSERT_EQUAL(config.min_velocity, -50.0);
ASSERT_EQUAL(config.max_velocity, 50.0);
ASSERT_TRUE(config.enable_safety_checks);
ASSERT_EQUAL(std::string_view(config.log_level), "INFO"sv);
TEST_SUCCEED();
}
/**
* Test 8: External JSON file (future #embed support)
*/
bool test_external_json_embed() {
TEST_START();
// Future C++26 with #embed:
// constexpr auto config = parse_json<#embed "test_config.json">();
// Current workaround - inline the JSON from test_config.json
constexpr auto config = parse_json<R"({
"system_name": "RealTimeController",
"version": "2.1.0",
"control_loop_hz": 1000,
"max_latency_us": 500,
"enable_diagnostics": true,
"log_level": "INFO"
})">();
static_assert(std::string_view(config.system_name) == "RealTimeController");
static_assert(std::string_view(config.version) == "2.1.0");
static_assert(config.control_loop_hz == 1000);
static_assert(config.max_latency_us == 500);
static_assert(config.enable_diagnostics == true);
static_assert(std::string_view(config.log_level) == "INFO");
ASSERT_EQUAL(std::string_view(config.system_name), "RealTimeController"sv);
ASSERT_EQUAL(std::string_view(config.version), "2.1.0"sv);
ASSERT_EQUAL(config.control_loop_hz, 1000);
ASSERT_EQUAL(config.max_latency_us, 500);
ASSERT_TRUE(config.enable_diagnostics);
ASSERT_EQUAL(std::string_view(config.log_level), "INFO"sv);
TEST_SUCCEED();
}
/**
* Test 9: JSON validation
*/
bool test_json_validation() {
TEST_START();
static_assert(validate_json<R"({"valid": true})">());
static_assert(validate_json<R"({"nested": {"deep": 42}})">());
TEST_SUCCEED();
}
/**
* Test 10: Null values
*/
bool test_null_values() {
TEST_START();
constexpr auto data = parse_json<R"({
"nullable_field": null,
"number": 42
})">();
static_assert(data.nullable_field == nullptr);
static_assert(data.number == 42);
ASSERT_EQUAL(data.nullable_field, nullptr);
ASSERT_EQUAL(data.number, 42);
TEST_SUCCEED();
}
/**
* Test 11: Arrays of primitives
*/
bool test_arrays_primitives() {
TEST_START();
constexpr auto data = parse_json<R"({
"values": [1, 2, 3, 4, 5],
"flags": [true, false, true]
})">();
static_assert(data.values.size() == 5);
static_assert(data.values[0] == 1);
static_assert(data.values[4] == 5);
static_assert(data.flags.size() == 3);
static_assert(data.flags[0] == true);
static_assert(data.flags[1] == false);
ASSERT_EQUAL(data.values.size(), 5);
ASSERT_EQUAL(data.values[0], 1);
ASSERT_EQUAL(data.values[4], 5);
ASSERT_EQUAL(data.flags.size(), 3);
ASSERT_TRUE(data.flags[0]);
ASSERT_FALSE(data.flags[1]);
TEST_SUCCEED();
}
/**
* Test 12: Arrays of objects
*/
bool test_arrays_of_objects() {
TEST_START();
constexpr auto data = parse_json<R"({
"users": [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25}
]
})">();
static_assert(data.users.size() == 2);
static_assert(std::string_view(data.users[0].name) == "Alice");
static_assert(data.users[0].age == 30);
static_assert(std::string_view(data.users[1].name) == "Bob");
static_assert(data.users[1].age == 25);
ASSERT_EQUAL(data.users.size(), 2);
ASSERT_EQUAL(std::string_view(data.users[0].name), "Alice"sv);
ASSERT_EQUAL(data.users[0].age, 30);
ASSERT_EQUAL(std::string_view(data.users[1].name), "Bob"sv);
ASSERT_EQUAL(data.users[1].age, 25);
TEST_SUCCEED();
}
/**
* Test 13: Nested arrays in objects
*/
bool test_nested_arrays() {
TEST_START();
constexpr auto data = parse_json<R"({
"config": {
"ports": [8080, 8081, 8082]
}
})">();
static_assert(data.config.ports.size() == 3);
static_assert(data.config.ports[0] == 8080);
static_assert(data.config.ports[2] == 8082);
ASSERT_EQUAL(data.config.ports.size(), 3);
ASSERT_EQUAL(data.config.ports[0], 8080);
ASSERT_EQUAL(data.config.ports[2], 8082);
TEST_SUCCEED();
}
/**
* Test 14: Complex mixed structure with arrays and nested objects
*/
bool test_complex_mixed() {
TEST_START();
constexpr auto config = parse_json<R"({
"app": "myapp",
"version": 1.0,
"config": {
"ports": [8080, 8081, 8082],
"enabled": true
},
"servers": [
{"host": "server1", "port": 3000},
{"host": "server2", "port": 3001}
]
})">();
static_assert(std::string_view(config.app) == "myapp");
static_assert(config.version == 1.0);
static_assert(config.config.ports[0] == 8080);
static_assert(config.config.enabled == true);
static_assert(std::string_view(config.servers[0].host) == "server1");
static_assert(config.servers[1].port == 3001);
ASSERT_EQUAL(std::string_view(config.app), "myapp"sv);
ASSERT_EQUAL(config.config.ports[0], 8080);
ASSERT_EQUAL(std::string_view(config.servers[0].host), "server1"sv);
ASSERT_EQUAL(config.servers[1].port, 3001);
TEST_SUCCEED();
}
/**
* Test 15: Empty arrays
*/
bool test_empty_arrays() {
TEST_START();
constexpr auto data = parse_json<R"({
"empty": []
})">();
static_assert(data.empty.size() == 0);
ASSERT_EQUAL(data.empty.size(), 0);
TEST_SUCCEED();
}
bool run() {
return test_basic_object() &&
test_nested_objects() &&
test_deeply_nested_objects() &&
test_empty_object() &&
test_negative_numbers() &&
test_whitespace() &&
test_realtime_config() &&
test_external_json_embed() &&
test_json_validation() &&
test_null_values() &&
test_arrays_primitives() &&
test_arrays_of_objects() &&
test_nested_arrays() &&
test_complex_mixed() &&
test_empty_arrays();
}
} // namespace compile_time_json_tests
int main(int argc, char *argv[]) {
#if SIMDJSON_STATIC_REFLECTION
return test_main(argc, argv, compile_time_json_tests::run);
#else
std::cout << "Compile-time JSON tests require SIMDJSON_STATIC_REFLECTION=ON" << std::endl;
return EXIT_SUCCESS;
#endif
}
+8
View File
@@ -0,0 +1,8 @@
{
"system_name": "RealTimeController",
"version": "2.1.0",
"control_loop_hz": 1000,
"max_latency_us": 500,
"enable_diagnostics": true,
"log_level": "INFO"
}