mirror of
https://github.com/simdjson/simdjson
synced 2026-06-08 17:27:07 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 01b4710ff8 | |||
| 049f70aef3 | |||
| d874ab7239 | |||
| a27a17484d | |||
| 20cea8b477 | |||
| 0517fb3aaa |
@@ -115,7 +115,6 @@ concept optional_type = requires(std::remove_cvref_t<T> obj) {
|
|||||||
{ obj.value() } -> std::same_as<typename std::remove_cvref_t<T>::value_type&>;
|
{ obj.value() } -> std::same_as<typename std::remove_cvref_t<T>::value_type&>;
|
||||||
requires requires(typename std::remove_cvref_t<T>::value_type &&val) {
|
requires requires(typename std::remove_cvref_t<T>::value_type &&val) {
|
||||||
obj.emplace(std::move(val));
|
obj.emplace(std::move(val));
|
||||||
obj = std::move(val);
|
|
||||||
{
|
{
|
||||||
obj.value_or(val)
|
obj.value_or(val)
|
||||||
} -> std::convertible_to<typename std::remove_cvref_t<T>::value_type>;
|
} -> std::convertible_to<typename std::remove_cvref_t<T>::value_type>;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include <charconv>
|
#include <charconv>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <experimental/meta>
|
#include <experimental/meta>
|
||||||
|
#include <memory>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -94,8 +95,13 @@ consteval std::string consteval_to_quoted_escaped(std::string_view input);
|
|||||||
template <class T>
|
template <class T>
|
||||||
requires(std::is_class_v<T> && !container_but_not_string<T> &&
|
requires(std::is_class_v<T> && !container_but_not_string<T> &&
|
||||||
!concepts::string_view_keyed_map<T> &&
|
!concepts::string_view_keyed_map<T> &&
|
||||||
|
!concepts::optional_type<T> &&
|
||||||
|
!concepts::smart_pointer<T> &&
|
||||||
|
!concepts::appendable_containers<T> &&
|
||||||
!std::is_same_v<T, std::string> &&
|
!std::is_same_v<T, std::string> &&
|
||||||
!std::is_same_v<T, std::string_view>)
|
!std::is_same_v<T, std::string_view> &&
|
||||||
|
!std::is_same_v<T, const char*> &&
|
||||||
|
!std::is_same_v<T, char>)
|
||||||
constexpr void atom(string_builder &b, const T &t) {
|
constexpr void atom(string_builder &b, const T &t) {
|
||||||
int i = 0;
|
int i = 0;
|
||||||
b.append('{');
|
b.append('{');
|
||||||
@@ -111,8 +117,127 @@ constexpr void atom(string_builder &b, const T &t) {
|
|||||||
b.append('}');
|
b.append('}');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Support for optional types (std::optional, etc.)
|
||||||
|
template <concepts::optional_type T>
|
||||||
|
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 <concepts::smart_pointer T>
|
||||||
|
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 <typename T>
|
||||||
|
requires(std::is_enum_v<T>)
|
||||||
|
void atom(string_builder &b, const T &e) {
|
||||||
|
#if SIMDJSON_STATIC_REFLECTION
|
||||||
|
std::string_view result = "<unnamed>";
|
||||||
|
[:expand(std::meta::enumerators_of(^^T)):] >> [&]<auto enum_val>{
|
||||||
|
if (e == [:enum_val:]) {
|
||||||
|
result = std::meta::identifier_of(enum_val);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (result != "<unnamed>") {
|
||||||
|
b.append_raw("\"");
|
||||||
|
b.append_raw(result);
|
||||||
|
b.append_raw("\"");
|
||||||
|
} else {
|
||||||
|
// Fallback to integer if enum value not found
|
||||||
|
atom(b, static_cast<std::underlying_type_t<T>>(e));
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// Fallback: serialize as integer if reflection not available
|
||||||
|
atom(b, static_cast<std::underlying_type_t<T>>(e));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Support for appendable containers that don't have operator[] (sets, etc.)
|
||||||
|
template <concepts::appendable_containers T>
|
||||||
|
requires(!container_but_not_string<T> && !concepts::string_view_keyed_map<T> &&
|
||||||
|
!concepts::optional_type<T> && !concepts::smart_pointer<T> &&
|
||||||
|
!std::is_same_v<T, std::string> &&
|
||||||
|
!std::is_same_v<T, std::string_view> && !std::is_same_v<T, const char*>)
|
||||||
|
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 <class T>
|
||||||
|
requires(std::is_arithmetic_v<T> && !std::is_same_v<T, char>)
|
||||||
|
void append(string_builder &b, const T &t) {
|
||||||
|
atom(b, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
requires(std::is_same_v<T, std::string> ||
|
||||||
|
std::is_same_v<T, std::string_view> ||
|
||||||
|
std::is_same_v<T, const char *> ||
|
||||||
|
std::is_same_v<T, char>)
|
||||||
|
void append(string_builder &b, const T &t) {
|
||||||
|
atom(b, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <concepts::optional_type T>
|
||||||
|
void append(string_builder &b, const T &t) {
|
||||||
|
atom(b, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <concepts::smart_pointer T>
|
||||||
|
void append(string_builder &b, const T &t) {
|
||||||
|
atom(b, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <concepts::appendable_containers T>
|
||||||
|
requires(!container_but_not_string<T> && !concepts::string_view_keyed_map<T> &&
|
||||||
|
!concepts::optional_type<T> && !concepts::smart_pointer<T> &&
|
||||||
|
!std::is_same_v<T, std::string> &&
|
||||||
|
!std::is_same_v<T, std::string_view> && !std::is_same_v<T, const char*>)
|
||||||
|
void append(string_builder &b, const T &t) {
|
||||||
|
atom(b, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <concepts::string_view_keyed_map T>
|
||||||
|
void append(string_builder &b, const T &t) {
|
||||||
|
atom(b, t);
|
||||||
|
}
|
||||||
|
|
||||||
// works for struct
|
// works for struct
|
||||||
template <class Z> void append(string_builder &b, const Z &z) {
|
template <class Z>
|
||||||
|
requires(std::is_class_v<Z> && !container_but_not_string<Z> &&
|
||||||
|
!concepts::string_view_keyed_map<Z> &&
|
||||||
|
!concepts::optional_type<Z> &&
|
||||||
|
!concepts::smart_pointer<Z> &&
|
||||||
|
!concepts::appendable_containers<Z> &&
|
||||||
|
!std::is_same_v<Z, std::string> &&
|
||||||
|
!std::is_same_v<Z, std::string_view> &&
|
||||||
|
!std::is_same_v<Z, const char*> &&
|
||||||
|
!std::is_same_v<Z, char>)
|
||||||
|
void append(string_builder &b, const Z &z) {
|
||||||
int i = 0;
|
int i = 0;
|
||||||
b.append('{');
|
b.append('{');
|
||||||
[:expand(std::meta::nonstatic_data_members_of(^^Z, std::meta::access_context::unchecked())):] >> [&]<auto dm>() {
|
[:expand(std::meta::nonstatic_data_members_of(^^Z, std::meta::access_context::unchecked())):] >> [&]<auto dm>() {
|
||||||
|
|||||||
@@ -9,6 +9,9 @@
|
|||||||
#include "simdjson/generic/ondemand/object.h"
|
#include "simdjson/generic/ondemand/object.h"
|
||||||
#include "simdjson/generic/ondemand/serialization.h"
|
#include "simdjson/generic/ondemand/serialization.h"
|
||||||
#include "simdjson/generic/ondemand/value.h"
|
#include "simdjson/generic/ondemand/value.h"
|
||||||
|
#if SIMDJSON_STATIC_REFLECTION
|
||||||
|
#include "simdjson/generic/ondemand/json_builder.h"
|
||||||
|
#endif
|
||||||
#endif // SIMDJSON_CONDITIONAL_INCLUDE
|
#endif // SIMDJSON_CONDITIONAL_INCLUDE
|
||||||
|
|
||||||
namespace simdjson {
|
namespace simdjson {
|
||||||
|
|||||||
@@ -35,6 +35,20 @@ inline simdjson_result<std::string_view> to_json_string(simdjson_result<SIMDJSON
|
|||||||
inline simdjson_result<std::string_view> to_json_string(simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> x);
|
inline simdjson_result<std::string_view> to_json_string(simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> x);
|
||||||
inline simdjson_result<std::string_view> to_json_string(simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::object> x);
|
inline simdjson_result<std::string_view> to_json_string(simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::object> x);
|
||||||
inline simdjson_result<std::string_view> to_json_string(simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::array> x);
|
inline simdjson_result<std::string_view> to_json_string(simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::array> 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<typename T>
|
||||||
|
requires(!std::same_as<T, SIMDJSON_IMPLEMENTATION::ondemand::document> &&
|
||||||
|
!std::same_as<T, SIMDJSON_IMPLEMENTATION::ondemand::value> &&
|
||||||
|
!std::same_as<T, SIMDJSON_IMPLEMENTATION::ondemand::object> &&
|
||||||
|
!std::same_as<T, SIMDJSON_IMPLEMENTATION::ondemand::array>)
|
||||||
|
inline std::string to_json_string(const T& obj);
|
||||||
|
#endif
|
||||||
|
|
||||||
} // namespace simdjson
|
} // namespace simdjson
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -255,10 +255,16 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept(nothrow_deser
|
|||||||
|
|
||||||
static_assert(
|
static_assert(
|
||||||
deserializable<value_type, ValT>,
|
deserializable<value_type, ValT>,
|
||||||
"The specified type inside the unique_ptr must itself be deserializable");
|
"The specified type inside the optional must itself be deserializable");
|
||||||
static_assert(
|
static_assert(
|
||||||
std::is_default_constructible_v<value_type>,
|
std::is_default_constructible_v<value_type>,
|
||||||
"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) {
|
if (!out) {
|
||||||
out.emplace();
|
out.emplace();
|
||||||
@@ -297,7 +303,7 @@ template<typename R>
|
|||||||
consteval auto expand(R range) {
|
consteval auto expand(R range) {
|
||||||
std::vector<std::meta::info> args;
|
std::vector<std::meta::info> args;
|
||||||
for (auto r : range) {
|
for (auto r : range) {
|
||||||
args.push_back(reflect_value(r));
|
args.push_back(reflect_constant(r));
|
||||||
}
|
}
|
||||||
return substitute(^^__impl::replicator, args);
|
return substitute(^^__impl::replicator, args);
|
||||||
}
|
}
|
||||||
@@ -328,6 +334,33 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept {
|
|||||||
};
|
};
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Support for enum deserialization - deserialize from string representation using expand approach from P2996R12
|
||||||
|
template <typename T, typename ValT>
|
||||||
|
requires(std::is_enum_v<T>)
|
||||||
|
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)):] >> [&]<auto enum_val>{
|
||||||
|
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<T> int_val;
|
||||||
|
SIMDJSON_TRY(val.get(int_val));
|
||||||
|
out = static_cast<T>(int_val);
|
||||||
|
return SUCCESS;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
template <typename simdjson_value, typename T>
|
template <typename simdjson_value, typename T>
|
||||||
requires(user_defined_type<std::remove_cvref_t<T>>)
|
requires(user_defined_type<std::remove_cvref_t<T>>)
|
||||||
error_code tag_invoke(deserialize_tag, simdjson_value &val, std::unique_ptr<T> &out) noexcept {
|
error_code tag_invoke(deserialize_tag, simdjson_value &val, std::unique_ptr<T> &out) noexcept {
|
||||||
@@ -366,6 +399,10 @@ error_code tag_invoke(deserialize_tag, simdjson_value &val, std::shared_ptr<T> &
|
|||||||
// Unique pointers
|
// Unique pointers
|
||||||
////////////////////////////////////////
|
////////////////////////////////////////
|
||||||
error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr<bool> &out) noexcept {
|
error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr<bool> &out) noexcept {
|
||||||
|
if (val.is_null()) {
|
||||||
|
out.reset();
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
if (!out) {
|
if (!out) {
|
||||||
out = std::make_unique<bool>();
|
out = std::make_unique<bool>();
|
||||||
if (!out) { return MEMALLOC; }
|
if (!out) { return MEMALLOC; }
|
||||||
@@ -375,6 +412,10 @@ error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr<bool> &out) no
|
|||||||
}
|
}
|
||||||
|
|
||||||
error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr<int64_t> &out) noexcept {
|
error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr<int64_t> &out) noexcept {
|
||||||
|
if (val.is_null()) {
|
||||||
|
out.reset();
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
if (!out) {
|
if (!out) {
|
||||||
out = std::make_unique<int64_t>();
|
out = std::make_unique<int64_t>();
|
||||||
if (!out) { return MEMALLOC; }
|
if (!out) { return MEMALLOC; }
|
||||||
@@ -384,6 +425,10 @@ error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr<int64_t> &out)
|
|||||||
}
|
}
|
||||||
|
|
||||||
error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr<uint64_t> &out) noexcept {
|
error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr<uint64_t> &out) noexcept {
|
||||||
|
if (val.is_null()) {
|
||||||
|
out.reset();
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
if (!out) {
|
if (!out) {
|
||||||
out = std::make_unique<uint64_t>();
|
out = std::make_unique<uint64_t>();
|
||||||
if (!out) { return MEMALLOC; }
|
if (!out) { return MEMALLOC; }
|
||||||
@@ -393,6 +438,10 @@ error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr<uint64_t> &out
|
|||||||
}
|
}
|
||||||
|
|
||||||
error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr<double> &out) noexcept {
|
error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr<double> &out) noexcept {
|
||||||
|
if (val.is_null()) {
|
||||||
|
out.reset();
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
if (!out) {
|
if (!out) {
|
||||||
out = std::make_unique<double>();
|
out = std::make_unique<double>();
|
||||||
if (!out) { return MEMALLOC; }
|
if (!out) { return MEMALLOC; }
|
||||||
@@ -402,6 +451,10 @@ error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr<double> &out)
|
|||||||
}
|
}
|
||||||
|
|
||||||
error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr<std::string_view> &out) noexcept {
|
error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr<std::string_view> &out) noexcept {
|
||||||
|
if (val.is_null()) {
|
||||||
|
out.reset();
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
if (!out) {
|
if (!out) {
|
||||||
out = std::make_unique<std::string_view>();
|
out = std::make_unique<std::string_view>();
|
||||||
if (!out) { return MEMALLOC; }
|
if (!out) { return MEMALLOC; }
|
||||||
@@ -415,6 +468,10 @@ error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr<std::string_vi
|
|||||||
// Shared pointers
|
// Shared pointers
|
||||||
////////////////////////////////////////
|
////////////////////////////////////////
|
||||||
error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr<bool> &out) noexcept {
|
error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr<bool> &out) noexcept {
|
||||||
|
if (val.is_null()) {
|
||||||
|
out.reset();
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
if (!out) {
|
if (!out) {
|
||||||
out = std::make_shared<bool>();
|
out = std::make_shared<bool>();
|
||||||
if (!out) { return MEMALLOC; }
|
if (!out) { return MEMALLOC; }
|
||||||
@@ -424,6 +481,10 @@ error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr<bool> &out) no
|
|||||||
}
|
}
|
||||||
|
|
||||||
error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr<int64_t> &out) noexcept {
|
error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr<int64_t> &out) noexcept {
|
||||||
|
if (val.is_null()) {
|
||||||
|
out.reset();
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
if (!out) {
|
if (!out) {
|
||||||
out = std::make_shared<int64_t>();
|
out = std::make_shared<int64_t>();
|
||||||
if (!out) { return MEMALLOC; }
|
if (!out) { return MEMALLOC; }
|
||||||
@@ -433,6 +494,10 @@ error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr<int64_t> &out)
|
|||||||
}
|
}
|
||||||
|
|
||||||
error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr<uint64_t> &out) noexcept {
|
error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr<uint64_t> &out) noexcept {
|
||||||
|
if (val.is_null()) {
|
||||||
|
out.reset();
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
if (!out) {
|
if (!out) {
|
||||||
out = std::make_shared<uint64_t>();
|
out = std::make_shared<uint64_t>();
|
||||||
if (!out) { return MEMALLOC; }
|
if (!out) { return MEMALLOC; }
|
||||||
@@ -442,6 +507,10 @@ error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr<uint64_t> &out
|
|||||||
}
|
}
|
||||||
|
|
||||||
error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr<double> &out) noexcept {
|
error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr<double> &out) noexcept {
|
||||||
|
if (val.is_null()) {
|
||||||
|
out.reset();
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
if (!out) {
|
if (!out) {
|
||||||
out = std::make_shared<double>();
|
out = std::make_shared<double>();
|
||||||
if (!out) { return MEMALLOC; }
|
if (!out) { return MEMALLOC; }
|
||||||
@@ -451,6 +520,10 @@ error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr<double> &out)
|
|||||||
}
|
}
|
||||||
|
|
||||||
error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr<std::string_view> &out) noexcept {
|
error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr<std::string_view> &out) noexcept {
|
||||||
|
if (val.is_null()) {
|
||||||
|
out.reset();
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
if (!out) {
|
if (!out) {
|
||||||
out = std::make_shared<std::string_view>();
|
out = std::make_shared<std::string_view>();
|
||||||
if (!out) { return MEMALLOC; }
|
if (!out) { return MEMALLOC; }
|
||||||
@@ -460,6 +533,92 @@ error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr<std::string_vi
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////
|
||||||
|
// Explicit optional specializations
|
||||||
|
////////////////////////////////////////
|
||||||
|
error_code tag_invoke(deserialize_tag, auto &val, concepts::optional_type auto&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, concepts::optional_type auto&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<int>(temp);
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////
|
||||||
|
// Explicit smart pointer specializations for string and int types
|
||||||
|
////////////////////////////////////////
|
||||||
|
error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr<std::string> &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>();
|
||||||
|
}
|
||||||
|
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<std::string> &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>();
|
||||||
|
}
|
||||||
|
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<int> &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<int>();
|
||||||
|
}
|
||||||
|
int64_t temp;
|
||||||
|
SIMDJSON_TRY(val.get_int64().get(temp));
|
||||||
|
*out = static_cast<int>(temp);
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace simdjson
|
} // namespace simdjson
|
||||||
|
|
||||||
#endif // SIMDJSON_ONDEMAND_DESERIALIZE_H
|
#endif // SIMDJSON_ONDEMAND_DESERIALIZE_H
|
||||||
|
|||||||
@@ -12,6 +12,28 @@ namespace simdjson {
|
|||||||
* @copydoc simdjson::builtin::builder
|
* @copydoc simdjson::builtin::builder
|
||||||
*/
|
*/
|
||||||
namespace builder = 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<typename T>
|
||||||
|
requires(!std::same_as<T, builtin::ondemand::document> &&
|
||||||
|
!std::same_as<T, builtin::ondemand::value> &&
|
||||||
|
!std::same_as<T, builtin::ondemand::object> &&
|
||||||
|
!std::same_as<T, builtin::ondemand::array>)
|
||||||
|
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
|
} // namespace simdjson
|
||||||
|
|
||||||
#endif // SIMDJSON_ONDEMAND_H
|
#endif // SIMDJSON_ONDEMAND_H
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ include_directories(..)
|
|||||||
add_cpp_test(builder_string_builder_tests LABELS ondemand acceptance per_implementation)
|
add_cpp_test(builder_string_builder_tests LABELS ondemand acceptance per_implementation)
|
||||||
if(SIMDJSON_STATIC_REFLECTION)
|
if(SIMDJSON_STATIC_REFLECTION)
|
||||||
add_cpp_test(static_reflection_builder_tests LABELS ondemand acceptance per_implementation)
|
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)
|
endif(SIMDJSON_STATIC_REFLECTION)
|
||||||
# Copy the simdjson dll into the tests directory
|
# Copy the simdjson dll into the tests directory
|
||||||
if(MSVC AND BUILD_SHARED_LIBS)
|
if(MSVC AND BUILD_SHARED_LIBS)
|
||||||
|
|||||||
@@ -0,0 +1,260 @@
|
|||||||
|
#include "simdjson.h"
|
||||||
|
#include "test_builder.h"
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
#include <set>
|
||||||
|
#include <map>
|
||||||
|
#include <optional>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
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<PrimitiveTypes>();
|
||||||
|
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<StringTypes>();
|
||||||
|
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<int> opt_int_with_value;
|
||||||
|
std::optional<std::string> opt_string_with_value;
|
||||||
|
std::optional<int> opt_int_null;
|
||||||
|
std::optional<std::string> 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<OptionalTypes>();
|
||||||
|
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<int> unique_int_with_value;
|
||||||
|
std::shared_ptr<std::string> shared_string_with_value;
|
||||||
|
std::unique_ptr<bool> unique_bool_with_value;
|
||||||
|
std::unique_ptr<int> unique_int_null;
|
||||||
|
std::shared_ptr<std::string> shared_string_null;
|
||||||
|
};
|
||||||
|
|
||||||
|
SmartPointerTypes test;
|
||||||
|
test.unique_int_with_value = std::make_unique<int>(123);
|
||||||
|
test.shared_string_with_value = std::make_shared<std::string>("shared_test");
|
||||||
|
test.unique_bool_with_value = std::make_unique<bool>(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<SmartPointerTypes>();
|
||||||
|
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> int_vector;
|
||||||
|
std::set<std::string> string_set;
|
||||||
|
std::map<std::string, int> 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<ContainerTypes>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
#include "simdjson.h"
|
||||||
|
#include "test_builder.h"
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <optional>
|
||||||
|
#include <memory>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
using namespace simdjson;
|
||||||
|
|
||||||
|
namespace builder_tests {
|
||||||
|
|
||||||
|
bool test_empty_values() {
|
||||||
|
TEST_START();
|
||||||
|
#if SIMDJSON_STATIC_REFLECTION
|
||||||
|
struct EmptyValues {
|
||||||
|
std::string empty_string;
|
||||||
|
std::vector<int> empty_vector;
|
||||||
|
std::optional<int> null_optional;
|
||||||
|
std::unique_ptr<int> null_unique_ptr;
|
||||||
|
std::shared_ptr<std::string> 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<EmptyValues>();
|
||||||
|
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<SpecialCharsNoNull>();
|
||||||
|
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<int>::max();
|
||||||
|
test.min_int = std::numeric_limits<int>::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<NumericLimits>();
|
||||||
|
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> inner_vector;
|
||||||
|
std::optional<Inner> optional_inner;
|
||||||
|
std::unique_ptr<Inner> 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>(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<Outer>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,258 @@
|
|||||||
|
#include "simdjson.h"
|
||||||
|
#include "test_builder.h"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
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<StatusStruct>();
|
||||||
|
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<StatusStruct>();
|
||||||
|
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<StatusStruct>();
|
||||||
|
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<Task>();
|
||||||
|
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<Response>();
|
||||||
|
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<Date>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user