mirror of
https://github.com/simdjson/simdjson
synced 2026-06-08 17:27:07 +00:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f4807d63a3 | |||
| 72ca766152 | |||
| e886511471 | |||
| 9eaeb6d314 | |||
| 0ac0107742 | |||
| 5ed1044056 | |||
| 62913867ff | |||
| 6700d48b57 | |||
| b5577d5e85 | |||
| b84a4ec2b9 | |||
| 1638a185f7 | |||
| 6fe450f5ce | |||
| c7b70de070 | |||
| 3279fbd55b | |||
| 66e64e0e5f | |||
| 03f81e66af | |||
| e3b7eddb37 | |||
| 99c4ba6e8f | |||
| 6aa7eea334 | |||
| 6a47cda07f | |||
| 617c69e104 | |||
| 625adceb24 | |||
| bd0e9c1336 | |||
| 7bf82b02d5 | |||
| 88a1b3e83b | |||
| c72954eade | |||
| 4456a10469 | |||
| d8ed2417ad | |||
| 5517df7aee | |||
| 6e618b0805 | |||
| bde288a623 | |||
| b2932d1b8f | |||
| a7811090ef | |||
| 3320885fac |
+1
-1
@@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.14)
|
||||
project(
|
||||
simdjson
|
||||
# The version number is modified by tools/release.py
|
||||
VERSION 4.0.5
|
||||
VERSION 4.0.7
|
||||
DESCRIPTION "Parsing gigabytes of JSON per second"
|
||||
HOMEPAGE_URL "https://simdjson.org/"
|
||||
LANGUAGES CXX C
|
||||
|
||||
@@ -38,7 +38,7 @@ PROJECT_NAME = simdjson
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = "4.0.5"
|
||||
PROJECT_NUMBER = "4.0.7"
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
|
||||
@@ -174,6 +174,10 @@ else()
|
||||
-Werror -Wall -Wextra -Weffc++ -Wsign-compare -Wshadow -Wwrite-strings
|
||||
-Wpointer-arith -Winit-self -Wconversion -Wno-sign-conversion
|
||||
)
|
||||
if(CMAKE_CXX_STANDARD VERSION_GREATER_EQUAL 20)
|
||||
target_compile_options(simdjson-internal-flags INTERFACE -Wctad-maybe-unsupported)
|
||||
endif()
|
||||
|
||||
endif()
|
||||
|
||||
option(SIMDJSON_GLIBCXX_ASSERTIONS "Set _GLIBCXX_ASSERTIONS" OFF)
|
||||
|
||||
+41
-4
@@ -1400,6 +1400,42 @@ maps it to parsing code. We call the default constructor,
|
||||
and then assign values to the public members.
|
||||
|
||||
|
||||
If a key is missing in the JSON document, an error is generated (`NO_SUCH_FIELD`),
|
||||
except if the attribute is of a type like `std::optional` (`simdjson::concepts::optional_type`).
|
||||
|
||||
|
||||
Sometimes you might want to only extract some attributes from the JSON. You can
|
||||
achieve this result with the `extract_into` method supported by both `object` and
|
||||
`document` instances. It returns an error code that evaluates to false when there
|
||||
is no error.
|
||||
|
||||
Consider the following example where you only want to parse the make and the model
|
||||
from the JSON:
|
||||
|
||||
```cpp
|
||||
struct car_type {
|
||||
std::string make;
|
||||
std::string model;
|
||||
uint64_t year;
|
||||
std::vector<double> tire_pressure;
|
||||
};
|
||||
|
||||
void f() {
|
||||
auto json = R"( {
|
||||
"make": "Toyota",
|
||||
"model": "Camry",
|
||||
"year": 2024,
|
||||
"tire_pressure": [ 40.1, 39.9 ]
|
||||
} )"_padded;
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc = parser.iterate(json);
|
||||
Car car{};
|
||||
auto error = doc.extract_into<"make","model">(car);
|
||||
if(error) { /** error handling */ }
|
||||
// only car.make and car.
|
||||
}
|
||||
```
|
||||
|
||||
#### Special cases
|
||||
|
||||
However, there are instances where the construction cannot
|
||||
@@ -1456,7 +1492,7 @@ The code might be as simple as the following.
|
||||
auto padded = R"({"time":["2023-03-15T12:00:00Z"],"temperature":[42]})"_padded;
|
||||
simdjson::ondemand::parser parser;
|
||||
simdjson::ondemand::document doc = parser.iterate(padded);
|
||||
complicated_weather_data p = doc.get<>(complicated_weather_data);
|
||||
complicated_weather_data p = doc.get<complicated_weather_data>();
|
||||
```
|
||||
|
||||
Thus you can combine C++26 static reflection with custom deserialization
|
||||
@@ -2771,15 +2807,16 @@ a parameter a reference to a `std::string`.
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc = parser.iterate(json);
|
||||
std::string name;
|
||||
doc["name"].get_string(name);
|
||||
auto error = doc["name"].get_string(name);
|
||||
if(error) { /* handle error */ }
|
||||
```
|
||||
|
||||
The same routine can be written without exceptions handling:
|
||||
|
||||
```C++
|
||||
std::string name;
|
||||
auto err = doc["name"].get_string(name);
|
||||
if (err) { /* handle error */ }
|
||||
auto error = doc["name"].get_string(name);
|
||||
if (error) { /* handle error */ }
|
||||
```
|
||||
|
||||
The `std::string` instance, once created, is independent. Unlike our `std::string_view` instances,
|
||||
|
||||
@@ -190,6 +190,38 @@ if(error) { /* there was an error */ }
|
||||
We do recommend that you create and reuse the `string_builder` instance for performance
|
||||
reasons.
|
||||
|
||||
You can also add custom serialization functions using a `tag_invoke` function.
|
||||
For example, the following
|
||||
function will allow you to serialize instances of the type `Car`.
|
||||
|
||||
```cpp
|
||||
#include <simdjson>
|
||||
|
||||
struct Car {
|
||||
std::string make;
|
||||
std::string model;
|
||||
int64_t year;
|
||||
std::vector<float> tire_pressure;
|
||||
};
|
||||
|
||||
namespace simdjson {
|
||||
|
||||
template <typename builder_type>
|
||||
void tag_invoke(serialize_tag, builder_type &builder, const Car& car) {
|
||||
builder.start_object();
|
||||
builder.append_key_value("make", car.make);
|
||||
builder.append_comma();
|
||||
builder.append_key_value("model", car.model);
|
||||
builder.append_comma();
|
||||
builder.append_key_value("year", car.year);
|
||||
builder.append_comma();
|
||||
builder.append_key_value("tire_pressure", car.tire_pressure);
|
||||
builder.end_object();
|
||||
}
|
||||
|
||||
} // namespace simdjson
|
||||
```
|
||||
|
||||
C++26 static reflection
|
||||
------------------------
|
||||
|
||||
@@ -263,6 +295,27 @@ if(error) { /* there was an error */ }
|
||||
|
||||
You can then also add a third parameter for the expected output size in bytes.
|
||||
|
||||
### Extracting just some fields
|
||||
In some instances, your class might have many fields that you do not want to serialize.
|
||||
You can achieve this result with the `simdjson::extract_from` template. In the following
|
||||
example, we serialize only the `year` and `price` fields on the `Car` instance.
|
||||
```cpp
|
||||
struct Car {
|
||||
std::string make;
|
||||
std::string model;
|
||||
int year;
|
||||
double price;
|
||||
bool electric;
|
||||
};
|
||||
Car car{"Ford", "F-150", 2024, 55000.0, false};
|
||||
// Extract year and price
|
||||
std::string json_result = simdjson::extract_from<"year", "price">(car);
|
||||
// Alternatively:
|
||||
// std::string json_result;
|
||||
// auto error = extract_from<"year", "price">(car).get(json_result);
|
||||
// if(error) { /* error handling */ }
|
||||
```
|
||||
|
||||
### Without `string_buffer` instance but with explicit error handling
|
||||
|
||||
If prefer a version without exceptions and explicit error handling, you can use the following
|
||||
@@ -275,4 +328,39 @@ pattern:
|
||||
} else {
|
||||
// json contain the serialized JSON
|
||||
}
|
||||
```
|
||||
|
||||
### Customization
|
||||
|
||||
If you want to serialize a value in a custome way, you can do it with a
|
||||
`tag_invoke` specialization like the following example which will map
|
||||
the year attribute to a string.
|
||||
|
||||
|
||||
```cpp
|
||||
#include <simdjson>
|
||||
|
||||
struct Car {
|
||||
std::string make;
|
||||
std::string model;
|
||||
int64_t year;
|
||||
std::vector<float> tire_pressure;
|
||||
};
|
||||
|
||||
namespace simdjson {
|
||||
|
||||
template <typename builder_type>
|
||||
void tag_invoke(serialize_tag, builder_type &builder, const Car& car) {
|
||||
builder.start_object();
|
||||
builder.append_key_value("make", car.make);
|
||||
builder.append_comma();
|
||||
builder.append_key_value("model", car.model);
|
||||
builder.append_comma();
|
||||
builder.append_key_value("year", std::to_string(car.year));
|
||||
builder.append_comma();
|
||||
builder.append_key_value("tire_pressure", car.tire_pressure);
|
||||
builder.end_object();
|
||||
}
|
||||
|
||||
} // namespace simdjson
|
||||
```
|
||||
+1
-1
@@ -253,7 +253,7 @@ long page_size() {
|
||||
// page boundary.
|
||||
bool need_allocation(const char *buf, size_t len) {
|
||||
return ((reinterpret_cast<uintptr_t>(buf + len - 1) % page_size())
|
||||
+ simdjson::SIMDJSON_PADDING > static_cast<uintptr_t>(page_size()));
|
||||
+ simdjson::SIMDJSON_PADDING >= static_cast<uintptr_t>(page_size()));
|
||||
}
|
||||
|
||||
simdjson::padded_string_view
|
||||
|
||||
@@ -17,7 +17,7 @@ using namespace simd;
|
||||
struct backslash_and_quote {
|
||||
public:
|
||||
static constexpr uint32_t BYTES_PROCESSED = 32;
|
||||
simdjson_inline static backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst);
|
||||
simdjson_inline backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst);
|
||||
|
||||
simdjson_inline bool has_quote_first() { return ((bs_bits - 1) & quote_bits) != 0; }
|
||||
simdjson_inline bool has_backslash() { return bs_bits != 0; }
|
||||
|
||||
@@ -122,11 +122,64 @@ concept optional_type = requires(std::remove_cvref_t<T> obj) {
|
||||
} -> std::convertible_to<typename std::remove_cvref_t<T>::value_type>;
|
||||
};
|
||||
{ static_cast<bool>(obj) } -> std::same_as<bool>; // convertible to bool
|
||||
{ obj.reset() } noexcept -> std::same_as<void>;
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Types we serialize as JSON strings (not as containers)
|
||||
template <typename T>
|
||||
concept string_like =
|
||||
std::is_same_v<std::remove_cvref_t<T>, std::string> ||
|
||||
std::is_same_v<std::remove_cvref_t<T>, std::string_view> ||
|
||||
std::is_same_v<std::remove_cvref_t<T>, const char*> ||
|
||||
std::is_same_v<std::remove_cvref_t<T>, char*>;
|
||||
|
||||
// Concept that checks if a type is a container but not a string (because
|
||||
// strings handling must be handled differently)
|
||||
// Now uses iterator-based approach for broader container support
|
||||
template <typename T>
|
||||
concept container_but_not_string =
|
||||
std::ranges::input_range<T> && !string_like<T> && !concepts::string_view_keyed_map<T>;
|
||||
|
||||
|
||||
// Concept: Indexable container that is not a string or associative container
|
||||
// Accepts: std::vector, std::array, std::deque (have operator[], value_type, not string_like)
|
||||
// Rejects: std::string (string_like), std::list (no operator[]), std::map (has key_type)
|
||||
template<typename Container>
|
||||
concept indexable_container = requires {
|
||||
typename Container::value_type;
|
||||
requires !concepts::string_like<Container>;
|
||||
requires !requires { typename Container::key_type; }; // Reject maps/sets
|
||||
requires requires(Container& c, std::size_t i) {
|
||||
{ c[i] } -> std::convertible_to<typename Container::value_type>;
|
||||
};
|
||||
};
|
||||
|
||||
// Variable template to use with std::meta::substitute
|
||||
template<typename Container>
|
||||
constexpr bool indexable_container_v = indexable_container<Container>;
|
||||
|
||||
|
||||
} // namespace concepts
|
||||
|
||||
|
||||
/**
|
||||
* We use tag_invoke as our customization point mechanism.
|
||||
*/
|
||||
template <typename Tag, typename... Args>
|
||||
concept tag_invocable = requires(Tag tag, Args... args) {
|
||||
tag_invoke(std::forward<Tag>(tag), std::forward<Args>(args)...);
|
||||
};
|
||||
|
||||
template <typename Tag, typename... Args>
|
||||
concept nothrow_tag_invocable =
|
||||
tag_invocable<Tag, Args...> && requires(Tag tag, Args... args) {
|
||||
{
|
||||
tag_invoke(std::forward<Tag>(tag), std::forward<Args>(args)...)
|
||||
} noexcept;
|
||||
};
|
||||
|
||||
} // namespace simdjson
|
||||
#endif // SIMDJSON_SUPPORTS_CONCEPTS
|
||||
#endif // SIMDJSON_CONCEPTS_H
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
#include <string_view>
|
||||
#include <array>
|
||||
|
||||
#if SIMDJSON_CONSTEVAL
|
||||
namespace simdjson {
|
||||
namespace constevalutil {
|
||||
#if SIMDJSON_CONSTEVAL
|
||||
|
||||
constexpr static std::array<uint8_t, 256> json_quotable_character = {
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
@@ -47,7 +47,29 @@ consteval std::string consteval_to_quoted_escaped(std::string_view input) {
|
||||
out.push_back('"');
|
||||
return out;
|
||||
}
|
||||
#endif // SIMDJSON_CONSTEVAL
|
||||
|
||||
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS
|
||||
template <std::size_t N>
|
||||
struct fixed_string {
|
||||
constexpr fixed_string(const char (&str)[N]) {
|
||||
for (std::size_t i = 0; i < N; ++i) {
|
||||
data[i] = str[i];
|
||||
}
|
||||
}
|
||||
char data[N];
|
||||
constexpr std::string_view view() const { return {data, N - 1}; }
|
||||
};
|
||||
template <std::size_t N>
|
||||
fixed_string(const char (&)[N]) -> fixed_string<N>;
|
||||
|
||||
template <fixed_string str>
|
||||
struct string_constant {
|
||||
static constexpr std::string_view value = str.view();
|
||||
};
|
||||
#endif // SIMDJSON_SUPPORTS_CONCEPTS
|
||||
|
||||
} // namespace constevalutil
|
||||
} // namespace simdjson
|
||||
#endif // SIMDJSON_CONSTEVAL
|
||||
#endif // SIMDJSON_CONSTEVALUTIL_H
|
||||
@@ -79,6 +79,7 @@ inline simdjson_result<ondemand::number> auto_parser<parser_type>::number() noex
|
||||
return result<ondemand::number>();
|
||||
}
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
template <typename parser_type>
|
||||
template <typename T>
|
||||
inline auto_parser<parser_type>::operator T() noexcept(false) {
|
||||
@@ -87,6 +88,7 @@ inline auto_parser<parser_type>::operator T() noexcept(false) {
|
||||
}
|
||||
return m_doc.get<T>();
|
||||
}
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
|
||||
template <typename parser_type>
|
||||
template <typename T>
|
||||
@@ -109,7 +111,7 @@ inline T to_adaptor<T>::operator()(simdjson_result<ondemand::value> &val) const
|
||||
|
||||
template <typename T>
|
||||
inline auto to_adaptor<T>::operator()(padded_string_view const str) const noexcept {
|
||||
return auto_parser{str};
|
||||
return auto_parser<ondemand::parser *>{str};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
@@ -119,7 +121,7 @@ inline auto to_adaptor<T>::operator()(ondemand::parser &parser, padded_string_vi
|
||||
|
||||
template <typename T>
|
||||
inline auto to_adaptor<T>::operator()(std::string str) const noexcept {
|
||||
return auto_parser{pad_with_reserve(str)};
|
||||
return auto_parser<ondemand::parser *>{pad_with_reserve(str)};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
|
||||
@@ -54,10 +54,11 @@ public:
|
||||
simdjson_warn_unused simdjson_inline simdjson_result<ondemand::object> object() noexcept;
|
||||
simdjson_warn_unused simdjson_inline simdjson_result<ondemand::number> number() noexcept;
|
||||
|
||||
//template <typename T>
|
||||
//simdjson_warn_unused simdjson_inline explicit(false) operator simdjson_result<T>() noexcept(is_nothrow_gettable<T>);
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
template <typename T>
|
||||
simdjson_warn_unused simdjson_inline explicit(false) operator T() noexcept(false);
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
|
||||
template <typename T>
|
||||
simdjson_warn_unused simdjson_inline std::optional<T> optional() noexcept(is_nothrow_gettable<T>);
|
||||
@@ -80,6 +81,8 @@ struct to_adaptor {
|
||||
auto operator()(std::string str) const noexcept;
|
||||
auto operator()(ondemand::parser &parser, std::string str) const noexcept;
|
||||
};
|
||||
// deduction guide
|
||||
auto_parser(padded_string_view const str) -> auto_parser<ondemand::parser*>;
|
||||
} // namespace internal
|
||||
} // namespace convert
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace {
|
||||
struct backslash_and_quote {
|
||||
public:
|
||||
static constexpr uint32_t BYTES_PROCESSED = 1;
|
||||
simdjson_inline static backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst);
|
||||
simdjson_inline backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst);
|
||||
|
||||
simdjson_inline bool has_quote_first() { return c == '"'; }
|
||||
simdjson_inline bool has_backslash() { return c == '\\'; }
|
||||
|
||||
@@ -29,13 +29,13 @@ simdjson_warn_unused simdjson_inline error_code implementation_simdjson_result_b
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
simdjson_inline error_code implementation_simdjson_result_base<T>::error() const noexcept {
|
||||
simdjson_warn_unused simdjson_inline error_code implementation_simdjson_result_base<T>::error() const noexcept {
|
||||
return this->second;
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
simdjson_inline bool implementation_simdjson_result_base<T>::has_value() const noexcept {
|
||||
simdjson_warn_unused simdjson_inline bool implementation_simdjson_result_base<T>::has_value() const noexcept {
|
||||
return this->error() == SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -67,17 +67,17 @@ struct implementation_simdjson_result_base {
|
||||
*
|
||||
* @param value The variable to assign the value to. May not be set if there is an error.
|
||||
*/
|
||||
simdjson_inline error_code get(T &value) && noexcept;
|
||||
simdjson_warn_unused simdjson_inline error_code get(T &value) && noexcept;
|
||||
|
||||
/**
|
||||
* The error.
|
||||
*/
|
||||
simdjson_inline error_code error() const noexcept;
|
||||
simdjson_warn_unused simdjson_inline error_code error() const noexcept;
|
||||
|
||||
/**
|
||||
* Whether there is a value.
|
||||
*/
|
||||
simdjson_inline bool has_value() const noexcept;
|
||||
simdjson_warn_unused simdjson_inline bool has_value() const noexcept;
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
|
||||
@@ -361,7 +361,7 @@ simdjson_inline bool is_digit(const uint8_t c) {
|
||||
return static_cast<uint8_t>(c - '0') <= 9;
|
||||
}
|
||||
|
||||
simdjson_inline error_code parse_decimal_after_separator(simdjson_unused const uint8_t *const src, const uint8_t *&p, uint64_t &i, int64_t &exponent) {
|
||||
simdjson_warn_unused simdjson_inline error_code parse_decimal_after_separator(simdjson_unused const uint8_t *const src, const uint8_t *&p, uint64_t &i, int64_t &exponent) {
|
||||
// we continue with the fiction that we have an integer. If the
|
||||
// floating point number is representable as x * 10^z for some integer
|
||||
// z that fits in 53 bits, then we will be able to convert back the
|
||||
@@ -389,7 +389,7 @@ simdjson_inline error_code parse_decimal_after_separator(simdjson_unused const u
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
simdjson_inline error_code parse_exponent(simdjson_unused const uint8_t *const src, const uint8_t *&p, int64_t &exponent) {
|
||||
simdjson_warn_unused simdjson_inline error_code parse_exponent(simdjson_unused const uint8_t *const src, const uint8_t *&p, int64_t &exponent) {
|
||||
// Exp Sign: -123.456e[-]78
|
||||
bool neg_exp = ('-' == *p);
|
||||
if (neg_exp || '+' == *p) { p++; } // Skip + as well
|
||||
@@ -478,7 +478,7 @@ static error_code slow_float_parsing(simdjson_unused const uint8_t * src, double
|
||||
|
||||
/** @private */
|
||||
template<typename W>
|
||||
simdjson_inline error_code write_float(const uint8_t *const src, bool negative, uint64_t i, const uint8_t * start_digits, size_t digit_count, int64_t exponent, W &writer) {
|
||||
simdjson_warn_unused simdjson_inline error_code write_float(const uint8_t *const src, bool negative, uint64_t i, const uint8_t * start_digits, size_t digit_count, int64_t exponent, W &writer) {
|
||||
// If we frequently had to deal with long strings of digits,
|
||||
// we could extend our code by using a 128-bit integer instead
|
||||
// of a 64-bit integer. However, this is uncommon in practice.
|
||||
@@ -541,13 +541,13 @@ simdjson_inline error_code write_float(const uint8_t *const src, bool negative,
|
||||
//
|
||||
// Our objective is accurate parsing (ULP of 0) at high speed.
|
||||
template<typename W>
|
||||
simdjson_inline error_code parse_number(const uint8_t *const src, W &writer);
|
||||
simdjson_warn_unused simdjson_inline error_code parse_number(const uint8_t *const src, W &writer);
|
||||
|
||||
// for performance analysis, it is sometimes useful to skip parsing
|
||||
#ifdef SIMDJSON_SKIPNUMBERPARSING
|
||||
|
||||
template<typename W>
|
||||
simdjson_inline error_code parse_number(const uint8_t *const, W &writer) {
|
||||
simdjson_warn_unused simdjson_inline error_code parse_number(const uint8_t *const, W &writer) {
|
||||
writer.append_s64(0); // always write zero
|
||||
return SUCCESS; // always succeeds
|
||||
}
|
||||
@@ -573,7 +573,7 @@ simdjson_unused simdjson_inline simdjson_result<number_type> get_number_type(con
|
||||
//
|
||||
// Our objective is accurate parsing (ULP of 0) at high speed.
|
||||
template<typename W>
|
||||
simdjson_inline error_code parse_number(const uint8_t *const src, W &writer) {
|
||||
simdjson_warn_unused simdjson_inline error_code parse_number(const uint8_t *const src, W &writer) {
|
||||
//
|
||||
// Check for minus sign
|
||||
//
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
#include "simdjson/generic/ondemand/raw_json_string.h"
|
||||
#include "simdjson/generic/ondemand/parser.h"
|
||||
|
||||
// JSON builder - needed for extract_into functionality
|
||||
#include "simdjson/generic/ondemand/json_string_builder.h"
|
||||
|
||||
// All other declarations
|
||||
#include "simdjson/generic/ondemand/array.h"
|
||||
#include "simdjson/generic/ondemand/array_iterator.h"
|
||||
@@ -45,9 +48,10 @@
|
||||
#include "simdjson/generic/ondemand/token_iterator-inl.h"
|
||||
#include "simdjson/generic/ondemand/value_iterator-inl.h"
|
||||
|
||||
// JSON builder, ideally they should not be part of the ondemand directory
|
||||
// but it is convenient for now to have them here.
|
||||
#include "simdjson/generic/ondemand/json_string_builder.h"
|
||||
// JSON builder inline definitions
|
||||
#include "simdjson/generic/ondemand/json_string_builder-inl.h"
|
||||
#include "simdjson/generic/ondemand/json_builder.h"
|
||||
|
||||
// JSON path accessor (compile-time) - must be after inline definitions
|
||||
#include "simdjson/generic/ondemand/compile_time_accessors.h"
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ simdjson_inline simdjson_result<array_iterator> array::begin() noexcept {
|
||||
simdjson_inline simdjson_result<array_iterator> array::end() noexcept {
|
||||
return array_iterator(iter);
|
||||
}
|
||||
simdjson_inline error_code array::consume() noexcept {
|
||||
simdjson_warn_unused simdjson_warn_unused simdjson_inline error_code array::consume() noexcept {
|
||||
auto error = iter.json_iter().skip_child(iter.depth()-1);
|
||||
if(error) { iter.abandon(); }
|
||||
return error;
|
||||
|
||||
@@ -141,7 +141,7 @@ public:
|
||||
* @returns SUCCESS If the parse succeeded and the out parameter was set to the value.
|
||||
*/
|
||||
template <typename T>
|
||||
simdjson_inline error_code get(T &out)
|
||||
simdjson_warn_unused simdjson_inline error_code get(T &out)
|
||||
noexcept(custom_deserializable<T, array> ? nothrow_custom_deserializable<T, array> : true) {
|
||||
static_assert(custom_deserializable<T, array>);
|
||||
return deserialize(*this, out);
|
||||
@@ -166,7 +166,7 @@ protected:
|
||||
/**
|
||||
* Go to the end of the array, no matter where you are right now.
|
||||
*/
|
||||
simdjson_inline error_code consume() noexcept;
|
||||
simdjson_warn_unused simdjson_inline error_code consume() noexcept;
|
||||
|
||||
/**
|
||||
* Begin array iteration.
|
||||
@@ -252,7 +252,7 @@ public:
|
||||
return first.get<T>();
|
||||
}
|
||||
template<typename T>
|
||||
simdjson_inline error_code get(T& out) noexcept {
|
||||
simdjson_warn_unused simdjson_inline error_code get(T& out) noexcept {
|
||||
if (error()) { return error(); }
|
||||
if constexpr (std::is_same_v<T, SIMDJSON_IMPLEMENTATION::ondemand::array>) {
|
||||
out = first;
|
||||
|
||||
@@ -0,0 +1,731 @@
|
||||
/**
|
||||
* Compile-time JSONPath and JSON Pointer accessors using C++26 reflection
|
||||
* This file provides functionality to pre-compile JSON paths and pointers at compile time
|
||||
* and generate optimized accessor code using reflection.
|
||||
*/
|
||||
#ifndef SIMDJSON_GENERIC_ONDEMAND_COMPILE_TIME_ACCESSORS_H
|
||||
|
||||
#ifndef SIMDJSON_CONDITIONAL_INCLUDE
|
||||
#define SIMDJSON_GENERIC_ONDEMAND_COMPILE_TIME_ACCESSORS_H
|
||||
|
||||
#endif // SIMDJSON_CONDITIONAL_INCLUDE
|
||||
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS && SIMDJSON_STATIC_REFLECTION
|
||||
|
||||
#include <string_view>
|
||||
#include <cstddef>
|
||||
#include <array>
|
||||
|
||||
namespace simdjson {
|
||||
namespace SIMDJSON_IMPLEMENTATION {
|
||||
namespace ondemand {
|
||||
/***
|
||||
* JSONPath implementation for compile-time access
|
||||
* RFC 9535 JSONPath: Query Expressions for JSON, https://www.rfc-editor.org/rfc/rfc9535
|
||||
*/
|
||||
namespace json_path {
|
||||
|
||||
// Note: value type must be fully defined before this header is included
|
||||
// This is ensured by including this in amalgamated.h after value-inl.h
|
||||
|
||||
// Path step types
|
||||
enum class step_type {
|
||||
field, // .field_name or ["field_name"]
|
||||
array_index // [index]
|
||||
};
|
||||
|
||||
// Represents a single step in a JSONPath expression
|
||||
template<std::size_t N>
|
||||
struct path_step {
|
||||
step_type type;
|
||||
char key[N]; // Field name (empty for array indices)
|
||||
std::size_t index; // Array index (0 for field access)
|
||||
|
||||
constexpr path_step(step_type t, const char (&k)[N], std::size_t idx = 0)
|
||||
: type(t), index(idx) {
|
||||
for (std::size_t i = 0; i < N; ++i) {
|
||||
key[i] = k[i];
|
||||
}
|
||||
}
|
||||
|
||||
constexpr std::string_view key_view() const {
|
||||
return {key, N - 1};
|
||||
}
|
||||
};
|
||||
|
||||
// Helper to create field step
|
||||
template<std::size_t N>
|
||||
consteval auto make_field_step(const char (&name)[N]) {
|
||||
return path_step<N>(step_type::field, name, 0);
|
||||
}
|
||||
|
||||
// Helper to create array index step
|
||||
consteval auto make_index_step(std::size_t idx) {
|
||||
return path_step<1>(step_type::array_index, "", idx);
|
||||
}
|
||||
|
||||
// Parse state for compile-time JSONPath parsing
|
||||
struct parse_result {
|
||||
bool success;
|
||||
std::size_t pos;
|
||||
std::string_view error_msg;
|
||||
};
|
||||
|
||||
// Compile-time JSONPath parser
|
||||
// Supports subset: .field, ["field"], [index], nested combinations
|
||||
template<constevalutil::fixed_string Path>
|
||||
struct json_path_parser {
|
||||
static constexpr std::string_view path_str = Path.view();
|
||||
|
||||
// Skip leading $ if present
|
||||
static consteval std::size_t skip_root() {
|
||||
if (!path_str.empty() && path_str[0] == '$') {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Count the number of steps in the path at compile time
|
||||
static consteval std::size_t count_steps() {
|
||||
std::size_t count = 0;
|
||||
std::size_t i = skip_root();
|
||||
|
||||
while (i < path_str.size()) {
|
||||
if (path_str[i] == '.') {
|
||||
// Field access: .field
|
||||
++i;
|
||||
if (i >= path_str.size()) break;
|
||||
|
||||
// Skip field name
|
||||
while (i < path_str.size() && path_str[i] != '.' && path_str[i] != '[') {
|
||||
++i;
|
||||
}
|
||||
++count;
|
||||
} else if (path_str[i] == '[') {
|
||||
// Array or bracket notation
|
||||
++i;
|
||||
if (i >= path_str.size()) break;
|
||||
|
||||
if (path_str[i] == '"' || path_str[i] == '\'') {
|
||||
// Field access: ["field"] or ['field']
|
||||
char quote = path_str[i];
|
||||
++i;
|
||||
while (i < path_str.size() && path_str[i] != quote) {
|
||||
++i;
|
||||
}
|
||||
if (i < path_str.size()) ++i; // skip closing quote
|
||||
if (i < path_str.size() && path_str[i] == ']') ++i;
|
||||
} else {
|
||||
// Array index: [0], [123]
|
||||
while (i < path_str.size() && path_str[i] != ']') {
|
||||
++i;
|
||||
}
|
||||
if (i < path_str.size()) ++i; // skip ]
|
||||
}
|
||||
++count;
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
// Parse a field name at compile time
|
||||
static consteval std::size_t parse_field_name(std::size_t start, char* out, std::size_t max_len) {
|
||||
std::size_t len = 0;
|
||||
std::size_t i = start;
|
||||
|
||||
while (i < path_str.size() && path_str[i] != '.' && path_str[i] != '[' && len < max_len - 1) {
|
||||
out[len++] = path_str[i++];
|
||||
}
|
||||
out[len] = '\0';
|
||||
return i;
|
||||
}
|
||||
|
||||
// Parse an array index at compile time
|
||||
static consteval std::pair<std::size_t, std::size_t> parse_array_index(std::size_t start) {
|
||||
std::size_t index = 0;
|
||||
std::size_t i = start;
|
||||
|
||||
while (i < path_str.size() && path_str[i] >= '0' && path_str[i] <= '9') {
|
||||
index = index * 10 + (path_str[i] - '0');
|
||||
++i;
|
||||
}
|
||||
|
||||
return {i, index};
|
||||
}
|
||||
};
|
||||
|
||||
// Compile-time path accessor generator
|
||||
template<typename T, constevalutil::fixed_string Path>
|
||||
struct path_accessor {
|
||||
using value = ::simdjson::SIMDJSON_IMPLEMENTATION::ondemand::value;
|
||||
|
||||
static constexpr auto parser = json_path_parser<Path>();
|
||||
static constexpr std::size_t num_steps = parser.count_steps();
|
||||
static constexpr std::string_view path_view = Path.view();
|
||||
|
||||
// Compile-time accessor generation
|
||||
// If T is a struct, validates the path at compile time
|
||||
// If T is void, skips validation
|
||||
template<typename DocOrValue>
|
||||
static inline simdjson_result<value> access(DocOrValue& doc_or_val) noexcept {
|
||||
// Validate path at compile time if T is a struct
|
||||
if constexpr (std::is_class_v<T>) {
|
||||
constexpr bool path_valid = validate_path();
|
||||
static_assert(path_valid, "JSONPath does not match struct definition");
|
||||
}
|
||||
|
||||
// Parse the path at compile time to build access steps
|
||||
return access_impl<parser.skip_root()>(doc_or_val.get_value());
|
||||
}
|
||||
|
||||
private:
|
||||
// Recursive template to generate compile-time accessor code
|
||||
// PathPos parameter is the position in the path string (compile-time constant)
|
||||
template<std::size_t PathPos>
|
||||
static inline simdjson_result<value> access_impl(simdjson_result<value> current) noexcept {
|
||||
if (current.error()) return current;
|
||||
|
||||
// Base case: if we've consumed the entire path, return current value
|
||||
if constexpr (PathPos >= path_view.size()) {
|
||||
return current;
|
||||
} else if constexpr (path_view[PathPos] == '.') {
|
||||
// Field access - extract field name at compile time
|
||||
constexpr auto field_info = parse_next_field(PathPos);
|
||||
constexpr std::string_view field_name = std::get<0>(field_info);
|
||||
constexpr std::size_t next_pos = std::get<1>(field_info);
|
||||
|
||||
// Generate field access code
|
||||
auto obj_result = current.get_object();
|
||||
if (obj_result.error()) return obj_result.error();
|
||||
|
||||
auto obj = obj_result.value_unsafe();
|
||||
auto next_value = obj.find_field_unordered(field_name);
|
||||
|
||||
// Recursively process next step at compile time
|
||||
return access_impl<next_pos>(next_value);
|
||||
|
||||
} else if constexpr (path_view[PathPos] == '[') {
|
||||
// Array or bracket notation
|
||||
constexpr auto bracket_info = parse_bracket(PathPos);
|
||||
constexpr bool is_field = std::get<0>(bracket_info);
|
||||
constexpr std::size_t next_pos = std::get<2>(bracket_info);
|
||||
|
||||
if constexpr (is_field) {
|
||||
// Field access with bracket notation
|
||||
constexpr std::string_view field_name = std::get<1>(bracket_info);
|
||||
|
||||
auto obj_result = current.get_object();
|
||||
if (obj_result.error()) return obj_result.error();
|
||||
|
||||
auto obj = obj_result.value_unsafe();
|
||||
auto next_value = obj.find_field_unordered(field_name);
|
||||
|
||||
return access_impl<next_pos>(next_value);
|
||||
|
||||
} else {
|
||||
// Array index access
|
||||
constexpr std::size_t index = std::get<3>(bracket_info);
|
||||
|
||||
auto arr_result = current.get_array();
|
||||
if (arr_result.error()) return arr_result.error();
|
||||
|
||||
auto arr = arr_result.value_unsafe();
|
||||
auto next_value = arr.at(index);
|
||||
|
||||
return access_impl<next_pos>(next_value);
|
||||
}
|
||||
} else {
|
||||
// Skip unexpected characters and continue
|
||||
return access_impl<PathPos + 1>(current);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: Parse next field name at compile time
|
||||
static consteval auto parse_next_field(std::size_t start) {
|
||||
std::size_t i = start + 1; // skip '.'
|
||||
std::size_t field_start = i;
|
||||
while (i < path_view.size() && path_view[i] != '.' && path_view[i] != '[') {
|
||||
++i;
|
||||
}
|
||||
std::string_view field_name = path_view.substr(field_start, i - field_start);
|
||||
return std::make_tuple(field_name, i);
|
||||
}
|
||||
|
||||
// Helper: Parse bracket notation at compile time
|
||||
// Returns: (is_field, field_name, next_pos, index)
|
||||
static consteval auto parse_bracket(std::size_t start) {
|
||||
std::size_t i = start + 1; // skip '['
|
||||
|
||||
if (i < path_view.size() && (path_view[i] == '"' || path_view[i] == '\'')) {
|
||||
// Field access
|
||||
char quote = path_view[i];
|
||||
++i;
|
||||
std::size_t field_start = i;
|
||||
while (i < path_view.size() && path_view[i] != quote) {
|
||||
++i;
|
||||
}
|
||||
std::string_view field_name = path_view.substr(field_start, i - field_start);
|
||||
if (i < path_view.size()) ++i; // skip closing quote
|
||||
if (i < path_view.size() && path_view[i] == ']') ++i;
|
||||
|
||||
return std::make_tuple(true, field_name, i, std::size_t(0));
|
||||
} else {
|
||||
// Array index
|
||||
std::size_t index = 0;
|
||||
while (i < path_view.size() && path_view[i] >= '0' && path_view[i] <= '9') {
|
||||
index = index * 10 + (path_view[i] - '0');
|
||||
++i;
|
||||
}
|
||||
if (i < path_view.size() && path_view[i] == ']') ++i;
|
||||
|
||||
return std::make_tuple(false, std::string_view{}, i, index);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: Check if a type has a member with given name using reflection
|
||||
template<typename Type>
|
||||
static consteval bool has_member(std::string_view member_name) {
|
||||
constexpr auto members = std::meta::nonstatic_data_members_of(
|
||||
^^Type, std::meta::access_context::unchecked()
|
||||
);
|
||||
|
||||
for (auto mem : members) {
|
||||
if (std::meta::identifier_of(mem) == member_name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Helper: Get type of member by name using reflection
|
||||
template<typename Type>
|
||||
static consteval auto get_member_type(std::string_view member_name) {
|
||||
constexpr auto members = std::meta::nonstatic_data_members_of(
|
||||
^^Type, std::meta::access_context::unchecked()
|
||||
);
|
||||
|
||||
for (auto mem : members) {
|
||||
if (std::meta::identifier_of(mem) == member_name) {
|
||||
return std::meta::type_of(mem);
|
||||
}
|
||||
}
|
||||
return ^^void; // Return void if not found
|
||||
}
|
||||
|
||||
public:
|
||||
// Helper: Check if type represents a JSON array (indexable sequence container)
|
||||
//
|
||||
// Rationale:
|
||||
// - We're validating JSONPath semantics: path[index] requires subscript access
|
||||
// - JSON arrays are ordered sequences with numeric indexed access
|
||||
// - Runtime JSON parsing uses operator[] for array element access
|
||||
//
|
||||
// Requirements:
|
||||
// 1. Must support operator[](size_t) for indexed access
|
||||
// 2. Must represent a sequence (have value_type)
|
||||
// 3. Must NOT be a string (strings are JSON strings, not arrays)
|
||||
// 4. Must NOT be associative (maps/sets have different JSON semantics)
|
||||
//
|
||||
// Helper to check if a reflected type satisfies the indexable_container concept
|
||||
// We use std::meta::substitute to evaluate the concept against a reflected type
|
||||
static consteval bool is_array_like_reflected(std::meta::info type_reflection) {
|
||||
// C-style arrays
|
||||
if (std::meta::is_array_type(type_reflection)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Test if the reflected type satisfies our indexable_container concept
|
||||
// substitute evaluates indexable_container_v<T> where T is the reflected type
|
||||
if (std::meta::can_substitute(^^concepts::indexable_container_v, {type_reflection})) {
|
||||
return std::meta::extract<bool>(std::meta::substitute(^^concepts::indexable_container_v, {type_reflection}));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Helper: Get element type from reflected array-like type
|
||||
static consteval std::meta::info get_element_type_reflected(std::meta::info type_reflection) {
|
||||
// Check for C-style arrays first using reflection predicates
|
||||
if (std::meta::is_array_type(type_reflection)) {
|
||||
// For C-style arrays (e.g., int[10]), extract element type using std::meta::remove_extent
|
||||
return std::meta::remove_extent(type_reflection);
|
||||
}
|
||||
|
||||
// Look for value_type member in the reflected type (standard containers)
|
||||
auto members = std::meta::members_of(type_reflection, std::meta::access_context::unchecked());
|
||||
for (auto mem : members) {
|
||||
if (std::meta::is_type(mem)) {
|
||||
auto name = std::meta::identifier_of(mem);
|
||||
if (name == "value_type") {
|
||||
// Return the reflected type of value_type
|
||||
return mem;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ^^void;
|
||||
}
|
||||
|
||||
// Helper: Check if a non-reflected type is array-like (for template metaprogramming)
|
||||
template<typename Type>
|
||||
static consteval bool is_container_type() {
|
||||
using BaseType = std::remove_cvref_t<Type>;
|
||||
|
||||
// Has value_type (std::vector, std::array, std::list, etc.)
|
||||
if constexpr (requires { typename BaseType::value_type; }) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// C-style array
|
||||
if constexpr (std::is_array_v<BaseType>) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Helper: Extract element type from non-reflected container
|
||||
template<typename Type>
|
||||
using extract_element_type = std::conditional_t<
|
||||
requires { typename std::remove_cvref_t<Type>::value_type; },
|
||||
typename std::remove_cvref_t<Type>::value_type,
|
||||
std::conditional_t<
|
||||
std::is_array_v<std::remove_cvref_t<Type>>,
|
||||
std::remove_extent_t<std::remove_cvref_t<Type>>,
|
||||
void
|
||||
>
|
||||
>;
|
||||
|
||||
public:
|
||||
// Validate that the path matches the struct definition using reflection
|
||||
static consteval bool validate_path() {
|
||||
if constexpr (!std::is_class_v<T>) {
|
||||
// If T is void or not a class, we can't validate - allow it
|
||||
return true;
|
||||
}
|
||||
|
||||
auto current_type = ^^T;
|
||||
std::size_t i = parser.skip_root();
|
||||
|
||||
while (i < path_view.size()) {
|
||||
if (path_view[i] == '.') {
|
||||
// Field access - validate member exists
|
||||
++i;
|
||||
std::size_t field_start = i;
|
||||
while (i < path_view.size() && path_view[i] != '.' && path_view[i] != '[') {
|
||||
++i;
|
||||
}
|
||||
|
||||
std::string_view field_name = path_view.substr(field_start, i - field_start);
|
||||
|
||||
// Check if current type has this member
|
||||
bool found = false;
|
||||
auto members = std::meta::nonstatic_data_members_of(
|
||||
current_type, std::meta::access_context::unchecked()
|
||||
);
|
||||
|
||||
for (auto mem : members) {
|
||||
if (std::meta::identifier_of(mem) == field_name) {
|
||||
current_type = std::meta::type_of(mem);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return false; // Member not found
|
||||
}
|
||||
|
||||
} else if (path_view[i] == '[') {
|
||||
++i;
|
||||
if (i >= path_view.size()) return false;
|
||||
|
||||
if (path_view[i] == '"' || path_view[i] == '\'') {
|
||||
// Field access with bracket notation
|
||||
char quote = path_view[i];
|
||||
++i;
|
||||
std::size_t field_start = i;
|
||||
while (i < path_view.size() && path_view[i] != quote) {
|
||||
++i;
|
||||
}
|
||||
|
||||
std::string_view field_name = path_view.substr(field_start, i - field_start);
|
||||
if (i < path_view.size()) ++i; // skip closing quote
|
||||
if (i < path_view.size() && path_view[i] == ']') ++i;
|
||||
|
||||
// Check if current type has this member
|
||||
bool found = false;
|
||||
auto members = std::meta::nonstatic_data_members_of(
|
||||
current_type, std::meta::access_context::unchecked()
|
||||
);
|
||||
|
||||
for (auto mem : members) {
|
||||
if (std::meta::identifier_of(mem) == field_name) {
|
||||
current_type = std::meta::type_of(mem);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return false; // Member not found
|
||||
}
|
||||
|
||||
} else {
|
||||
// Array index - verify current type is array-like and extract element type
|
||||
while (i < path_view.size() && path_view[i] >= '0' && path_view[i] <= '9') {
|
||||
++i;
|
||||
}
|
||||
|
||||
if (i < path_view.size() && path_view[i] == ']') ++i;
|
||||
|
||||
// Check if current type is array-like
|
||||
if (!is_array_like_reflected(current_type)) {
|
||||
return false; // Not an array/container type
|
||||
}
|
||||
|
||||
// Extract element type and continue validation
|
||||
auto new_type = get_element_type_reflected(current_type);
|
||||
|
||||
// If we couldn't extract element type (returns ^^void), fail validation
|
||||
if (new_type == ^^void) {
|
||||
return false; // Could not determine element type
|
||||
}
|
||||
|
||||
current_type = new_type;
|
||||
}
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
return true; // Path validated successfully
|
||||
}
|
||||
};
|
||||
|
||||
// User-facing API: compile-time path accessor
|
||||
// When used with a struct type T, validates the path at compile time
|
||||
// Example: at_path_compiled<User, ".name">(doc)
|
||||
template<typename T, constevalutil::fixed_string Path, typename DocOrValue>
|
||||
inline simdjson_result<::simdjson::SIMDJSON_IMPLEMENTATION::ondemand::value> at_path_compiled(DocOrValue& doc_or_val) noexcept {
|
||||
using accessor = path_accessor<T, Path>;
|
||||
return accessor::access(doc_or_val);
|
||||
}
|
||||
|
||||
// Convenience overload without type parameter (no validation, just compile-time parsing)
|
||||
// Example: at_path_compiled<".name">(doc)
|
||||
template<constevalutil::fixed_string Path, typename DocOrValue>
|
||||
inline simdjson_result<::simdjson::SIMDJSON_IMPLEMENTATION::ondemand::value> at_path_compiled(DocOrValue& doc_or_val) noexcept {
|
||||
using accessor = path_accessor<void, Path>;
|
||||
return accessor::access(doc_or_val);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// JSON Pointer Compile-Time Support (RFC 6901)
|
||||
// ============================================================================
|
||||
|
||||
// JSON Pointer parser - simpler syntax than JSONPath
|
||||
// Format: /field/0/nested (slash-separated, numeric for arrays)
|
||||
template<constevalutil::fixed_string Pointer>
|
||||
struct json_pointer_parser {
|
||||
static constexpr std::string_view pointer_str = Pointer.view();
|
||||
|
||||
// Unescape JSON Pointer token: ~0 -> ~, ~1 -> /
|
||||
static consteval void unescape_token(std::string_view src, char* dest, std::size_t& out_len) {
|
||||
out_len = 0;
|
||||
for (std::size_t i = 0; i < src.size(); ++i) {
|
||||
if (src[i] == '~' && i + 1 < src.size()) {
|
||||
if (src[i + 1] == '0') {
|
||||
dest[out_len++] = '~';
|
||||
++i;
|
||||
} else if (src[i + 1] == '1') {
|
||||
dest[out_len++] = '/';
|
||||
++i;
|
||||
} else {
|
||||
dest[out_len++] = src[i];
|
||||
}
|
||||
} else {
|
||||
dest[out_len++] = src[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if token is numeric (array index)
|
||||
static consteval bool is_numeric(std::string_view token) {
|
||||
if (token.empty()) return false;
|
||||
if (token[0] == '0' && token.size() > 1) return false; // Leading zeros not allowed
|
||||
for (char c : token) {
|
||||
if (c < '0' || c > '9') return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parse numeric token to index
|
||||
static consteval std::size_t parse_index(std::string_view token) {
|
||||
std::size_t result = 0;
|
||||
for (char c : token) {
|
||||
result = result * 10 + (c - '0');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Count number of tokens (path segments)
|
||||
static consteval std::size_t count_tokens() {
|
||||
if (pointer_str.empty() || pointer_str == "/") return 0;
|
||||
|
||||
std::size_t count = 0;
|
||||
std::size_t pos = pointer_str[0] == '/' ? 1 : 0;
|
||||
|
||||
while (pos < pointer_str.size()) {
|
||||
++count;
|
||||
std::size_t next_slash = pointer_str.find('/', pos);
|
||||
if (next_slash == std::string_view::npos) break;
|
||||
pos = next_slash + 1;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
// Get the Nth token at compile time
|
||||
static consteval std::string_view get_token(std::size_t token_index) {
|
||||
std::size_t pos = pointer_str[0] == '/' ? 1 : 0;
|
||||
std::size_t current_token = 0;
|
||||
|
||||
while (current_token < token_index) {
|
||||
std::size_t next_slash = pointer_str.find('/', pos);
|
||||
pos = next_slash + 1;
|
||||
++current_token;
|
||||
}
|
||||
|
||||
std::size_t token_end = pointer_str.find('/', pos);
|
||||
if (token_end == std::string_view::npos) token_end = pointer_str.size();
|
||||
|
||||
return pointer_str.substr(pos, token_end - pos);
|
||||
}
|
||||
};
|
||||
|
||||
// JSON Pointer accessor - similar to path_accessor but for JSON Pointer syntax
|
||||
template<typename T, constevalutil::fixed_string Pointer>
|
||||
struct pointer_accessor {
|
||||
using parser = json_pointer_parser<Pointer>;
|
||||
static constexpr std::string_view pointer_view = Pointer.view();
|
||||
static constexpr std::size_t token_count = parser::count_tokens();
|
||||
|
||||
// Validate JSON Pointer against struct definition
|
||||
static consteval bool validate_pointer() {
|
||||
if constexpr (!std::is_class_v<T>) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto current_type = ^^T;
|
||||
std::size_t pos = pointer_view[0] == '/' ? 1 : 0;
|
||||
|
||||
while (pos < pointer_view.size()) {
|
||||
// Extract token up to next /
|
||||
std::size_t token_end = pointer_view.find('/', pos);
|
||||
if (token_end == std::string_view::npos) token_end = pointer_view.size();
|
||||
|
||||
std::string_view token = pointer_view.substr(pos, token_end - pos);
|
||||
|
||||
// Check if it's an array index
|
||||
if (parser::is_numeric(token)) {
|
||||
// Validate current type is array-like
|
||||
if (!path_accessor<T, Pointer>::is_array_like_reflected(current_type)) {
|
||||
return false;
|
||||
}
|
||||
current_type = path_accessor<T, Pointer>::get_element_type_reflected(current_type);
|
||||
} else {
|
||||
// Field access - validate member exists
|
||||
bool found = false;
|
||||
auto members = std::meta::nonstatic_data_members_of(
|
||||
current_type, std::meta::access_context::unchecked()
|
||||
);
|
||||
|
||||
for (auto mem : members) {
|
||||
if (std::meta::identifier_of(mem) == token) {
|
||||
current_type = std::meta::type_of(mem);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) return false;
|
||||
}
|
||||
|
||||
pos = token_end + 1;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Recursive accessor implementation
|
||||
template<std::size_t TokenIndex>
|
||||
static inline simdjson_result<value> access_impl(simdjson_result<value> current) noexcept {
|
||||
if constexpr (TokenIndex >= token_count) {
|
||||
return current;
|
||||
} else {
|
||||
// Get token at compile time
|
||||
constexpr std::string_view token = parser::get_token(TokenIndex);
|
||||
|
||||
if constexpr (parser::is_numeric(token)) {
|
||||
// Array index access
|
||||
constexpr std::size_t index = parser::parse_index(token);
|
||||
auto arr = current.get_array().value_unsafe();
|
||||
auto next_value = arr.at(index);
|
||||
return access_impl<TokenIndex + 1>(next_value);
|
||||
} else {
|
||||
// Field access
|
||||
auto obj = current.get_object().value_unsafe();
|
||||
auto next_value = obj.find_field_unordered(token);
|
||||
return access_impl<TokenIndex + 1>(next_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Main entry point
|
||||
template<typename DocOrValue>
|
||||
static inline simdjson_result<value> access(DocOrValue& doc_or_val) noexcept {
|
||||
if constexpr (std::is_class_v<T>) {
|
||||
constexpr bool pointer_valid = validate_pointer();
|
||||
static_assert(pointer_valid, "JSON Pointer does not match struct definition");
|
||||
}
|
||||
|
||||
if (pointer_view.empty() || pointer_view == "/") {
|
||||
// Root pointer
|
||||
if constexpr (requires { doc_or_val.get_value(); }) {
|
||||
return doc_or_val.get_value();
|
||||
} else {
|
||||
return doc_or_val;
|
||||
}
|
||||
}
|
||||
|
||||
simdjson_result<value> current = doc_or_val.get_value();
|
||||
return access_impl<0>(current);
|
||||
}
|
||||
};
|
||||
|
||||
// User-facing API: compile-time JSON Pointer accessor with validation
|
||||
// Example: at_pointer_compiled<User, "/name">(doc)
|
||||
template<typename T, constevalutil::fixed_string Pointer, typename DocOrValue>
|
||||
inline simdjson_result<::simdjson::SIMDJSON_IMPLEMENTATION::ondemand::value> at_pointer_compiled(DocOrValue& doc_or_val) noexcept {
|
||||
using accessor = pointer_accessor<T, Pointer>;
|
||||
return accessor::access(doc_or_val);
|
||||
}
|
||||
|
||||
// Convenience overload without type parameter (no validation)
|
||||
// Example: at_pointer_compiled<"/name">(doc)
|
||||
template<constevalutil::fixed_string Pointer, typename DocOrValue>
|
||||
inline simdjson_result<::simdjson::SIMDJSON_IMPLEMENTATION::ondemand::value> at_pointer_compiled(DocOrValue& doc_or_val) noexcept {
|
||||
using accessor = pointer_accessor<void, Pointer>;
|
||||
return accessor::access(doc_or_val);
|
||||
}
|
||||
|
||||
} // namespace json_path
|
||||
} // namespace ondemand
|
||||
} // namespace SIMDJSON_IMPLEMENTATION
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_SUPPORTS_CONCEPTS && SIMDJSON_STATIC_REFLECTION
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_COMPILE_TIME_ACCESSORS_H
|
||||
|
||||
@@ -7,55 +7,8 @@
|
||||
#include "simdjson/generic/ondemand/array.h"
|
||||
#endif // SIMDJSON_CONDITIONAL_INCLUDE
|
||||
|
||||
#include <concepts>
|
||||
namespace simdjson {
|
||||
|
||||
namespace tag_invoke_fn_ns {
|
||||
void tag_invoke();
|
||||
|
||||
struct tag_invoke_fn {
|
||||
template <typename Tag, typename... Args>
|
||||
requires requires(Tag tag, Args &&...args) {
|
||||
tag_invoke(std::forward<Tag>(tag), std::forward<Args>(args)...);
|
||||
}
|
||||
constexpr auto operator()(Tag tag, Args &&...args) const
|
||||
noexcept(noexcept(tag_invoke(std::forward<Tag>(tag),
|
||||
std::forward<Args>(args)...)))
|
||||
-> decltype(tag_invoke(std::forward<Tag>(tag),
|
||||
std::forward<Args>(args)...)) {
|
||||
return tag_invoke(std::forward<Tag>(tag), std::forward<Args>(args)...);
|
||||
}
|
||||
};
|
||||
} // namespace tag_invoke_fn_ns
|
||||
|
||||
inline namespace tag_invoke_ns {
|
||||
inline constexpr tag_invoke_fn_ns::tag_invoke_fn tag_invoke = {};
|
||||
} // namespace tag_invoke_ns
|
||||
|
||||
template <typename Tag, typename... Args>
|
||||
concept tag_invocable = requires(Tag tag, Args... args) {
|
||||
tag_invoke(std::forward<Tag>(tag), std::forward<Args>(args)...);
|
||||
};
|
||||
|
||||
template <typename Tag, typename... Args>
|
||||
concept nothrow_tag_invocable =
|
||||
tag_invocable<Tag, Args...> && requires(Tag tag, Args... args) {
|
||||
{
|
||||
tag_invoke(std::forward<Tag>(tag), std::forward<Args>(args)...)
|
||||
} noexcept;
|
||||
};
|
||||
|
||||
template <typename Tag, typename... Args>
|
||||
using tag_invoke_result =
|
||||
std::invoke_result<decltype(tag_invoke), Tag, Args...>;
|
||||
|
||||
template <typename Tag, typename... Args>
|
||||
using tag_invoke_result_t =
|
||||
std::invoke_result_t<decltype(tag_invoke), Tag, Args...>;
|
||||
|
||||
template <auto &Tag> using tag_t = std::decay_t<decltype(Tag)>;
|
||||
|
||||
|
||||
struct deserialize_tag;
|
||||
|
||||
/// These types are deserializable in a built-in way
|
||||
|
||||
@@ -141,7 +141,7 @@ simdjson_inline simdjson_result<std::string_view> document::get_string(bool allo
|
||||
return get_root_value_iterator().get_root_string(true, allow_replacement);
|
||||
}
|
||||
template <typename string_type>
|
||||
simdjson_inline error_code document::get_string(string_type& receiver, bool allow_replacement) noexcept {
|
||||
simdjson_warn_unused simdjson_inline error_code document::get_string(string_type& receiver, bool allow_replacement) noexcept {
|
||||
return get_root_value_iterator().get_root_string(receiver, true, allow_replacement);
|
||||
}
|
||||
simdjson_inline simdjson_result<std::string_view> document::get_wobbly_string() noexcept {
|
||||
@@ -167,15 +167,15 @@ template<> simdjson_inline simdjson_result<int64_t> document::get() & noexcept {
|
||||
template<> simdjson_inline simdjson_result<bool> document::get() & noexcept { return get_bool(); }
|
||||
template<> simdjson_inline simdjson_result<value> document::get() & noexcept { return get_value(); }
|
||||
|
||||
template<> simdjson_inline error_code document::get(array& out) & noexcept { return get_array().get(out); }
|
||||
template<> simdjson_inline error_code document::get(object& out) & noexcept { return get_object().get(out); }
|
||||
template<> simdjson_inline error_code document::get(raw_json_string& out) & noexcept { return get_raw_json_string().get(out); }
|
||||
template<> simdjson_inline error_code document::get(std::string_view& out) & noexcept { return get_string(false).get(out); }
|
||||
template<> simdjson_inline error_code document::get(double& out) & noexcept { return get_double().get(out); }
|
||||
template<> simdjson_inline error_code document::get(uint64_t& out) & noexcept { return get_uint64().get(out); }
|
||||
template<> simdjson_inline error_code document::get(int64_t& out) & noexcept { return get_int64().get(out); }
|
||||
template<> simdjson_inline error_code document::get(bool& out) & noexcept { return get_bool().get(out); }
|
||||
template<> simdjson_inline error_code document::get(value& out) & noexcept { return get_value().get(out); }
|
||||
template<> simdjson_warn_unused simdjson_inline error_code document::get(array& out) & noexcept { return get_array().get(out); }
|
||||
template<> simdjson_warn_unused simdjson_inline error_code document::get(object& out) & noexcept { return get_object().get(out); }
|
||||
template<> simdjson_warn_unused simdjson_inline error_code document::get(raw_json_string& out) & noexcept { return get_raw_json_string().get(out); }
|
||||
template<> simdjson_warn_unused simdjson_inline error_code document::get(std::string_view& out) & noexcept { return get_string(false).get(out); }
|
||||
template<> simdjson_warn_unused simdjson_inline error_code document::get(double& out) & noexcept { return get_double().get(out); }
|
||||
template<> simdjson_warn_unused simdjson_inline error_code document::get(uint64_t& out) & noexcept { return get_uint64().get(out); }
|
||||
template<> simdjson_warn_unused simdjson_inline error_code document::get(int64_t& out) & noexcept { return get_int64().get(out); }
|
||||
template<> simdjson_warn_unused simdjson_inline error_code document::get(bool& out) & noexcept { return get_bool().get(out); }
|
||||
template<> simdjson_warn_unused simdjson_inline error_code document::get(value& out) & noexcept { return get_value().get(out); }
|
||||
|
||||
template<> simdjson_deprecated simdjson_inline simdjson_result<raw_json_string> document::get() && noexcept { return get_raw_json_string(); }
|
||||
template<> simdjson_deprecated simdjson_inline simdjson_result<std::string_view> document::get() && noexcept { return get_string(false); }
|
||||
@@ -245,7 +245,7 @@ simdjson_inline simdjson_result<value> document::operator[](const char *key) & n
|
||||
return start_or_resume_object()[key];
|
||||
}
|
||||
|
||||
simdjson_inline error_code document::consume() noexcept {
|
||||
simdjson_warn_unused simdjson_inline error_code document::consume() noexcept {
|
||||
bool scalar = false;
|
||||
auto error = is_scalar().get(scalar);
|
||||
if(error) { return error; }
|
||||
@@ -347,6 +347,54 @@ simdjson_inline simdjson_result<value> document::at_path(std::string_view json_p
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS && SIMDJSON_STATIC_REFLECTION
|
||||
|
||||
template<constevalutil::fixed_string... FieldNames, typename T>
|
||||
requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0))
|
||||
simdjson_warn_unused simdjson_inline error_code document::extract_into(T& out) & noexcept {
|
||||
// Helper to check if a field name matches any of the requested fields
|
||||
auto should_extract = [](std::string_view field_name) constexpr -> bool {
|
||||
return ((FieldNames.view() == field_name) || ...);
|
||||
};
|
||||
|
||||
// Iterate through all members of T using reflection
|
||||
template for (constexpr auto mem : std::define_static_array(
|
||||
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));
|
||||
|
||||
// Only extract this field if it's in our list of requested fields
|
||||
if constexpr (should_extract(key)) {
|
||||
// Try to find and extract the field
|
||||
if constexpr (concepts::optional_type<decltype(out.[:mem:])>) {
|
||||
// For optional fields, it's ok if they're missing
|
||||
auto field_result = find_field_unordered(key);
|
||||
if (!field_result.error()) {
|
||||
auto error = field_result.get(out.[:mem:]);
|
||||
if (error && error != NO_SUCH_FIELD) {
|
||||
return error;
|
||||
}
|
||||
} else if (field_result.error() != NO_SUCH_FIELD) {
|
||||
return field_result.error();
|
||||
} else {
|
||||
out.[:mem:].reset();
|
||||
}
|
||||
} else {
|
||||
// For required fields (in the requested list), fail if missing
|
||||
SIMDJSON_TRY((*this)[key].get(out.[:mem:]));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
#endif // SIMDJSON_SUPPORTS_CONCEPTS && SIMDJSON_STATIC_REFLECTION
|
||||
|
||||
} // namespace ondemand
|
||||
} // namespace SIMDJSON_IMPLEMENTATION
|
||||
} // namespace simdjson
|
||||
@@ -454,7 +502,7 @@ simdjson_inline simdjson_result<std::string_view> simdjson_result<SIMDJSON_IMPLE
|
||||
return first.get_string(allow_replacement);
|
||||
}
|
||||
template <typename string_type>
|
||||
simdjson_inline error_code simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::get_string(string_type& receiver, bool allow_replacement) noexcept {
|
||||
simdjson_warn_unused simdjson_inline error_code simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::get_string(string_type& receiver, bool allow_replacement) noexcept {
|
||||
if (error()) { return error(); }
|
||||
return first.get_string(receiver, allow_replacement);
|
||||
}
|
||||
@@ -490,12 +538,12 @@ simdjson_deprecated simdjson_inline simdjson_result<T> simdjson_result<SIMDJSON_
|
||||
return std::forward<SIMDJSON_IMPLEMENTATION::ondemand::document>(first).get<T>();
|
||||
}
|
||||
template<typename T>
|
||||
simdjson_inline error_code simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::get(T &out) & noexcept {
|
||||
simdjson_warn_unused simdjson_inline error_code simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::get(T &out) & noexcept {
|
||||
if (error()) { return error(); }
|
||||
return first.get<T>(out);
|
||||
}
|
||||
template<typename T>
|
||||
simdjson_inline error_code simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::get(T &out) && noexcept {
|
||||
simdjson_warn_unused simdjson_inline error_code simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::get(T &out) && noexcept {
|
||||
if (error()) { return error(); }
|
||||
return std::forward<SIMDJSON_IMPLEMENTATION::ondemand::document>(first).get<T>(out);
|
||||
}
|
||||
@@ -505,8 +553,8 @@ template<> simdjson_deprecated simdjson_inline simdjson_result<SIMDJSON_IMPLEMEN
|
||||
if (error()) { return error(); }
|
||||
return std::forward<SIMDJSON_IMPLEMENTATION::ondemand::document>(first);
|
||||
}
|
||||
template<> simdjson_inline error_code simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::get<SIMDJSON_IMPLEMENTATION::ondemand::document>(SIMDJSON_IMPLEMENTATION::ondemand::document &out) & noexcept = delete;
|
||||
template<> simdjson_inline error_code simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::get<SIMDJSON_IMPLEMENTATION::ondemand::document>(SIMDJSON_IMPLEMENTATION::ondemand::document &out) && noexcept {
|
||||
template<> simdjson_warn_unused simdjson_inline error_code simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::get<SIMDJSON_IMPLEMENTATION::ondemand::document>(SIMDJSON_IMPLEMENTATION::ondemand::document &out) & noexcept = delete;
|
||||
template<> simdjson_warn_unused simdjson_inline error_code simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::get<SIMDJSON_IMPLEMENTATION::ondemand::document>(SIMDJSON_IMPLEMENTATION::ondemand::document &out) && noexcept {
|
||||
if (error()) { return error(); }
|
||||
out = std::forward<SIMDJSON_IMPLEMENTATION::ondemand::document>(first);
|
||||
return SUCCESS;
|
||||
@@ -624,6 +672,15 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> simdjs
|
||||
return first.at_path(json_path);
|
||||
}
|
||||
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
template<constevalutil::fixed_string... FieldNames, typename T>
|
||||
requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0))
|
||||
simdjson_warn_unused simdjson_inline error_code simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::extract_into(T& out) & noexcept {
|
||||
if (error()) { return error(); }
|
||||
return first.extract_into<FieldNames...>(out);
|
||||
}
|
||||
#endif // SIMDJSON_STATIC_REFLECTION
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
|
||||
@@ -658,7 +715,7 @@ simdjson_inline simdjson_result<double> document_reference::get_double() noexcep
|
||||
simdjson_inline simdjson_result<double> document_reference::get_double_in_string() noexcept { return doc->get_root_value_iterator().get_root_double(false); }
|
||||
simdjson_inline simdjson_result<std::string_view> document_reference::get_string(bool allow_replacement) noexcept { return doc->get_root_value_iterator().get_root_string(false, allow_replacement); }
|
||||
template <typename string_type>
|
||||
simdjson_inline error_code document_reference::get_string(string_type& receiver, bool allow_replacement) noexcept { return doc->get_root_value_iterator().get_root_string(receiver, false, allow_replacement); }
|
||||
simdjson_warn_unused simdjson_inline error_code document_reference::get_string(string_type& receiver, bool allow_replacement) noexcept { return doc->get_root_value_iterator().get_root_string(receiver, false, allow_replacement); }
|
||||
simdjson_inline simdjson_result<std::string_view> document_reference::get_wobbly_string() noexcept { return doc->get_root_value_iterator().get_root_wobbly_string(false); }
|
||||
simdjson_inline simdjson_result<raw_json_string> document_reference::get_raw_json_string() noexcept { return doc->get_root_value_iterator().get_root_raw_json_string(false); }
|
||||
simdjson_inline simdjson_result<bool> document_reference::get_bool() noexcept { return doc->get_root_value_iterator().get_root_bool(false); }
|
||||
@@ -711,7 +768,13 @@ simdjson_inline simdjson_result<value> document_reference::at_pointer(std::strin
|
||||
simdjson_inline simdjson_result<value> document_reference::at_path(std::string_view json_path) noexcept { return doc->at_path(json_path); }
|
||||
simdjson_inline simdjson_result<std::string_view> document_reference::raw_json() noexcept { return doc->raw_json();}
|
||||
simdjson_inline document_reference::operator document&() const noexcept { return *doc; }
|
||||
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS && SIMDJSON_STATIC_REFLECTION
|
||||
template<constevalutil::fixed_string... FieldNames, typename T>
|
||||
requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0))
|
||||
simdjson_warn_unused simdjson_inline error_code document_reference::extract_into(T& out) & noexcept {
|
||||
return doc->extract_into<FieldNames...>(out);
|
||||
}
|
||||
#endif // SIMDJSON_SUPPORTS_CONCEPTS && SIMDJSON_STATIC_REFLECTION
|
||||
} // namespace ondemand
|
||||
} // namespace SIMDJSON_IMPLEMENTATION
|
||||
} // namespace simdjson
|
||||
@@ -808,7 +871,7 @@ simdjson_inline simdjson_result<std::string_view> simdjson_result<SIMDJSON_IMPLE
|
||||
return first.get_string(allow_replacement);
|
||||
}
|
||||
template <typename string_type>
|
||||
simdjson_inline error_code simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::get_string(string_type& receiver, bool allow_replacement) noexcept {
|
||||
simdjson_warn_unused simdjson_inline error_code simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::get_string(string_type& receiver, bool allow_replacement) noexcept {
|
||||
if (error()) { return error(); }
|
||||
return first.get_string(receiver, allow_replacement);
|
||||
}
|
||||
@@ -843,12 +906,12 @@ simdjson_inline simdjson_result<T> simdjson_result<SIMDJSON_IMPLEMENTATION::onde
|
||||
return std::forward<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>(first).get<T>();
|
||||
}
|
||||
template <class T>
|
||||
simdjson_inline error_code simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::get(T &out) & noexcept {
|
||||
simdjson_warn_unused simdjson_inline error_code simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::get(T &out) & noexcept {
|
||||
if (error()) { return error(); }
|
||||
return first.get<T>(out);
|
||||
}
|
||||
template <class T>
|
||||
simdjson_inline error_code simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::get(T &out) && noexcept {
|
||||
simdjson_warn_unused simdjson_inline error_code simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::get(T &out) && noexcept {
|
||||
if (error()) { return error(); }
|
||||
return std::forward<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>(first).get<T>(out);
|
||||
}
|
||||
@@ -865,13 +928,13 @@ simdjson_inline simdjson_result<bool> simdjson_result<SIMDJSON_IMPLEMENTATION::o
|
||||
return first.is_string();
|
||||
}
|
||||
template <>
|
||||
simdjson_inline error_code simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::get(SIMDJSON_IMPLEMENTATION::ondemand::document_reference &out) & noexcept {
|
||||
simdjson_warn_unused simdjson_inline error_code simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::get(SIMDJSON_IMPLEMENTATION::ondemand::document_reference &out) & noexcept {
|
||||
if (error()) { return error(); }
|
||||
out = first;
|
||||
return SUCCESS;
|
||||
}
|
||||
template <>
|
||||
simdjson_inline error_code simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::get(SIMDJSON_IMPLEMENTATION::ondemand::document_reference &out) && noexcept {
|
||||
simdjson_warn_unused simdjson_inline error_code simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::get(SIMDJSON_IMPLEMENTATION::ondemand::document_reference &out) && noexcept {
|
||||
if (error()) { return error(); }
|
||||
out = first;
|
||||
return SUCCESS;
|
||||
@@ -959,7 +1022,14 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> simdjs
|
||||
}
|
||||
return first.at_path(json_path);
|
||||
}
|
||||
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
template<constevalutil::fixed_string... FieldNames, typename T>
|
||||
requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0))
|
||||
simdjson_warn_unused simdjson_inline error_code simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::extract_into(T& out) & noexcept {
|
||||
if (error()) { return error(); }
|
||||
return first.extract_into<FieldNames...>(out);
|
||||
}
|
||||
#endif // SIMDJSON_STATIC_REFLECTION
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_DOCUMENT_INL_H
|
||||
|
||||
@@ -117,7 +117,7 @@ public:
|
||||
* @returns INCORRECT_TYPE if the JSON value is not a string. Otherwise, we return SUCCESS.
|
||||
*/
|
||||
template <typename string_type>
|
||||
simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept;
|
||||
simdjson_warn_unused simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept;
|
||||
/**
|
||||
* Cast this JSON value to a string.
|
||||
*
|
||||
@@ -228,7 +228,7 @@ public:
|
||||
* @returns SUCCESS If the parse succeeded and the out parameter was set to the value.
|
||||
*/
|
||||
template<typename T>
|
||||
simdjson_inline error_code get(T &out) &
|
||||
simdjson_warn_unused simdjson_inline error_code get(T &out) &
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS
|
||||
noexcept(custom_deserializable<T, document> ? nothrow_custom_deserializable<T, document> : true)
|
||||
#else
|
||||
@@ -725,11 +725,41 @@ public:
|
||||
* the JSON document.
|
||||
*/
|
||||
simdjson_inline simdjson_result<std::string_view> raw_json() noexcept;
|
||||
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
/**
|
||||
* Extract only specific fields from the JSON object into a struct.
|
||||
*
|
||||
* This allows selective deserialization of only the fields you need,
|
||||
* potentially improving performance by skipping unwanted fields.
|
||||
*
|
||||
* Example:
|
||||
* ```c++
|
||||
* struct Car {
|
||||
* std::string make;
|
||||
* std::string model;
|
||||
* int year;
|
||||
* double price;
|
||||
* };
|
||||
*
|
||||
* Car car;
|
||||
* doc.extract_into<"make", "model">(car);
|
||||
* // Only 'make' and 'model' fields are extracted from JSON
|
||||
* ```
|
||||
*
|
||||
* @tparam FieldNames Compile-time string literals specifying which fields to extract
|
||||
* @param out The output struct to populate with selected fields
|
||||
* @returns SUCCESS on success, or an error code if a required field is missing or has wrong type
|
||||
*/
|
||||
template<constevalutil::fixed_string... FieldNames, typename T>
|
||||
requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0))
|
||||
simdjson_warn_unused simdjson_inline error_code extract_into(T& out) & noexcept;
|
||||
#endif // SIMDJSON_STATIC_REFLECTION
|
||||
protected:
|
||||
/**
|
||||
* Consumes the document.
|
||||
*/
|
||||
simdjson_inline error_code consume() noexcept;
|
||||
simdjson_warn_unused simdjson_inline error_code consume() noexcept;
|
||||
|
||||
simdjson_inline document(ondemand::json_iterator &&iter) noexcept;
|
||||
simdjson_inline const uint8_t *text(uint32_t idx) const noexcept;
|
||||
@@ -782,7 +812,7 @@ public:
|
||||
simdjson_inline simdjson_result<double> get_double_in_string() noexcept;
|
||||
simdjson_inline simdjson_result<std::string_view> get_string(bool allow_replacement = false) noexcept;
|
||||
template <typename string_type>
|
||||
simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept;
|
||||
simdjson_warn_unused simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept;
|
||||
simdjson_inline simdjson_result<std::string_view> get_wobbly_string() noexcept;
|
||||
simdjson_inline simdjson_result<raw_json_string> get_raw_json_string() noexcept;
|
||||
simdjson_inline simdjson_result<bool> get_bool() noexcept;
|
||||
@@ -826,7 +856,7 @@ public:
|
||||
* @returns SUCCESS If the parse succeeded and the out parameter was set to the value.
|
||||
*/
|
||||
template<typename T>
|
||||
simdjson_inline error_code get(T &out) &
|
||||
simdjson_warn_unused simdjson_inline error_code get(T &out) &
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS
|
||||
noexcept(custom_deserializable<T, document> ? nothrow_custom_deserializable<T, document_reference> : true)
|
||||
#else
|
||||
@@ -861,6 +891,11 @@ public:
|
||||
/** @overload template<typename T> error_code get(T &out) & noexcept */
|
||||
template<typename T> simdjson_inline error_code get(T &out) && noexcept;
|
||||
simdjson_inline simdjson_result<std::string_view> raw_json() noexcept;
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
template<constevalutil::fixed_string... FieldNames, typename T>
|
||||
requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0))
|
||||
simdjson_warn_unused simdjson_inline error_code extract_into(T& out) & noexcept;
|
||||
#endif // SIMDJSON_STATIC_REFLECTION
|
||||
simdjson_inline operator document&() const noexcept;
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
template <class T>
|
||||
@@ -929,7 +964,7 @@ public:
|
||||
simdjson_inline simdjson_result<double> get_double_in_string() noexcept;
|
||||
simdjson_inline simdjson_result<std::string_view> get_string(bool allow_replacement = false) noexcept;
|
||||
template <typename string_type>
|
||||
simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept;
|
||||
simdjson_warn_unused simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept;
|
||||
simdjson_inline simdjson_result<std::string_view> get_wobbly_string() noexcept;
|
||||
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string> get_raw_json_string() noexcept;
|
||||
simdjson_inline simdjson_result<bool> get_bool() noexcept;
|
||||
@@ -984,6 +1019,11 @@ public:
|
||||
|
||||
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> at_pointer(std::string_view json_pointer) noexcept;
|
||||
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> at_path(std::string_view json_path) noexcept;
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
template<constevalutil::fixed_string... FieldNames, typename T>
|
||||
requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0))
|
||||
simdjson_warn_unused simdjson_inline error_code extract_into(T& out) & noexcept;
|
||||
#endif // SIMDJSON_STATIC_REFLECTION
|
||||
};
|
||||
|
||||
|
||||
@@ -1010,7 +1050,7 @@ public:
|
||||
simdjson_inline simdjson_result<double> get_double_in_string() noexcept;
|
||||
simdjson_inline simdjson_result<std::string_view> get_string(bool allow_replacement = false) noexcept;
|
||||
template <typename string_type>
|
||||
simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept;
|
||||
simdjson_warn_unused simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept;
|
||||
simdjson_inline simdjson_result<std::string_view> get_wobbly_string() noexcept;
|
||||
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string> get_raw_json_string() noexcept;
|
||||
simdjson_inline simdjson_result<bool> get_bool() noexcept;
|
||||
@@ -1061,6 +1101,11 @@ public:
|
||||
|
||||
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> at_pointer(std::string_view json_pointer) noexcept;
|
||||
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> at_path(std::string_view json_path) noexcept;
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
template<constevalutil::fixed_string... FieldNames, typename T>
|
||||
requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0))
|
||||
simdjson_warn_unused simdjson_inline error_code extract_into(T& out) & noexcept;
|
||||
#endif // SIMDJSON_STATIC_REFLECTION
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -312,7 +312,10 @@ inline void document_stream::next_document() noexcept {
|
||||
// Always set depth=1 at the start of document
|
||||
doc.iter._depth = 1;
|
||||
// consume comma if comma separated is allowed
|
||||
if (allow_comma_separated) { doc.iter.consume_character(','); }
|
||||
if (allow_comma_separated) {
|
||||
error_code ignored = doc.iter.consume_character(',');
|
||||
static_cast<void>(ignored); // ignored on purpose
|
||||
}
|
||||
// Resets the string buffer at the beginning, thus invalidating the strings.
|
||||
doc.iter._string_buf_loc = parser->string_buf.get();
|
||||
doc.iter._root = doc.iter.position();
|
||||
|
||||
@@ -114,7 +114,7 @@ simdjson_inline simdjson_result<std::string_view> simdjson_result<SIMDJSON_IMPLE
|
||||
}
|
||||
|
||||
template<typename string_type>
|
||||
simdjson_inline error_code simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::field>::unescaped_key(string_type &receiver, bool allow_replacement) noexcept {
|
||||
simdjson_warn_unused simdjson_inline error_code simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::field>::unescaped_key(string_type &receiver, bool allow_replacement) noexcept {
|
||||
if (error()) { return error(); }
|
||||
return first.unescaped_key(receiver, allow_replacement);
|
||||
}
|
||||
|
||||
@@ -25,30 +25,21 @@ namespace simdjson {
|
||||
namespace SIMDJSON_IMPLEMENTATION {
|
||||
namespace builder {
|
||||
|
||||
// Concept that checks if a type is a container but not a string (because
|
||||
// strings handling must be handled differently)
|
||||
template <typename T>
|
||||
concept container_but_not_string =
|
||||
requires(T a) {
|
||||
{ a.size() } -> std::convertible_to<std::size_t>;
|
||||
{
|
||||
a[std::declval<std::size_t>()]
|
||||
}; // check if elements are accessible for the subscript operator
|
||||
} && !std::is_same_v<T, std::string> &&
|
||||
!std::is_same_v<T, std::string_view> && !std::is_same_v<T, const char *>;
|
||||
|
||||
template <class T>
|
||||
requires(container_but_not_string<T>)
|
||||
requires(concepts::container_but_not_string<T> && !require_custom_serialization<T>)
|
||||
constexpr void atom(string_builder &b, const T &t) {
|
||||
if (t.size() == 0) {
|
||||
auto it = t.begin();
|
||||
auto end = t.end();
|
||||
if (it == end) {
|
||||
b.append_raw("[]");
|
||||
return;
|
||||
}
|
||||
b.append('[');
|
||||
atom(b, t[0]);
|
||||
for (size_t i = 1; i < t.size(); ++i) {
|
||||
atom(b, *it);
|
||||
++it;
|
||||
for (; it != end; ++it) {
|
||||
b.append(',');
|
||||
atom(b, t[i]);
|
||||
atom(b, *it);
|
||||
}
|
||||
b.append(']');
|
||||
}
|
||||
@@ -63,6 +54,7 @@ constexpr void atom(string_builder &b, const T &t) {
|
||||
}
|
||||
|
||||
template <concepts::string_view_keyed_map T>
|
||||
requires(!require_custom_serialization<T>)
|
||||
constexpr void atom(string_builder &b, const T &m) {
|
||||
if (m.empty()) {
|
||||
b.append_raw("{}");
|
||||
@@ -91,7 +83,7 @@ constexpr void atom(string_builder &b, const number_type t) {
|
||||
}
|
||||
|
||||
template <class T>
|
||||
requires(std::is_class_v<T> && !container_but_not_string<T> &&
|
||||
requires(std::is_class_v<T> && !concepts::container_but_not_string<T> &&
|
||||
!concepts::string_view_keyed_map<T> &&
|
||||
!concepts::optional_type<T> &&
|
||||
!concepts::smart_pointer<T> &&
|
||||
@@ -99,7 +91,7 @@ template <class T>
|
||||
!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>)
|
||||
!std::is_same_v<T, char> && !require_custom_serialization<T>)
|
||||
constexpr void atom(string_builder &b, const T &t) {
|
||||
int i = 0;
|
||||
b.append('{');
|
||||
@@ -117,6 +109,7 @@ constexpr void atom(string_builder &b, const T &t) {
|
||||
|
||||
// Support for optional types (std::optional, etc.)
|
||||
template <concepts::optional_type T>
|
||||
requires(!require_custom_serialization<T>)
|
||||
constexpr void atom(string_builder &b, const T &opt) {
|
||||
if (opt) {
|
||||
atom(b, opt.value());
|
||||
@@ -127,6 +120,7 @@ constexpr void atom(string_builder &b, const T &opt) {
|
||||
|
||||
// Support for smart pointers (std::unique_ptr, std::shared_ptr, etc.)
|
||||
template <concepts::smart_pointer T>
|
||||
requires(!require_custom_serialization<T>)
|
||||
constexpr void atom(string_builder &b, const T &ptr) {
|
||||
if (ptr) {
|
||||
atom(b, *ptr);
|
||||
@@ -137,7 +131,7 @@ constexpr void atom(string_builder &b, const T &ptr) {
|
||||
|
||||
// Support for enums - serialize as string representation using expand approach from P2996R12
|
||||
template <typename T>
|
||||
requires(std::is_enum_v<T>)
|
||||
requires(std::is_enum_v<T> && !require_custom_serialization<T>)
|
||||
void atom(string_builder &b, const T &e) {
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
constexpr auto enumerators = std::define_static_array(std::meta::enumerators_of(^^T));
|
||||
@@ -158,10 +152,10 @@ void atom(string_builder &b, const T &e) {
|
||||
|
||||
// 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> &&
|
||||
requires(!concepts::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*>)
|
||||
!std::is_same_v<T, std::string_view> && !std::is_same_v<T, const char*> && !require_custom_serialization<T>)
|
||||
constexpr void atom(string_builder &b, const T &container) {
|
||||
if (container.empty()) {
|
||||
b.append_raw("[]");
|
||||
@@ -196,32 +190,35 @@ void append(string_builder &b, const T &t) {
|
||||
}
|
||||
|
||||
template <concepts::optional_type T>
|
||||
requires(!require_custom_serialization<T>)
|
||||
void append(string_builder &b, const T &t) {
|
||||
atom(b, t);
|
||||
}
|
||||
|
||||
template <concepts::smart_pointer T>
|
||||
requires(!require_custom_serialization<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> &&
|
||||
requires(!concepts::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*>)
|
||||
!std::is_same_v<T, std::string_view> && !std::is_same_v<T, const char*> && !require_custom_serialization<T>)
|
||||
void append(string_builder &b, const T &t) {
|
||||
atom(b, t);
|
||||
}
|
||||
|
||||
template <concepts::string_view_keyed_map T>
|
||||
requires(!require_custom_serialization<T>)
|
||||
void append(string_builder &b, const T &t) {
|
||||
atom(b, t);
|
||||
}
|
||||
|
||||
// works for struct
|
||||
template <class Z>
|
||||
requires(std::is_class_v<Z> && !container_but_not_string<Z> &&
|
||||
requires(std::is_class_v<Z> && !concepts::container_but_not_string<Z> &&
|
||||
!concepts::string_view_keyed_map<Z> &&
|
||||
!concepts::optional_type<Z> &&
|
||||
!concepts::smart_pointer<Z> &&
|
||||
@@ -229,7 +226,7 @@ template <class 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>)
|
||||
!std::is_same_v<Z, char> && !require_custom_serialization<Z>)
|
||||
void append(string_builder &b, const Z &z) {
|
||||
int i = 0;
|
||||
b.append('{');
|
||||
@@ -245,23 +242,33 @@ void append(string_builder &b, const Z &z) {
|
||||
b.append('}');
|
||||
}
|
||||
|
||||
// works for container
|
||||
// works for container that have begin() and end() iterators
|
||||
template <class Z>
|
||||
requires(container_but_not_string<Z>)
|
||||
requires(concepts::container_but_not_string<Z> && !require_custom_serialization<Z>)
|
||||
void append(string_builder &b, const Z &z) {
|
||||
if (z.size() == 0) {
|
||||
auto it = z.begin();
|
||||
auto end = z.end();
|
||||
if (it == end) {
|
||||
b.append_raw("[]");
|
||||
return;
|
||||
}
|
||||
b.append('[');
|
||||
atom(b, z[0]);
|
||||
for (size_t i = 1; i < z.size(); ++i) {
|
||||
atom(b, *it);
|
||||
++it;
|
||||
for (; it != end; ++it) {
|
||||
b.append(',');
|
||||
atom(b, z[i]);
|
||||
atom(b, *it);
|
||||
}
|
||||
b.append(']');
|
||||
}
|
||||
|
||||
template <class Z>
|
||||
requires (require_custom_serialization<Z>)
|
||||
void append(string_builder &b, const Z &z) {
|
||||
b.append(z);
|
||||
}
|
||||
|
||||
|
||||
template <class Z>
|
||||
simdjson_warn_unused simdjson_result<std::string> to_json_string(const Z &z, size_t initial_capacity = string_builder::DEFAULT_INITIAL_CAPACITY) {
|
||||
string_builder b(initial_capacity);
|
||||
@@ -286,17 +293,88 @@ string_builder& operator<<(string_builder& b, const Z& z) {
|
||||
append(b, z);
|
||||
return b;
|
||||
}
|
||||
|
||||
// extract_from: Serialize only specific fields from a struct to JSON
|
||||
template<constevalutil::fixed_string... FieldNames, typename T>
|
||||
requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0))
|
||||
void extract_from(string_builder &b, const T &obj) {
|
||||
// Helper to check if a field name matches any of the requested fields
|
||||
auto should_extract = [](std::string_view field_name) constexpr -> bool {
|
||||
return ((FieldNames.view() == field_name) || ...);
|
||||
};
|
||||
|
||||
b.append('{');
|
||||
bool first = true;
|
||||
|
||||
// Iterate through all members of T using reflection
|
||||
template for (constexpr auto mem : std::define_static_array(
|
||||
std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
|
||||
|
||||
if constexpr (std::meta::is_public(mem)) {
|
||||
constexpr std::string_view key = std::define_static_string(std::meta::identifier_of(mem));
|
||||
|
||||
// Only serialize this field if it's in our list of requested fields
|
||||
if constexpr (should_extract(key)) {
|
||||
if (!first) {
|
||||
b.append(',');
|
||||
}
|
||||
first = false;
|
||||
|
||||
// Serialize the key
|
||||
constexpr auto quoted_key = std::define_static_string(constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(mem)));
|
||||
b.append_raw(quoted_key);
|
||||
b.append(':');
|
||||
|
||||
// Serialize the value
|
||||
atom(b, obj.[:mem:]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
b.append('}');
|
||||
}
|
||||
|
||||
template<constevalutil::fixed_string... FieldNames, typename T>
|
||||
requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0))
|
||||
simdjson_warn_unused simdjson_result<std::string> extract_from(const T &obj, size_t initial_capacity = string_builder::DEFAULT_INITIAL_CAPACITY) {
|
||||
string_builder b(initial_capacity);
|
||||
extract_from<FieldNames...>(b, obj);
|
||||
std::string_view s;
|
||||
if(auto e = b.view().get(s); e) { return e; }
|
||||
return std::string(s);
|
||||
}
|
||||
|
||||
} // namespace builder
|
||||
} // namespace SIMDJSON_IMPLEMENTATION
|
||||
// Alias the function template to 'to' in the global namespace
|
||||
template <class Z>
|
||||
simdjson_warn_unused simdjson_result<std::string> to_json(const Z &z, size_t initial_capacity = SIMDJSON_IMPLEMENTATION::builder::string_builder::DEFAULT_INITIAL_CAPACITY) {
|
||||
return SIMDJSON_IMPLEMENTATION::builder::to_json_string(z, initial_capacity);
|
||||
SIMDJSON_IMPLEMENTATION::builder::string_builder b(initial_capacity);
|
||||
SIMDJSON_IMPLEMENTATION::builder::append(b, z);
|
||||
std::string_view s;
|
||||
if(auto e = b.view().get(s); e) { return e; }
|
||||
return std::string(s);
|
||||
}
|
||||
template <class Z>
|
||||
simdjson_warn_unused simdjson_error to_json(const Z &z, std::string &s, size_t initial_capacity = SIMDJSON_IMPLEMENTATION::builder::string_builder::DEFAULT_INITIAL_CAPACITY) {
|
||||
return SIMDJSON_IMPLEMENTATION::builder::to_json(z, s, initial_capacity);
|
||||
SIMDJSON_IMPLEMENTATION::builder::string_builder b(initial_capacity);
|
||||
SIMDJSON_IMPLEMENTATION::builder::append(b, z);
|
||||
std::string_view view;
|
||||
if(auto e = b.view().get(view); e) { return e; }
|
||||
s.assign(view);
|
||||
return SUCCESS;
|
||||
}
|
||||
// Global namespace function for extract_from
|
||||
template<constevalutil::fixed_string... FieldNames, typename T>
|
||||
requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0))
|
||||
simdjson_warn_unused simdjson_result<std::string> extract_from(const T &obj, size_t initial_capacity = SIMDJSON_IMPLEMENTATION::builder::string_builder::DEFAULT_INITIAL_CAPACITY) {
|
||||
SIMDJSON_IMPLEMENTATION::builder::string_builder b(initial_capacity);
|
||||
SIMDJSON_IMPLEMENTATION::builder::extract_from<FieldNames...>(b, obj);
|
||||
std::string_view s;
|
||||
if(auto e = b.view().get(s); e) { return e; }
|
||||
return std::string(s);
|
||||
}
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_STATIC_REFLECTION
|
||||
|
||||
@@ -344,7 +344,7 @@ simdjson_inline uint8_t *&json_iterator::string_buf_loc() noexcept {
|
||||
return _string_buf_loc;
|
||||
}
|
||||
|
||||
simdjson_inline error_code json_iterator::report_error(error_code _error, const char *message) noexcept {
|
||||
simdjson_warn_unused simdjson_inline error_code json_iterator::report_error(error_code _error, const char *message) noexcept {
|
||||
SIMDJSON_ASSUME(_error != SUCCESS && _error != UNINITIALIZED && _error != INCORRECT_TYPE && _error != NO_SUCH_FIELD);
|
||||
logger::log_error(*this, message);
|
||||
error = _error;
|
||||
@@ -388,7 +388,7 @@ simdjson_inline void json_iterator::reenter_child(token_position position, depth
|
||||
_depth = child_depth;
|
||||
}
|
||||
|
||||
simdjson_inline error_code json_iterator::consume_character(char c) noexcept {
|
||||
simdjson_warn_unused simdjson_inline error_code json_iterator::consume_character(char c) noexcept {
|
||||
if (*peek() == c) {
|
||||
return_current_and_advance();
|
||||
return SUCCESS;
|
||||
@@ -411,7 +411,7 @@ simdjson_inline void json_iterator::set_start_position(depth_t depth, token_posi
|
||||
#endif
|
||||
|
||||
|
||||
simdjson_inline error_code json_iterator::optional_error(error_code _error, const char *message) noexcept {
|
||||
simdjson_warn_unused simdjson_inline error_code json_iterator::optional_error(error_code _error, const char *message) noexcept {
|
||||
SIMDJSON_ASSUME(_error == INCORRECT_TYPE || _error == NO_SUCH_FIELD);
|
||||
logger::log_error(*this, message);
|
||||
return _error;
|
||||
|
||||
@@ -238,14 +238,14 @@ public:
|
||||
* @param error The error to report. Must not be SUCCESS, UNINITIALIZED, INCORRECT_TYPE, or NO_SUCH_FIELD.
|
||||
* @param message An error message to report with the error.
|
||||
*/
|
||||
simdjson_inline error_code report_error(error_code error, const char *message) noexcept;
|
||||
simdjson_warn_unused simdjson_inline error_code report_error(error_code error, const char *message) noexcept;
|
||||
|
||||
/**
|
||||
* Log error, but don't stop iteration.
|
||||
* @param error The error to report. Must be INCORRECT_TYPE, or NO_SUCH_FIELD.
|
||||
* @param message An error message to report with the error.
|
||||
*/
|
||||
simdjson_inline error_code optional_error(error_code error, const char *message) noexcept;
|
||||
simdjson_warn_unused simdjson_inline error_code optional_error(error_code error, const char *message) noexcept;
|
||||
|
||||
/**
|
||||
* Take an input in json containing max_len characters and attempt to copy it over to tmpbuf, a buffer with
|
||||
@@ -265,7 +265,7 @@ public:
|
||||
|
||||
simdjson_inline void reenter_child(token_position position, depth_t child_depth) noexcept;
|
||||
|
||||
simdjson_inline error_code consume_character(char c) noexcept;
|
||||
simdjson_warn_unused simdjson_inline error_code consume_character(char c) noexcept;
|
||||
#if SIMDJSON_DEVELOPMENT_CHECKS
|
||||
simdjson_inline token_position start_position(depth_t depth) const noexcept;
|
||||
simdjson_inline void set_start_position(depth_t depth, token_position position) noexcept;
|
||||
|
||||
@@ -42,24 +42,25 @@ namespace simdjson {
|
||||
namespace SIMDJSON_IMPLEMENTATION {
|
||||
namespace builder {
|
||||
|
||||
static SIMDJSON_CONSTEXPR_LAMBDA std::array<uint8_t, 256> json_quotable_character = {
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
static SIMDJSON_CONSTEXPR_LAMBDA std::array<uint8_t, 256>
|
||||
json_quotable_character = {
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
||||
/**
|
||||
|
||||
A possible SWAR implementation of has_json_escapable_byte. It is not used because
|
||||
it is slower than the current implementation. It is kept here for reference (to show
|
||||
that we tried it).
|
||||
A possible SWAR implementation of has_json_escapable_byte. It is not used
|
||||
because it is slower than the current implementation. It is kept here for
|
||||
reference (to show that we tried it).
|
||||
|
||||
inline bool has_json_escapable_byte(uint64_t x) {
|
||||
uint64_t is_ascii = 0x8080808080808080ULL & ~x;
|
||||
@@ -76,7 +77,7 @@ SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline bool
|
||||
simple_needs_escaping(std::string_view v) {
|
||||
for (char c : v) {
|
||||
// a table lookup is faster than a series of comparisons
|
||||
if(json_quotable_character[static_cast<uint8_t>(c)]) {
|
||||
if (json_quotable_character[static_cast<uint8_t>(c)]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -117,7 +118,8 @@ simdjson_inline bool fast_needs_escaping(std::string_view view) {
|
||||
__m128i running = _mm_setzero_si128();
|
||||
for (; i + 15 < view.size(); i += 16) {
|
||||
|
||||
__m128i word = _mm_loadu_si128(reinterpret_cast<const __m128i *>(view.data() + i));
|
||||
__m128i word =
|
||||
_mm_loadu_si128(reinterpret_cast<const __m128i *>(view.data() + i));
|
||||
running = _mm_or_si128(running, _mm_cmpeq_epi8(word, _mm_set1_epi8(34)));
|
||||
running = _mm_or_si128(running, _mm_cmpeq_epi8(word, _mm_set1_epi8(92)));
|
||||
running = _mm_or_si128(
|
||||
@@ -125,8 +127,8 @@ simdjson_inline bool fast_needs_escaping(std::string_view view) {
|
||||
_mm_setzero_si128()));
|
||||
}
|
||||
if (i < view.size()) {
|
||||
__m128i word =
|
||||
_mm_loadu_si128(reinterpret_cast<const __m128i *>(view.data() + view.length() - 16));
|
||||
__m128i word = _mm_loadu_si128(
|
||||
reinterpret_cast<const __m128i *>(view.data() + view.length() - 16));
|
||||
running = _mm_or_si128(running, _mm_cmpeq_epi8(word, _mm_set1_epi8(34)));
|
||||
running = _mm_or_si128(running, _mm_cmpeq_epi8(word, _mm_set1_epi8(92)));
|
||||
running = _mm_or_si128(
|
||||
@@ -141,7 +143,6 @@ simdjson_inline bool fast_needs_escaping(std::string_view view) {
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
SIMDJSON_CONSTEXPR_LAMBDA inline size_t
|
||||
find_next_json_quotable_character(const std::string_view view,
|
||||
size_t location) noexcept {
|
||||
@@ -156,15 +157,15 @@ find_next_json_quotable_character(const std::string_view view,
|
||||
|
||||
SIMDJSON_CONSTEXPR_LAMBDA static std::string_view control_chars[] = {
|
||||
"\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006",
|
||||
"\\u0007", "\\b", "\\t", "\\n", "\\u000b", "\\f", "\\r",
|
||||
"\\u0007", "\\b", "\\t", "\\n", "\\u000b", "\\f", "\\r",
|
||||
"\\u000e", "\\u000f", "\\u0010", "\\u0011", "\\u0012", "\\u0013", "\\u0014",
|
||||
"\\u0015", "\\u0016", "\\u0017", "\\u0018", "\\u0019", "\\u001a", "\\u001b",
|
||||
"\\u001c", "\\u001d", "\\u001e", "\\u001f"};
|
||||
|
||||
// All Unicode characters may be placed within the quotation marks, except for the
|
||||
// characters that MUST be escaped: quotation mark, reverse solidus, and the control
|
||||
// characters (U+0000 through U+001F).
|
||||
// There are two-character sequence escape representations of some popular characters:
|
||||
// All Unicode characters may be placed within the quotation marks, except for
|
||||
// the characters that MUST be escaped: quotation mark, reverse solidus, and the
|
||||
// control characters (U+0000 through U+001F). There are two-character sequence
|
||||
// escape representations of some popular characters:
|
||||
// \", \\, \b, \f, \n, \r, \t.
|
||||
SIMDJSON_CONSTEXPR_LAMBDA void escape_json_char(char c, char *&out) {
|
||||
if (c == '"') {
|
||||
@@ -284,10 +285,11 @@ simdjson_inline void string_builder::clear() noexcept {
|
||||
|
||||
namespace internal {
|
||||
|
||||
|
||||
template <typename number_type, typename = typename std::enable_if<
|
||||
std::is_unsigned<number_type>::value>::type>
|
||||
simdjson_really_inline int int_log2(number_type x) { return 63 - leading_zeroes(uint64_t(x) | 1); }
|
||||
simdjson_really_inline int int_log2(number_type x) {
|
||||
return 63 - leading_zeroes(uint64_t(x) | 1);
|
||||
}
|
||||
|
||||
simdjson_really_inline int fast_digit_count_32(uint32_t x) {
|
||||
static uint64_t table[] = {
|
||||
@@ -301,7 +303,6 @@ simdjson_really_inline int fast_digit_count_32(uint32_t x) {
|
||||
return uint32_t((x + table[int_log2(x)]) >> 32);
|
||||
}
|
||||
|
||||
|
||||
simdjson_really_inline int fast_digit_count_64(uint64_t x) {
|
||||
static uint64_t table[] = {9,
|
||||
99,
|
||||
@@ -335,28 +336,29 @@ simdjson_really_inline size_t digit_count(number_type v) noexcept {
|
||||
"We only support 8-bit, 16-bit, 32-bit and 64-bit numbers");
|
||||
SIMDJSON_IF_CONSTEXPR(sizeof(number_type) <= 4) {
|
||||
return fast_digit_count_32(static_cast<uint32_t>(v));
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return fast_digit_count_64(static_cast<uint64_t>(v));
|
||||
}
|
||||
}
|
||||
static const char decimal_table[200] = {
|
||||
0x30, 0x30, 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, 0x30, 0x34, 0x30, 0x35,
|
||||
0x30, 0x36, 0x30, 0x37, 0x30, 0x38, 0x30, 0x39, 0x31, 0x30, 0x31, 0x31,
|
||||
0x31, 0x32, 0x31, 0x33, 0x31, 0x34, 0x31, 0x35, 0x31, 0x36, 0x31, 0x37,
|
||||
0x31, 0x38, 0x31, 0x39, 0x32, 0x30, 0x32, 0x31, 0x32, 0x32, 0x32, 0x33,
|
||||
0x32, 0x34, 0x32, 0x35, 0x32, 0x36, 0x32, 0x37, 0x32, 0x38, 0x32, 0x39,
|
||||
0x33, 0x30, 0x33, 0x31, 0x33, 0x32, 0x33, 0x33, 0x33, 0x34, 0x33, 0x35,
|
||||
0x33, 0x36, 0x33, 0x37, 0x33, 0x38, 0x33, 0x39, 0x34, 0x30, 0x34, 0x31,
|
||||
0x34, 0x32, 0x34, 0x33, 0x34, 0x34, 0x34, 0x35, 0x34, 0x36, 0x34, 0x37,
|
||||
0x34, 0x38, 0x34, 0x39, 0x35, 0x30, 0x35, 0x31, 0x35, 0x32, 0x35, 0x33,
|
||||
0x35, 0x34, 0x35, 0x35, 0x35, 0x36, 0x35, 0x37, 0x35, 0x38, 0x35, 0x39,
|
||||
0x36, 0x30, 0x36, 0x31, 0x36, 0x32, 0x36, 0x33, 0x36, 0x34, 0x36, 0x35,
|
||||
0x36, 0x36, 0x36, 0x37, 0x36, 0x38, 0x36, 0x39, 0x37, 0x30, 0x37, 0x31,
|
||||
0x37, 0x32, 0x37, 0x33, 0x37, 0x34, 0x37, 0x35, 0x37, 0x36, 0x37, 0x37,
|
||||
0x37, 0x38, 0x37, 0x39, 0x38, 0x30, 0x38, 0x31, 0x38, 0x32, 0x38, 0x33,
|
||||
0x38, 0x34, 0x38, 0x35, 0x38, 0x36, 0x38, 0x37, 0x38, 0x38, 0x38, 0x39,
|
||||
0x39, 0x30, 0x39, 0x31, 0x39, 0x32, 0x39, 0x33, 0x39, 0x34, 0x39, 0x35,
|
||||
0x39, 0x36, 0x39, 0x37, 0x39, 0x38, 0x39, 0x39,
|
||||
0x30, 0x30, 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, 0x30, 0x34, 0x30, 0x35,
|
||||
0x30, 0x36, 0x30, 0x37, 0x30, 0x38, 0x30, 0x39, 0x31, 0x30, 0x31, 0x31,
|
||||
0x31, 0x32, 0x31, 0x33, 0x31, 0x34, 0x31, 0x35, 0x31, 0x36, 0x31, 0x37,
|
||||
0x31, 0x38, 0x31, 0x39, 0x32, 0x30, 0x32, 0x31, 0x32, 0x32, 0x32, 0x33,
|
||||
0x32, 0x34, 0x32, 0x35, 0x32, 0x36, 0x32, 0x37, 0x32, 0x38, 0x32, 0x39,
|
||||
0x33, 0x30, 0x33, 0x31, 0x33, 0x32, 0x33, 0x33, 0x33, 0x34, 0x33, 0x35,
|
||||
0x33, 0x36, 0x33, 0x37, 0x33, 0x38, 0x33, 0x39, 0x34, 0x30, 0x34, 0x31,
|
||||
0x34, 0x32, 0x34, 0x33, 0x34, 0x34, 0x34, 0x35, 0x34, 0x36, 0x34, 0x37,
|
||||
0x34, 0x38, 0x34, 0x39, 0x35, 0x30, 0x35, 0x31, 0x35, 0x32, 0x35, 0x33,
|
||||
0x35, 0x34, 0x35, 0x35, 0x35, 0x36, 0x35, 0x37, 0x35, 0x38, 0x35, 0x39,
|
||||
0x36, 0x30, 0x36, 0x31, 0x36, 0x32, 0x36, 0x33, 0x36, 0x34, 0x36, 0x35,
|
||||
0x36, 0x36, 0x36, 0x37, 0x36, 0x38, 0x36, 0x39, 0x37, 0x30, 0x37, 0x31,
|
||||
0x37, 0x32, 0x37, 0x33, 0x37, 0x34, 0x37, 0x35, 0x37, 0x36, 0x37, 0x37,
|
||||
0x37, 0x38, 0x37, 0x39, 0x38, 0x30, 0x38, 0x31, 0x38, 0x32, 0x38, 0x33,
|
||||
0x38, 0x34, 0x38, 0x35, 0x38, 0x36, 0x38, 0x37, 0x38, 0x38, 0x38, 0x39,
|
||||
0x39, 0x30, 0x39, 0x31, 0x39, 0x32, 0x39, 0x33, 0x39, 0x34, 0x39, 0x35,
|
||||
0x39, 0x36, 0x39, 0x37, 0x39, 0x38, 0x39, 0x39,
|
||||
};
|
||||
} // namespace internal
|
||||
|
||||
@@ -392,7 +394,7 @@ simdjson_inline void string_builder::append(number_type v) noexcept {
|
||||
size_t dc = internal::digit_count(pv);
|
||||
char *write_pointer = buffer.get() + position + dc - 1;
|
||||
while (pv >= 100) {
|
||||
memcpy(write_pointer - 1, &internal::decimal_table[(pv % 100)*2], 2);
|
||||
memcpy(write_pointer - 1, &internal::decimal_table[(pv % 100) * 2], 2);
|
||||
write_pointer -= 2;
|
||||
pv /= 100;
|
||||
}
|
||||
@@ -419,7 +421,7 @@ simdjson_inline void string_builder::append(number_type v) noexcept {
|
||||
}
|
||||
char *write_pointer = buffer.get() + position + dc - 1;
|
||||
while (pv >= 100) {
|
||||
memcpy(write_pointer - 1, &internal::decimal_table[(pv % 100)*2], 2);
|
||||
memcpy(write_pointer - 1, &internal::decimal_table[(pv % 100) * 2], 2);
|
||||
write_pointer -= 2;
|
||||
pv /= 100;
|
||||
}
|
||||
@@ -471,14 +473,15 @@ string_builder::escape_and_append_with_quotes(char input) noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
simdjson_inline void string_builder::escape_and_append_with_quotes(const char* input) noexcept {
|
||||
simdjson_inline void
|
||||
string_builder::escape_and_append_with_quotes(const char *input) noexcept {
|
||||
std::string_view cinput(input);
|
||||
escape_and_append_with_quotes(cinput);
|
||||
}
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS
|
||||
template<internal::fixed_string key>
|
||||
simdjson_inline void string_builder::escape_and_append_with_quotes() noexcept {
|
||||
escape_and_append_with_quotes(internal::string_constant<key>::value);
|
||||
template <constevalutil::fixed_string key>
|
||||
simdjson_inline void string_builder::escape_and_append_with_quotes() noexcept {
|
||||
escape_and_append_with_quotes(constevalutil::string_constant<key>::value);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -505,6 +508,7 @@ simdjson_inline void string_builder::append_raw(const char *str,
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS
|
||||
// Support for optional types (std::optional, etc.)
|
||||
template <concepts::optional_type T>
|
||||
requires(!require_custom_serialization<T>)
|
||||
simdjson_inline void string_builder::append(const T &opt) {
|
||||
if (opt) {
|
||||
append(*opt);
|
||||
@@ -512,18 +516,25 @@ simdjson_inline void string_builder::append(const T &opt) {
|
||||
append_null();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
requires(std::is_convertible<T, std::string_view>::value ||
|
||||
std::is_same<T, const char*>::value )
|
||||
requires(require_custom_serialization<T>)
|
||||
simdjson_inline void string_builder::append(const T &val) {
|
||||
serialize(*this, val);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
requires(std::is_convertible<T, std::string_view>::value ||
|
||||
std::is_same<T, const char *>::value)
|
||||
simdjson_inline void string_builder::append(const T &value) {
|
||||
escape_and_append_with_quotes(value);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if SIMDJSON_SUPPORTS_RANGES && SIMDJSON_SUPPORTS_CONCEPTS
|
||||
// Support for range-based appending (std::ranges::view, etc.)
|
||||
// Support for range-based appending (std::ranges::view, etc.)
|
||||
template <std::ranges::range R>
|
||||
requires (!std::is_convertible<R, std::string_view>::value)
|
||||
requires(!std::is_convertible<R, std::string_view>::value)
|
||||
simdjson_inline void string_builder::append(const R &range) noexcept {
|
||||
auto it = std::ranges::begin(range);
|
||||
auto end = std::ranges::end(range);
|
||||
@@ -540,8 +551,8 @@ simdjson_inline void string_builder::append(const R &range) noexcept {
|
||||
|
||||
// Append remaining items with preceding commas
|
||||
for (; it != end; ++it) {
|
||||
append_comma();
|
||||
append_key_value(it->first, it->second);
|
||||
append_comma();
|
||||
append_key_value(it->first, it->second);
|
||||
}
|
||||
end_object();
|
||||
} else {
|
||||
@@ -557,11 +568,10 @@ simdjson_inline void string_builder::append(const R &range) noexcept {
|
||||
|
||||
// Append remaining items with preceding commas
|
||||
for (; it != end; ++it) {
|
||||
append_comma();
|
||||
append(*it);
|
||||
append_comma();
|
||||
append(*it);
|
||||
}
|
||||
end_array();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -569,7 +579,7 @@ simdjson_inline void string_builder::append(const R &range) noexcept {
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
simdjson_inline string_builder::operator std::string() const noexcept(false) {
|
||||
return std::string(std::string_view());
|
||||
return std::string(operator std::string_view());
|
||||
}
|
||||
|
||||
simdjson_inline string_builder::operator std::string_view() const
|
||||
@@ -598,82 +608,88 @@ simdjson_inline bool string_builder::validate_unicode() const noexcept {
|
||||
return simdjson::validate_utf8(buffer.get(), position);
|
||||
}
|
||||
|
||||
simdjson_inline void string_builder::start_object() noexcept {
|
||||
simdjson_inline void string_builder::start_object() noexcept {
|
||||
if (capacity_check(1)) {
|
||||
buffer.get()[position++] = '{';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
simdjson_inline void string_builder::end_object() noexcept {
|
||||
simdjson_inline void string_builder::end_object() noexcept {
|
||||
if (capacity_check(1)) {
|
||||
buffer.get()[position++] = '}';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
simdjson_inline void string_builder::start_array() noexcept {
|
||||
simdjson_inline void string_builder::start_array() noexcept {
|
||||
if (capacity_check(1)) {
|
||||
buffer.get()[position++] = '[';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
simdjson_inline void string_builder::end_array() noexcept {
|
||||
simdjson_inline void string_builder::end_array() noexcept {
|
||||
if (capacity_check(1)) {
|
||||
buffer.get()[position++] = ']';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
simdjson_inline void string_builder::append_comma() noexcept {
|
||||
simdjson_inline void string_builder::append_comma() noexcept {
|
||||
if (capacity_check(1)) {
|
||||
buffer.get()[position++] = ',';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
simdjson_inline void string_builder::append_colon() noexcept {
|
||||
simdjson_inline void string_builder::append_colon() noexcept {
|
||||
if (capacity_check(1)) {
|
||||
buffer.get()[position++] = ':';
|
||||
}
|
||||
}
|
||||
|
||||
template<typename key_type, typename value_type>
|
||||
simdjson_inline void string_builder::append_key_value(key_type key, value_type value) noexcept {
|
||||
static_assert(
|
||||
std::is_same<key_type, const char*>::value ||
|
||||
std::is_convertible<key_type, std::string_view>::value,
|
||||
"Unsupported key type");
|
||||
template <typename key_type, typename value_type>
|
||||
simdjson_inline void
|
||||
string_builder::append_key_value(key_type key, value_type value) noexcept {
|
||||
static_assert(std::is_same<key_type, const char *>::value ||
|
||||
std::is_convertible<key_type, std::string_view>::value,
|
||||
"Unsupported key type");
|
||||
escape_and_append_with_quotes(key);
|
||||
append_colon();
|
||||
SIMDJSON_IF_CONSTEXPR(std::is_same<value_type, std::nullptr_t>::value) {
|
||||
append_null();
|
||||
} else SIMDJSON_IF_CONSTEXPR(std::is_same<value_type, char>::value) {
|
||||
}
|
||||
else SIMDJSON_IF_CONSTEXPR(std::is_same<value_type, char>::value) {
|
||||
escape_and_append_with_quotes(value);
|
||||
} else SIMDJSON_IF_CONSTEXPR(std::is_convertible<value_type, std::string_view>::value) {
|
||||
}
|
||||
else SIMDJSON_IF_CONSTEXPR(
|
||||
std::is_convertible<value_type, std::string_view>::value) {
|
||||
escape_and_append_with_quotes(value);
|
||||
} else SIMDJSON_IF_CONSTEXPR(std::is_same<value_type, const char*>::value) {
|
||||
}
|
||||
else SIMDJSON_IF_CONSTEXPR(std::is_same<value_type, const char *>::value) {
|
||||
escape_and_append_with_quotes(value);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
append(value);
|
||||
}
|
||||
}
|
||||
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS
|
||||
template<internal::fixed_string key, typename value_type>
|
||||
simdjson_inline void string_builder::append_key_value(value_type value) noexcept {
|
||||
template <constevalutil::fixed_string key, typename value_type>
|
||||
simdjson_inline void
|
||||
string_builder::append_key_value(value_type value) noexcept {
|
||||
escape_and_append_with_quotes<key>();
|
||||
append_colon();
|
||||
SIMDJSON_IF_CONSTEXPR(std::is_same<value_type, std::nullptr_t>::value) {
|
||||
append_null();
|
||||
} else SIMDJSON_IF_CONSTEXPR(std::is_same<value_type, char>::value) {
|
||||
}
|
||||
else SIMDJSON_IF_CONSTEXPR(std::is_same<value_type, char>::value) {
|
||||
escape_and_append_with_quotes(value);
|
||||
} else SIMDJSON_IF_CONSTEXPR(std::is_convertible<value_type, std::string_view>::value) {
|
||||
}
|
||||
else SIMDJSON_IF_CONSTEXPR(
|
||||
std::is_convertible<value_type, std::string_view>::value) {
|
||||
escape_and_append_with_quotes(value);
|
||||
} else SIMDJSON_IF_CONSTEXPR(std::is_same<value_type, const char*>::value) {
|
||||
}
|
||||
else SIMDJSON_IF_CONSTEXPR(std::is_same<value_type, const char *>::value) {
|
||||
escape_and_append_with_quotes(value);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
append(value);
|
||||
}
|
||||
}
|
||||
@@ -683,4 +699,4 @@ simdjson_inline void string_builder::append_key_value(value_type value) noexcept
|
||||
} // namespace SIMDJSON_IMPLEMENTATION
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_GENERIC_STRING_BUILDER_INL_H
|
||||
#endif // SIMDJSON_GENERIC_STRING_BUILDER_INL_H
|
||||
|
||||
@@ -10,28 +10,40 @@
|
||||
#endif // SIMDJSON_CONDITIONAL_INCLUDE
|
||||
|
||||
namespace simdjson {
|
||||
|
||||
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS
|
||||
|
||||
namespace SIMDJSON_IMPLEMENTATION {
|
||||
namespace builder {
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS
|
||||
// Helper to create string constants
|
||||
namespace internal {
|
||||
template <std::size_t N>
|
||||
struct fixed_string {
|
||||
constexpr fixed_string(const char (&str)[N]) {
|
||||
for (std::size_t i = 0; i < N; ++i) {
|
||||
data[i] = str[i];
|
||||
}
|
||||
}
|
||||
char data[N];
|
||||
constexpr std::string_view view() const { return {data, N - 1}; }
|
||||
};
|
||||
class string_builder;
|
||||
}}
|
||||
|
||||
template <fixed_string str>
|
||||
struct string_constant {
|
||||
static constexpr std::string_view value = str.view();
|
||||
};
|
||||
} // namespace internal
|
||||
template <typename T, typename = void>
|
||||
struct has_custom_serialization : std::false_type {};
|
||||
|
||||
inline constexpr struct serialize_tag {
|
||||
template <typename T>
|
||||
requires custom_deserializable<T>
|
||||
constexpr void operator()(SIMDJSON_IMPLEMENTATION::builder::string_builder& b, T& obj) const{
|
||||
return tag_invoke(*this, b, obj);
|
||||
}
|
||||
|
||||
|
||||
} serialize{};
|
||||
template <typename T>
|
||||
struct has_custom_serialization<T, std::void_t<
|
||||
decltype(tag_invoke(serialize, std::declval<SIMDJSON_IMPLEMENTATION::builder::string_builder&>(), std::declval<T&>()))
|
||||
>> : std::true_type {};
|
||||
|
||||
template <typename T>
|
||||
constexpr bool require_custom_serialization = has_custom_serialization<T>::value;
|
||||
#else
|
||||
struct has_custom_serialization : std::false_type {};
|
||||
#endif // SIMDJSON_SUPPORTS_CONCEPTS
|
||||
|
||||
namespace SIMDJSON_IMPLEMENTATION {
|
||||
namespace builder {
|
||||
/**
|
||||
* A builder for JSON strings representing documents. This is a low-level
|
||||
* builder that is not meant to be used directly by end-users. Though it
|
||||
@@ -54,7 +66,7 @@ public:
|
||||
* represents the number.
|
||||
*/
|
||||
template<typename number_type,
|
||||
typename = typename std::enable_if<std::is_arithmetic<number_type>::value>::type>
|
||||
typename = typename std::enable_if<std::is_arithmetic<number_type>::value>::type>
|
||||
simdjson_inline void append(number_type v) noexcept;
|
||||
|
||||
/**
|
||||
@@ -84,7 +96,7 @@ public:
|
||||
*/
|
||||
simdjson_inline void escape_and_append_with_quotes(std::string_view input) noexcept;
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS
|
||||
template<internal::fixed_string key>
|
||||
template<constevalutil::fixed_string key>
|
||||
simdjson_inline void escape_and_append_with_quotes() noexcept;
|
||||
#endif
|
||||
/**
|
||||
@@ -143,13 +155,18 @@ public:
|
||||
template<typename key_type, typename value_type>
|
||||
simdjson_inline void append_key_value(key_type key, value_type value) noexcept;
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS
|
||||
template<internal::fixed_string key, typename value_type>
|
||||
template<constevalutil::fixed_string key, typename value_type>
|
||||
simdjson_inline void append_key_value(value_type value) noexcept;
|
||||
|
||||
// Support for optional types (std::optional, etc.)
|
||||
template <concepts::optional_type T>
|
||||
requires(!require_custom_serialization<T>)
|
||||
simdjson_inline void append(const T &opt);
|
||||
|
||||
template <typename T>
|
||||
requires(require_custom_serialization<T>)
|
||||
simdjson_inline void append(const T &val);
|
||||
|
||||
// Support for string-like types
|
||||
template <typename T>
|
||||
requires(std::is_convertible<T, std::string_view>::value ||
|
||||
@@ -279,6 +296,8 @@ simdjson_warn_unused simdjson_error to_json(const Z &z, std::string &s, size_t i
|
||||
}
|
||||
#endif
|
||||
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS
|
||||
#endif // SIMDJSON_SUPPORTS_CONCEPTS
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
#include "simdjson/generic/ondemand/raw_json_string.h"
|
||||
#include "simdjson/generic/ondemand/json_iterator.h"
|
||||
#include "simdjson/generic/ondemand/value-inl.h"
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
#include "simdjson/generic/ondemand/json_string_builder.h" // for constevalutil::fixed_string
|
||||
#include <meta>
|
||||
#endif
|
||||
#endif // SIMDJSON_CONDITIONAL_INCLUDE
|
||||
|
||||
namespace simdjson {
|
||||
@@ -66,7 +70,7 @@ simdjson_inline simdjson_result<object> object::start_root(value_iterator &iter)
|
||||
SIMDJSON_TRY( iter.start_root_object().error() );
|
||||
return object(iter);
|
||||
}
|
||||
simdjson_inline error_code object::consume() noexcept {
|
||||
simdjson_warn_unused simdjson_inline error_code object::consume() noexcept {
|
||||
if(iter.is_at_key()) {
|
||||
/**
|
||||
* whenever you are pointing at a key, calling skip_child() is
|
||||
@@ -195,6 +199,52 @@ simdjson_inline simdjson_result<bool> object::reset() & noexcept {
|
||||
return iter.reset_object();
|
||||
}
|
||||
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS && SIMDJSON_STATIC_REFLECTION
|
||||
|
||||
template<constevalutil::fixed_string... FieldNames, typename T>
|
||||
requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0))
|
||||
simdjson_warn_unused simdjson_inline error_code object::extract_into(T& out) & noexcept {
|
||||
// Helper to check if a field name matches any of the requested fields
|
||||
auto should_extract = [](std::string_view field_name) constexpr -> bool {
|
||||
return ((FieldNames.view() == field_name) || ...);
|
||||
};
|
||||
|
||||
// Iterate through all members of T using reflection
|
||||
template for (constexpr auto mem : std::define_static_array(
|
||||
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));
|
||||
|
||||
// Only extract this field if it's in our list of requested fields
|
||||
if constexpr (should_extract(key)) {
|
||||
// Try to find and extract the field
|
||||
if constexpr (concepts::optional_type<decltype(out.[:mem:])>) {
|
||||
// For optional fields, it's ok if they're missing
|
||||
auto field_result = find_field_unordered(key);
|
||||
if (!field_result.error()) {
|
||||
auto error = field_result.get(out.[:mem:]);
|
||||
if (error && error != NO_SUCH_FIELD) {
|
||||
return error;
|
||||
}
|
||||
} else if (field_result.error() != NO_SUCH_FIELD) {
|
||||
return field_result.error();
|
||||
} else {
|
||||
out.[:mem:].reset();
|
||||
}
|
||||
} else {
|
||||
// For required fields (in the requested list), fail if missing
|
||||
SIMDJSON_TRY((*this)[key].get(out.[:mem:]));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
#endif // SIMDJSON_SUPPORTS_CONCEPTS && SIMDJSON_STATIC_REFLECTION
|
||||
|
||||
} // namespace ondemand
|
||||
} // namespace SIMDJSON_IMPLEMENTATION
|
||||
} // namespace simdjson
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
#include "simdjson/generic/ondemand/base.h"
|
||||
#include "simdjson/generic/implementation_simdjson_result_base.h"
|
||||
#include "simdjson/generic/ondemand/value_iterator.h"
|
||||
#if SIMDJSON_STATIC_REFLECTION && SIMDJSON_SUPPORTS_CONCEPTS
|
||||
#include "simdjson/generic/ondemand/json_string_builder.h" // for constevalutil::fixed_string
|
||||
#endif
|
||||
#endif // SIMDJSON_CONDITIONAL_INCLUDE
|
||||
|
||||
namespace simdjson {
|
||||
@@ -211,7 +214,7 @@ public:
|
||||
* @returns SUCCESS If the parse succeeded and the out parameter was set to the value.
|
||||
*/
|
||||
template <typename T>
|
||||
simdjson_inline error_code get(T &out)
|
||||
simdjson_warn_unused simdjson_inline error_code get(T &out)
|
||||
noexcept(custom_deserializable<T, object> ? nothrow_custom_deserializable<T, object> : true) {
|
||||
static_assert(custom_deserializable<T, object>);
|
||||
return deserialize(*this, out);
|
||||
@@ -231,12 +234,42 @@ public:
|
||||
SIMDJSON_TRY(get<T>(out));
|
||||
return out;
|
||||
}
|
||||
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
/**
|
||||
* Extract only specific fields from the JSON object into a struct.
|
||||
*
|
||||
* This allows selective deserialization of only the fields you need,
|
||||
* potentially improving performance by skipping unwanted fields.
|
||||
*
|
||||
* Example:
|
||||
* ```c++
|
||||
* struct Car {
|
||||
* std::string make;
|
||||
* std::string model;
|
||||
* int year;
|
||||
* double price;
|
||||
* };
|
||||
*
|
||||
* Car car;
|
||||
* object.extract_into<"make", "model">(car);
|
||||
* // Only 'make' and 'model' fields are extracted from JSON
|
||||
* ```
|
||||
*
|
||||
* @tparam FieldNames Compile-time string literals specifying which fields to extract
|
||||
* @param out The output struct to populate with selected fields
|
||||
* @returns SUCCESS on success, or an error code if a required field is missing or has wrong type
|
||||
*/
|
||||
template<constevalutil::fixed_string... FieldNames, typename T>
|
||||
requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0))
|
||||
simdjson_warn_unused simdjson_inline error_code extract_into(T& out) & noexcept;
|
||||
#endif // SIMDJSON_STATIC_REFLECTION
|
||||
#endif // SIMDJSON_SUPPORTS_CONCEPTS
|
||||
protected:
|
||||
/**
|
||||
* Go to the end of the object, no matter where you are right now.
|
||||
*/
|
||||
simdjson_inline error_code consume() noexcept;
|
||||
simdjson_warn_unused simdjson_inline error_code consume() noexcept;
|
||||
static simdjson_inline simdjson_result<object> start(value_iterator &iter) noexcept;
|
||||
static simdjson_inline simdjson_result<object> start_root(value_iterator &iter) noexcept;
|
||||
static simdjson_inline simdjson_result<object> started(value_iterator &iter) noexcept;
|
||||
@@ -291,7 +324,7 @@ public:
|
||||
return first.get<T>();
|
||||
}
|
||||
template<typename T>
|
||||
simdjson_inline error_code get(T& out) noexcept {
|
||||
simdjson_warn_unused simdjson_inline error_code get(T& out) noexcept {
|
||||
if (error()) { return error(); }
|
||||
if constexpr (std::is_same_v<T, SIMDJSON_IMPLEMENTATION::ondemand::object>) {
|
||||
out = first;
|
||||
@@ -300,6 +333,16 @@ public:
|
||||
}
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
// TODO: move this code into object-inl.h
|
||||
template<constevalutil::fixed_string... FieldNames, typename T>
|
||||
requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0))
|
||||
simdjson_warn_unused simdjson_inline error_code extract_into(T& out) noexcept {
|
||||
if (error()) { return error(); }
|
||||
return first.extract_into<FieldNames...>(out);
|
||||
}
|
||||
#endif // SIMDJSON_STATIC_REFLECTION
|
||||
#endif // SIMDJSON_SUPPORTS_CONCEPTS
|
||||
};
|
||||
|
||||
|
||||
@@ -16,15 +16,12 @@
|
||||
#endif
|
||||
|
||||
namespace simdjson {
|
||||
template <typename T>
|
||||
constexpr bool require_custom_serialization = false;
|
||||
|
||||
//////////////////////////////
|
||||
// Number deserialization
|
||||
//////////////////////////////
|
||||
|
||||
template <std::unsigned_integral T>
|
||||
requires(!require_custom_serialization<T>)
|
||||
error_code tag_invoke(deserialize_tag, auto &val, T &out) noexcept {
|
||||
using limits = std::numeric_limits<T>;
|
||||
|
||||
@@ -38,7 +35,6 @@ error_code tag_invoke(deserialize_tag, auto &val, T &out) noexcept {
|
||||
}
|
||||
|
||||
template <std::floating_point T>
|
||||
requires(!require_custom_serialization<T>)
|
||||
error_code tag_invoke(deserialize_tag, auto &val, T &out) noexcept {
|
||||
double x;
|
||||
SIMDJSON_TRY(val.get_double().get(x));
|
||||
@@ -47,7 +43,6 @@ error_code tag_invoke(deserialize_tag, auto &val, T &out) noexcept {
|
||||
}
|
||||
|
||||
template <std::signed_integral T>
|
||||
requires(!require_custom_serialization<T>)
|
||||
error_code tag_invoke(deserialize_tag, auto &val, T &out) noexcept {
|
||||
using limits = std::numeric_limits<T>;
|
||||
|
||||
@@ -77,7 +72,6 @@ error_code tag_invoke(deserialize_tag, auto &val, char &out) noexcept {
|
||||
|
||||
// any string-like type (can be constructed from std::string_view)
|
||||
template <concepts::constructible_from_string_view T, typename ValT>
|
||||
requires(!require_custom_serialization<T>)
|
||||
error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept(std::is_nothrow_constructible_v<T, std::string_view>) {
|
||||
std::string_view str;
|
||||
SIMDJSON_TRY(val.get_string().get(str));
|
||||
@@ -94,7 +88,6 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept(std::is_nothr
|
||||
* doc.get<std::vector<int>>().
|
||||
*/
|
||||
template <concepts::appendable_containers T, typename ValT>
|
||||
requires(!require_custom_serialization<T>)
|
||||
error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept(false) {
|
||||
using value_type = typename std::remove_cvref_t<T>::value_type;
|
||||
static_assert(
|
||||
@@ -140,7 +133,6 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept(false) {
|
||||
* string-keyed types.
|
||||
*/
|
||||
template <concepts::string_view_keyed_map T, typename ValT>
|
||||
requires(!require_custom_serialization<T>)
|
||||
error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept(false) {
|
||||
using value_type = typename std::remove_cvref_t<T>::mapped_type;
|
||||
static_assert(
|
||||
@@ -222,7 +214,6 @@ error_code tag_invoke(deserialize_tag, SIMDJSON_IMPLEMENTATION::ondemand::docume
|
||||
* @return status of the conversion
|
||||
*/
|
||||
template <concepts::smart_pointer T, typename ValT>
|
||||
requires(!require_custom_serialization<T>)
|
||||
error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept(nothrow_deserializable<typename std::remove_cvref_t<T>::element_type, ValT>) {
|
||||
using element_type = typename std::remove_cvref_t<T>::element_type;
|
||||
|
||||
@@ -248,7 +239,6 @@ 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 <concepts::optional_type T>
|
||||
requires(!require_custom_serialization<T>)
|
||||
error_code tag_invoke(deserialize_tag, auto &val, T &out) noexcept(nothrow_deserializable<typename std::remove_cvref_t<T>::value_type, decltype(val)>) {
|
||||
using value_type = typename std::remove_cvref_t<T>::value_type;
|
||||
|
||||
@@ -274,7 +264,7 @@ error_code tag_invoke(deserialize_tag, auto &val, T &out) noexcept(nothrow_deser
|
||||
template <typename T>
|
||||
constexpr bool user_defined_type = (std::is_class_v<T>
|
||||
&& !std::is_same_v<T, std::string> && !std::is_same_v<T, std::string_view> && !concepts::optional_type<T> &&
|
||||
!concepts::appendable_containers<T> && !require_custom_serialization<T>);
|
||||
!concepts::appendable_containers<T>);
|
||||
|
||||
|
||||
template <typename T, typename ValT>
|
||||
@@ -286,18 +276,26 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept {
|
||||
} else {
|
||||
SIMDJSON_TRY(val.get_object().get(obj));
|
||||
}
|
||||
error_code e = simdjson::SUCCESS;
|
||||
template for (constexpr auto mem : std::define_static_array(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));
|
||||
// Note: removed static assert as optional types are now handled generically
|
||||
// as long we are successful or the field is not found, we continue
|
||||
if(e == simdjson::SUCCESS || e == simdjson::NO_SUCH_FIELD) {
|
||||
e = obj[key].get(out.[:mem:]);
|
||||
if constexpr (concepts::optional_type<decltype(out.[:mem:])>) {
|
||||
// for optional members, it's ok if the key is missing
|
||||
auto error = obj[key].get(out.[:mem:]);
|
||||
if (error && error != NO_SUCH_FIELD) {
|
||||
if(error == NO_SUCH_FIELD) {
|
||||
out.[:mem:].reset();
|
||||
continue;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
} else {
|
||||
// for non-optional members, the key must be present
|
||||
SIMDJSON_TRY(obj[key].get(out.[:mem:]));
|
||||
}
|
||||
}
|
||||
};
|
||||
return e;
|
||||
return simdjson::SUCCESS;
|
||||
}
|
||||
|
||||
// Support for enum deserialization - deserialize from string representation using expand approach from P2996R12
|
||||
|
||||
@@ -48,7 +48,7 @@ simdjson_inline simdjson_result<std::string_view> value::get_string(bool allow_r
|
||||
return iter.get_string(allow_replacement);
|
||||
}
|
||||
template <typename string_type>
|
||||
simdjson_inline error_code value::get_string(string_type& receiver, bool allow_replacement) noexcept {
|
||||
simdjson_warn_unused simdjson_inline error_code value::get_string(string_type& receiver, bool allow_replacement) noexcept {
|
||||
return iter.get_string(receiver, allow_replacement);
|
||||
}
|
||||
simdjson_inline simdjson_result<std::string_view> value::get_wobbly_string() noexcept {
|
||||
@@ -90,15 +90,15 @@ template<> simdjson_inline simdjson_result<int64_t> value::get() noexcept { retu
|
||||
template<> simdjson_inline simdjson_result<bool> value::get() noexcept { return get_bool(); }
|
||||
|
||||
|
||||
template<> simdjson_inline error_code value::get(array& out) noexcept { return get_array().get(out); }
|
||||
template<> simdjson_inline error_code value::get(object& out) noexcept { return get_object().get(out); }
|
||||
template<> simdjson_inline error_code value::get(raw_json_string& out) noexcept { return get_raw_json_string().get(out); }
|
||||
template<> simdjson_inline error_code value::get(std::string_view& out) noexcept { return get_string(false).get(out); }
|
||||
template<> simdjson_inline error_code value::get(number& out) noexcept { return get_number().get(out); }
|
||||
template<> simdjson_inline error_code value::get(double& out) noexcept { return get_double().get(out); }
|
||||
template<> simdjson_inline error_code value::get(uint64_t& out) noexcept { return get_uint64().get(out); }
|
||||
template<> simdjson_inline error_code value::get(int64_t& out) noexcept { return get_int64().get(out); }
|
||||
template<> simdjson_inline error_code value::get(bool& out) noexcept { return get_bool().get(out); }
|
||||
template<> simdjson_warn_unused simdjson_inline error_code value::get(array& out) noexcept { return get_array().get(out); }
|
||||
template<> simdjson_warn_unused simdjson_inline error_code value::get(object& out) noexcept { return get_object().get(out); }
|
||||
template<> simdjson_warn_unused simdjson_inline error_code value::get(raw_json_string& out) noexcept { return get_raw_json_string().get(out); }
|
||||
template<> simdjson_warn_unused simdjson_inline error_code value::get(std::string_view& out) noexcept { return get_string(false).get(out); }
|
||||
template<> simdjson_warn_unused simdjson_inline error_code value::get(number& out) noexcept { return get_number().get(out); }
|
||||
template<> simdjson_warn_unused simdjson_inline error_code value::get(double& out) noexcept { return get_double().get(out); }
|
||||
template<> simdjson_warn_unused simdjson_inline error_code value::get(uint64_t& out) noexcept { return get_uint64().get(out); }
|
||||
template<> simdjson_warn_unused simdjson_inline error_code value::get(int64_t& out) noexcept { return get_int64().get(out); }
|
||||
template<> simdjson_warn_unused simdjson_inline error_code value::get(bool& out) noexcept { return get_bool().get(out); }
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
template <class T>
|
||||
|
||||
@@ -65,7 +65,7 @@ public:
|
||||
* @returns SUCCESS If the parse succeeded and the out parameter was set to the value.
|
||||
*/
|
||||
template <typename T>
|
||||
simdjson_inline error_code get(T &out)
|
||||
simdjson_warn_unused simdjson_inline error_code get(T &out)
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS
|
||||
noexcept(custom_deserializable<T, value> ? nothrow_custom_deserializable<T, value> : true)
|
||||
#else
|
||||
@@ -217,7 +217,7 @@ public:
|
||||
* @returns INCORRECT_TYPE if the JSON value is not a string. Otherwise, we return SUCCESS.
|
||||
*/
|
||||
template <typename string_type>
|
||||
simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept;
|
||||
simdjson_warn_unused simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept;
|
||||
|
||||
/**
|
||||
* Cast this JSON value to a "wobbly" string.
|
||||
@@ -738,7 +738,7 @@ public:
|
||||
simdjson_inline simdjson_result<double> get_double_in_string() noexcept;
|
||||
simdjson_inline simdjson_result<std::string_view> get_string(bool allow_replacement = false) noexcept;
|
||||
template <typename string_type>
|
||||
simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept;
|
||||
simdjson_warn_unused simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept;
|
||||
simdjson_inline simdjson_result<std::string_view> get_wobbly_string() noexcept;
|
||||
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string> get_raw_json_string() noexcept;
|
||||
simdjson_inline simdjson_result<bool> get_bool() noexcept;
|
||||
|
||||
@@ -41,7 +41,7 @@ simdjson_warn_unused simdjson_inline simdjson_result<bool> value_iterator::start
|
||||
if (*_json_iter->peek() == '}') {
|
||||
logger::log_value(*_json_iter, "empty object");
|
||||
_json_iter->return_current_and_advance();
|
||||
end_container();
|
||||
SIMDJSON_TRY(end_container());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -895,7 +895,7 @@ simdjson_inline void value_iterator::advance_scalar(const char *type) noexcept {
|
||||
_json_iter->ascend_to(depth()-1);
|
||||
}
|
||||
|
||||
simdjson_inline error_code value_iterator::start_container(uint8_t start_char, const char *incorrect_type_message, const char *type) noexcept {
|
||||
simdjson_warn_unused simdjson_inline error_code value_iterator::start_container(uint8_t start_char, const char *incorrect_type_message, const char *type) noexcept {
|
||||
logger::log_start_value(*_json_iter, start_position(), depth(), type);
|
||||
// If we're not at the position anymore, we don't want to advance the cursor.
|
||||
const uint8_t *json;
|
||||
|
||||
@@ -335,7 +335,7 @@ public:
|
||||
simdjson_warn_unused simdjson_inline simdjson_result<number> get_root_number(bool check_trailing) noexcept;
|
||||
simdjson_warn_unused simdjson_inline simdjson_result<bool> is_root_null(bool check_trailing) noexcept;
|
||||
|
||||
simdjson_inline error_code error() const noexcept;
|
||||
simdjson_warn_unused simdjson_inline error_code error() const noexcept;
|
||||
simdjson_inline uint8_t *&string_buf_loc() noexcept;
|
||||
simdjson_inline const json_iterator &json_iter() const noexcept;
|
||||
simdjson_inline json_iterator &json_iter() noexcept;
|
||||
@@ -419,8 +419,8 @@ protected:
|
||||
simdjson_inline const uint8_t *peek_non_root_scalar(const char *type) noexcept;
|
||||
|
||||
|
||||
simdjson_inline error_code start_container(uint8_t start_char, const char *incorrect_type_message, const char *type) noexcept;
|
||||
simdjson_inline error_code end_container() noexcept;
|
||||
simdjson_warn_unused simdjson_inline error_code start_container(uint8_t start_char, const char *incorrect_type_message, const char *type) noexcept;
|
||||
simdjson_warn_unused simdjson_inline error_code end_container() noexcept;
|
||||
|
||||
/**
|
||||
* Advance to a place expecting a value (increasing depth).
|
||||
@@ -430,8 +430,8 @@ protected:
|
||||
*/
|
||||
simdjson_inline simdjson_result<const uint8_t *> advance_to_value() noexcept;
|
||||
|
||||
simdjson_inline error_code incorrect_type_error(const char *message) const noexcept;
|
||||
simdjson_inline error_code error_unless_more_tokens(uint32_t tokens=1) const noexcept;
|
||||
simdjson_warn_unused simdjson_inline error_code incorrect_type_error(const char *message) const noexcept;
|
||||
simdjson_warn_unused simdjson_inline error_code error_unless_more_tokens(uint32_t tokens=1) const noexcept;
|
||||
|
||||
simdjson_inline bool is_at_start() const noexcept;
|
||||
/**
|
||||
@@ -468,7 +468,7 @@ protected:
|
||||
/** @copydoc error_code json_iterator::end_position() const noexcept; */
|
||||
simdjson_inline token_position end_position() const noexcept;
|
||||
/** @copydoc error_code json_iterator::report_error(error_code error, const char *message) noexcept; */
|
||||
simdjson_inline error_code report_error(error_code error, const char *message) noexcept;
|
||||
simdjson_warn_unused simdjson_inline error_code report_error(error_code error, const char *message) noexcept;
|
||||
|
||||
friend class document;
|
||||
friend class object;
|
||||
|
||||
@@ -17,7 +17,7 @@ using namespace simd;
|
||||
struct backslash_and_quote {
|
||||
public:
|
||||
static constexpr uint32_t BYTES_PROCESSED = 32;
|
||||
simdjson_inline static backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst);
|
||||
simdjson_inline backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst);
|
||||
|
||||
simdjson_inline bool has_quote_first() { return ((bs_bits - 1) & quote_bits) != 0; }
|
||||
simdjson_inline bool has_backslash() { return ((quote_bits - 1) & bs_bits) != 0; }
|
||||
|
||||
@@ -17,7 +17,7 @@ using namespace simd;
|
||||
struct backslash_and_quote {
|
||||
public:
|
||||
static constexpr uint32_t BYTES_PROCESSED = 64;
|
||||
simdjson_inline static backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst);
|
||||
simdjson_inline backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst);
|
||||
|
||||
simdjson_inline bool has_quote_first() { return ((bs_bits - 1) & quote_bits) != 0; }
|
||||
simdjson_inline bool has_backslash() { return ((quote_bits - 1) & bs_bits) != 0; }
|
||||
|
||||
@@ -17,7 +17,7 @@ using namespace simd;
|
||||
struct backslash_and_quote {
|
||||
public:
|
||||
static constexpr uint32_t BYTES_PROCESSED = 32;
|
||||
simdjson_inline static backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst);
|
||||
simdjson_inline backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst);
|
||||
|
||||
simdjson_inline bool has_quote_first() { return ((bs_bits - 1) & quote_bits) != 0; }
|
||||
simdjson_inline bool has_backslash() { return bs_bits != 0; }
|
||||
|
||||
@@ -17,7 +17,7 @@ using namespace simd;
|
||||
struct backslash_and_quote {
|
||||
public:
|
||||
static constexpr uint32_t BYTES_PROCESSED = 32;
|
||||
simdjson_inline static backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst);
|
||||
simdjson_inline backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst);
|
||||
|
||||
simdjson_inline bool has_quote_first() { return ((bs_bits - 1) & quote_bits) != 0; }
|
||||
simdjson_inline bool has_backslash() { return bs_bits != 0; }
|
||||
@@ -67,7 +67,7 @@ simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *ds
|
||||
simd8<bool> is_backslash = (v == '\\');
|
||||
simd8<bool> is_control = (v < 32);
|
||||
return {
|
||||
(is_backslash | is_quote | is_control).to_bitmask()
|
||||
static_cast<uint64_t>((is_backslash | is_quote | is_control).to_bitmask())
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ using namespace simd;
|
||||
struct backslash_and_quote {
|
||||
public:
|
||||
static constexpr uint32_t BYTES_PROCESSED = 32;
|
||||
simdjson_inline static backslash_and_quote
|
||||
simdjson_inline backslash_and_quote
|
||||
copy_and_find(const uint8_t *src, uint8_t *dst);
|
||||
|
||||
simdjson_inline bool has_quote_first() {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#define SIMDJSON_SIMDJSON_VERSION_H
|
||||
|
||||
/** The version of simdjson being used (major.minor.revision) */
|
||||
#define SIMDJSON_VERSION "4.0.5"
|
||||
#define SIMDJSON_VERSION "4.0.7"
|
||||
|
||||
namespace simdjson {
|
||||
enum {
|
||||
@@ -19,7 +19,7 @@ enum {
|
||||
/**
|
||||
* The revision (major.minor.REVISION) of simdjson being used.
|
||||
*/
|
||||
SIMDJSON_VERSION_REVISION = 5
|
||||
SIMDJSON_VERSION_REVISION = 7
|
||||
};
|
||||
} // namespace simdjson
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ using namespace simd;
|
||||
struct backslash_and_quote {
|
||||
public:
|
||||
static constexpr uint32_t BYTES_PROCESSED = 32;
|
||||
simdjson_inline static backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst);
|
||||
simdjson_inline backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst);
|
||||
|
||||
simdjson_inline bool has_quote_first() { return ((bs_bits - 1) & quote_bits) != 0; }
|
||||
simdjson_inline bool has_backslash() { return bs_bits != 0; }
|
||||
|
||||
+241
-168
File diff suppressed because it is too large
Load Diff
+6745
-4475
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1
-1
@@ -209,7 +209,7 @@ simdjson_inline bool validate_string() {
|
||||
//
|
||||
// Parse the entire input in STEP_SIZE-byte chunks.
|
||||
//
|
||||
simdjson_inline error_code scan() {
|
||||
simdjson_warn_unused simdjson_inline error_code scan() {
|
||||
bool unclosed_string = false;
|
||||
for (;idx<len;idx++) {
|
||||
do {
|
||||
|
||||
@@ -29,7 +29,7 @@ private:
|
||||
template<size_t STEP_SIZE>
|
||||
simdjson_inline void step(const uint8_t *block_buf, buf_block_reader<STEP_SIZE> &reader) noexcept;
|
||||
simdjson_inline void next(const simd::simd8x64<uint8_t>& in, const json_block& block);
|
||||
simdjson_inline error_code finish(uint8_t *dst_start, size_t &dst_len);
|
||||
simdjson_warn_unused simdjson_inline error_code finish(uint8_t *dst_start, size_t &dst_len);
|
||||
json_scanner scanner{};
|
||||
uint8_t *dst;
|
||||
};
|
||||
@@ -39,7 +39,7 @@ simdjson_inline void json_minifier::next(const simd::simd8x64<uint8_t>& in, cons
|
||||
dst += in.compress(mask, dst);
|
||||
}
|
||||
|
||||
simdjson_inline error_code json_minifier::finish(uint8_t *dst_start, size_t &dst_len) {
|
||||
simdjson_warn_unused simdjson_inline error_code json_minifier::finish(uint8_t *dst_start, size_t &dst_len) {
|
||||
error_code error = scanner.finish();
|
||||
if (error) { dst_len = 0; return error; }
|
||||
dst_len = dst - dst_start;
|
||||
|
||||
@@ -108,7 +108,7 @@ public:
|
||||
json_scanner() = default;
|
||||
simdjson_inline json_block next(const simd::simd8x64<uint8_t>& in);
|
||||
// Returns either UNCLOSED_STRING or SUCCESS
|
||||
simdjson_inline error_code finish();
|
||||
simdjson_warn_unused simdjson_inline error_code finish();
|
||||
|
||||
private:
|
||||
// Whether the last character of the previous iteration is part of a scalar token
|
||||
@@ -156,7 +156,7 @@ simdjson_inline json_block json_scanner::next(const simd::simd8x64<uint8_t>& in)
|
||||
);
|
||||
}
|
||||
|
||||
simdjson_inline error_code json_scanner::finish() {
|
||||
simdjson_warn_unused simdjson_inline error_code json_scanner::finish() {
|
||||
return string_scanner.finish();
|
||||
}
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ private:
|
||||
template<size_t STEP_SIZE>
|
||||
simdjson_inline void step(const uint8_t *block, buf_block_reader<STEP_SIZE> &reader) noexcept;
|
||||
simdjson_inline void next(const simd::simd8x64<uint8_t>& in, const json_block& block, size_t idx);
|
||||
simdjson_inline error_code finish(dom_parser_implementation &parser, size_t idx, size_t len, stage1_mode partial);
|
||||
simdjson_warn_unused simdjson_inline error_code finish(dom_parser_implementation &parser, size_t idx, size_t len, stage1_mode partial);
|
||||
|
||||
json_scanner scanner{};
|
||||
utf8_checker checker{};
|
||||
|
||||
@@ -195,7 +195,7 @@ using namespace simd;
|
||||
}
|
||||
}
|
||||
// do not forget to call check_eof!
|
||||
simdjson_inline error_code errors() {
|
||||
simdjson_warn_unused simdjson_inline error_code errors() {
|
||||
return this->error.any_bits_set_anywhere() ? error_code::UTF8_ERROR : error_code::SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -151,7 +151,8 @@ simdjson_inline bool handle_unicode_codepoint_wobbly(const uint8_t **src_ptr,
|
||||
simdjson_warn_unused simdjson_inline uint8_t *parse_string(const uint8_t *src, uint8_t *dst, bool allow_replacement) {
|
||||
while (1) {
|
||||
// Copy the next n bytes, and find the backslash and quote in them.
|
||||
auto bs_quote = backslash_and_quote::copy_and_find(src, dst);
|
||||
auto b = backslash_and_quote{};
|
||||
auto bs_quote = b.copy_and_find(src, dst);
|
||||
// If the next thing is the end quote, copy and return
|
||||
if (bs_quote.has_quote_first()) {
|
||||
// we encountered quotes first. Move dst to point to quotes and exit
|
||||
@@ -196,7 +197,8 @@ simdjson_warn_unused simdjson_inline uint8_t *parse_wobbly_string(const uint8_t
|
||||
// It is not ideal that this function is nearly identical to parse_string.
|
||||
while (1) {
|
||||
// Copy the next n bytes, and find the backslash and quote in them.
|
||||
auto bs_quote = backslash_and_quote::copy_and_find(src, dst);
|
||||
auto b = backslash_and_quote{};
|
||||
auto bs_quote = b.copy_and_find(src, dst);
|
||||
// If the next thing is the end quote, copy and return
|
||||
if (bs_quote.has_quote_first()) {
|
||||
// we encountered quotes first. Move dst to point to quotes and exit
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# All remaining tests link with simdjson proper
|
||||
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)
|
||||
add_cpp_test(static_reflection_custom_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)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -50,9 +50,8 @@ namespace builder_tests {
|
||||
TEST_START();
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
Car c = {"Toyota", "Corolla", 2017, {30.0,30.2,30.513,30.79}};
|
||||
auto result = builder::to_json_string(c);
|
||||
ASSERT_SUCCESS(result);
|
||||
std::string pstr = result.value();
|
||||
std::string pstr;
|
||||
ASSERT_SUCCESS(builder::to_json_string(c).get(pstr));
|
||||
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;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
@@ -12,6 +13,84 @@ using namespace simdjson;
|
||||
|
||||
namespace builder_tests {
|
||||
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
// Custom type for testing tag_invoke with extract_into
|
||||
struct Price {
|
||||
double amount;
|
||||
std::string currency;
|
||||
|
||||
// Custom deserializer that applies currency conversion
|
||||
friend error_code tag_invoke(deserialize_tag,
|
||||
ondemand::value& val,
|
||||
Price& price) noexcept {
|
||||
ondemand::object obj;
|
||||
auto error = val.get_object().get(obj);
|
||||
if (error) return error;
|
||||
|
||||
// Get the raw amount
|
||||
error = obj["amount"].get(price.amount);
|
||||
if (error) return error;
|
||||
|
||||
// Get the currency
|
||||
std::string_view currency_sv;
|
||||
error = obj["currency"].get(currency_sv);
|
||||
if (error) return error;
|
||||
price.currency = std::string(currency_sv);
|
||||
|
||||
// Custom logic: Convert EUR to USD for consistency
|
||||
if (price.currency == "EUR") {
|
||||
price.amount = price.amount * 1.1; // Simplified conversion
|
||||
price.currency = "USD";
|
||||
}
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
};
|
||||
|
||||
struct Product {
|
||||
std::string name;
|
||||
Price price; // Custom deserializable type
|
||||
int stock;
|
||||
};
|
||||
|
||||
// Another custom type for testing
|
||||
struct Dimensions {
|
||||
double value;
|
||||
std::string unit;
|
||||
|
||||
// Custom deserializer that converts to metric
|
||||
friend error_code tag_invoke(deserialize_tag,
|
||||
ondemand::value& val,
|
||||
Dimensions& dim) noexcept {
|
||||
ondemand::object obj;
|
||||
auto error = val.get_object().get(obj);
|
||||
if (error) return error;
|
||||
|
||||
error = obj["value"].get(dim.value);
|
||||
if (error) return error;
|
||||
|
||||
std::string_view unit_sv;
|
||||
error = obj["unit"].get(unit_sv);
|
||||
if (error) return error;
|
||||
dim.unit = std::string(unit_sv);
|
||||
|
||||
// Convert inches to cm
|
||||
if (dim.unit == "inches") {
|
||||
dim.value = dim.value * 2.54;
|
||||
dim.unit = "cm";
|
||||
}
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
};
|
||||
|
||||
struct Package {
|
||||
std::string id;
|
||||
Dimensions weight;
|
||||
Dimensions length;
|
||||
};
|
||||
#endif
|
||||
|
||||
bool test_primitive_types() {
|
||||
TEST_START();
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
@@ -25,10 +104,9 @@ namespace builder_tests {
|
||||
|
||||
PrimitiveTypes test{true, 'X', 42, 3.14159, 2.71f};
|
||||
|
||||
auto result = builder::to_json_string(test);
|
||||
ASSERT_SUCCESS(result);
|
||||
std::string json;
|
||||
ASSERT_SUCCESS(builder::to_json_string(test).get(json));
|
||||
|
||||
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);
|
||||
@@ -39,10 +117,9 @@ namespace builder_tests {
|
||||
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;
|
||||
ASSERT_SUCCESS(doc_result.get<PrimitiveTypes>().get(deserialized));
|
||||
|
||||
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);
|
||||
@@ -60,11 +137,9 @@ namespace builder_tests {
|
||||
};
|
||||
|
||||
StringTypes test{"hello world", "test_view"};
|
||||
std::string json;
|
||||
ASSERT_SUCCESS(builder::to_json_string(test).get(json));
|
||||
|
||||
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);
|
||||
|
||||
@@ -72,11 +147,9 @@ namespace builder_tests {
|
||||
ondemand::parser parser;
|
||||
auto doc_result = parser.iterate(pad(json));
|
||||
ASSERT_SUCCESS(doc_result);
|
||||
StringTypes deserialized;
|
||||
ASSERT_SUCCESS(doc_result.get<StringTypes>().get(deserialized));
|
||||
|
||||
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();
|
||||
@@ -98,10 +171,8 @@ namespace builder_tests {
|
||||
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();
|
||||
std::string json;
|
||||
ASSERT_SUCCESS(builder::to_json_string(test).get(json));
|
||||
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);
|
||||
@@ -112,10 +183,9 @@ namespace builder_tests {
|
||||
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;
|
||||
ASSERT_SUCCESS(doc_result.get<OptionalTypes>().get(deserialized));
|
||||
|
||||
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());
|
||||
@@ -144,10 +214,8 @@ namespace builder_tests {
|
||||
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();
|
||||
std::string json;
|
||||
ASSERT_SUCCESS(builder::to_json_string(test).get(json));
|
||||
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);
|
||||
@@ -157,12 +225,9 @@ namespace builder_tests {
|
||||
// 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());
|
||||
SmartPointerTypes deserialized;
|
||||
ASSERT_SUCCESS(doc_result.get<SmartPointerTypes>().get(deserialized));
|
||||
ASSERT_TRUE(deserialized.unique_int_with_value != nullptr);
|
||||
ASSERT_EQUAL(*deserialized.unique_int_with_value, 123);
|
||||
ASSERT_TRUE(deserialized.shared_string_with_value != nullptr);
|
||||
@@ -178,6 +243,7 @@ namespace builder_tests {
|
||||
bool test_container_types() {
|
||||
TEST_START();
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
// Test basic container types
|
||||
struct ContainerTypes {
|
||||
std::vector<int> int_vector;
|
||||
std::set<std::string> string_set;
|
||||
@@ -192,7 +258,8 @@ namespace builder_tests {
|
||||
auto result = builder::to_json_string(test);
|
||||
ASSERT_SUCCESS(result);
|
||||
|
||||
std::string json = result.value();
|
||||
std::string json;
|
||||
ASSERT_SUCCESS(builder::to_json_string(test).get(json));
|
||||
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);
|
||||
@@ -201,27 +268,447 @@ namespace builder_tests {
|
||||
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());
|
||||
ContainerTypes deserialized;
|
||||
ASSERT_SUCCESS(doc_result.get<ContainerTypes>().get(deserialized));
|
||||
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);
|
||||
|
||||
// Test std::list with iterator-based serialization
|
||||
struct ListContainer {
|
||||
std::list<int> int_list;
|
||||
std::list<std::string> string_list;
|
||||
};
|
||||
|
||||
ListContainer list_test;
|
||||
list_test.int_list = {10, 20, 30, 40, 50};
|
||||
list_test.string_list = {"first", "second", "third"};
|
||||
|
||||
std::string list_result;
|
||||
ASSERT_SUCCESS( builder::to_json_string(list_test).get(list_result));
|
||||
|
||||
// Check that list serialization produces correct JSON array format
|
||||
ASSERT_TRUE(list_result.find("\"int_list\":[10,20,30,40,50]") != std::string::npos);
|
||||
ASSERT_TRUE(list_result.find("\"string_list\":[\"first\",\"second\",\"third\"]") != std::string::npos);
|
||||
|
||||
// Test list round-trip
|
||||
auto list_doc_result = parser.iterate(pad(list_result));
|
||||
ASSERT_SUCCESS(list_doc_result);
|
||||
|
||||
ListContainer list_deserialized;
|
||||
ASSERT_SUCCESS(list_doc_result.get<ListContainer>().get(list_deserialized));
|
||||
ASSERT_EQUAL(list_deserialized.int_list.size(), 5);
|
||||
ASSERT_EQUAL(list_deserialized.string_list.size(), 3);
|
||||
|
||||
// Check list values
|
||||
auto it = list_deserialized.int_list.begin();
|
||||
ASSERT_EQUAL(*it, 10);
|
||||
std::advance(it, 4);
|
||||
ASSERT_EQUAL(*it, 50);
|
||||
|
||||
auto str_it = list_deserialized.string_list.begin();
|
||||
ASSERT_EQUAL(*str_it, "first");
|
||||
std::advance(str_it, 2);
|
||||
ASSERT_EQUAL(*str_it, "third");
|
||||
#endif
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool test_extract_into() {
|
||||
TEST_START();
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
struct Car {
|
||||
std::string make;
|
||||
std::string model;
|
||||
int year;
|
||||
double price;
|
||||
std::optional<std::string> color;
|
||||
};
|
||||
|
||||
ondemand::parser parser;
|
||||
|
||||
// Test 1: Extract only specific fields
|
||||
{
|
||||
auto padded = R"({
|
||||
"make": "Toyota",
|
||||
"model": "Camry",
|
||||
"year": 2024,
|
||||
"price": 28999.99,
|
||||
"color": "Blue",
|
||||
"engine": "V6",
|
||||
"transmission": "Automatic"
|
||||
})"_padded;
|
||||
|
||||
Car car{};
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS( parser.iterate(padded).get(doc) );
|
||||
|
||||
ondemand::object obj;
|
||||
ASSERT_SUCCESS(doc.get_object().get(obj));
|
||||
|
||||
// Extract only 'make' and 'model'
|
||||
ASSERT_SUCCESS(obj.extract_into<"make","model">(car));
|
||||
|
||||
ASSERT_EQUAL(car.make, "Toyota");
|
||||
ASSERT_EQUAL(car.model, "Camry");
|
||||
ASSERT_EQUAL(car.year, 0); // Not extracted
|
||||
ASSERT_EQUAL(car.price, 0); // Not extracted
|
||||
|
||||
ASSERT_SUCCESS(parser.iterate(padded).get(doc));
|
||||
|
||||
// Extract only 'make' and 'model'
|
||||
ASSERT_SUCCESS(doc.extract_into<"make", "model">(car));
|
||||
|
||||
ASSERT_EQUAL(car.make, "Toyota");
|
||||
ASSERT_EQUAL(car.model, "Camry");
|
||||
ASSERT_EQUAL(car.year, 0); // Not extracted
|
||||
ASSERT_EQUAL(car.price, 0); // Not extracted
|
||||
}
|
||||
|
||||
// Test 2: Extract with optional field
|
||||
{
|
||||
auto padded = R"({
|
||||
"make": "Honda",
|
||||
"model": "Accord",
|
||||
"year": 2023,
|
||||
"price": 26999.99,
|
||||
"color": "Red"
|
||||
})"_padded;
|
||||
|
||||
Car car{};
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(padded).get(doc));
|
||||
|
||||
ondemand::object obj;
|
||||
ASSERT_SUCCESS(doc.get_object().get(obj));
|
||||
|
||||
// Extract including optional 'color'
|
||||
ASSERT_SUCCESS(obj.extract_into<"make", "model", "color">(car));
|
||||
|
||||
ASSERT_EQUAL(car.make, "Honda");
|
||||
ASSERT_EQUAL(car.model, "Accord");
|
||||
ASSERT_TRUE(car.color.has_value());
|
||||
ASSERT_EQUAL(*car.color, "Red");
|
||||
}
|
||||
|
||||
// Test 3: Extract with missing optional field
|
||||
{
|
||||
auto padded = R"({
|
||||
"make": "Ford",
|
||||
"model": "F-150",
|
||||
"year": 2024,
|
||||
"price": 35999.99
|
||||
})"_padded;
|
||||
|
||||
Car car{};
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(padded).get(doc));
|
||||
|
||||
ondemand::object obj;
|
||||
ASSERT_SUCCESS(doc.get_object().get(obj));
|
||||
|
||||
// Try to extract including optional 'color' which doesn't exist
|
||||
ASSERT_SUCCESS(obj.extract_into<"make", "model", "color">(car));
|
||||
|
||||
ASSERT_EQUAL(car.make, "Ford");
|
||||
ASSERT_EQUAL(car.model, "F-150");
|
||||
ASSERT_FALSE(car.color.has_value()); // Should be empty
|
||||
}
|
||||
|
||||
// Test 4: Extract with custom deserializable type using tag_invoke
|
||||
{
|
||||
// Test using the Price struct defined at namespace level
|
||||
|
||||
auto padded = R"({
|
||||
"name": "Laptop",
|
||||
"price": {
|
||||
"amount": 1000,
|
||||
"currency": "EUR"
|
||||
},
|
||||
"stock": 15,
|
||||
"description": "High-end gaming laptop"
|
||||
})"_padded;
|
||||
|
||||
Product product{};
|
||||
auto doc = parser.iterate(padded);
|
||||
ASSERT_SUCCESS(doc);
|
||||
|
||||
ondemand::object obj;
|
||||
auto obj_result = doc.get_object().get(obj);
|
||||
ASSERT_SUCCESS(obj_result);
|
||||
|
||||
// Extract including price field with custom deserializer
|
||||
ASSERT_SUCCESS(obj.extract_into<"name", "price">(product));
|
||||
|
||||
ASSERT_EQUAL(product.name, "Laptop");
|
||||
// Verify custom deserializer was invoked: EUR should be converted to USD
|
||||
ASSERT_EQUAL(product.price.currency, "USD"); // Should be converted from EUR
|
||||
// Verify custom deserializer was invoked: amount should be 1100 (1000 * 1.1)
|
||||
ASSERT_EQUAL(product.price.amount, 1100); // Should be exactly 1100 after conversion
|
||||
ASSERT_EQUAL(product.stock, 0); // Not extracted
|
||||
}
|
||||
|
||||
// Test 5: Extract with nested custom deserializable types
|
||||
{
|
||||
// Test using the Dimensions struct defined at namespace level
|
||||
|
||||
auto padded = R"({
|
||||
"id": "PKG123",
|
||||
"weight": {
|
||||
"value": 10,
|
||||
"unit": "pounds"
|
||||
},
|
||||
"length": {
|
||||
"value": 12,
|
||||
"unit": "inches"
|
||||
},
|
||||
"fragile": true
|
||||
})"_padded;
|
||||
|
||||
Package pkg{};
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(padded).get(doc));
|
||||
|
||||
ondemand::object obj;
|
||||
ASSERT_SUCCESS(doc.get_object().get(obj));
|
||||
|
||||
// Extract only length (with custom deserializer), skip weight
|
||||
ASSERT_SUCCESS(obj.extract_into<"id", "length">(pkg));
|
||||
|
||||
ASSERT_EQUAL(pkg.id, "PKG123");
|
||||
// Verify custom deserializer was invoked: inches should be converted to cm
|
||||
ASSERT_EQUAL(pkg.length.unit, "cm"); // Should be converted from inches
|
||||
// Verify exact conversion: 12 inches * 2.54 = 30.48 cm
|
||||
ASSERT_EQUAL(pkg.length.value, 30.48); // Should be exactly 30.48 after conversion
|
||||
ASSERT_EQUAL(pkg.weight.unit, ""); // Weight not extracted
|
||||
ASSERT_EQUAL(pkg.weight.value, 0); // Weight value should remain at default
|
||||
}
|
||||
#endif
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
|
||||
bool test_extract_from() {
|
||||
TEST_START();
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
// Test 1: Extract specific fields from Car struct
|
||||
{
|
||||
struct Car {
|
||||
std::string make;
|
||||
std::string model;
|
||||
int year;
|
||||
double price;
|
||||
bool electric;
|
||||
};
|
||||
|
||||
Car car{"Tesla", "Model 3", 2023, 42000.0, true};
|
||||
|
||||
// Extract only make and model
|
||||
std::string json;
|
||||
ASSERT_SUCCESS((extract_from<"make", "model">(car).get(json)));
|
||||
|
||||
// Parse back to verify correctness
|
||||
auto padded = pad(json);
|
||||
ondemand::parser parser;
|
||||
auto doc = parser.iterate(padded);
|
||||
ASSERT_SUCCESS(doc);
|
||||
|
||||
std::string_view make;
|
||||
std::string_view model;
|
||||
ASSERT_SUCCESS(doc["make"].get(make));
|
||||
ASSERT_SUCCESS(doc["model"].get(model));
|
||||
ASSERT_EQUAL(make, "Tesla");
|
||||
ASSERT_EQUAL(model, "Model 3");
|
||||
|
||||
// Verify excluded fields are not present
|
||||
auto year_result = doc["year"];
|
||||
ASSERT_ERROR(year_result.error(), NO_SUCH_FIELD);
|
||||
auto price_result = doc["price"];
|
||||
ASSERT_ERROR(price_result.error(), NO_SUCH_FIELD);
|
||||
auto electric_result = doc["electric"];
|
||||
ASSERT_ERROR(electric_result.error(), NO_SUCH_FIELD);
|
||||
}
|
||||
|
||||
// Test 2: Extract different field combination
|
||||
{
|
||||
struct Car {
|
||||
std::string make;
|
||||
std::string model;
|
||||
int year;
|
||||
double price;
|
||||
bool electric;
|
||||
};
|
||||
|
||||
Car car{"Ford", "F-150", 2024, 55000.0, false};
|
||||
|
||||
// Extract year and price
|
||||
std::string json;
|
||||
ASSERT_SUCCESS((extract_from<"year", "price">(car).get(json)));
|
||||
|
||||
auto padded = pad(json);
|
||||
ondemand::parser parser;
|
||||
auto doc = parser.iterate(padded);
|
||||
ASSERT_SUCCESS(doc);
|
||||
|
||||
int64_t year;
|
||||
double price;
|
||||
ASSERT_SUCCESS(doc["year"].get(year));
|
||||
ASSERT_SUCCESS(doc["price"].get(price));
|
||||
ASSERT_EQUAL(year, 2024);
|
||||
ASSERT_EQUAL(price, 55000.0);
|
||||
|
||||
// Verify excluded fields
|
||||
auto make_result = doc["make"];
|
||||
ASSERT_ERROR(make_result.error(), NO_SUCH_FIELD);
|
||||
}
|
||||
|
||||
// Test 3: Extract from struct with optional fields
|
||||
{
|
||||
struct Person {
|
||||
std::string name;
|
||||
int age;
|
||||
std::optional<std::string> email;
|
||||
std::optional<std::string> phone;
|
||||
};
|
||||
|
||||
Person person{"John Doe", 30, "john@example.com", std::nullopt};
|
||||
|
||||
// Extract name and email
|
||||
std::string json;
|
||||
ASSERT_SUCCESS((extract_from<"name", "email">(person).get(json)));
|
||||
|
||||
auto padded = pad(json);
|
||||
ondemand::parser parser;
|
||||
auto doc = parser.iterate(padded);
|
||||
ASSERT_SUCCESS(doc);
|
||||
|
||||
std::string_view name;
|
||||
std::string_view email;
|
||||
ASSERT_SUCCESS(doc["name"].get(name));
|
||||
ASSERT_SUCCESS(doc["email"].get(email));
|
||||
ASSERT_EQUAL(name, "John Doe");
|
||||
ASSERT_EQUAL(email, "john@example.com");
|
||||
}
|
||||
|
||||
// Test 4: Extract with optional that has value
|
||||
{
|
||||
struct Person {
|
||||
std::string name;
|
||||
int age;
|
||||
std::optional<std::string> email;
|
||||
std::optional<std::string> phone;
|
||||
};
|
||||
|
||||
Person person{"Jane Smith", 25, "jane@example.com", "555-1234"};
|
||||
|
||||
// Extract name, age, and phone
|
||||
std::string json;
|
||||
ASSERT_SUCCESS((extract_from<"name", "age", "phone">(person).get(json)));
|
||||
|
||||
auto padded = pad(json);
|
||||
ondemand::parser parser;
|
||||
auto doc = parser.iterate(padded);
|
||||
ASSERT_SUCCESS(doc);
|
||||
|
||||
std::string_view name;
|
||||
int64_t age;
|
||||
std::string_view phone;
|
||||
ASSERT_SUCCESS(doc["name"].get(name));
|
||||
ASSERT_SUCCESS(doc["age"].get(age));
|
||||
ASSERT_SUCCESS(doc["phone"].get(phone));
|
||||
ASSERT_EQUAL(name, "Jane Smith");
|
||||
ASSERT_EQUAL(age, 25);
|
||||
ASSERT_EQUAL(phone, "555-1234");
|
||||
|
||||
// Email should not be present
|
||||
auto email_result = doc["email"];
|
||||
ASSERT_ERROR(email_result.error(), NO_SUCH_FIELD);
|
||||
}
|
||||
|
||||
// Test 5: Round-trip test - serialize with extract_from, deserialize with extract_into
|
||||
{
|
||||
struct Product {
|
||||
std::string id;
|
||||
std::string name;
|
||||
double price;
|
||||
int stock;
|
||||
};
|
||||
|
||||
Product original{"P123", "Widget", 19.99, 100};
|
||||
|
||||
// Extract specific fields to JSON
|
||||
std::string json;
|
||||
ASSERT_SUCCESS((extract_from<"id", "name", "price">(original).get(json)));
|
||||
|
||||
// Parse and extract back
|
||||
Product restored{"", "", 0.0, 0};
|
||||
auto padded = pad(json);
|
||||
ondemand::parser parser;
|
||||
auto doc = parser.iterate(padded);
|
||||
ASSERT_SUCCESS(doc);
|
||||
|
||||
ondemand::object obj;
|
||||
ASSERT_SUCCESS(doc.get_object().get(obj));
|
||||
auto extract_result = obj.extract_into<"id", "name", "price">(restored);
|
||||
ASSERT_SUCCESS(extract_result);
|
||||
|
||||
// Verify fields match
|
||||
ASSERT_EQUAL(restored.id, original.id);
|
||||
ASSERT_EQUAL(restored.name, original.name);
|
||||
ASSERT_EQUAL(restored.price, original.price);
|
||||
ASSERT_EQUAL(restored.stock, 0); // Stock should remain at default
|
||||
}
|
||||
|
||||
// Test 6: Extract from nested structs
|
||||
{
|
||||
struct Address {
|
||||
std::string street;
|
||||
std::string city;
|
||||
std::string zip;
|
||||
};
|
||||
|
||||
struct Company {
|
||||
std::string name;
|
||||
Address headquarters;
|
||||
int employees;
|
||||
};
|
||||
|
||||
Company company{"TechCorp", {"123 Main St", "San Francisco", "94105"}, 500};
|
||||
|
||||
// Extract name and employees only
|
||||
std::string json;
|
||||
ASSERT_SUCCESS((extract_from<"name", "employees">(company).get(json)));
|
||||
|
||||
auto padded = pad(json);
|
||||
ondemand::parser parser;
|
||||
auto doc = parser.iterate(padded);
|
||||
ASSERT_SUCCESS(doc);
|
||||
|
||||
std::string_view name;
|
||||
int64_t employees;
|
||||
ASSERT_SUCCESS(doc["name"].get(name));
|
||||
ASSERT_SUCCESS(doc["employees"].get(employees));
|
||||
ASSERT_EQUAL(name, "TechCorp");
|
||||
ASSERT_EQUAL(employees, 500);
|
||||
|
||||
// headquarters should not be present
|
||||
auto hq_result = doc["headquarters"];
|
||||
ASSERT_ERROR(hq_result.error(), NO_SUCH_FIELD);
|
||||
}
|
||||
#endif
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool run() {
|
||||
return test_primitive_types() &&
|
||||
test_string_types() &&
|
||||
test_optional_types() &&
|
||||
test_smart_pointer_types() &&
|
||||
test_container_types();
|
||||
test_container_types() &&
|
||||
test_extract_into() &&
|
||||
test_extract_from();
|
||||
}
|
||||
|
||||
} // namespace builder_tests
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
|
||||
|
||||
|
||||
#include "simdjson.h"
|
||||
#include "test_builder.h"
|
||||
#include <charconv>
|
||||
#include <string>
|
||||
|
||||
using namespace simdjson;
|
||||
|
||||
// Suppose that we want to serialize/deserialize Car using
|
||||
// strings for the year
|
||||
struct Car {
|
||||
std::string make;
|
||||
std::string model;
|
||||
int64_t year;
|
||||
std::vector<float> tire_pressure;
|
||||
};
|
||||
|
||||
|
||||
|
||||
namespace simdjson {
|
||||
// This tag_invoke MUST be inside simdjson namespace
|
||||
template <typename simdjson_value>
|
||||
auto tag_invoke(deserialize_tag, simdjson_value &val, Car& car) {
|
||||
ondemand::object obj;
|
||||
auto error = val.get_object().get(obj);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
if ((error = obj["make"].get_string(car.make))) {
|
||||
return error;
|
||||
}
|
||||
if ((error = obj["model"].get_string(car.model))) {
|
||||
return error;
|
||||
}
|
||||
std::string_view year_str;
|
||||
if ((error = obj["year"].get(year_str))) {
|
||||
return error;
|
||||
}
|
||||
int64_t year_value;
|
||||
auto [ptr, ec] = std::from_chars(year_str.data(), year_str.data() + year_str.size(), year_value);
|
||||
if (ec != std::errc{}) {
|
||||
return INCORRECT_TYPE;
|
||||
}
|
||||
car.year = year_value;
|
||||
if ((error = obj["tire_pressure"].get<std::vector<float>>().get(
|
||||
car.tire_pressure))) {
|
||||
return error;
|
||||
}
|
||||
return simdjson::SUCCESS;
|
||||
}
|
||||
|
||||
template <typename builder_type>
|
||||
void tag_invoke(serialize_tag, builder_type &builder, const Car& car) {
|
||||
builder.start_object();
|
||||
builder.append_key_value("make", car.make);
|
||||
builder.append_comma();
|
||||
builder.append_key_value("model", car.model);
|
||||
builder.append_comma();
|
||||
builder.append_key_value("year", std::to_string(car.year));
|
||||
builder.append_comma();
|
||||
builder.append_key_value("tire_pressure", car.tire_pressure);
|
||||
builder.end_object();
|
||||
}
|
||||
|
||||
} // namespace simdjson
|
||||
namespace custom_tests {
|
||||
|
||||
bool test_car_deserialization() {
|
||||
TEST_START();
|
||||
auto json = R"({"make":"Toyota","model":"Camry","year":"2018","tire_pressure":[30.5,30.6,30.7,30.8]})"_padded;
|
||||
ondemand::parser parser;
|
||||
auto doc_result = parser.iterate(json);
|
||||
ASSERT_SUCCESS(doc_result);
|
||||
Car car;
|
||||
ASSERT_SUCCESS(doc_result.get(car));
|
||||
if (car.make != "Toyota" || car.model != "Camry" || std::to_string(car.year) != "2018" || car.tire_pressure.size() != 4 || car.tire_pressure[0] != 30.5f) {
|
||||
return false;
|
||||
}
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
bool test_car_serialization() {
|
||||
TEST_START();
|
||||
Car car{"Toyota", "Camry", 2018, {30.5f,30.6f,30.7f,30.8f}};
|
||||
simdjson::builder::string_builder builder;
|
||||
builder.append(car);
|
||||
std::string_view json;
|
||||
ASSERT_SUCCESS(builder.view().get(json));
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
bool test_car_roundtrip() {
|
||||
TEST_START();
|
||||
Car car{"Toyota", "Camry", 2018, {30.5f,30.6f,30.7f,30.8f}};
|
||||
simdjson::builder::string_builder builder;
|
||||
builder.append(car);
|
||||
std::string json;
|
||||
ASSERT_SUCCESS(builder.view().get(json));
|
||||
ondemand::parser parser;
|
||||
auto doc_result = parser.iterate(json);
|
||||
ASSERT_SUCCESS(doc_result);
|
||||
Car carback;
|
||||
ASSERT_SUCCESS(doc_result.get(carback));
|
||||
|
||||
if (carback.make != car.make || carback.model != car.model || carback.year != car.year || carback.tire_pressure != car.tire_pressure) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool test_car_roundtrip_to_json() {
|
||||
TEST_START();
|
||||
Car car{"Toyota", "Camry", 2018, {30.5f,30.6f,30.7f,30.8f}};
|
||||
std::string json;
|
||||
ASSERT_SUCCESS(simdjson::to_json(car).get(json));
|
||||
simdjson::builder::string_builder builder;
|
||||
ondemand::parser parser;
|
||||
auto doc_result = parser.iterate(json);
|
||||
ASSERT_SUCCESS(doc_result);
|
||||
Car carback;
|
||||
ASSERT_SUCCESS(doc_result.get(carback));
|
||||
|
||||
if (carback.make != car.make || carback.model != car.model || carback.year != car.year || carback.tire_pressure != car.tire_pressure) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
bool run() {
|
||||
return test_car_deserialization() && test_car_serialization() && test_car_roundtrip() && test_car_roundtrip_to_json();
|
||||
}
|
||||
|
||||
} // namespace builder_tests
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
return test_main(argc, argv, custom_tests::run);
|
||||
}
|
||||
@@ -28,10 +28,8 @@ namespace builder_tests {
|
||||
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();
|
||||
std::string json;
|
||||
ASSERT_SUCCESS(builder::to_json_string(test).get(json));
|
||||
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);
|
||||
@@ -43,10 +41,8 @@ namespace builder_tests {
|
||||
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());
|
||||
EmptyValues deserialized;
|
||||
ASSERT_SUCCESS(doc_result.get<EmptyValues>().get(deserialized));
|
||||
ASSERT_EQUAL(deserialized.empty_string, "");
|
||||
ASSERT_EQUAL(deserialized.empty_vector.size(), 0);
|
||||
ASSERT_FALSE(deserialized.null_optional.has_value());
|
||||
@@ -74,10 +70,8 @@ namespace builder_tests {
|
||||
test.unicode = "Café résumé";
|
||||
test.null_char = '\0';
|
||||
|
||||
auto result = builder::to_json_string(test);
|
||||
ASSERT_SUCCESS(result);
|
||||
|
||||
std::string json = result.value();
|
||||
std::string json;
|
||||
ASSERT_SUCCESS(builder::to_json_string(test).get(json));
|
||||
// Test that quotes are properly escaped
|
||||
ASSERT_TRUE(json.find("\\\"Hello\\\"") != std::string::npos);
|
||||
// Test that backslashes are properly escaped
|
||||
@@ -100,17 +94,17 @@ namespace builder_tests {
|
||||
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);
|
||||
std::string result_no_null;
|
||||
ASSERT_SUCCESS(builder::to_json_string(test_no_null).get(result_no_null));
|
||||
|
||||
ondemand::parser parser;
|
||||
auto doc_result = parser.iterate(pad(result_no_null.value()));
|
||||
ASSERT_SUCCESS(doc_result);
|
||||
ondemand::document doc_result;
|
||||
|
||||
auto get_result = doc_result.value().get<SpecialCharsNoNull>();
|
||||
ASSERT_SUCCESS(get_result);
|
||||
ASSERT_SUCCESS(parser.iterate(pad(result_no_null)).get(doc_result));
|
||||
|
||||
SpecialCharsNoNull deserialized;
|
||||
ASSERT_SUCCESS(doc_result.get<SpecialCharsNoNull>().get(deserialized));
|
||||
|
||||
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);
|
||||
@@ -139,22 +133,18 @@ namespace builder_tests {
|
||||
test.true_val = true;
|
||||
test.false_val = false;
|
||||
|
||||
auto result = builder::to_json_string(test);
|
||||
ASSERT_SUCCESS(result);
|
||||
|
||||
std::string json = result.value();
|
||||
std::string json;
|
||||
ASSERT_SUCCESS(builder::to_json_string(test).get(json));
|
||||
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);
|
||||
ondemand::document doc_result;
|
||||
|
||||
auto get_result = doc_result.value().get<NumericLimits>();
|
||||
ASSERT_SUCCESS(get_result);
|
||||
|
||||
NumericLimits deserialized = std::move(get_result.value());
|
||||
ASSERT_SUCCESS(parser.iterate(pad(json)).get(doc_result));
|
||||
NumericLimits deserialized;
|
||||
ASSERT_SUCCESS(doc_result.get<NumericLimits>().get(deserialized));
|
||||
ASSERT_EQUAL(deserialized.max_int, test.max_int);
|
||||
ASSERT_EQUAL(deserialized.min_int, test.min_int);
|
||||
ASSERT_EQUAL(deserialized.true_val, true);
|
||||
@@ -184,10 +174,8 @@ namespace builder_tests {
|
||||
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();
|
||||
std::string json;
|
||||
ASSERT_SUCCESS(builder::to_json_string(test).get(json));
|
||||
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);
|
||||
@@ -198,10 +186,8 @@ namespace builder_tests {
|
||||
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());
|
||||
Outer deserialized;
|
||||
ASSERT_SUCCESS(doc_result.get<Outer>().get(deserialized));
|
||||
ASSERT_EQUAL(deserialized.inner_obj.value, 42);
|
||||
ASSERT_EQUAL(deserialized.inner_obj.name, "inner");
|
||||
ASSERT_EQUAL(deserialized.inner_vector.size(), 2);
|
||||
|
||||
@@ -21,26 +21,21 @@ namespace builder_tests {
|
||||
};
|
||||
|
||||
EnumStruct test{Color::Red, 42};
|
||||
|
||||
auto result = builder::to_json_string(test);
|
||||
ASSERT_SUCCESS(result);
|
||||
|
||||
std::string json = result.value();
|
||||
std::string json;
|
||||
ASSERT_SUCCESS(builder::to_json_string(test).get(json));
|
||||
// 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();
|
||||
std::string json2;
|
||||
ASSERT_SUCCESS(builder::to_json_string(test).get(json2));
|
||||
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();
|
||||
std::string json3;
|
||||
ASSERT_SUCCESS(builder::to_json_string(test).get(json3));
|
||||
ASSERT_TRUE(json3.find("\"color\":\"Blue\"") != std::string::npos);
|
||||
#endif
|
||||
TEST_SUCCEED();
|
||||
@@ -61,41 +56,37 @@ namespace builder_tests {
|
||||
};
|
||||
|
||||
// Test deserialization of different enum values with string representation
|
||||
std::string json1 = "{\"status\":\"Active\",\"name\":\"test1\"}";
|
||||
auto json1 = R"({"status":"Active","name":"test1"})"_padded;
|
||||
ondemand::parser parser1;
|
||||
auto doc_result1 = parser1.iterate(pad(json1));
|
||||
ASSERT_SUCCESS(doc_result1);
|
||||
ondemand::document doc_result1;
|
||||
ASSERT_SUCCESS(parser1.iterate(json1).get(doc_result1));
|
||||
StatusStruct deserialized1;
|
||||
ASSERT_SUCCESS(doc_result1.get<StatusStruct>().get(deserialized1));
|
||||
|
||||
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\"}";
|
||||
auto json2 = R"({"status":"Inactive","name":"test2"})"_padded;
|
||||
ondemand::parser parser2;
|
||||
auto doc_result2 = parser2.iterate(pad(json2));
|
||||
ASSERT_SUCCESS(doc_result2);
|
||||
ondemand::document doc_result2;
|
||||
ASSERT_SUCCESS(parser2.iterate(json2).get(doc_result2));
|
||||
|
||||
auto get_result2 = doc_result2.value().get<StatusStruct>();
|
||||
ASSERT_SUCCESS(get_result2);
|
||||
StatusStruct deserialized2;
|
||||
ASSERT_SUCCESS(doc_result2.get<StatusStruct>().get(deserialized2));
|
||||
|
||||
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);
|
||||
ondemand::document doc_result3;
|
||||
ASSERT_SUCCESS(parser3.iterate(pad(json3)).get(doc_result3));
|
||||
|
||||
auto get_result3 = doc_result3.value().get<StatusStruct>();
|
||||
ASSERT_SUCCESS(get_result3);
|
||||
StatusStruct deserialized3;
|
||||
ASSERT_SUCCESS(doc_result3.get<StatusStruct>().get(deserialized3));
|
||||
|
||||
StatusStruct deserialized3 = std::move(get_result3.value());
|
||||
ASSERT_TRUE(deserialized3.status == Status::Pending);
|
||||
ASSERT_EQUAL(deserialized3.name, "test3");
|
||||
#endif
|
||||
@@ -121,10 +112,8 @@ namespace builder_tests {
|
||||
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();
|
||||
std::string json;
|
||||
ASSERT_SUCCESS(builder::to_json_string(original).get(json));
|
||||
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);
|
||||
@@ -132,13 +121,12 @@ namespace builder_tests {
|
||||
// Deserialize
|
||||
ondemand::parser parser;
|
||||
std::cout << json << std::endl;
|
||||
auto doc_result = parser.iterate(pad(json));
|
||||
ASSERT_SUCCESS(doc_result);
|
||||
ondemand::document doc_result;
|
||||
ASSERT_SUCCESS(parser.iterate(pad(json)).get(doc_result));
|
||||
|
||||
auto get_result = doc_result.value().get<Task>();
|
||||
ASSERT_SUCCESS(get_result);
|
||||
Task deserialized;
|
||||
ASSERT_SUCCESS(doc_result.get<Task>().get(deserialized));
|
||||
|
||||
Task deserialized = std::move(get_result.value());
|
||||
ASSERT_TRUE(deserialized.priority == Priority::High);
|
||||
ASSERT_EQUAL(deserialized.description, "Important task");
|
||||
ASSERT_EQUAL(deserialized.id, 123);
|
||||
@@ -162,22 +150,18 @@ namespace builder_tests {
|
||||
|
||||
Response test{ErrorCode::NotFound, "Resource not found"};
|
||||
|
||||
auto result = builder::to_json_string(test);
|
||||
ASSERT_SUCCESS(result);
|
||||
|
||||
std::string json = result.value();
|
||||
std::string json;
|
||||
ASSERT_SUCCESS(builder::to_json_string(test).get(json));
|
||||
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);
|
||||
ondemand::document doc_result;
|
||||
ASSERT_SUCCESS(parser.iterate(pad(json)).get(doc_result));
|
||||
Response deserialized;
|
||||
ASSERT_SUCCESS(doc_result.get<Response>().get(deserialized));
|
||||
|
||||
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
|
||||
@@ -220,10 +204,8 @@ namespace builder_tests {
|
||||
|
||||
Date test{Day::Friday, Month::July, 2024};
|
||||
|
||||
auto result = builder::to_json_string(test);
|
||||
ASSERT_SUCCESS(result);
|
||||
|
||||
std::string json = result.value();
|
||||
std::string json;
|
||||
ASSERT_SUCCESS(builder::to_json_string(test).get(json));
|
||||
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);
|
||||
@@ -232,11 +214,8 @@ namespace builder_tests {
|
||||
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());
|
||||
Date deserialized;
|
||||
ASSERT_SUCCESS(doc_result.get<Date>().get(deserialized));
|
||||
ASSERT_TRUE(deserialized.day == Day::Friday);
|
||||
ASSERT_TRUE(deserialized.month == Month::July);
|
||||
ASSERT_EQUAL(deserialized.year, 2024);
|
||||
|
||||
@@ -16,6 +16,8 @@ add_cpp_test(ondemand_error_tests LABELS ondemand acceptance
|
||||
add_cpp_test(ondemand_error_location_tests LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(ondemand_json_pointer_tests LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(ondemand_json_path_tests LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(compile_time_json_path_tests LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(compile_time_json_pointer_tests LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(ondemand_key_string_tests LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(ondemand_misc_tests LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(ondemand_number_tests LABELS ondemand acceptance per_implementation)
|
||||
|
||||
@@ -0,0 +1,374 @@
|
||||
#include "simdjson.h"
|
||||
#include "test_ondemand.h"
|
||||
#include <string>
|
||||
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS && SIMDJSON_STATIC_REFLECTION
|
||||
|
||||
using namespace simdjson;
|
||||
|
||||
namespace compile_time_json_path_tests {
|
||||
|
||||
// Test structures
|
||||
struct User {
|
||||
std::string name;
|
||||
int age;
|
||||
std::string email;
|
||||
};
|
||||
|
||||
struct TirePressure {
|
||||
std::vector<double> values;
|
||||
};
|
||||
|
||||
struct Car {
|
||||
std::string make;
|
||||
std::string model;
|
||||
int64_t year;
|
||||
std::vector<double> tire_pressure;
|
||||
};
|
||||
|
||||
const padded_string TEST_USER_JSON = R"(
|
||||
{
|
||||
"name": "John Doe",
|
||||
"age": 30,
|
||||
"email": "john@example.com"
|
||||
}
|
||||
)"_padded;
|
||||
|
||||
const padded_string TEST_CAR_JSON = R"(
|
||||
{
|
||||
"make": "Toyota",
|
||||
"model": "Camry",
|
||||
"year": 2018,
|
||||
"tire_pressure": [40.1, 39.9, 37.7, 40.4]
|
||||
}
|
||||
)"_padded;
|
||||
|
||||
const padded_string TEST_NESTED_JSON = R"(
|
||||
{
|
||||
"users": [
|
||||
{
|
||||
"name": "Alice",
|
||||
"age": 25,
|
||||
"email": "alice@example.com"
|
||||
},
|
||||
{
|
||||
"name": "Bob",
|
||||
"age": 35,
|
||||
"email": "bob@example.com"
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"count": 2,
|
||||
"version": "1.0"
|
||||
}
|
||||
}
|
||||
)"_padded;
|
||||
|
||||
const padded_string TEST_ARRAY_JSON = R"(
|
||||
[
|
||||
{"make": "Toyota", "model": "Camry", "year": 2018, "tire_pressure": [40.1, 39.9, 37.7, 40.4]},
|
||||
{"make": "Kia", "model": "Soul", "year": 2012, "tire_pressure": [30.1, 31.0, 28.6, 28.7]},
|
||||
{"make": "Toyota", "model": "Tercel", "year": 1999, "tire_pressure": [29.8, 30.0, 30.2, 30.5]}
|
||||
]
|
||||
)"_padded;
|
||||
|
||||
// Test 1: Simple field access with dot notation
|
||||
bool test_simple_field_access() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_USER_JSON).get(doc));
|
||||
|
||||
// Test compile-time accessor with validation
|
||||
auto result = ondemand::json_path::at_path_compiled<".name">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
std::string_view name;
|
||||
ASSERT_SUCCESS(result.get_string().get(name));
|
||||
ASSERT_EQUAL(name, "John Doe");
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 2: Field access with bracket notation
|
||||
bool test_bracket_field_access() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_USER_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_path_compiled<R"(["email"])">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
std::string_view email;
|
||||
ASSERT_SUCCESS(result.get_string().get(email));
|
||||
ASSERT_EQUAL(email, "john@example.com");
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 3: Integer field access
|
||||
bool test_integer_field() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_USER_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_path_compiled<".age">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
int64_t age;
|
||||
ASSERT_SUCCESS(result.get_int64().get(age));
|
||||
ASSERT_EQUAL(age, 30);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 4: Array index access
|
||||
bool test_array_index_access() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_CAR_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_path_compiled<".tire_pressure[1]">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
double pressure;
|
||||
ASSERT_SUCCESS(result.get_double().get(pressure));
|
||||
ASSERT_EQUAL(pressure, 39.9);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 5: Nested field access
|
||||
bool test_nested_field_access() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_NESTED_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_path_compiled<".metadata.version">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
std::string_view version;
|
||||
ASSERT_SUCCESS(result.get_string().get(version));
|
||||
ASSERT_EQUAL(version, "1.0");
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 6: Array of objects with nested path
|
||||
bool test_array_object_nested_path() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_NESTED_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_path_compiled<".users[0].name">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
std::string_view name;
|
||||
ASSERT_SUCCESS(result.get_string().get(name));
|
||||
ASSERT_EQUAL(name, "Alice");
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 7: Root array access
|
||||
bool test_root_array_access() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_ARRAY_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_path_compiled<"[1].make">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
std::string_view make;
|
||||
ASSERT_SUCCESS(result.get_string().get(make));
|
||||
ASSERT_EQUAL(make, "Kia");
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 8: Deep nested array access
|
||||
bool test_deep_nested_array() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_ARRAY_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_path_compiled<"[0].tire_pressure[2]">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
double pressure;
|
||||
ASSERT_SUCCESS(result.get_double().get(pressure));
|
||||
ASSERT_EQUAL(pressure, 37.7);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 9: Path with $ prefix
|
||||
bool test_path_with_dollar_prefix() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_USER_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_path_compiled<"$.name">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
std::string_view name;
|
||||
ASSERT_SUCCESS(result.get_string().get(name));
|
||||
ASSERT_EQUAL(name, "John Doe");
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 10: Multiple array indices in path
|
||||
bool test_multiple_indices() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_NESTED_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_path_compiled<".users[1].age">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
int64_t age;
|
||||
ASSERT_SUCCESS(result.get_int64().get(age));
|
||||
ASSERT_EQUAL(age, 35);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 11: Compare compile-time vs runtime path
|
||||
bool test_compile_vs_runtime() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_USER_JSON).get(doc));
|
||||
|
||||
// Compile-time version
|
||||
auto compile_result = ondemand::json_path::at_path_compiled<".name">(doc);
|
||||
ASSERT_SUCCESS(compile_result.error());
|
||||
std::string_view compile_name;
|
||||
ASSERT_SUCCESS(compile_result.get_string().get(compile_name));
|
||||
|
||||
// Runtime version for comparison
|
||||
ondemand::parser parser2;
|
||||
ondemand::document doc2;
|
||||
ASSERT_SUCCESS(parser2.iterate(TEST_USER_JSON).get(doc2));
|
||||
auto runtime_result = doc2.at_path(".name");
|
||||
ASSERT_SUCCESS(runtime_result.error());
|
||||
std::string_view runtime_name;
|
||||
ASSERT_SUCCESS(runtime_result.get_string().get(runtime_name));
|
||||
|
||||
// Should produce same result
|
||||
ASSERT_EQUAL(compile_name, runtime_name);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 12: Bracket notation with single quotes
|
||||
bool test_bracket_single_quotes() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_CAR_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_path_compiled<"['model']">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
std::string_view model;
|
||||
ASSERT_SUCCESS(result.get_string().get(model));
|
||||
ASSERT_EQUAL(model, "Camry");
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 13: Access first array element
|
||||
bool test_first_array_element() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_CAR_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_path_compiled<".tire_pressure[0]">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
double pressure;
|
||||
ASSERT_SUCCESS(result.get_double().get(pressure));
|
||||
ASSERT_EQUAL(pressure, 40.1);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 14: Mixed bracket and dot notation
|
||||
bool test_mixed_notation() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_NESTED_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_path_compiled<R"(.users[0]["email"])">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
std::string_view email;
|
||||
ASSERT_SUCCESS(result.get_string().get(email));
|
||||
ASSERT_EQUAL(email, "alice@example.com");
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 15: Integer field in nested object
|
||||
bool test_nested_integer() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_NESTED_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_path_compiled<".metadata.count">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
int64_t count;
|
||||
ASSERT_SUCCESS(result.get_int64().get(count));
|
||||
ASSERT_EQUAL(count, 2);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
} // namespace compile_time_json_path_tests
|
||||
|
||||
#endif // SIMDJSON_SUPPORTS_CONCEPTS && SIMDJSON_STATIC_REFLECTION
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS && SIMDJSON_STATIC_REFLECTION
|
||||
std::cout << "Running compile-time JSON path tests" << std::endl;
|
||||
|
||||
if (!compile_time_json_path_tests::test_simple_field_access()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_path_tests::test_bracket_field_access()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_path_tests::test_integer_field()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_path_tests::test_array_index_access()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_path_tests::test_nested_field_access()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_path_tests::test_array_object_nested_path()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_path_tests::test_root_array_access()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_path_tests::test_deep_nested_array()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_path_tests::test_path_with_dollar_prefix()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_path_tests::test_multiple_indices()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_path_tests::test_compile_vs_runtime()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_path_tests::test_bracket_single_quotes()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_path_tests::test_first_array_element()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_path_tests::test_mixed_notation()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_path_tests::test_nested_integer()) { return EXIT_FAILURE; }
|
||||
|
||||
std::cout << "All compile-time JSON path tests passed!" << std::endl;
|
||||
return EXIT_SUCCESS;
|
||||
#else
|
||||
std::cout << "Compile-time JSON path tests require C++26 reflection support" << std::endl;
|
||||
return EXIT_SUCCESS;
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
#include "simdjson.h"
|
||||
#include "test_ondemand.h"
|
||||
#include <string>
|
||||
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS && SIMDJSON_STATIC_REFLECTION
|
||||
|
||||
using namespace simdjson;
|
||||
|
||||
namespace compile_time_json_pointer_tests {
|
||||
|
||||
// Test structures
|
||||
struct User {
|
||||
std::string name;
|
||||
int age;
|
||||
std::string email;
|
||||
};
|
||||
|
||||
struct Car {
|
||||
std::string make;
|
||||
std::string model;
|
||||
int64_t year;
|
||||
std::vector<double> tire_pressure;
|
||||
};
|
||||
|
||||
const padded_string TEST_USER_JSON = R"(
|
||||
{
|
||||
"name": "John Doe",
|
||||
"age": 30,
|
||||
"email": "john@example.com"
|
||||
}
|
||||
)"_padded;
|
||||
|
||||
const padded_string TEST_CAR_JSON = R"(
|
||||
{
|
||||
"make": "Toyota",
|
||||
"model": "Camry",
|
||||
"year": 2018,
|
||||
"tire_pressure": [40.1, 39.9, 37.7, 40.4]
|
||||
}
|
||||
)"_padded;
|
||||
|
||||
const padded_string TEST_NESTED_JSON = R"(
|
||||
{
|
||||
"users": [
|
||||
{
|
||||
"name": "Alice",
|
||||
"age": 25,
|
||||
"email": "alice@example.com"
|
||||
},
|
||||
{
|
||||
"name": "Bob",
|
||||
"age": 35,
|
||||
"email": "bob@example.com"
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"count": 2,
|
||||
"version": "1.0"
|
||||
}
|
||||
}
|
||||
)"_padded;
|
||||
|
||||
const padded_string TEST_ARRAY_JSON = R"(
|
||||
[
|
||||
{"make": "Toyota", "model": "Camry", "year": 2018, "tire_pressure": [40.1, 39.9, 37.7, 40.4]},
|
||||
{"make": "Kia", "model": "Soul", "year": 2012, "tire_pressure": [30.1, 31.0, 28.6, 28.7]},
|
||||
{"make": "Toyota", "model": "Tercel", "year": 1999, "tire_pressure": [29.8, 30.0, 30.2, 30.5]}
|
||||
]
|
||||
)"_padded;
|
||||
|
||||
// Test 1: Simple field access
|
||||
bool test_simple_field() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_USER_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_pointer_compiled<"/name">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
std::string_view name;
|
||||
ASSERT_SUCCESS(result.get_string().get(name));
|
||||
ASSERT_EQUAL(name, "John Doe");
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 2: Integer field access
|
||||
bool test_integer_field() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_USER_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_pointer_compiled<"/age">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
int64_t age;
|
||||
ASSERT_SUCCESS(result.get_int64().get(age));
|
||||
ASSERT_EQUAL(age, 30);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 3: Array index access
|
||||
bool test_array_index() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_CAR_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_pointer_compiled<"/tire_pressure/1">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
double pressure;
|
||||
ASSERT_SUCCESS(result.get_double().get(pressure));
|
||||
ASSERT_EQUAL(pressure, 39.9);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 4: Nested field access
|
||||
bool test_nested_field() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_NESTED_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_pointer_compiled<"/metadata/version">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
std::string_view version;
|
||||
ASSERT_SUCCESS(result.get_string().get(version));
|
||||
ASSERT_EQUAL(version, "1.0");
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 5: Array of objects with nested path
|
||||
bool test_array_object_nested() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_NESTED_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_pointer_compiled<"/users/0/name">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
std::string_view name;
|
||||
ASSERT_SUCCESS(result.get_string().get(name));
|
||||
ASSERT_EQUAL(name, "Alice");
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 6: Root array access
|
||||
bool test_root_array() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_ARRAY_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_pointer_compiled<"/1/make">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
std::string_view make;
|
||||
ASSERT_SUCCESS(result.get_string().get(make));
|
||||
ASSERT_EQUAL(make, "Kia");
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 7: Deep nested array
|
||||
bool test_deep_nested_array() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_ARRAY_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_pointer_compiled<"/0/tire_pressure/2">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
double pressure;
|
||||
ASSERT_SUCCESS(result.get_double().get(pressure));
|
||||
ASSERT_EQUAL(pressure, 37.7);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 8: Root pointer (empty or "/")
|
||||
bool test_root_pointer() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_USER_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_pointer_compiled<"">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
auto obj = result.get_object();
|
||||
ASSERT_SUCCESS(obj.error());
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 9: First array element
|
||||
bool test_first_array_element() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_CAR_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_pointer_compiled<"/tire_pressure/0">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
double pressure;
|
||||
ASSERT_SUCCESS(result.get_double().get(pressure));
|
||||
ASSERT_EQUAL(pressure, 40.1);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 10: Multiple indices in path
|
||||
bool test_multiple_indices() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_NESTED_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_pointer_compiled<"/users/1/age">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
int64_t age;
|
||||
ASSERT_SUCCESS(result.get_int64().get(age));
|
||||
ASSERT_EQUAL(age, 35);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 11: Compare compile-time vs runtime pointer
|
||||
bool test_compile_vs_runtime() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_USER_JSON).get(doc));
|
||||
|
||||
// Compile-time version
|
||||
auto compile_result = ondemand::json_path::at_pointer_compiled<"/name">(doc);
|
||||
ASSERT_SUCCESS(compile_result.error());
|
||||
std::string_view compile_name;
|
||||
ASSERT_SUCCESS(compile_result.get_string().get(compile_name));
|
||||
|
||||
// Runtime version for comparison
|
||||
ondemand::parser parser2;
|
||||
ondemand::document doc2;
|
||||
ASSERT_SUCCESS(parser2.iterate(TEST_USER_JSON).get(doc2));
|
||||
auto runtime_result = doc2.at_pointer("/name");
|
||||
ASSERT_SUCCESS(runtime_result.error());
|
||||
std::string_view runtime_name;
|
||||
ASSERT_SUCCESS(runtime_result.get_string().get(runtime_name));
|
||||
|
||||
// Should produce same result
|
||||
ASSERT_EQUAL(compile_name, runtime_name);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 12: Nested integer field
|
||||
bool test_nested_integer() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_NESTED_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_pointer_compiled<"/metadata/count">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
int64_t count;
|
||||
ASSERT_SUCCESS(result.get_int64().get(count));
|
||||
ASSERT_EQUAL(count, 2);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 13: Last array element
|
||||
bool test_last_array_element() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_CAR_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_pointer_compiled<"/tire_pressure/3">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
double pressure;
|
||||
ASSERT_SUCCESS(result.get_double().get(pressure));
|
||||
ASSERT_EQUAL(pressure, 40.4);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 14: Access second user's email
|
||||
bool test_second_user_email() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_NESTED_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_pointer_compiled<"/users/1/email">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
std::string_view email;
|
||||
ASSERT_SUCCESS(result.get_string().get(email));
|
||||
ASSERT_EQUAL(email, "bob@example.com");
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Test 15: Root array first element field
|
||||
bool test_root_array_first_field() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(TEST_ARRAY_JSON).get(doc));
|
||||
|
||||
auto result = ondemand::json_path::at_pointer_compiled<"/0/model">(doc);
|
||||
ASSERT_SUCCESS(result.error());
|
||||
|
||||
std::string_view model;
|
||||
ASSERT_SUCCESS(result.get_string().get(model));
|
||||
ASSERT_EQUAL(model, "Camry");
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
} // namespace compile_time_json_pointer_tests
|
||||
|
||||
#endif // SIMDJSON_SUPPORTS_CONCEPTS && SIMDJSON_STATIC_REFLECTION
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS && SIMDJSON_STATIC_REFLECTION
|
||||
std::cout << "Running compile-time JSON Pointer tests" << std::endl;
|
||||
|
||||
if (!compile_time_json_pointer_tests::test_simple_field()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_pointer_tests::test_integer_field()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_pointer_tests::test_array_index()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_pointer_tests::test_nested_field()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_pointer_tests::test_array_object_nested()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_pointer_tests::test_root_array()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_pointer_tests::test_deep_nested_array()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_pointer_tests::test_root_pointer()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_pointer_tests::test_first_array_element()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_pointer_tests::test_multiple_indices()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_pointer_tests::test_compile_vs_runtime()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_pointer_tests::test_nested_integer()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_pointer_tests::test_last_array_element()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_pointer_tests::test_second_user_email()) { return EXIT_FAILURE; }
|
||||
if (!compile_time_json_pointer_tests::test_root_array_first_field()) { return EXIT_FAILURE; }
|
||||
|
||||
std::cout << "All compile-time JSON Pointer tests passed!" << std::endl;
|
||||
return EXIT_SUCCESS;
|
||||
#else
|
||||
std::cout << "Compile-time JSON Pointer tests require C++26 reflection support" << std::endl;
|
||||
return EXIT_SUCCESS;
|
||||
#endif
|
||||
}
|
||||
@@ -375,7 +375,7 @@ namespace array_tests {
|
||||
ASSERT_EQUAL(i*sizeof(uint64_t), sizeof(expected_value));
|
||||
std::vector<int64_t> container(i); // container of size 'i'.
|
||||
|
||||
doc_result.rewind();
|
||||
ASSERT_SUCCESS( doc_result.rewind() );
|
||||
ASSERT_RESULT( doc_result.type(), json_type::array );
|
||||
ASSERT_SUCCESS( doc_result.get(array) );
|
||||
i = 0;
|
||||
@@ -783,7 +783,7 @@ namespace array_tests {
|
||||
for (auto value : array) { (void) value; i++; }
|
||||
ASSERT_EQUAL(i, 0);
|
||||
|
||||
doc_result.rewind();
|
||||
ASSERT_SUCCESS( doc_result.rewind() );
|
||||
ASSERT_RESULT( doc_result.type(), json_type::array );
|
||||
ASSERT_SUCCESS( doc_result.get(array) );
|
||||
i = 0;
|
||||
|
||||
@@ -23,7 +23,7 @@ long page_size() {
|
||||
// page boundary.
|
||||
bool need_allocation(const char *buf, size_t len) {
|
||||
return ((reinterpret_cast<uintptr_t>(buf + len - 1) % page_size())
|
||||
+ simdjson::SIMDJSON_PADDING > static_cast<uintptr_t>(page_size()));
|
||||
+ simdjson::SIMDJSON_PADDING >= static_cast<uintptr_t>(page_size()));
|
||||
}
|
||||
|
||||
bool check_need_allocation() {
|
||||
|
||||
@@ -151,13 +151,42 @@ simdjson::padded_string json_cars =
|
||||
}
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
struct Player {
|
||||
std::string username;
|
||||
int level;
|
||||
double health;
|
||||
};
|
||||
struct BadPlayer {
|
||||
int username; // Oops, should be string!
|
||||
int level;
|
||||
double health;
|
||||
};
|
||||
struct OptionalPlayer {
|
||||
std::string username;
|
||||
std::optional<int> level;
|
||||
double health;
|
||||
};
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
bool missing_key_player() {
|
||||
TEST_START();
|
||||
std::string json = R"({"username":"Alice","health":100.0})";
|
||||
simdjson::padded_string padded(json);
|
||||
Player p;
|
||||
simdjson::ondemand::parser parser;
|
||||
auto doc = parser.iterate(padded);
|
||||
ASSERT_ERROR(doc.get(p), simdjson::NO_SUCH_FIELD);
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
bool missing_key_optional_player() {
|
||||
TEST_START();
|
||||
std::string json = R"({"username":"Alice","health":100.0})";
|
||||
simdjson::padded_string padded(json);
|
||||
OptionalPlayer p;
|
||||
simdjson::ondemand::parser parser;
|
||||
auto doc = parser.iterate(padded);
|
||||
ASSERT_SUCCESS(doc.get(p));
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
bool bad_player() {
|
||||
TEST_START();
|
||||
// username is a string but we declared it as an int
|
||||
@@ -223,7 +252,7 @@ simdjson::padded_string json_cars =
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
#endif // SIMDJSON_STATIC_REFLECTION
|
||||
bool broken() {
|
||||
TEST_START();
|
||||
simdjson::padded_string short_json_cars = R"( { "make )"_padded;
|
||||
@@ -440,15 +469,6 @@ bool test_to_adaptor_with_single_value() {
|
||||
if (obj_result.error()) {
|
||||
return false;
|
||||
}
|
||||
/* simdjson::ondemand::object obj = std::move(obj_result.value());
|
||||
|
||||
// We deliberately omit this part because simdjson::to has been removed.
|
||||
auto year_val = obj["year"];
|
||||
int64_t year = simdjson::to<int64_t>(year_val);
|
||||
if (year != 2018) {
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
@@ -477,8 +497,8 @@ bool test_to_vs_from_equivalence() {
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
bool run() {
|
||||
return
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
meeting_time_test() && meeting_test() && bad_player() && good_player() && complicated_weather_test() &&
|
||||
#if SIMDJSON_STATIC_REFLECTION && SIMDJSON_EXCEPTIONS && SIMDJSON_SUPPORTS_CONCEPTS
|
||||
missing_key_optional_player() && missing_key_player() && meeting_time_test() && meeting_test() && bad_player() && good_player() && complicated_weather_test() &&
|
||||
#endif
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS
|
||||
simple_no_except() &&
|
||||
|
||||
@@ -485,7 +485,7 @@ namespace object_tests {
|
||||
i++;
|
||||
}
|
||||
ASSERT_EQUAL( i*sizeof(uint64_t), sizeof(expected_value) );
|
||||
doc_result.rewind();
|
||||
ASSERT_SUCCESS( doc_result.rewind() );
|
||||
ASSERT_RESULT( doc_result.type(), json_type::object );
|
||||
ASSERT_SUCCESS( doc_result.get(object) );
|
||||
i = 0;
|
||||
|
||||
@@ -233,10 +233,47 @@ bool to_string_example() {
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc = parser.iterate(json);
|
||||
std::string name;
|
||||
doc["name"].get_string(name);
|
||||
ASSERT_SUCCESS(doc["name"].get_string(name));
|
||||
ASSERT_EQUAL(name, "Daniel");
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
bool partial_car_extract() {
|
||||
TEST_START();
|
||||
auto json = R"( {
|
||||
"make": "Toyota",
|
||||
"model": "Camry",
|
||||
"year": 2024,
|
||||
"tire_pressure": [ 40.1, 39.9 ]
|
||||
} )"_padded;
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc = parser.iterate(json);
|
||||
Car car{};
|
||||
ASSERT_SUCCESS( (doc.extract_into<"make","model">(car)) );
|
||||
ASSERT_EQUAL(car.make, "Toyota");
|
||||
ASSERT_EQUAL(car.model, "Camry");
|
||||
ASSERT_EQUAL(car.year, 0); // Not extracted
|
||||
ASSERT_EQUAL(car.tire_pressure.size(), 0); // Not extracted
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool temperature_example() {
|
||||
TEST_START();
|
||||
struct complicated_weather_data {
|
||||
std::vector<std::string> time;
|
||||
std::vector<float> temperature;
|
||||
};
|
||||
auto padded = R"({"time":["2023-03-15T12:00:00Z"],"temperature":[42]})"_padded;
|
||||
simdjson::ondemand::parser parser;
|
||||
simdjson::ondemand::document doc = parser.iterate(padded);
|
||||
complicated_weather_data p = doc.get<complicated_weather_data>();
|
||||
ASSERT_EQUAL(p.time.size(), 1);
|
||||
ASSERT_EQUAL(p.time[0], "2023-03-15T12:00:00Z");
|
||||
ASSERT_EQUAL(p.temperature.size(), 1);
|
||||
ASSERT_EQUAL(p.temperature[0], 42);
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
#endif // SIMDJSON_STATIC_REFLECTION
|
||||
|
||||
bool gen_raw1() {
|
||||
TEST_START();
|
||||
|
||||
+1
-1
@@ -130,7 +130,7 @@ simdjson_inline bool assert_iterate_error(T &arr, simdjson::error_code expected,
|
||||
#define SUBTEST(NAME, TEST) do { std::cout << " - Subtest " << (NAME) << " ..." << std::endl; if (!(TEST)) { return false; } } while (0);
|
||||
#define ASSERT_EQUAL(ACTUAL, EXPECTED) do { if (!::assert_equal ((ACTUAL), (EXPECTED), #ACTUAL)) { return false; } } while (0);
|
||||
#define ASSERT_RESULT(ACTUAL, EXPECTED) do { if (!::assert_result ((ACTUAL), (EXPECTED), #ACTUAL)) { return false; } } while (0);
|
||||
#define ASSERT_SUCCESS(ACTUAL) do { if (!::assert_success((ACTUAL), #ACTUAL)) { return false; } } while (0);
|
||||
#define ASSERT_SUCCESS(...) do { if (!::assert_success((__VA_ARGS__), #__VA_ARGS__)) { return false; } } while (0);
|
||||
#define ASSERT_FAILURE(ACTUAL) do { if (::assert_success((ACTUAL), #ACTUAL)) { return false; } } while (0);
|
||||
#define ASSERT_ERROR(ACTUAL, EXPECTED) do { if (!::assert_error ((ACTUAL), (EXPECTED), #ACTUAL)) { return false; } } while (0);
|
||||
#define ASSERT_TRUE(ACTUAL) do { if (!::assert_true ((ACTUAL), #ACTUAL)) { return false; } } while (0);
|
||||
|
||||
Reference in New Issue
Block a user