diff --git a/doc/compile_time.md b/doc/compile_time.md index a9b3e5076..65956ce59 100644 --- a/doc/compile_time.md +++ b/doc/compile_time.md @@ -1,6 +1,7 @@ # Parse json at compile time * [Introduction](#introduction) * [Example](#example) + * [Concepts](#concepts) * [Loading from disk](#loading-from-disk) * [Limitations (compile-time errors)](#limitations-compile-time-errors) @@ -106,6 +107,68 @@ static_assert(arr.size() == 3); static_assert(arr[1] == 2); ``` + +## Concepts + +Given that the parsed data is made of structures that depend on the JSON input, you might +want to check that it conforms to your expectation. You can do so with concepts. + +Let us consider this example: + +```cpp + constexpr auto config = R"( + + [ + { "name": "Alice", "age": 30 }, + { "name": "Bob", "age": 25 }, + { "name": "Charlie", "age": 35 } + ] + + )"_json; +``` + +You might want to ensure that the result is an array of persons. You can define your +expection with concepts like so: + +```cpp +template +concept person = requires(T p) { + std::string_view(p.name); // has name field convertible to string_view + p.age; // has age field + requires std::is_integral_v; // age is integral +}; + +/** + * Concept to validate that a type is an array of person objects + */ +template +concept array_of_person = requires(T arr) { + arr.size(); // has size method + arr[0]; // can access elements with [] + requires person; // elements satisfy person concept +}; +``` + +And then a simple static assert with `decltype` is sufficient to check that the expectation is met: + +```cpp + constexpr auto config = R"( + + [ + { "name": "Alice", "age": 30 }, + { "name": "Bob", "age": 25 }, + { "name": "Charlie", "age": 35 } + ] + + )"_json; + + + // Validate that the array satisfies the array_of_person concept + static_assert(array_of_person); +``` + + + ## Loading from disk In practice, you may have a JSON file, say `json_data` that you want to parse diff --git a/include/simdjson/error.h b/include/simdjson/error.h index 823912881..c79f0e675 100644 --- a/include/simdjson/error.h +++ b/include/simdjson/error.h @@ -265,6 +265,8 @@ struct simdjson_result_base : protected std::pair { */ simdjson_inline T&& value_unsafe() && noexcept; + using value_type = T; + using error_type = error_code; }; // struct simdjson_result_base } // namespace internal @@ -376,6 +378,8 @@ struct simdjson_result : public internal::simdjson_result_base { */ simdjson_inline T&& value_unsafe() && noexcept; + using value_type = T; + using error_type = error_code; }; // struct simdjson_result #if SIMDJSON_EXCEPTIONS diff --git a/include/simdjson/generic/implementation_simdjson_result_base.h b/include/simdjson/generic/implementation_simdjson_result_base.h index c909a4fa5..f57473b3e 100644 --- a/include/simdjson/generic/implementation_simdjson_result_base.h +++ b/include/simdjson/generic/implementation_simdjson_result_base.h @@ -138,6 +138,9 @@ struct implementation_simdjson_result_base { */ simdjson_inline T&& value_unsafe() && noexcept; + using value_type = T; + using error_type = error_code; + protected: /** users should never directly access first and second. **/ T first{}; /** Users should never directly access 'first'. **/ diff --git a/tests/cast_tester.h b/tests/cast_tester.h index 98fb823fd..efa616353 100644 --- a/tests/cast_tester.h +++ b/tests/cast_tester.h @@ -87,16 +87,16 @@ bool cast_tester::test_get_error(simdjson_result element, error_code template bool cast_tester::test_get_t(element element, T expected) { - auto actual = element.get(); - ASSERT_SUCCESS(actual.error()); - return assert_equal(actual.value_unsafe(), expected); + T value; + ASSERT_SUCCESS(element.get().get(value)); + return assert_equal(value, expected); } template bool cast_tester::test_get_t(simdjson_result element, T expected) { - auto actual = element.get(); - ASSERT_SUCCESS(actual.error()); - return assert_equal(actual.value_unsafe(), expected); + T value; + ASSERT_SUCCESS(element.get().get(value)); + return assert_equal(value, expected); } template diff --git a/tests/compile_time/compile_time_json_tests.cpp b/tests/compile_time/compile_time_json_tests.cpp index b3a42053f..aca747c54 100644 --- a/tests/compile_time/compile_time_json_tests.cpp +++ b/tests/compile_time/compile_time_json_tests.cpp @@ -597,6 +597,98 @@ bool test_top_level_array_example() { TEST_SUCCEED(); } +/** + * Concept to validate that a type represents a person with name and age + */ +template +concept person = requires(T p) { + std::string_view(p.name); // has name field convertible to string_view + p.age; // has age field + requires std::is_integral_v; // age is integral +}; + +/** + * Concept to validate that a type is an array of person objects + */ +template +concept array_of_person = requires(T arr) { + arr.size(); // has size method + arr[0]; // can access elements with [] + requires person; // elements satisfy person concept +}; + +/** + * Test: Array of objects with concept validation + */ +bool test_array_of_objects_with_concept() { + TEST_START(); + + constexpr auto config = R"( + + [ + { "name": "Alice", "age": 30 }, + { "name": "Bob", "age": 25 }, + { "name": "Charlie", "age": 35 } + ] + + )"_json; + + std::print("Array size: {}\n", config.size()); + + static_assert(config.size() == 3); + + // Validate that the array satisfies the array_of_person concept + static_assert(array_of_person); + + + // Test the actual values + static_assert(std::string_view(config[0].name) == "Alice"); + static_assert(config[0].age == 30); + static_assert(std::string_view(config[1].name) == "Bob"); + static_assert(config[1].age == 25); + static_assert(std::string_view(config[2].name) == "Charlie"); + static_assert(config[2].age == 35); + + // Runtime assertions + ASSERT_EQUAL(config.size(), 3); + ASSERT_EQUAL(std::string_view(config[0].name), "Alice"sv); + ASSERT_EQUAL(config[0].age, 30); + ASSERT_EQUAL(std::string_view(config[1].name), "Bob"sv); + ASSERT_EQUAL(config[1].age, 25); + ASSERT_EQUAL(std::string_view(config[2].name), "Charlie"sv); + ASSERT_EQUAL(config[2].age, 35); + + TEST_SUCCEED(); +} + +/** + * Test: #embed support for external JSON files (C++26) + */ +bool test_embed_twitter_json() { + TEST_START(); + + // C++26 #embed allows embedding files directly into the binary at compile time + // This creates a const char array with the file contents plus null terminator + constexpr const char twitter_json[] = { + #embed TWITTER_JSON + , 0 + }; + + // Parse the embedded JSON at compile time + constexpr auto parsed_twitter = simdjson::compile_time::parse_json(); + + // Verify the structure - twitter.json should have a "statuses" array + static_assert(parsed_twitter.statuses.size() > 0); + + // Runtime verification + ASSERT_TRUE(parsed_twitter.statuses.size() > 0); + + std::cout << "Successfully parsed embedded twitter.json with " + << parsed_twitter.statuses.size() << " statuses" << std::endl; + + TEST_SUCCEED(); +} + bool run() { return test_basic_object() && test_nested_objects() && @@ -620,7 +712,9 @@ bool run() { array_of_objects() && test_user_config_example() && test_nested_servers_example() && - test_top_level_array_example(); + test_top_level_array_example() && + test_array_of_objects_with_concept() && + test_embed_twitter_json(); } } // namespace compile_time_json_tests