Note: This feature requires C++26 Static Reflection support (P2996) and is currently only available with experimental compilers. You must enable it with -DSIMDJSON_STATIC_REFLECTION=ON when building.
Overview
simdjson provides compile-time JSONPath and JSON Pointer accessors that validate paths against struct definitions at compile time and generate optimized accessor code with zero runtime overhead. This combines the safety of compile-time type checking with the performance of pre-parsed, pre-validated access paths.
Requirements
- C++26 compiler with Static Reflection support (P2996)
- Experimental compiler flags:
- Clang with P2996 support:
-std=c++26 -freflection -fexpansion-statements
- Build configuration:
-DSIMDJSON_STATIC_REFLECTION=ON
How It Works
Compile Time:
- Path string is parsed and converted to access steps
- Path is validated against struct definition using reflection
- Field types are checked and verified
- Optimized accessor code is generated
Runtime:
- Direct navigation with no parsing
- No validation overhead
- No string comparisons for path components
- Type-safe extraction
Two Usage Modes
Mode 1: With Type Validation (Recommended)
When you provide a struct type, the compiler validates the entire path at compile time:
struct User {
std::string name;
int age;
std::vector<std::string> emails;
};
const padded_string json = R"({
"name": "Alice",
"age": 30,
"emails": ["alice@example.com", "alice@work.com"]
})"_padded;
ondemand::parser parser;
auto doc = parser.iterate(json);
std::string name;
auto result = ondemand::json_path::at_path_compiled<User, ".name">(doc);
result.get(name);
std::string email;
result = ondemand::json_path::at_path_compiled<User, ".emails[0]">(doc);
result.get(email);
Benefits:
- Compile-time errors if path doesn't exist in struct
- Type safety - verifies field types match expected types
- Refactoring protection - renaming struct fields causes compile errors
What gets validated:
- Field existence
- Field types
- Array/container access validity
- Nested struct navigation
Mode 2: Without Validation
When you omit the struct type, the path is parsed at compile time but not validated:
const padded_string json = R"({
"name": "Alice",
"age": 30,
"address": {"city": "Boston"}
})"_padded;
ondemand::parser parser;
auto doc = parser.iterate(json);
std::string name;
auto result = ondemand::json_path::at_path_compiled<".name">(doc);
result.get(name);
std::string_view city;
result = ondemand::json_path::at_path_compiled<".address.city">(doc);
result.get(city);
Benefits:
- Works with dynamic/unknown JSON structures
- Still benefits from compile-time path parsing
- No runtime string parsing overhead
Use when:
- JSON structure is not known at compile time
- Working with varied JSON schemas
- Prototyping or exploratory parsing
JSONPath Syntax
JSONPath uses dot notation and bracket notation for field access:
Supported Syntax
| Syntax | Description | Example |
.field | Dot notation for field access | .name, .address.city |
["field"] | Bracket notation with quotes | ["name"], ["address"]["city"] |
[index] | Array index access | [0], [1] |
| Mixed | Combination of notations | .emails[0], ["users"][0].name |
$ prefix | Optional root indicator | $.name, $["name"] |
Examples
struct Address {
std::string city;
int zip;
};
struct Person {
std::string name;
int age;
Address address;
std::vector<std::string> emails;
};
at_path_compiled<Person, ".name">(doc)
at_path_compiled<Person, ".address.city">(doc)
at_path_compiled<Person, "[\"name\"]">(doc)
at_path_compiled<Person, "[\"address\"][\"city\"]">(doc)
at_path_compiled<Person, ".emails[0]">(doc)
at_path_compiled<Person, ".emails[1]">(doc)
at_path_compiled<Person, ".address[\"zip\"]">(doc)
at_path_compiled<Person, "[\"emails\"][0]">(doc)
at_path_compiled<Person, "$.name">(doc)
at_path_compiled<Person, "$.address.city">(doc)
JSON Pointer Syntax
JSON Pointer (RFC 6901) uses slash-separated paths:
Supported Syntax
| Syntax | Description | Example |
/field | Field access | /name, /address/city |
/index | Array index | /0, /1 |
~0 | Escaped ~ | /field~0name → field~name |
~1 | Escaped / | /field~1name → field/name |
Examples
struct Car {
std::string make;
std::string model;
int64_t year;
std::vector<double> tire_pressure;
};
at_pointer_compiled<Car, "/make">(doc)
at_pointer_compiled<Car, "/model">(doc)
at_pointer_compiled<Car, "/tire_pressure/0">(doc)
at_pointer_compiled<Car, "/tire_pressure/1">(doc)
at_pointer_compiled<Car, "">(doc)
at_pointer_compiled<Car, "/">(doc)
API Reference
JSONPath Functions
template<typename T, constevalutil::fixed_string Path, typename DocOrValue>
simdjson_result<value> at_path_compiled(DocOrValue& doc_or_val);
template<constevalutil::fixed_string Path, typename DocOrValue>
simdjson_result<value> at_path_compiled(DocOrValue& doc_or_val);
JSON Pointer Functions
template<typename T, constevalutil::fixed_string Pointer, typename DocOrValue>
simdjson_result<value> at_pointer_compiled(DocOrValue& doc_or_val);
template<constevalutil::fixed_string Pointer, typename DocOrValue>
simdjson_result<value> at_pointer_compiled(DocOrValue& doc_or_val);
Direct Field Extraction
Extract values directly into variables with compile-time type checking:
template<typename T, constevalutil::fixed_string Path>
struct path_accessor {
template<typename DocOrValue, typename FieldType>
static error_code extract_field(DocOrValue& doc_or_val, FieldType& target);
};
template<typename T, constevalutil::fixed_string Pointer>
struct pointer_accessor {
template<typename DocOrValue, typename FieldType>
static error_code extract_field(DocOrValue& doc_or_val, FieldType& target);
};
Example:
struct User {
std::string name;
int age;
};
ondemand::parser parser;
auto doc = parser.iterate(json);
std::string name;
ondemand::json_path::path_accessor<User, ".name">::extract_field(doc, name);
int age;
ondemand::json_path::pointer_accessor<User, "/age">::extract_field(doc, age);
The compiler verifies that the target variable type matches the field type at the path.
Complete Examples
Example 1: Validated Access
struct Car {
std::string make;
std::string model;
int64_t year;
std::vector<double> tire_pressure;
};
int main() {
"make": "Toyota",
"model": "Camry",
"year": 2018,
"tire_pressure": [40.1, 39.9, 37.7, 40.4]
})"_padded;
ondemand::parser parser;
auto doc = parser.iterate(json);
std::string make;
auto result = ondemand::json_path::at_path_compiled<Car, ".make">(doc);
result.get(make);
double pressure;
result = ondemand::json_path::at_path_compiled<Car, ".tire_pressure[1]">(doc);
result.get(pressure);
return 0;
}
The top level simdjson namespace, containing everything the library provides.
String with extra allocation for ease of use with parser::parse()
Example 2: Non-Validated Access
int main() {
"user": {
"name": "Alice",
"preferences": {
"theme": "dark",
"notifications": true
}
}
})"_padded;
ondemand::parser parser;
auto doc = parser.iterate(json);
std::string_view theme;
auto result = ondemand::json_path::at_path_compiled<".user.preferences.theme">(doc);
result.get(theme);
bool notifications;
result = ondemand::json_path::at_path_compiled<".user.preferences.notifications">(doc);
result.get(notifications);
return 0;
}
Example 3: Direct Extraction
struct Person {
std::string name;
int age;
std::vector<std::string> emails;
};
int main() {
"name": "Bob",
"age": 25,
"emails": ["bob@example.com", "bob@work.com"]
})"_padded;
ondemand::parser parser;
auto doc = parser.iterate(json);
std::string name;
ondemand::json_path::path_accessor<Person, ".name">::extract_field(doc, name);
int age;
ondemand::json_path::pointer_accessor<Person, "/age">::extract_field(doc, age);
std::string email;
ondemand::json_path::path_accessor<Person, ".emails[0]">::extract_field(doc, email);
return 0;
}
Error Handling
Compile-time errors occur when:
- Path doesn't exist in struct:
static_assert failure
- Field type mismatch:
static_assert failure
- Invalid array access on non-array field:
static_assert failure
Runtime errors occur when:
- JSON structure doesn't match expected structure
- Array index out of bounds
- Type conversion failures
struct User {
std::string name;
int age;
};
auto result = ondemand::json_path::at_path_compiled<User, ".name">(doc);
std::string name;
if (result.get(name) != SUCCESS) {
}
Performance
Compile-time accessors provide:
- Zero path parsing overhead - paths parsed at compile time
- Zero validation overhead - validation done at compile time
- Direct field access - no runtime path traversal
- Type-safe extraction - no dynamic type checking
Compared to runtime at_path() and at_pointer():
- Eliminates runtime path string parsing
- Eliminates runtime path validation
- Generates optimal code path directly
Limitations
- Requires C++26 compiler with P2996 support (experimental)
- Paths must be compile-time constants (string literals)
- Cannot use runtime-computed paths
- Limited to struct types that support reflection
- Array indices must be compile-time constants in the path
When to Use
Use compile-time accessors when:
- You have well-defined struct types
- JSON structure is known at compile time
- You want maximum type safety
- Performance is critical
Use runtime at_path()/at_pointer() when:
- JSON structure varies or is unknown
- Paths are computed at runtime
- Working with C++20 or earlier
- Flexibility is more important than compile-time checks
See Also