mirror of
https://github.com/simdjson/simdjson
synced 2026-06-08 17:27:07 +00:00
Compile-time support for parsing json objects!
This commit is contained in:
@@ -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,3 +52,7 @@
|
||||
#include "simdjson/generic/ondemand/json_string_builder-inl.h"
|
||||
#include "simdjson/generic/ondemand/json_builder.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"
|
||||
|
||||
|
||||
@@ -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,366 @@
|
||||
/**
|
||||
* @file compile_time_json.h
|
||||
* @brief Compile-time JSON parsing using C++26 P2996 reflection
|
||||
*
|
||||
* Parses JSON at compile-time and generates strongly-typed structs automatically.
|
||||
* Uses std::meta::substitute() pattern for recursive structures.
|
||||
*
|
||||
* @warning Requires C++26 with P2996R13 (experimental clang-p2996 compiler)
|
||||
*
|
||||
* Inspired by: https://brevzin.github.io/c++/2025/06/26/json-reflection/
|
||||
* https://godbolt.org/z/Kn5b46T8j
|
||||
*/
|
||||
|
||||
#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 <vector>
|
||||
#include <array>
|
||||
|
||||
namespace simdjson {
|
||||
namespace SIMDJSON_IMPLEMENTATION {
|
||||
namespace compile_time {
|
||||
|
||||
/**
|
||||
* @brief Helper template for dynamic type generation via substitute()
|
||||
*/
|
||||
template <std::meta::info ...Ms>
|
||||
struct Outer {
|
||||
struct Inner;
|
||||
consteval {
|
||||
std::meta::define_aggregate(^^Inner, {Ms...});
|
||||
}
|
||||
};
|
||||
|
||||
template <std::meta::info ...Ms>
|
||||
using Cls = Outer<Ms...>::Inner;
|
||||
|
||||
/**
|
||||
* @brief Helper template for aggregate initialization from reflected values
|
||||
*/
|
||||
template <typename T, auto ... Vs>
|
||||
constexpr auto construct_from = T{Vs...};
|
||||
|
||||
/**
|
||||
* @brief Parsing state and helper functions
|
||||
*/
|
||||
struct ParseContext {
|
||||
std::string_view json;
|
||||
std::size_t pos = 0;
|
||||
|
||||
constexpr ParseContext(std::string_view input) : json(input), pos(0) {}
|
||||
|
||||
constexpr void skip_whitespace() {
|
||||
while (pos < json.size() && (json[pos] == ' ' || json[pos] == '\t' ||
|
||||
json[pos] == '\n' || json[pos] == '\r')) {
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr char peek() const {
|
||||
return pos < json.size() ? json[pos] : '\0';
|
||||
}
|
||||
|
||||
constexpr char consume() {
|
||||
return pos < json.size() ? json[pos++] : '\0';
|
||||
}
|
||||
|
||||
constexpr bool expect(char ch) {
|
||||
skip_whitespace();
|
||||
if (peek() == ch) {
|
||||
consume();
|
||||
return true;
|
||||
}
|
||||
throw "expected character";
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool match(std::string_view str) {
|
||||
if (json.substr(pos, str.size()) == str) {
|
||||
pos += str.size();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr std::string_view parse_string() {
|
||||
if (!expect('"')) throw "expected quote";
|
||||
std::size_t start = pos;
|
||||
while (peek() != '"' && peek() != '\0') {
|
||||
if (peek() == '\\') {
|
||||
consume();
|
||||
if (pos < json.size()) consume();
|
||||
} else {
|
||||
consume();
|
||||
}
|
||||
}
|
||||
std::size_t end = pos;
|
||||
expect('"');
|
||||
return json.substr(start, end - start);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr double parse_number() {
|
||||
std::size_t start = pos;
|
||||
if (peek() == '-') consume();
|
||||
while (peek() >= '0' && peek() <= '9') consume();
|
||||
if (peek() == '.') {
|
||||
consume();
|
||||
while (peek() >= '0' && peek() <= '9') consume();
|
||||
}
|
||||
|
||||
std::string_view num_str = json.substr(start, pos - start);
|
||||
double result = 0.0, sign = 1.0;
|
||||
std::size_t i = 0;
|
||||
|
||||
if (i < num_str.size() && num_str[i] == '-') {
|
||||
sign = -1.0;
|
||||
++i;
|
||||
}
|
||||
|
||||
while (i < num_str.size() && num_str[i] >= '0' && num_str[i] <= '9') {
|
||||
result = result * 10.0 + (num_str[i] - '0');
|
||||
++i;
|
||||
}
|
||||
|
||||
if (i < num_str.size() && num_str[i] == '.') {
|
||||
++i;
|
||||
double fraction = 0.0, divisor = 1.0;
|
||||
while (i < num_str.size() && num_str[i] >= '0' && num_str[i] <= '9') {
|
||||
fraction = fraction * 10.0 + (num_str[i] - '0');
|
||||
divisor *= 10.0;
|
||||
++i;
|
||||
}
|
||||
result += fraction / divisor;
|
||||
}
|
||||
|
||||
return result * sign;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr std::string_view extract_nested() {
|
||||
std::size_t start = pos;
|
||||
char open_char = peek();
|
||||
char close_char = (open_char == '{') ? '}' : ']';
|
||||
int depth = 0;
|
||||
do {
|
||||
if (peek() == open_char) depth++;
|
||||
if (peek() == close_char) depth--;
|
||||
consume();
|
||||
} while (depth > 0 && pos < json.size());
|
||||
if (depth != 0) throw "unclosed bracket";
|
||||
return json.substr(start, pos - start);
|
||||
}
|
||||
};
|
||||
|
||||
consteval std::meta::info parse_json_impl(std::string_view json);
|
||||
consteval std::meta::info parse_array_impl(std::string_view json);
|
||||
|
||||
consteval std::meta::info parse_array_impl(std::string_view json) {
|
||||
ParseContext ctx{json};
|
||||
ctx.skip_whitespace();
|
||||
|
||||
if (!ctx.expect('[')) {
|
||||
throw "expected '['";
|
||||
}
|
||||
|
||||
ctx.skip_whitespace();
|
||||
|
||||
if (ctx.peek() == ']') {
|
||||
ctx.consume();
|
||||
return std::meta::substitute(^^construct_from, {^^std::array<void, 0>, ^^void});
|
||||
}
|
||||
|
||||
std::vector<std::meta::info> element_values;
|
||||
|
||||
while (ctx.peek() != ']') {
|
||||
ctx.skip_whitespace();
|
||||
char ch = ctx.peek();
|
||||
|
||||
if (ch == '"') {
|
||||
auto str_value = ctx.parse_string();
|
||||
element_values.push_back(std::meta::reflect_constant_string(str_value));
|
||||
} else if (ch == 't' || ch == 'f') {
|
||||
bool bool_value = ctx.peek() == 't';
|
||||
if (bool_value) {
|
||||
if (!ctx.match("true")) throw "expected 'true'";
|
||||
} else {
|
||||
if (!ctx.match("false")) throw "expected 'false'";
|
||||
}
|
||||
element_values.push_back(std::meta::reflect_constant(bool_value));
|
||||
} else if (ch == 'n') {
|
||||
if (!ctx.match("null")) throw "expected 'null'";
|
||||
element_values.push_back(std::meta::reflect_constant(nullptr));
|
||||
} else if (ch == '{') {
|
||||
auto nested_json = ctx.extract_nested();
|
||||
std::meta::info parsed = parse_json_impl(nested_json);
|
||||
element_values.push_back(parsed);
|
||||
} else if (ch == '[') {
|
||||
auto nested_json = ctx.extract_nested();
|
||||
std::meta::info parsed = parse_array_impl(nested_json);
|
||||
element_values.push_back(parsed);
|
||||
} else if ((ch >= '0' && ch <= '9') || ch == '-') {
|
||||
double num_value = ctx.parse_number();
|
||||
element_values.push_back(std::meta::reflect_constant(num_value));
|
||||
} else {
|
||||
throw "unexpected array element type";
|
||||
}
|
||||
|
||||
ctx.skip_whitespace();
|
||||
if (ctx.peek() == ',') {
|
||||
ctx.consume();
|
||||
ctx.skip_whitespace();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ctx.expect(']');
|
||||
|
||||
if (element_values.empty()) {
|
||||
return std::meta::substitute(^^construct_from, {^^std::array<void, 0>, ^^void});
|
||||
}
|
||||
|
||||
std::meta::info element_type = std::meta::type_of(element_values[0]);
|
||||
std::size_t count = element_values.size();
|
||||
std::meta::info array_type = std::meta::substitute(^^std::array, {element_type, std::meta::reflect_constant(count)});
|
||||
|
||||
std::vector<std::meta::info> values;
|
||||
values.push_back(array_type);
|
||||
for (auto& elem : element_values) {
|
||||
values.push_back(elem);
|
||||
}
|
||||
|
||||
return std::meta::substitute(^^construct_from, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Main compile-time JSON parser using substitute() pattern
|
||||
*
|
||||
* Returns std::meta::info containing type and value information.
|
||||
* This can be recursively called for nested objects without compiler crashes.
|
||||
*/
|
||||
consteval std::meta::info parse_json_impl(std::string_view json) {
|
||||
ParseContext ctx{json};
|
||||
ctx.skip_whitespace();
|
||||
|
||||
if (!ctx.expect('{')) {
|
||||
throw "expected '{'";
|
||||
}
|
||||
|
||||
std::vector<std::meta::info> members;
|
||||
std::vector<std::meta::info> values = {^^void};
|
||||
|
||||
using std::meta::reflect_constant;
|
||||
|
||||
while (ctx.peek() != '}') {
|
||||
ctx.skip_whitespace();
|
||||
if (ctx.peek() == '}') break;
|
||||
|
||||
auto field_name = ctx.parse_string();
|
||||
ctx.skip_whitespace();
|
||||
ctx.expect(':');
|
||||
ctx.skip_whitespace();
|
||||
|
||||
char ch = ctx.peek();
|
||||
|
||||
if (ch == '"') {
|
||||
auto str_value = ctx.parse_string();
|
||||
auto dms = std::meta::data_member_spec(^^char const*, {.name=field_name});
|
||||
members.push_back(reflect_constant(dms));
|
||||
values.push_back(std::meta::reflect_constant_string(str_value));
|
||||
} else if (ch == 't' || ch == 'f') {
|
||||
bool bool_value = ctx.peek() == 't';
|
||||
if (bool_value) {
|
||||
if (!ctx.match("true")) throw "expected 'true'";
|
||||
} else {
|
||||
if (!ctx.match("false")) throw "expected 'false'";
|
||||
}
|
||||
auto dms = std::meta::data_member_spec(^^bool, {.name=field_name});
|
||||
members.push_back(reflect_constant(dms));
|
||||
values.push_back(reflect_constant(bool_value));
|
||||
} else if (ch == 'n') {
|
||||
if (!ctx.match("null")) throw "expected '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 (ch == '[') {
|
||||
auto array_json = ctx.extract_nested();
|
||||
std::meta::info parsed = parse_array_impl(array_json);
|
||||
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 (ch == '{') {
|
||||
auto nested_json = ctx.extract_nested();
|
||||
std::meta::info parsed = parse_json_impl(nested_json);
|
||||
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 ((ch >= '0' && ch <= '9') || ch == '-') {
|
||||
double num_value = ctx.parse_number();
|
||||
auto dms = std::meta::data_member_spec(^^double, {.name=field_name});
|
||||
members.push_back(reflect_constant(dms));
|
||||
values.push_back(reflect_constant(num_value));
|
||||
} else {
|
||||
throw "unexpected value type";
|
||||
}
|
||||
|
||||
ctx.skip_whitespace();
|
||||
if (ctx.peek() == ',') ctx.consume();
|
||||
}
|
||||
|
||||
ctx.expect('}');
|
||||
|
||||
values[0] = std::meta::substitute(^^Cls, members);
|
||||
return std::meta::substitute(^^construct_from, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief JSON string wrapper for template parameters
|
||||
*/
|
||||
struct JSONString {
|
||||
std::meta::info Rep;
|
||||
consteval JSONString(const char *Json) : Rep{parse_json_impl(Json)} {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Main parse_json function - template variable pattern
|
||||
*
|
||||
* Usage:
|
||||
* constexpr auto config = json_to_object<R"({"port":8080,"host":"localhost"})">;
|
||||
* static_assert(config.port == 8080);
|
||||
* static_assert(std::string_view(config.host) == "localhost");
|
||||
*/
|
||||
template <JSONString json>
|
||||
inline constexpr auto json_to_object = [:json.Rep:];
|
||||
|
||||
/**
|
||||
* @brief Alternative: User-defined literal for JSON parsing
|
||||
*
|
||||
* Usage:
|
||||
* constexpr auto config = R"({"port":8080})"_json;
|
||||
*/
|
||||
template <JSONString json>
|
||||
consteval auto operator""_json() {
|
||||
return [:json.Rep:];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Validate JSON syntax at compile-time
|
||||
*/
|
||||
template <JSONString json>
|
||||
consteval bool validate_json() {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace compile_time
|
||||
} // namespace SIMDJSON_IMPLEMENTATION
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_STATIC_REFLECTION
|
||||
#endif // SIMDJSON_GENERIC_COMPILE_TIME_JSON_H
|
||||
@@ -0,0 +1,425 @@
|
||||
/**
|
||||
* @file compile_time_json_tests.cpp
|
||||
* @brief 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 = json_to_object<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 = json_to_object<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 = json_to_object<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: Arrays of primitives
|
||||
*/
|
||||
bool test_arrays_primitives() {
|
||||
TEST_START();
|
||||
|
||||
constexpr auto data = json_to_object<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 5: Arrays of objects
|
||||
*/
|
||||
bool test_arrays_of_objects() {
|
||||
TEST_START();
|
||||
|
||||
constexpr auto data = json_to_object<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 6: Nested arrays in objects
|
||||
*/
|
||||
bool test_nested_arrays() {
|
||||
TEST_START();
|
||||
|
||||
constexpr auto data = json_to_object<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 7: Complex mixed structure
|
||||
*/
|
||||
bool test_complex_mixed() {
|
||||
TEST_START();
|
||||
|
||||
constexpr auto config = json_to_object<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 8: Empty object
|
||||
*/
|
||||
bool test_empty_object() {
|
||||
TEST_START();
|
||||
|
||||
constexpr auto config = json_to_object<"{}">;
|
||||
(void)config; // Suppress unused warning
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 9: Negative numbers
|
||||
*/
|
||||
bool test_negative_numbers() {
|
||||
TEST_START();
|
||||
|
||||
constexpr auto data = json_to_object<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 10: Whitespace handling
|
||||
*/
|
||||
bool test_whitespace() {
|
||||
TEST_START();
|
||||
|
||||
constexpr auto data = json_to_object<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 11: String unescaping from inline helpers
|
||||
*/
|
||||
bool test_string_unescape() {
|
||||
TEST_START();
|
||||
|
||||
{
|
||||
constexpr auto pair = unescape_json_string<100>("hello world");
|
||||
ASSERT_EQUAL(std::string_view(pair.first.data(), pair.second), "hello world"sv);
|
||||
}
|
||||
|
||||
{
|
||||
constexpr auto pair = unescape_json_string<100>(R"(hello\nworld)");
|
||||
ASSERT_EQUAL(std::string_view(pair.first.data(), pair.second), "hello\nworld"sv);
|
||||
}
|
||||
|
||||
{
|
||||
constexpr auto pair = unescape_json_string<100>(R"(quote: \")");
|
||||
ASSERT_EQUAL(std::string_view(pair.first.data(), pair.second), "quote: \""sv);
|
||||
}
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 12: 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 13: User-defined literal
|
||||
*/
|
||||
bool test_user_defined_literal() {
|
||||
TEST_START();
|
||||
|
||||
constexpr auto config = R"({"version": 1.0, "name": "test"})"_json;
|
||||
|
||||
static_assert(config.version == 1.0);
|
||||
static_assert(std::string_view(config.name) == "test");
|
||||
|
||||
ASSERT_EQUAL(config.version, 1.0);
|
||||
ASSERT_EQUAL(std::string_view(config.name), "test"sv);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 14: Real-time system configuration
|
||||
*/
|
||||
bool test_realtime_config() {
|
||||
TEST_START();
|
||||
|
||||
constexpr auto config = json_to_object<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 15: Future #embed support demonstration
|
||||
*
|
||||
* Note: This test shows how external JSON files would be used with #embed.
|
||||
* Currently uses inline JSON matching test_config.json for compatibility.
|
||||
*/
|
||||
bool test_external_json_embed() {
|
||||
TEST_START();
|
||||
|
||||
// Future C++26 with #embed:
|
||||
// constexpr auto config = json_to_object<#embed "test_config.json">;
|
||||
|
||||
// Current workaround - inline the JSON from test_config.json
|
||||
constexpr auto config = json_to_object<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();
|
||||
}
|
||||
|
||||
bool run() {
|
||||
return test_basic_object() &&
|
||||
test_nested_objects() &&
|
||||
test_deeply_nested_objects() &&
|
||||
test_arrays_primitives() &&
|
||||
test_arrays_of_objects() &&
|
||||
test_nested_arrays() &&
|
||||
test_complex_mixed() &&
|
||||
test_empty_object() &&
|
||||
test_negative_numbers() &&
|
||||
test_whitespace() &&
|
||||
test_string_unescape() &&
|
||||
test_json_validation() &&
|
||||
test_user_defined_literal() &&
|
||||
test_realtime_config() &&
|
||||
test_external_json_embed();
|
||||
}
|
||||
|
||||
} // 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