From 20cea8b47708deead2088cd9e40f670e8291600b Mon Sep 17 00:00:00 2001 From: Francisco Geiman Thiesen Date: Fri, 18 Jul 2025 05:01:40 +0000 Subject: [PATCH] Adding support for string-based enum serlalization and deserialization. --- .../simdjson/generic/ondemand/json_builder.h | 26 ++ .../generic/ondemand/std_deserialize.h | 29 +- tests/builder/CMakeLists.txt | 1 + .../builder/static_reflection_enum_tests.cpp | 258 ++++++++++++++++++ 4 files changed, 313 insertions(+), 1 deletion(-) create mode 100644 tests/builder/static_reflection_enum_tests.cpp diff --git a/include/simdjson/generic/ondemand/json_builder.h b/include/simdjson/generic/ondemand/json_builder.h index e9646f3e5..e6a4daf33 100644 --- a/include/simdjson/generic/ondemand/json_builder.h +++ b/include/simdjson/generic/ondemand/json_builder.h @@ -138,6 +138,32 @@ constexpr void atom(string_builder &b, const T &ptr) { } } +// Support for enums - serialize as string representation using expand approach from P2996R12 +template + requires(std::is_enum_v) +void atom(string_builder &b, const T &e) { +#if SIMDJSON_STATIC_REFLECTION + std::string_view result = ""; + [:expand(std::meta::enumerators_of(^^T)):] >> [&]{ + if (e == [:enum_val:]) { + result = std::meta::identifier_of(enum_val); + } + }; + + if (result != "") { + b.append_raw("\""); + b.append_raw(result); + b.append_raw("\""); + } else { + // Fallback to integer if enum value not found + atom(b, static_cast>(e)); + } +#else + // Fallback: serialize as integer if reflection not available + atom(b, static_cast>(e)); +#endif +} + // Support for appendable containers that don't have operator[] (sets, etc.) template requires(!container_but_not_string && !concepts::string_view_keyed_map && diff --git a/include/simdjson/generic/ondemand/std_deserialize.h b/include/simdjson/generic/ondemand/std_deserialize.h index 322b2b4e2..25ef2f5df 100644 --- a/include/simdjson/generic/ondemand/std_deserialize.h +++ b/include/simdjson/generic/ondemand/std_deserialize.h @@ -334,6 +334,33 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept { }; return e; } + +// Support for enum deserialization - deserialize from string representation using expand approach from P2996R12 +template + requires(std::is_enum_v) +error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept { +#if SIMDJSON_STATIC_REFLECTION + std::string_view str; + SIMDJSON_TRY(val.get_string().get(str)); + + bool found = false; + [:expand(std::meta::enumerators_of(^^T)):] >> [&]{ + if (!found && str == std::meta::identifier_of(enum_val)) { + out = [:enum_val:]; + found = true; + } + }; + + return found ? SUCCESS : INCORRECT_TYPE; +#else + // Fallback: deserialize as integer if reflection not available + std::underlying_type_t int_val; + SIMDJSON_TRY(val.get(int_val)); + out = static_cast(int_val); + return SUCCESS; +#endif +} + template requires(user_defined_type>) error_code tag_invoke(deserialize_tag, simdjson_value &val, std::unique_ptr &out) noexcept { @@ -507,7 +534,7 @@ error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { // Check if the value is null diff --git a/tests/builder/CMakeLists.txt b/tests/builder/CMakeLists.txt index 833cc8ee5..2bf59484d 100644 --- a/tests/builder/CMakeLists.txt +++ b/tests/builder/CMakeLists.txt @@ -5,6 +5,7 @@ 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) + add_cpp_test(static_reflection_enum_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_enum_tests.cpp b/tests/builder/static_reflection_enum_tests.cpp new file mode 100644 index 000000000..095dcc99a --- /dev/null +++ b/tests/builder/static_reflection_enum_tests.cpp @@ -0,0 +1,258 @@ +#include "simdjson.h" +#include "test_builder.h" +#include + +using namespace simdjson; + +namespace builder_tests { + + bool test_enum_serialization() { + TEST_START(); +#if SIMDJSON_STATIC_REFLECTION + enum class Color { + Red, + Green, + Blue + }; + + struct EnumStruct { + Color color; + int value; + }; + + EnumStruct test{Color::Red, 42}; + + auto result = builder::to_json_string(test); + ASSERT_SUCCESS(result); + + std::string json = result.value(); + // Enum should be serialized as string (Red) + ASSERT_TRUE(json.find("\"color\":\"Red\"") != std::string::npos); + ASSERT_TRUE(json.find("\"value\":42") != std::string::npos); + + // Test different enum values + test.color = Color::Green; + auto result2 = builder::to_json_string(test); + ASSERT_SUCCESS(result2); + std::string json2 = result2.value(); + ASSERT_TRUE(json2.find("\"color\":\"Green\"") != std::string::npos); + + test.color = Color::Blue; + auto result3 = builder::to_json_string(test); + ASSERT_SUCCESS(result3); + std::string json3 = result3.value(); + ASSERT_TRUE(json3.find("\"color\":\"Blue\"") != std::string::npos); +#endif + TEST_SUCCEED(); + } + + bool test_enum_deserialization() { + TEST_START(); +#if SIMDJSON_STATIC_REFLECTION + enum class Status { + Active, + Inactive, + Pending + }; + + struct StatusStruct { + Status status; + std::string name; + }; + + // Test deserialization of different enum values with string representation + std::string json1 = "{\"status\":\"Active\",\"name\":\"test1\"}"; + ondemand::parser parser1; + auto doc_result1 = parser1.iterate(pad(json1)); + ASSERT_SUCCESS(doc_result1); + + auto get_result1 = doc_result1.value().get(); + ASSERT_SUCCESS(get_result1); + + StatusStruct deserialized1 = std::move(get_result1.value()); + ASSERT_TRUE(deserialized1.status == Status::Active); + ASSERT_EQUAL(deserialized1.name, "test1"); + + // Test Status::Inactive + std::string json2 = "{\"status\":\"Inactive\",\"name\":\"test2\"}"; + ondemand::parser parser2; + auto doc_result2 = parser2.iterate(pad(json2)); + ASSERT_SUCCESS(doc_result2); + + auto get_result2 = doc_result2.value().get(); + ASSERT_SUCCESS(get_result2); + + StatusStruct deserialized2 = std::move(get_result2.value()); + ASSERT_TRUE(deserialized2.status == Status::Inactive); + ASSERT_EQUAL(deserialized2.name, "test2"); + + // Test Status::Pending + std::string json3 = "{\"status\":\"Pending\",\"name\":\"test3\"}"; + ondemand::parser parser3; + auto doc_result3 = parser3.iterate(pad(json3)); + ASSERT_SUCCESS(doc_result3); + + auto get_result3 = doc_result3.value().get(); + ASSERT_SUCCESS(get_result3); + + StatusStruct deserialized3 = std::move(get_result3.value()); + ASSERT_TRUE(deserialized3.status == Status::Pending); + ASSERT_EQUAL(deserialized3.name, "test3"); +#endif + TEST_SUCCEED(); + } + + bool test_enum_round_trip() { + TEST_START(); +#if SIMDJSON_STATIC_REFLECTION + enum class Priority { + Low, + Medium, + High, + Critical + }; + + struct Task { + Priority priority; + std::string description; + int id; + }; + + Task original{Priority::High, "Important task", 123}; + + // Serialize + auto serialize_result = builder::to_json_string(original); + ASSERT_SUCCESS(serialize_result); + + std::string json = serialize_result.value(); + ASSERT_TRUE(json.find("\"priority\":\"High\"") != std::string::npos); // High as string + ASSERT_TRUE(json.find("\"description\":\"Important task\"") != std::string::npos); + ASSERT_TRUE(json.find("\"id\":123") != std::string::npos); + + // Deserialize + 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); + + Task deserialized = std::move(get_result.value()); + ASSERT_TRUE(deserialized.priority == Priority::High); + ASSERT_EQUAL(deserialized.description, "Important task"); + ASSERT_EQUAL(deserialized.id, 123); +#endif + TEST_SUCCEED(); + } + + bool test_enum_with_underlying_type() { + TEST_START(); +#if SIMDJSON_STATIC_REFLECTION + enum class ErrorCode : int { + Success = 0, + NotFound = 404, + ServerError = 500 + }; + + struct Response { + ErrorCode error; + std::string message; + }; + + Response test{ErrorCode::NotFound, "Resource not found"}; + + auto result = builder::to_json_string(test); + ASSERT_SUCCESS(result); + + std::string json = result.value(); + ASSERT_TRUE(json.find("\"error\":\"NotFound\"") != std::string::npos); + ASSERT_TRUE(json.find("\"message\":\"Resource not found\"") != 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); + + Response deserialized = std::move(get_result.value()); + ASSERT_TRUE(deserialized.error == ErrorCode::NotFound); + ASSERT_EQUAL(deserialized.message, "Resource not found"); +#endif + TEST_SUCCEED(); + } + + bool test_multiple_enums() { + TEST_START(); +#if SIMDJSON_STATIC_REFLECTION + enum class Day { + Monday, + Tuesday, + Wednesday, + Thursday, + Friday, + Saturday, + Sunday + }; + + enum class Month { + January, + February, + March, + April, + May, + June, + July, + August, + September, + October, + November, + December + }; + + struct Date { + Day day; + Month month; + int year; + }; + + Date test{Day::Friday, Month::July, 2024}; + + auto result = builder::to_json_string(test); + ASSERT_SUCCESS(result); + + std::string json = result.value(); + ASSERT_TRUE(json.find("\"day\":\"Friday\"") != std::string::npos); // Friday as string + ASSERT_TRUE(json.find("\"month\":\"July\"") != std::string::npos); // July as string + ASSERT_TRUE(json.find("\"year\":2024") != 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); + + Date deserialized = std::move(get_result.value()); + ASSERT_TRUE(deserialized.day == Day::Friday); + ASSERT_TRUE(deserialized.month == Month::July); + ASSERT_EQUAL(deserialized.year, 2024); +#endif + TEST_SUCCEED(); + } + + bool run() { + return test_enum_serialization() && + test_enum_deserialization() && + test_enum_round_trip() && + test_enum_with_underlying_type() && + test_multiple_enums(); + } + +} // namespace builder_tests + +int main(int argc, char *argv[]) { + return test_main(argc, argv, builder_tests::run); +} \ No newline at end of file