diff --git a/include/simdjson/concepts.h b/include/simdjson/concepts.h index ffe344c07..371ad223f 100644 --- a/include/simdjson/concepts.h +++ b/include/simdjson/concepts.h @@ -115,7 +115,6 @@ concept optional_type = requires(std::remove_cvref_t obj) { { obj.value() } -> std::same_as::value_type&>; requires requires(typename std::remove_cvref_t::value_type &&val) { obj.emplace(std::move(val)); - obj = std::move(val); { obj.value_or(val) } -> std::convertible_to::value_type>; diff --git a/include/simdjson/generic/ondemand/json_builder.h b/include/simdjson/generic/ondemand/json_builder.h index 14fb24699..e9646f3e5 100644 --- a/include/simdjson/generic/ondemand/json_builder.h +++ b/include/simdjson/generic/ondemand/json_builder.h @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include #include #include @@ -94,8 +96,13 @@ consteval std::string consteval_to_quoted_escaped(std::string_view input); template requires(std::is_class_v && !container_but_not_string && !concepts::string_view_keyed_map && + !concepts::optional_type && + !concepts::smart_pointer && + !concepts::appendable_containers && !std::is_same_v && - !std::is_same_v) + !std::is_same_v && + !std::is_same_v && + !std::is_same_v) constexpr void atom(string_builder &b, const T &t) { int i = 0; b.append('{'); @@ -111,8 +118,101 @@ constexpr void atom(string_builder &b, const T &t) { b.append('}'); } +// Support for optional types (std::optional, etc.) +template +constexpr void atom(string_builder &b, const T &opt) { + if (opt) { + atom(b, opt.value()); + } else { + b.append_raw("null"); + } +} + +// Support for smart pointers (std::unique_ptr, std::shared_ptr, etc.) +template +constexpr void atom(string_builder &b, const T &ptr) { + if (ptr) { + atom(b, *ptr); + } else { + b.append_raw("null"); + } +} + +// Support for appendable containers that don't have operator[] (sets, etc.) +template + requires(!container_but_not_string && !concepts::string_view_keyed_map && + !concepts::optional_type && !concepts::smart_pointer && + !std::is_same_v && + !std::is_same_v && !std::is_same_v) +constexpr void atom(string_builder &b, const T &container) { + if (container.empty()) { + b.append_raw("[]"); + return; + } + b.append('['); + bool first = true; + for (const auto& item : container) { + if (!first) { + b.append(','); + } + first = false; + atom(b, item); + } + b.append(']'); +} + +// append functions that delegate to atom functions for primitive types +template + requires(std::is_arithmetic_v && !std::is_same_v) +void append(string_builder &b, const T &t) { + atom(b, t); +} + +template + requires(std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v) +void append(string_builder &b, const T &t) { + atom(b, t); +} + +template +void append(string_builder &b, const T &t) { + atom(b, t); +} + +template +void append(string_builder &b, const T &t) { + atom(b, t); +} + +template + requires(!container_but_not_string && !concepts::string_view_keyed_map && + !concepts::optional_type && !concepts::smart_pointer && + !std::is_same_v && + !std::is_same_v && !std::is_same_v) +void append(string_builder &b, const T &t) { + atom(b, t); +} + +template +void append(string_builder &b, const T &t) { + atom(b, t); +} + // works for struct -template void append(string_builder &b, const Z &z) { +template + requires(std::is_class_v && !container_but_not_string && + !concepts::string_view_keyed_map && + !concepts::optional_type && + !concepts::smart_pointer && + !concepts::appendable_containers && + !std::is_same_v && + !std::is_same_v && + !std::is_same_v && + !std::is_same_v) +void append(string_builder &b, const Z &z) { int i = 0; b.append('{'); [:expand(std::meta::nonstatic_data_members_of(^^Z, std::meta::access_context::unchecked())):] >> [&]() { diff --git a/include/simdjson/generic/ondemand/serialization-inl.h b/include/simdjson/generic/ondemand/serialization-inl.h index 77be39a11..6c0c81d6e 100644 --- a/include/simdjson/generic/ondemand/serialization-inl.h +++ b/include/simdjson/generic/ondemand/serialization-inl.h @@ -9,6 +9,9 @@ #include "simdjson/generic/ondemand/object.h" #include "simdjson/generic/ondemand/serialization.h" #include "simdjson/generic/ondemand/value.h" +#if SIMDJSON_STATIC_REFLECTION +#include "simdjson/generic/ondemand/json_builder.h" +#endif #endif // SIMDJSON_CONDITIONAL_INCLUDE namespace simdjson { @@ -108,6 +111,8 @@ inline simdjson_result to_json_string(simdjson_result to_json_string(simdjson_result to_json_string(simdjson_result x); inline simdjson_result to_json_string(simdjson_result x); inline simdjson_result to_json_string(simdjson_result x); + +#if SIMDJSON_STATIC_REFLECTION +/** + * Create a JSON string from any user-defined type using static reflection. + * Only available when SIMDJSON_STATIC_REFLECTION is enabled. + */ +template + requires(!std::same_as && + !std::same_as && + !std::same_as && + !std::same_as) +inline std::string to_json_string(const T& obj); +#endif + } // namespace simdjson /** diff --git a/include/simdjson/generic/ondemand/std_deserialize.h b/include/simdjson/generic/ondemand/std_deserialize.h index 0e945bc82..322b2b4e2 100644 --- a/include/simdjson/generic/ondemand/std_deserialize.h +++ b/include/simdjson/generic/ondemand/std_deserialize.h @@ -255,10 +255,16 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept(nothrow_deser static_assert( deserializable, - "The specified type inside the unique_ptr must itself be deserializable"); + "The specified type inside the optional must itself be deserializable"); static_assert( std::is_default_constructible_v, - "The specified type inside the unique_ptr must default constructible."); + "The specified type inside the optional must default constructible."); + + // Check if the value is null + if (val.is_null()) { + out.reset(); // Set to nullopt + return SUCCESS; + } if (!out) { out.emplace(); @@ -297,7 +303,7 @@ template consteval auto expand(R range) { std::vector args; for (auto r : range) { - args.push_back(reflect_value(r)); + args.push_back(reflect_constant(r)); } return substitute(^^__impl::replicator, args); } @@ -366,6 +372,10 @@ error_code tag_invoke(deserialize_tag, simdjson_value &val, std::shared_ptr & // Unique pointers //////////////////////////////////////// error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { + if (val.is_null()) { + out.reset(); + return SUCCESS; + } if (!out) { out = std::make_unique(); if (!out) { return MEMALLOC; } @@ -375,6 +385,10 @@ error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) no } error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { + if (val.is_null()) { + out.reset(); + return SUCCESS; + } if (!out) { out = std::make_unique(); if (!out) { return MEMALLOC; } @@ -384,6 +398,10 @@ error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) } error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { + if (val.is_null()) { + out.reset(); + return SUCCESS; + } if (!out) { out = std::make_unique(); if (!out) { return MEMALLOC; } @@ -393,6 +411,10 @@ error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out } error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { + if (val.is_null()) { + out.reset(); + return SUCCESS; + } if (!out) { out = std::make_unique(); if (!out) { return MEMALLOC; } @@ -402,6 +424,10 @@ error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) } error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { + if (val.is_null()) { + out.reset(); + return SUCCESS; + } if (!out) { out = std::make_unique(); if (!out) { return MEMALLOC; } @@ -415,6 +441,10 @@ error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { + if (val.is_null()) { + out.reset(); + return SUCCESS; + } if (!out) { out = std::make_shared(); if (!out) { return MEMALLOC; } @@ -424,6 +454,10 @@ error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) no } error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { + if (val.is_null()) { + out.reset(); + return SUCCESS; + } if (!out) { out = std::make_shared(); if (!out) { return MEMALLOC; } @@ -433,6 +467,10 @@ error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) } error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { + if (val.is_null()) { + out.reset(); + return SUCCESS; + } if (!out) { out = std::make_shared(); if (!out) { return MEMALLOC; } @@ -442,6 +480,10 @@ error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out } error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { + if (val.is_null()) { + out.reset(); + return SUCCESS; + } if (!out) { out = std::make_shared(); if (!out) { return MEMALLOC; } @@ -451,6 +493,10 @@ error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) } error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { + if (val.is_null()) { + out.reset(); + return SUCCESS; + } if (!out) { out = std::make_shared(); if (!out) { return MEMALLOC; } @@ -460,6 +506,92 @@ error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { + // Check if the value is null + if (val.is_null()) { + out.reset(); // Set to nullopt + return SUCCESS; + } + + if (!out) { + out.emplace(); + } + std::string_view str; + SIMDJSON_TRY(val.get_string().get(str)); + out.value() = std::string{str}; + return SUCCESS; +} + +error_code tag_invoke(deserialize_tag, auto &val, std::optional &out) noexcept { + // Check if the value is null + if (val.is_null()) { + out.reset(); // Set to nullopt + return SUCCESS; + } + + if (!out) { + out.emplace(); + } + int64_t temp; + SIMDJSON_TRY(val.get_int64().get(temp)); + out.value() = static_cast(temp); + return SUCCESS; +} + +//////////////////////////////////////// +// Explicit smart pointer specializations for string and int types +//////////////////////////////////////// +error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { + // Check if the value is null + if (val.is_null()) { + out.reset(); // Set to nullptr + return SUCCESS; + } + + if (!out) { + out = std::make_unique(); + } + std::string_view str; + SIMDJSON_TRY(val.get_string().get(str)); + *out = std::string{str}; + return SUCCESS; +} + +error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { + // Check if the value is null + if (val.is_null()) { + out.reset(); // Set to nullptr + return SUCCESS; + } + + if (!out) { + out = std::make_shared(); + } + std::string_view str; + SIMDJSON_TRY(val.get_string().get(str)); + *out = std::string{str}; + return SUCCESS; +} + +error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { + // Check if the value is null + if (val.is_null()) { + out.reset(); // Set to nullptr + return SUCCESS; + } + + if (!out) { + out = std::make_unique(); + } + int64_t temp; + SIMDJSON_TRY(val.get_int64().get(temp)); + *out = static_cast(temp); + return SUCCESS; +} + } // namespace simdjson #endif // SIMDJSON_ONDEMAND_DESERIALIZE_H diff --git a/include/simdjson/ondemand.h b/include/simdjson/ondemand.h index 3cfa69ba7..6394fa3fe 100644 --- a/include/simdjson/ondemand.h +++ b/include/simdjson/ondemand.h @@ -12,6 +12,28 @@ namespace simdjson { * @copydoc simdjson::builtin::builder */ namespace builder = builtin::builder; + +#if SIMDJSON_STATIC_REFLECTION + /** + * Create a JSON string from any user-defined type using static reflection. + * Only available when SIMDJSON_STATIC_REFLECTION is enabled. + */ + template + requires(!std::same_as && + !std::same_as && + !std::same_as && + !std::same_as) + inline std::string to_json_string(const T& obj) { + builder::string_builder str_builder; + append(str_builder, obj); + std::string_view view; + if (str_builder.view().get(view) == SUCCESS) { + return std::string(view); + } + return ""; + } +#endif + } // namespace simdjson #endif // SIMDJSON_ONDEMAND_H diff --git a/tests/builder/CMakeLists.txt b/tests/builder/CMakeLists.txt index b8985fa0d..833cc8ee5 100644 --- a/tests/builder/CMakeLists.txt +++ b/tests/builder/CMakeLists.txt @@ -3,6 +3,8 @@ include_directories(..) add_cpp_test(builder_string_builder_tests LABELS ondemand acceptance per_implementation) if(SIMDJSON_STATIC_REFLECTION) add_cpp_test(static_reflection_builder_tests LABELS ondemand acceptance per_implementation) + add_cpp_test(static_reflection_comprehensive_tests LABELS ondemand acceptance per_implementation) + add_cpp_test(static_reflection_edge_cases_tests LABELS ondemand acceptance per_implementation) endif(SIMDJSON_STATIC_REFLECTION) # Copy the simdjson dll into the tests directory if(MSVC AND BUILD_SHARED_LIBS) diff --git a/tests/builder/static_reflection_comprehensive_tests.cpp b/tests/builder/static_reflection_comprehensive_tests.cpp new file mode 100644 index 000000000..2227f503e --- /dev/null +++ b/tests/builder/static_reflection_comprehensive_tests.cpp @@ -0,0 +1,260 @@ +#include "simdjson.h" +#include "test_builder.h" +#include +#include +#include +#include +#include +#include +#include + +using namespace simdjson; + +namespace builder_tests { + + bool test_primitive_types() { + TEST_START(); +#if SIMDJSON_STATIC_REFLECTION + struct PrimitiveTypes { + bool bool_val; + char char_val; + int int_val; + double double_val; + float float_val; + }; + + PrimitiveTypes test{true, 'X', 42, 3.14159, 2.71f}; + + auto result = builder::to_json_string(test); + ASSERT_SUCCESS(result); + + std::string json = result.value(); + ASSERT_TRUE(json.find("\"bool_val\":true") != std::string::npos); + ASSERT_TRUE(json.find("\"char_val\":\"X\"") != std::string::npos); + ASSERT_TRUE(json.find("\"int_val\":42") != std::string::npos); + ASSERT_TRUE(json.find("\"double_val\":3.14159") != std::string::npos); + + // Test round-trip + ondemand::parser parser; + auto doc_result = parser.iterate(pad(json)); + ASSERT_SUCCESS(doc_result); + + auto get_result = doc_result.value().get(); + ASSERT_SUCCESS(get_result); + + PrimitiveTypes deserialized = std::move(get_result.value()); + ASSERT_EQUAL(deserialized.bool_val, test.bool_val); + ASSERT_EQUAL(deserialized.char_val, test.char_val); + ASSERT_EQUAL(deserialized.int_val, test.int_val); + ASSERT_EQUAL(deserialized.double_val, test.double_val); +#endif + TEST_SUCCEED(); + } + + bool test_string_types() { + TEST_START(); +#if SIMDJSON_STATIC_REFLECTION + struct StringTypes { + std::string string_val; + std::string_view string_view_val; + }; + + StringTypes test{"hello world", "test_view"}; + + auto result = builder::to_json_string(test); + ASSERT_SUCCESS(result); + + std::string json = result.value(); + ASSERT_TRUE(json.find("\"string_val\":\"hello world\"") != std::string::npos); + ASSERT_TRUE(json.find("\"string_view_val\":\"test_view\"") != std::string::npos); + + // Test round-trip + ondemand::parser parser; + auto doc_result = parser.iterate(pad(json)); + ASSERT_SUCCESS(doc_result); + + auto get_result = doc_result.value().get(); + ASSERT_SUCCESS(get_result); + + StringTypes deserialized = std::move(get_result.value()); + ASSERT_EQUAL(deserialized.string_val, test.string_val); +#endif + TEST_SUCCEED(); + } + + bool test_optional_types() { + TEST_START(); +#if SIMDJSON_STATIC_REFLECTION + struct OptionalTypes { + std::optional opt_int_with_value; + std::optional opt_string_with_value; + std::optional opt_int_null; + std::optional opt_string_null; + }; + + OptionalTypes test; + test.opt_int_with_value = 42; + test.opt_string_with_value = "optional_test"; + test.opt_int_null = std::nullopt; + test.opt_string_null = std::nullopt; + + auto result = builder::to_json_string(test); + ASSERT_SUCCESS(result); + + std::string json = result.value(); + ASSERT_TRUE(json.find("\"opt_int_with_value\":42") != std::string::npos); + ASSERT_TRUE(json.find("\"opt_string_with_value\":\"optional_test\"") != std::string::npos); + ASSERT_TRUE(json.find("\"opt_int_null\":null") != std::string::npos); + ASSERT_TRUE(json.find("\"opt_string_null\":null") != std::string::npos); + + // Test round-trip + ondemand::parser parser; + auto doc_result = parser.iterate(pad(json)); + ASSERT_SUCCESS(doc_result); + + auto get_result = doc_result.value().get(); + ASSERT_SUCCESS(get_result); + + OptionalTypes deserialized = std::move(get_result.value()); + ASSERT_TRUE(deserialized.opt_int_with_value.has_value()); + ASSERT_EQUAL(*deserialized.opt_int_with_value, 42); + ASSERT_TRUE(deserialized.opt_string_with_value.has_value()); + ASSERT_EQUAL(*deserialized.opt_string_with_value, "optional_test"); + ASSERT_FALSE(deserialized.opt_int_null.has_value()); + ASSERT_FALSE(deserialized.opt_string_null.has_value()); +#endif + TEST_SUCCEED(); + } + + bool test_smart_pointer_types() { + TEST_START(); +#if SIMDJSON_STATIC_REFLECTION + struct SmartPointerTypes { + std::unique_ptr unique_int_with_value; + std::shared_ptr shared_string_with_value; + std::unique_ptr unique_bool_with_value; + std::unique_ptr unique_int_null; + std::shared_ptr shared_string_null; + }; + + SmartPointerTypes test; + test.unique_int_with_value = std::make_unique(123); + test.shared_string_with_value = std::make_shared("shared_test"); + test.unique_bool_with_value = std::make_unique(true); + test.unique_int_null = nullptr; + test.shared_string_null = nullptr; + + auto result = builder::to_json_string(test); + ASSERT_SUCCESS(result); + + std::string json = result.value(); + ASSERT_TRUE(json.find("\"unique_int_with_value\":123") != std::string::npos); + ASSERT_TRUE(json.find("\"shared_string_with_value\":\"shared_test\"") != std::string::npos); + ASSERT_TRUE(json.find("\"unique_bool_with_value\":true") != std::string::npos); + ASSERT_TRUE(json.find("\"unique_int_null\":null") != std::string::npos); + ASSERT_TRUE(json.find("\"shared_string_null\":null") != std::string::npos); + + // Test round-trip + ondemand::parser parser; + auto doc_result = parser.iterate(pad(json)); + ASSERT_SUCCESS(doc_result); + + auto get_result = doc_result.value().get(); + ASSERT_SUCCESS(get_result); + + SmartPointerTypes deserialized = std::move(get_result.value()); + ASSERT_TRUE(deserialized.unique_int_with_value != nullptr); + ASSERT_EQUAL(*deserialized.unique_int_with_value, 123); + ASSERT_TRUE(deserialized.shared_string_with_value != nullptr); + ASSERT_EQUAL(*deserialized.shared_string_with_value, "shared_test"); + ASSERT_TRUE(deserialized.unique_bool_with_value != nullptr); + ASSERT_EQUAL(*deserialized.unique_bool_with_value, true); + ASSERT_TRUE(deserialized.unique_int_null == nullptr); + ASSERT_TRUE(deserialized.shared_string_null == nullptr); +#endif + TEST_SUCCEED(); + } + + bool test_container_types() { + TEST_START(); +#if SIMDJSON_STATIC_REFLECTION + struct ContainerTypes { + std::vector int_vector; + std::set string_set; + std::map string_map; + }; + + ContainerTypes test; + test.int_vector = {1, 2, 3, 4, 5}; + test.string_set = {"apple", "banana", "cherry"}; + test.string_map = {{"key1", 10}, {"key2", 20}, {"key3", 30}}; + + auto result = builder::to_json_string(test); + ASSERT_SUCCESS(result); + + std::string json = result.value(); + ASSERT_TRUE(json.find("\"int_vector\":[1,2,3,4,5]") != std::string::npos); + ASSERT_TRUE(json.find("\"string_set\":[") != std::string::npos); + ASSERT_TRUE(json.find("\"string_map\":{") != std::string::npos); + + // Test round-trip + ondemand::parser parser; + auto doc_result = parser.iterate(pad(json)); + ASSERT_SUCCESS(doc_result); + + auto get_result = doc_result.value().get(); + ASSERT_SUCCESS(get_result); + + ContainerTypes deserialized = std::move(get_result.value()); + ASSERT_EQUAL(deserialized.int_vector.size(), 5); + ASSERT_EQUAL(deserialized.string_set.size(), 3); + ASSERT_EQUAL(deserialized.string_map.size(), 3); + ASSERT_EQUAL(deserialized.int_vector[0], 1); + ASSERT_EQUAL(deserialized.int_vector[4], 5); +#endif + TEST_SUCCEED(); + } + + bool test_individual_serialization() { + TEST_START(); + + // Test individual type serialization (not part of structs) + auto bool_result = builder::to_json_string(true); + ASSERT_SUCCESS(bool_result); + ASSERT_EQUAL(bool_result.value(), "true"); + + auto int_result = builder::to_json_string(42); + ASSERT_SUCCESS(int_result); + ASSERT_EQUAL(int_result.value(), "42"); + + auto string_result = builder::to_json_string(std::string("hello")); + ASSERT_SUCCESS(string_result); + ASSERT_EQUAL(string_result.value(), "\"hello\""); + + auto char_result = builder::to_json_string('X'); + ASSERT_SUCCESS(char_result); + ASSERT_EQUAL(char_result.value(), "\"X\""); + + // Test const char* serialization (serialization only, not round-trip) + const char* cstring = "cstring"; + auto cstring_result = builder::to_json_string(cstring); + ASSERT_SUCCESS(cstring_result); + ASSERT_EQUAL(cstring_result.value(), "\"cstring\""); + + TEST_SUCCEED(); + } + + bool run() { + return test_primitive_types() && + test_string_types() && + test_optional_types() && + test_smart_pointer_types() && + test_container_types() && + test_individual_serialization(); + } + +} // namespace builder_tests + +int main(int argc, char *argv[]) { + return test_main(argc, argv, builder_tests::run); +} \ No newline at end of file diff --git a/tests/builder/static_reflection_edge_cases_tests.cpp b/tests/builder/static_reflection_edge_cases_tests.cpp new file mode 100644 index 000000000..1c4e78566 --- /dev/null +++ b/tests/builder/static_reflection_edge_cases_tests.cpp @@ -0,0 +1,249 @@ +#include "simdjson.h" +#include "test_builder.h" +#include +#include +#include +#include +#include + +using namespace simdjson; + +namespace builder_tests { + + bool test_empty_values() { + TEST_START(); +#if SIMDJSON_STATIC_REFLECTION + struct EmptyValues { + std::string empty_string; + std::vector empty_vector; + std::optional null_optional; + std::unique_ptr null_unique_ptr; + std::shared_ptr null_shared_ptr; + }; + + EmptyValues test; + test.empty_string = ""; + // empty_vector is already empty by default + test.null_optional = std::nullopt; + test.null_unique_ptr = nullptr; + test.null_shared_ptr = nullptr; + + auto result = builder::to_json_string(test); + ASSERT_SUCCESS(result); + + std::string json = result.value(); + ASSERT_TRUE(json.find("\"empty_string\":\"\"") != std::string::npos); + ASSERT_TRUE(json.find("\"empty_vector\":[]") != std::string::npos); + ASSERT_TRUE(json.find("\"null_optional\":null") != std::string::npos); + ASSERT_TRUE(json.find("\"null_unique_ptr\":null") != std::string::npos); + ASSERT_TRUE(json.find("\"null_shared_ptr\":null") != std::string::npos); + + // Test round-trip + ondemand::parser parser; + auto doc_result = parser.iterate(pad(json)); + ASSERT_SUCCESS(doc_result); + + auto get_result = doc_result.value().get(); + ASSERT_SUCCESS(get_result); + + EmptyValues deserialized = std::move(get_result.value()); + ASSERT_EQUAL(deserialized.empty_string, ""); + ASSERT_EQUAL(deserialized.empty_vector.size(), 0); + ASSERT_FALSE(deserialized.null_optional.has_value()); + ASSERT_TRUE(deserialized.null_unique_ptr == nullptr); + ASSERT_TRUE(deserialized.null_shared_ptr == nullptr); +#endif + TEST_SUCCEED(); + } + + bool test_special_characters() { + TEST_START(); +#if SIMDJSON_STATIC_REFLECTION + struct SpecialChars { + std::string quotes; + std::string backslashes; + std::string newlines; + std::string unicode; + char null_char; + }; + + SpecialChars test; + test.quotes = "He said \"Hello\""; + test.backslashes = "Path\\to\\file"; + test.newlines = "Line1\nLine2\tTabbed"; + test.unicode = "Café résumé"; + test.null_char = '\0'; + + auto result = builder::to_json_string(test); + ASSERT_SUCCESS(result); + + std::string json = result.value(); + // Test that quotes are properly escaped + ASSERT_TRUE(json.find("\\\"Hello\\\"") != std::string::npos); + // Test that backslashes are properly escaped + ASSERT_TRUE(json.find("\\\\to\\\\") != std::string::npos); + // Test that newlines are properly escaped + ASSERT_TRUE(json.find("\\n") != std::string::npos); + ASSERT_TRUE(json.find("\\t") != std::string::npos); + + // Test round-trip (excluding null char which has special handling) + struct SpecialCharsNoNull { + std::string quotes; + std::string backslashes; + std::string newlines; + std::string unicode; + }; + + SpecialCharsNoNull test_no_null; + test_no_null.quotes = test.quotes; + test_no_null.backslashes = test.backslashes; + test_no_null.newlines = test.newlines; + test_no_null.unicode = test.unicode; + + auto result_no_null = builder::to_json_string(test_no_null); + ASSERT_SUCCESS(result_no_null); + + ondemand::parser parser; + auto doc_result = parser.iterate(pad(result_no_null.value())); + ASSERT_SUCCESS(doc_result); + + auto get_result = doc_result.value().get(); + ASSERT_SUCCESS(get_result); + + SpecialCharsNoNull deserialized = std::move(get_result.value()); + ASSERT_EQUAL(deserialized.quotes, test.quotes); + ASSERT_EQUAL(deserialized.backslashes, test.backslashes); + ASSERT_EQUAL(deserialized.newlines, test.newlines); + ASSERT_EQUAL(deserialized.unicode, test.unicode); +#endif + TEST_SUCCEED(); + } + + bool test_numeric_limits() { + TEST_START(); +#if SIMDJSON_STATIC_REFLECTION + struct NumericLimits { + int max_int; + int min_int; + double max_double; + double min_double; + bool true_val; + bool false_val; + }; + + NumericLimits test; + test.max_int = std::numeric_limits::max(); + test.min_int = std::numeric_limits::min(); + test.max_double = 1e100; // Large but safe double value + test.min_double = -1e100; // Large negative but safe double value + test.true_val = true; + test.false_val = false; + + auto result = builder::to_json_string(test); + ASSERT_SUCCESS(result); + + std::string json = result.value(); + ASSERT_TRUE(json.find("\"true_val\":true") != std::string::npos); + ASSERT_TRUE(json.find("\"false_val\":false") != std::string::npos); + + // Test round-trip + ondemand::parser parser; + auto doc_result = parser.iterate(pad(json)); + ASSERT_SUCCESS(doc_result); + + auto get_result = doc_result.value().get(); + ASSERT_SUCCESS(get_result); + + NumericLimits deserialized = std::move(get_result.value()); + ASSERT_EQUAL(deserialized.max_int, test.max_int); + ASSERT_EQUAL(deserialized.min_int, test.min_int); + ASSERT_EQUAL(deserialized.true_val, true); + ASSERT_EQUAL(deserialized.false_val, false); +#endif + TEST_SUCCEED(); + } + + bool test_nested_structures() { + TEST_START(); +#if SIMDJSON_STATIC_REFLECTION + struct Inner { + int value; + std::string name; + }; + + struct Outer { + Inner inner_obj; + std::vector inner_vector; + std::optional optional_inner; + std::unique_ptr unique_inner; + }; + + Outer test; + test.inner_obj = {42, "inner"}; + test.inner_vector = {{1, "first"}, {2, "second"}}; + test.optional_inner = Inner{99, "optional"}; + test.unique_inner = std::make_unique(Inner{123, "unique"}); + + auto result = builder::to_json_string(test); + ASSERT_SUCCESS(result); + + std::string json = result.value(); + ASSERT_TRUE(json.find("\"inner_obj\":{") != std::string::npos); + ASSERT_TRUE(json.find("\"inner_vector\":[") != std::string::npos); + ASSERT_TRUE(json.find("\"optional_inner\":{") != std::string::npos); + ASSERT_TRUE(json.find("\"unique_inner\":{") != std::string::npos); + + // Test round-trip + ondemand::parser parser; + auto doc_result = parser.iterate(pad(json)); + ASSERT_SUCCESS(doc_result); + + auto get_result = doc_result.value().get(); + ASSERT_SUCCESS(get_result); + + Outer deserialized = std::move(get_result.value()); + ASSERT_EQUAL(deserialized.inner_obj.value, 42); + ASSERT_EQUAL(deserialized.inner_obj.name, "inner"); + ASSERT_EQUAL(deserialized.inner_vector.size(), 2); + ASSERT_EQUAL(deserialized.inner_vector[0].value, 1); + ASSERT_EQUAL(deserialized.inner_vector[1].name, "second"); + ASSERT_TRUE(deserialized.optional_inner.has_value()); + ASSERT_EQUAL(deserialized.optional_inner->value, 99); + ASSERT_TRUE(deserialized.unique_inner != nullptr); + ASSERT_EQUAL(deserialized.unique_inner->value, 123); +#endif + TEST_SUCCEED(); + } + + bool test_const_char_serialization_only() { + TEST_START(); + + // Test that const char* can be serialized (but not round-tripped) + const char* test_cstring = "const char test"; + auto result = builder::to_json_string(test_cstring); + ASSERT_SUCCESS(result); + ASSERT_EQUAL(result.value(), "\"const char test\""); + + // Test with special characters in const char* + const char* special_cstring = "quotes\"and\\backslashes"; + auto special_result = builder::to_json_string(special_cstring); + ASSERT_SUCCESS(special_result); + ASSERT_TRUE(special_result.value().find("\\\"") != std::string::npos); + ASSERT_TRUE(special_result.value().find("\\\\") != std::string::npos); + + TEST_SUCCEED(); + } + + bool run() { + return test_empty_values() && + test_special_characters() && + test_numeric_limits() && + test_nested_structures() && + test_const_char_serialization_only(); + } + +} // namespace builder_tests + +int main(int argc, char *argv[]) { + return test_main(argc, argv, builder_tests::run); +} \ No newline at end of file