mirror of
https://github.com/simdjson/simdjson
synced 2026-06-08 17:27:07 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d326f2ce9f | |||
| ca42a49fba | |||
| a7f8fb71c5 | |||
| a553db4c67 | |||
| 1fa1af8c15 |
@@ -1,4 +1,4 @@
|
||||
name: Ubuntu ppc64le (GCC 11)
|
||||
name: Ubuntu aarch64 (GCC 13)
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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"]`.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user