From 6029af75b34cc1719358733f675416d8c1cd3b83 Mon Sep 17 00:00:00 2001 From: evbse <133204505+evbse@users.noreply.github.com> Date: Tue, 15 Jul 2025 21:39:09 +0300 Subject: [PATCH 1/6] Improve fallback implementation (#2393) --- src/fallback.cpp | 120 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 88 insertions(+), 32 deletions(-) diff --git a/src/fallback.cpp b/src/fallback.cpp index 46881a228..0f8865de0 100644 --- a/src/fallback.cpp +++ b/src/fallback.cpp @@ -121,10 +121,78 @@ simdjson_inline void validate_utf8_character() { idx += 4; } +static const uint8_t CHAR_TYPE_SPACE = 1 << 0; +static const uint8_t CHAR_TYPE_OPERATOR = 1 << 1; +static const uint8_t CHAR_TYPE_ESC_ASCII = 1 << 2; +static const uint8_t CHAR_TYPE_NON_ASCII = 1 << 3; + +const uint8_t char_table[256] = { + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x05, 0x05, 0x04, 0x04, 0x05, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x04, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08 +}; + +simdjson_inline bool char_is_type(uint8_t c, uint8_t type) { + return (char_table[c] & type); +} + +simdjson_inline bool char_is_space(uint8_t c) { + return char_is_type(c, CHAR_TYPE_SPACE); +} + +simdjson_inline bool char_is_operator(uint8_t c) { + return char_is_type(c, CHAR_TYPE_OPERATOR); +} + +simdjson_inline bool char_is_space_or_operator(uint8_t c) { + return char_is_type(c, CHAR_TYPE_SPACE | CHAR_TYPE_OPERATOR); +} + +simdjson_inline bool char_is_ascii_stop(uint8_t c) { + return char_is_type(c, CHAR_TYPE_ESC_ASCII | CHAR_TYPE_NON_ASCII); +} + // Returns true if the string is unclosed. simdjson_inline bool validate_string() { idx++; // skip first quote - while (idx < len && buf[idx] != '"') { + while (idx < len) { + do { + if (char_is_ascii_stop(buf[idx])) { break; } + idx++; + } while (idx < len); + if (idx >= len) { return true; } + if (buf[idx] == '"') { + return false; + } if (buf[idx] == '\\') { idx += 2; } else if (simdjson_unlikely(buf[idx] & 0x80)) { @@ -138,43 +206,31 @@ simdjson_inline bool validate_string() { return false; } -simdjson_inline bool is_whitespace_or_operator(uint8_t c) { - switch (c) { - case '{': case '}': case '[': case ']': case ',': case ':': - case ' ': case '\r': case '\n': case '\t': - return true; - default: - return false; - } -} - // // Parse the entire input in STEP_SIZE-byte chunks. // simdjson_inline error_code scan() { bool unclosed_string = false; for (;idx= len) { break; } + // String + if (buf[idx] == '"') { + add_structural(); + unclosed_string |= validate_string(); + // Operator + } else if (char_is_operator(buf[idx])) { + add_structural(); + // Primitive or invalid character (invalid characters will be checked in stage 2) + } else { + // Anything else, add the structural and go until we find the next one + add_structural(); + while (idx+1 Date: Wed, 23 Jul 2025 10:20:06 +0300 Subject: [PATCH 2/6] Add version to amalgamated files (#2400) --- singleheader/amalgamate.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/singleheader/amalgamate.py b/singleheader/amalgamate.py index d6590f28a..6b556a783 100755 --- a/singleheader/amalgamate.py +++ b/singleheader/amalgamate.py @@ -296,10 +296,10 @@ class SimdjsonRepository: class Amalgamator: @classmethod - def amalgamate(cls, output_path: str, filename: str, roots: List[RelativeRoot], timestamp: str): + def amalgamate(cls, output_path: str, filename: str, roots: List[RelativeRoot], timestamp: str, version: str): print(f"Creating {output_path}") fid = open(output_path, 'w') - print(f"/* auto-generated on {timestamp}. Do not edit! */", file=fid) + print(f"/* auto-generated on {timestamp}. version {version} Do not edit! */", file=fid) amalgamator = cls(fid, SimdjsonRepository(PROJECTPATH, roots)) file = amalgamator.repository[filename] assert file, f"{filename} not found in {[os.path.join(PROJECTPATH, root) for root in roots]}!" @@ -480,8 +480,13 @@ AMAL_C = os.path.join(AMALGAMATE_OUTPUT_PATH, "simdjson.cpp") DEMOCPP = os.path.join(AMALGAMATE_OUTPUT_PATH, "amalgamate_demo.cpp") README = os.path.join(AMALGAMATE_OUTPUT_PATH, "README.md") -Amalgamator.amalgamate(AMAL_H, "simdjson.h", ['include'], timestamp).validate_all_files_used('include') -Amalgamator.amalgamate(AMAL_C, "simdjson.cpp", ['src', 'include'], timestamp).validate_all_files_used('src') +def read_version(): + with open(os.path.join(PROJECTPATH, 'include/simdjson/simdjson_version.h')) as f: + return re.search(r'\d+\.\d+\.\d+', f.read()).group(0) + +version = read_version() +Amalgamator.amalgamate(AMAL_H, "simdjson.h", ['include'], timestamp, version).validate_all_files_used('include') +Amalgamator.amalgamate(AMAL_C, "simdjson.cpp", ['src', 'include'], timestamp, version).validate_all_files_used('src') # copy the README and DEMOCPP if SCRIPTPATH != AMALGAMATE_OUTPUT_PATH: From 90e4a66c93f7d0d685b2e13c42c49f4992f6f827 Mon Sep 17 00:00:00 2001 From: Francisco Geiman Thiesen Date: Wed, 23 Jul 2025 00:24:05 -0700 Subject: [PATCH 3/6] Bringing a bit more use-cases for reflection based serializations (optional). Also adding string-based enum handling as requested on X. (#2395) * Adding type validation, enhancing optional type support and adding test a few more tests. * Adding support for string-based enum serlalization and deserialization. * Removing unintentional endline. * Removing trailing whitespace. * Adding simpler api as suggested by moisrex. * Removing explicit optiona and optional references and using concepts instead! Credit goes to Lemire for pointing this out and suggesting a concepts based approach here. * Removing tests that are not relevant for this branch. * Removing api related changes. That will be done by moisrex. * Removing unnecessary new endlines. * Removing tests related to api changes and cleaning-up irrelevant tests. * removing broken reference * Removing trailing whitespace --- include/simdjson/concepts.h | 1 - .../simdjson/generic/ondemand/deserialize.h | 2 +- .../simdjson/generic/ondemand/json_builder.h | 130 ++++++++- .../generic/ondemand/serialization-inl.h | 3 + .../simdjson/generic/ondemand/serialization.h | 14 + .../generic/ondemand/std_deserialize.h | 145 +++++++++- include/simdjson/generic/ondemand/value.h | 13 + include/simdjson/ondemand.h | 22 ++ tests/builder/CMakeLists.txt | 3 + .../static_reflection_builder_tests.cpp | 18 +- .../static_reflection_comprehensive_tests.cpp | 231 ++++++++++++++++ .../static_reflection_edge_cases_tests.cpp | 230 ++++++++++++++++ .../builder/static_reflection_enum_tests.cpp | 258 ++++++++++++++++++ 13 files changed, 1045 insertions(+), 25 deletions(-) create mode 100644 tests/builder/static_reflection_comprehensive_tests.cpp create mode 100644 tests/builder/static_reflection_edge_cases_tests.cpp create mode 100644 tests/builder/static_reflection_enum_tests.cpp 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/deserialize.h b/include/simdjson/generic/ondemand/deserialize.h index 8bad5699b..1e310fba6 100644 --- a/include/simdjson/generic/ondemand/deserialize.h +++ b/include/simdjson/generic/ondemand/deserialize.h @@ -77,7 +77,7 @@ template concept custom_deserializable = tag_invocable; template -concept deserializable = custom_deserializable || is_builtin_deserializable_v; +concept deserializable = custom_deserializable || is_builtin_deserializable_v || concepts::optional_type; template concept nothrow_custom_deserializable = nothrow_tag_invocable; diff --git a/include/simdjson/generic/ondemand/json_builder.h b/include/simdjson/generic/ondemand/json_builder.h index 14fb24699..e6a4daf33 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,127 @@ 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 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 && + !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..37a501789 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 { diff --git a/include/simdjson/generic/ondemand/serialization.h b/include/simdjson/generic/ondemand/serialization.h index 048c73cda..3d2c8ed85 100644 --- a/include/simdjson/generic/ondemand/serialization.h +++ b/include/simdjson/generic/ondemand/serialization.h @@ -35,6 +35,20 @@ inline 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..7c8f518af 100644 --- a/include/simdjson/generic/ondemand/std_deserialize.h +++ b/include/simdjson/generic/ondemand/std_deserialize.h @@ -248,17 +248,16 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept(nothrow_deser /** * This CPO (Customization Point Object) will help deserialize into optional types. */ -template +template requires(!require_custom_serialization) -error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept(nothrow_deserializable::value_type, ValT>) { +error_code tag_invoke(deserialize_tag, auto &val, T &out) noexcept(nothrow_deserializable::value_type, decltype(val)>) { using value_type = typename std::remove_cvref_t::value_type; - static_assert( - deserializable, - "The specified type inside the unique_ptr must itself be deserializable"); - static_assert( - std::is_default_constructible_v, - "The specified type inside the unique_ptr 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 +296,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); } @@ -317,17 +316,42 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept { [:expand(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked())):] >> [&]() { if constexpr (!std::meta::is_const(mem) && std::meta::is_public(mem)) { constexpr std::string_view key = std::define_static_string(std::meta::identifier_of(mem)); - static_assert( - deserializable, - "The specified type inside the class must itself be deserializable"); + // Note: removed static assert as optional types are now handled generically // as long we are succesful or the field is not found, we continue if(e == simdjson::SUCCESS || e == simdjson::NO_SUCH_FIELD) { - obj[key].get(out.[:mem:]); + e = obj[key].get(out.[:mem:]); } } }; 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 { @@ -366,6 +390,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 +403,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 +416,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 +429,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 +442,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 +459,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 +472,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 +485,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 +498,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 +511,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 +524,61 @@ 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_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/generic/ondemand/value.h b/include/simdjson/generic/ondemand/value.h index dc175bab4..6d3c8d279 100644 --- a/include/simdjson/generic/ondemand/value.h +++ b/include/simdjson/generic/ondemand/value.h @@ -75,6 +75,19 @@ public: #if SIMDJSON_SUPPORTS_DESERIALIZATION if constexpr (custom_deserializable) { return deserialize(*this, out); + } else if constexpr (concepts::optional_type) { + using value_type = typename std::remove_cvref_t::value_type; + + // Check if the value is null + if (is_null()) { + out.reset(); // Set to nullopt + return SUCCESS; + } + + if (!out) { + out.emplace(); + } + return get(out.value()); } else { static_assert(!sizeof(T), "The get method with type T is not implemented by the simdjson library. " "And you do not seem to have added support for it. Indeed, we have that " 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..2bf59484d 100644 --- a/tests/builder/CMakeLists.txt +++ b/tests/builder/CMakeLists.txt @@ -3,6 +3,9 @@ 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) + 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_builder_tests.cpp b/tests/builder/static_reflection_builder_tests.cpp index d48086fd2..3b0981db2 100644 --- a/tests/builder/static_reflection_builder_tests.cpp +++ b/tests/builder/static_reflection_builder_tests.cpp @@ -48,14 +48,11 @@ namespace builder_tests { bool car_test() { TEST_START(); - simdjson::builder::string_builder sb; +#if SIMDJSON_STATIC_REFLECTION Car c = {"Toyota", "Corolla", 2017, {30.0,30.2,30.513,30.79}}; - append(sb, c); - std::string_view p; - auto result = sb.view().get(p); + auto result = builder::to_json_string(c); ASSERT_SUCCESS(result); - ASSERT_EQUAL(p, "{\"make\":\"Toyota\",\"model\":\"Corolla\",\"year\":2017,\"tire_pressure\":[30.0,30.2,30.513,30.79]}"); - std::string pstr(p.begin(), p.end()); + std::string pstr = result.value(); ASSERT_EQUAL(pstr, "{\"make\":\"Toyota\",\"model\":\"Corolla\",\"year\":2017,\"tire_pressure\":[30.0,30.2,30.513,30.79]}"); simdjson::ondemand::parser parser; simdjson::ondemand::document doc; @@ -70,12 +67,14 @@ namespace builder_tests { ASSERT_EQUAL(c2.tire_pressure[1], 30.2); ASSERT_EQUAL(c2.tire_pressure[2], 30.513); ASSERT_EQUAL(c2.tire_pressure[3], 30.79); +#endif TEST_SUCCEED(); } bool serialize_deserialize_kid() { TEST_START(); +#if SIMDJSON_STATIC_REFLECTION simdjson::padded_string json_str = R"({"age": 12, "name": "John", "toys": ["car", "ball"]})"_padded; simdjson::ondemand::parser parser; @@ -90,7 +89,7 @@ bool serialize_deserialize_kid() { ASSERT_EQUAL(k.toys[1], "ball"); // Now, go the other direction: std::string json; - ASSERT_SUCCESS(simdjson::builder::to_json_string(k).get(json)); + ASSERT_SUCCESS(builder::to_json_string(k).get(json)); std::cout << json << std::endl; // Now we parse it back: simdjson::ondemand::parser parser2; @@ -103,11 +102,13 @@ bool serialize_deserialize_kid() { ASSERT_EQUAL(k2.toys.size(), 2); ASSERT_EQUAL(k2.toys[0], "car"); ASSERT_EQUAL(k2.toys[1], "ball"); +#endif TEST_SUCCEED(); } bool serialize_deserialize_x_y_z() { TEST_START(); +#if SIMDJSON_STATIC_REFLECTION X s1 = {.a = '1', .b = 10, .c = 0, @@ -119,7 +120,7 @@ bool serialize_deserialize_x_y_z() { .i = {1, 2, 3}, .z = {.x = 1000}}}; std::string pstr; - ASSERT_SUCCESS(simdjson::builder::to_json_string(s1).get(pstr)); + ASSERT_SUCCESS(builder::to_json_string(s1).get(pstr)); ASSERT_EQUAL( pstr, R"({"a":"1","b":10,"c":0,"d":"test string\n\r\"","e":[1,2,3],"f":["ab","cd","fg"],"y":{"g":100,"h":"test string\n\r\"","i":[1,2,3],"z":{"x":1000}}})"); @@ -129,6 +130,7 @@ bool serialize_deserialize_x_y_z() { X s2; ASSERT_SUCCESS(doc.get().get(s2)); ASSERT_TRUE(s1 == s2); +#endif TEST_SUCCEED(); } diff --git a/tests/builder/static_reflection_comprehensive_tests.cpp b/tests/builder/static_reflection_comprehensive_tests.cpp new file mode 100644 index 000000000..426dfc5e1 --- /dev/null +++ b/tests/builder/static_reflection_comprehensive_tests.cpp @@ -0,0 +1,231 @@ +#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 run() { + return test_primitive_types() && + test_string_types() && + test_optional_types() && + test_smart_pointer_types() && + test_container_types(); + } + +} // 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..3037414a4 --- /dev/null +++ b/tests/builder/static_reflection_edge_cases_tests.cpp @@ -0,0 +1,230 @@ +#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 run() { + return test_empty_values() && + test_special_characters() && + test_numeric_limits() && + test_nested_structures(); + } + +} // 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_enum_tests.cpp b/tests/builder/static_reflection_enum_tests.cpp new file mode 100644 index 000000000..22ceb37c7 --- /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 From aa6817d5aa8e1383eede14bb560ccdbe44cf095c Mon Sep 17 00:00:00 2001 From: Brad Bramble Date: Mon, 28 Jul 2025 11:44:32 -0400 Subject: [PATCH 4/6] Fix linker errors for downstream users caused by non-inline symbols in header (#2403) --- .../simdjson/generic/ondemand/json_string_builder-inl.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/simdjson/generic/ondemand/json_string_builder-inl.h b/include/simdjson/generic/ondemand/json_string_builder-inl.h index 42e42f7da..39e5d5cdc 100644 --- a/include/simdjson/generic/ondemand/json_string_builder-inl.h +++ b/include/simdjson/generic/ondemand/json_string_builder-inl.h @@ -303,9 +303,9 @@ simdjson_inline void string_builder::clear() noexcept { namespace internal { // We could specialize further for 32-bit integers. -int int_log2(uint32_t x) { return (63 - leading_zeroes(x | 1)); } +inline int int_log2(uint32_t x) { return (63 - leading_zeroes(x | 1)); } -int fast_digit_count(uint32_t x) { +inline int fast_digit_count(uint32_t x) { static uint64_t table[] = { 4294967296, 8589934582, 8589934582, 8589934582, 12884901788, 12884901788, 12884901788, 17179868184, 17179868184, 17179868184, @@ -317,9 +317,9 @@ int fast_digit_count(uint32_t x) { return uint32_t((x + table[int_log2(x)]) >> 32); } -int int_log2(uint64_t x) { return 63 - leading_zeroes(x | 1); } +inline int int_log2(uint64_t x) { return 63 - leading_zeroes(x | 1); } -int fast_digit_count(uint64_t x) { +inline int fast_digit_count(uint64_t x) { static uint64_t table[] = {9, 99, 999, From b9e308a727e52976a2cd504c8bd4d2af0e3c63c8 Mon Sep 17 00:00:00 2001 From: Daniel Lemire Date: Mon, 28 Jul 2025 11:47:09 -0400 Subject: [PATCH 5/6] marking them as 'really inline' --- .../generic/ondemand/json_string_builder-inl.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/simdjson/generic/ondemand/json_string_builder-inl.h b/include/simdjson/generic/ondemand/json_string_builder-inl.h index 39e5d5cdc..30d078f86 100644 --- a/include/simdjson/generic/ondemand/json_string_builder-inl.h +++ b/include/simdjson/generic/ondemand/json_string_builder-inl.h @@ -303,9 +303,9 @@ simdjson_inline void string_builder::clear() noexcept { namespace internal { // We could specialize further for 32-bit integers. -inline int int_log2(uint32_t x) { return (63 - leading_zeroes(x | 1)); } +simdjson_really_inline int int_log2(uint32_t x) { return (63 - leading_zeroes(x | 1)); } -inline int fast_digit_count(uint32_t x) { +simdjson_really_inline int fast_digit_count(uint32_t x) { static uint64_t table[] = { 4294967296, 8589934582, 8589934582, 8589934582, 12884901788, 12884901788, 12884901788, 17179868184, 17179868184, 17179868184, @@ -317,9 +317,9 @@ inline int fast_digit_count(uint32_t x) { return uint32_t((x + table[int_log2(x)]) >> 32); } -inline int int_log2(uint64_t x) { return 63 - leading_zeroes(x | 1); } +simdjson_really_inline int int_log2(uint64_t x) { return 63 - leading_zeroes(x | 1); } -inline int fast_digit_count(uint64_t x) { +simdjson_really_inline int fast_digit_count(uint64_t x) { static uint64_t table[] = {9, 99, 999, @@ -346,7 +346,7 @@ inline int fast_digit_count(uint64_t x) { template ::value>::type> -simdjson_inline size_t digit_count(number_type v) noexcept { +simdjson_really_inline size_t digit_count(number_type v) noexcept { static_assert(sizeof(number_type) == 8 || sizeof(number_type) == 4 || sizeof(number_type) == 2 || sizeof(number_type) == 1, "We only support 8-bit, 16-bit, 32-bit and 64-bit numbers"); From d365cfb4a15027fa378453e139444d66b0f7ad44 Mon Sep 17 00:00:00 2001 From: Borislav Stanimirov Date: Thu, 31 Jul 2025 17:18:28 +0300 Subject: [PATCH 6/6] remove cmake_policy (#2404) * properly gitignore Visual Studio artifacts * remove cmake_policy --- .gitignore | 4 ++-- CMakeLists.txt | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 0608e31b2..3709099fb 100644 --- a/.gitignore +++ b/.gitignore @@ -38,7 +38,7 @@ cmake-build-release/ .history/ # Visual Studio artifacts -/VS/ +/.vs/ # C/C++ build outputs .build/ @@ -106,4 +106,4 @@ objs !.vscode/extensions.json # clangd -.cache \ No newline at end of file +.cache diff --git a/CMakeLists.txt b/CMakeLists.txt index af1e25d97..f26dd4b9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,4 @@ cmake_minimum_required(VERSION 3.14) -cmake_policy(VERSION 3.5) # For doctest - project( simdjson