Merge pull request #2406 from simdjson/fix-convert-ci-failures

Introducing simplified api with from/to adapters (thanks to @the-moisrex for driving this) and fixing all the CI errors.
This commit is contained in:
Francisco Geiman Thiesen
2025-08-08 10:42:32 -07:00
committed by GitHub
19 changed files with 3869 additions and 344 deletions
+53
View File
@@ -1346,6 +1346,34 @@ auto tag_invoke(deserialize_tag, simdjson_value &val, std::list<Car>& car) {
With this code, deserializing an `std::list<Car>` instance would capture only the cars
that are not made by Toyota.
For even more convenience, you can do it directly without a parser instance like so:
```cpp
Car car = simdjson::from(json);
```
You can also use C++20 ranges to iterate over an array:
```cpp
simdjson::padded_string json_cars =
R"( [ { "make": "Toyota", "model": "Camry", "year": 2018,
"tire_pressure": [ 40.1, 39.9 ] },
{ "make": "Kia", "model": "Soul", "year": 2012,
"tire_pressure": [ 30.1, 31.0 ] },
{ "make": "Toyota", "model": "Tercel", "year": 1999,
"tire_pressure": [ 29.8, 30.0 ] }
])"_padded;
for (Car car : simdjson::from(json_cars) | simdjson::as<Car>()) {
if (car.year < 1998) {
return false;
}
}
```
### 3. Using static reflection (C++26)
If you have a C++26 compatible compiler, you can compile
@@ -1367,6 +1395,31 @@ simdjson::ondemand::document doc = parser.iterate(simdjson::pad(json));
Car c = doc.get<Car>();
```
Just like when using `tag_invoke` for custom types (but without the `tag_invoke` code), you can parse a class instance directly without a parser instance:
```cpp
Car car = simdjson::from(json);
```
Similarly, you can also use C++20 ranges to iterate over an array:
```cpp
simdjson::padded_string json_cars =
R"( [ { "make": "Toyota", "model": "Camry", "year": 2018,
"tire_pressure": [ 40.1, 39.9 ] },
{ "make": "Kia", "model": "Soul", "year": 2012,
"tire_pressure": [ 30.1, 31.0 ] },
{ "make": "Toyota", "model": "Tercel", "year": 1999,
"tire_pressure": [ 29.8, 30.0 ] }
])"_padded;
for (Car car : simdjson::from(json_cars) | simdjson::as<Car>()) {
if (car.year < 1998) {
return false;
}
}
```
You can also automatically serialize the `Car` instance to a JSON string, see
our [Builder documentation](builder.md).
+4 -4
View File
@@ -165,7 +165,7 @@ automatically. In most cases, it should work automatically:
In some instances, you might want to create a string directly from your own data type.
You can create a string directly, without an explicit `string_builder` instance
with the `simdjson::builder::to_json_string` function.
with the `simdjson::to_json` template function.
(Under the hood a `string_builder` instance may still be created.)
```cpp
@@ -178,12 +178,12 @@ with the `simdjson::builder::to_json_string` function.
void f() {
Car c = {"Toyota", "Corolla", 2017, {30.0,30.2,30.513,30.79}};
std::string json = simdjson::builder::to_json_string(c);
std::string json = simdjson::to_json(c);
}
```
If you know the output size, in bytes, of your JSON string, you may
pass it as a second parameter (e.g., `simdjson::builder::to_json_string(c, 31123)`).
pass it as a second parameter (e.g., `simdjson::to_json(c, 31123)`).
@@ -194,7 +194,7 @@ pattern:
```cpp
std::string json;
if(simdjson::builder::to_json_string(c).get(json)) {
if(simdjson::to(c).get(json)) {
// there was an error
} else {
// json contain the serialized JSON
+1
View File
@@ -53,4 +53,5 @@
#include "simdjson/dom.h"
#include "simdjson/ondemand.h"
#include "simdjson/convert.h"
#endif // SIMDJSON_H
+307
View File
@@ -0,0 +1,307 @@
#ifndef SIMDJSON_CONVERT_H
#define SIMDJSON_CONVERT_H
#if __cpp_concepts
#include "simdjson/ondemand.h"
#include <optional>
#ifdef __cpp_lib_ranges
#include <ranges>
#endif
namespace simdjson {
struct [[nodiscard]] auto_iterator_end {};
/**
* A Wrapper for simdjson_result<ondemand::array_iterator> in order to make it
* compatible with ranges (to satisfy std::ranges::input_range).
*/
struct [[nodiscard]] auto_iterator {
using iterator_category = std::forward_iterator_tag;
using type = simdjson_result<ondemand::array_iterator>;
using value_type = simdjson_result<ondemand::value>; // type::value_type
using reference = value_type &;
using const_reference = const value_type &;
using difference_type = std::ptrdiff_t;
struct auto_iterator_storage {
type m_iter{};
mutable value_type m_value{};
};
private:
auto_iterator_storage *m_storage = nullptr;
public:
constexpr auto_iterator() noexcept = default;
explicit auto_iterator(auto_iterator_storage &storage) noexcept
: m_storage{&storage} {};
auto_iterator(auto_iterator const &) = default;
auto_iterator(auto_iterator &&) = default;
auto_iterator &operator=(auto_iterator const &) = default;
auto_iterator &operator=(auto_iterator &&) noexcept = default;
~auto_iterator() = default;
reference operator*() const noexcept { return m_storage->m_value; }
reference operator*() noexcept { return m_storage->m_value; }
auto_iterator &operator++() noexcept {
++m_storage->m_iter;
m_storage->m_value =
m_storage->m_iter.at_end() || m_storage->m_iter.error() != SUCCESS
? value_type{}
: *m_storage->m_iter;
return *this;
}
auto_iterator operator++(int) noexcept {
auto_iterator const tmp = *this;
operator++();
return tmp;
}
[[nodiscard]] bool operator==(auto_iterator const &other) const noexcept {
return m_storage == other.m_storage &&
m_storage->m_iter == other.m_storage->m_iter;
}
[[nodiscard]] bool operator==(auto_iterator_end) const noexcept {
return m_storage != nullptr && m_storage->m_iter.at_end();
}
};
template <typename ParserType = ondemand::parser>
struct [[nodiscard]] auto_parser
#if __cpp_lib_ranges
: std::ranges::view_interface<auto_parser<ParserType>>
#endif
{
using value_type = simdjson_result<ondemand::value>;
using size_type = size_t;
using difference_type = std::ptrdiff_t;
using pointer = value_type *;
using const_pointer = const value_type *;
using reference = value_type &;
using const_reference = const value_type &;
using iterator = auto_iterator;
using const_iterator = auto_iterator; // auto_iterator is already const
private:
ParserType m_parser;
ondemand::document m_doc;
error_code m_error{SUCCESS};
// Caching the iterator here:
iterator::auto_iterator_storage iter_storage{};
template <typename T>
static constexpr bool is_nothrow_gettable = requires(ondemand::document doc) {
{ doc.get<T>() } noexcept;
};
public:
// non-pointer constructors:
explicit auto_parser(ParserType &&parser, ondemand::document &&doc) noexcept
requires(!std::is_pointer_v<ParserType>)
: m_parser{std::move(parser)}, m_doc{std::move(doc)} {}
explicit auto_parser(ParserType &&parser,
padded_string_view const str) noexcept
requires(!std::is_pointer_v<ParserType>)
: m_parser{std::move(parser)}, m_doc{}, m_error{SUCCESS} {
m_error = m_parser.iterate(str).get(m_doc);
}
explicit auto_parser(padded_string_view const str) noexcept
requires(!std::is_pointer_v<ParserType>)
: auto_parser{ParserType{}, str} {}
// pointer constructors:
explicit auto_parser(std::remove_pointer_t<ParserType> &parser,
ondemand::document &&doc) noexcept
requires(std::is_pointer_v<ParserType>)
: m_parser{&parser}, m_doc{std::move(doc)} {}
explicit auto_parser(std::remove_pointer_t<ParserType> &parser,
padded_string_view const str) noexcept
requires(std::is_pointer_v<ParserType>)
: m_parser{&parser}, m_doc{}, m_error{SUCCESS} {
m_error = m_parser->iterate(str).get(m_doc);
}
explicit auto_parser(ParserType parser, ondemand::document &&doc) noexcept
requires(std::is_pointer_v<ParserType>)
: auto_parser{*parser, std::move(doc)} {}
auto_parser(auto_parser const &) = delete;
auto_parser &operator=(auto_parser const &) = delete;
auto_parser(auto_parser &&) noexcept = default;
auto_parser &operator=(auto_parser &&) noexcept = default;
~auto_parser() = default;
/// Get the parser
[[nodiscard]] std::remove_pointer_t<ParserType> &parser() noexcept {
if constexpr (std::is_pointer_v<ParserType>) {
return *m_parser;
} else {
return m_parser;
}
}
template <typename T>
[[nodiscard]] simdjson_inline simdjson_result<T>
result() noexcept(is_nothrow_gettable<T>) {
if (m_error != SUCCESS) {
return m_error;
}
// For array and object types, we need to be at the start of the document
return m_doc.get<T>();
}
[[nodiscard]] simdjson_inline simdjson_result<ondemand::array>
array() noexcept {
return result<ondemand::array>();
}
[[nodiscard]] simdjson_inline simdjson_result<ondemand::object>
object() noexcept {
return result<ondemand::object>();
}
[[nodiscard]] simdjson_inline simdjson_result<ondemand::number>
number() noexcept {
return result<ondemand::number>();
}
template <typename T>
[[nodiscard]] simdjson_inline explicit(false)
operator simdjson_result<T>() noexcept(is_nothrow_gettable<T>) {
return result<T>();
}
template <typename T>
[[nodiscard]] simdjson_inline explicit(false) operator T() noexcept(false) {
if (m_error != SUCCESS) {
throw simdjson_error(m_error);
}
return m_doc.get<T>();
}
// We can't have "operator std::optional<T>" because it would create an
// ambiguity for the compiler.
// We also cannot have "operator T*" without manual memory management.
// We also cannot have "operator T&" without manual memory management either.
template <typename T>
[[nodiscard]] simdjson_inline std::optional<T>
optional() noexcept(is_nothrow_gettable<T>) {
if (m_error != SUCCESS) {
return std::nullopt;
}
T value;
// For std::optional<T>
if (m_doc.get<T>().get(value)) [[unlikely]] {
return std::nullopt;
}
return {std::move(value)};
}
simdjson_inline auto_iterator begin() noexcept {
if (m_error != SUCCESS) {
// Create an iterator with the error
iter_storage.m_iter = iterator::type(m_error);
iter_storage.m_value = value_type{};
return auto_iterator{iter_storage};
}
if (iter_storage.m_iter.error() != SUCCESS &&
!iter_storage.m_iter.at_end()) {
// Try to get the document as an array
ondemand::array arr;
if(auto error = m_doc.get_array().get(arr); error == SUCCESS) {
iter_storage = {.m_iter = iterator::type{arr.begin()},
.m_value = iterator::value_type{
iter_storage.m_iter.at_end() ||
iter_storage.m_iter.error() != SUCCESS
? value_type{}
: *iter_storage.m_iter}};
} else {
// If it's not an array, create an error iterator
iter_storage.m_iter = iterator::type(error);
iter_storage.m_value = value_type{};
}
}
return auto_iterator{iter_storage};
}
simdjson_inline auto_iterator_end end() noexcept { return {}; }
};
#ifdef __cpp_lib_ranges
// For C++20, we implement our own pipe operator since range_adaptor_closure is C++23
static constexpr struct [[nodiscard]] no_errors_adaptor {
[[nodiscard]] bool
operator()(simdjson_result<ondemand::value> const &val) const noexcept {
return val.error() == SUCCESS;
}
template <std::ranges::range Range>
auto operator()(Range &&rng) const noexcept {
return std::forward<Range>(rng) | std::views::filter(*this);
}
} no_errors;
template <typename T = void>
struct [[nodiscard]] to_adaptor {
/// Convert to T
[[nodiscard]] T
operator()(simdjson_result<ondemand::value> &val) const noexcept {
return val.get<T>();
}
/// Make it an adaptor
template <std::ranges::range Range>
auto operator()(Range &&rng) const noexcept {
return std::forward<Range>(rng) | no_errors | std::views::transform(*this);
}
/**
* Parse input string into any object if possible.
*/
auto operator()(padded_string_view const str) const noexcept {
return auto_parser{str};
}
/**
* Parse the input using the specified parser into any object if possible.
*/
auto operator()(ondemand::parser &parser,
padded_string_view const str) const noexcept {
return auto_parser<ondemand::parser *>{parser, str};
}
};
template <typename T> static constexpr to_adaptor<T> to{};
static constexpr to_adaptor<> from{};
template <typename T = void>
using as = to_adaptor<T>;
// For C++20 ranges without range_adaptor_closure, we need to define pipe operators
template <std::ranges::range Range>
inline auto operator|(Range&& range, const no_errors_adaptor& adaptor) {
return adaptor(std::forward<Range>(range));
}
template <std::ranges::range Range, typename T>
inline auto operator|(Range&& range, const to_adaptor<T>& adaptor) {
return adaptor(std::forward<Range>(range));
}
#endif // __cpp_lib_ranges
} // namespace simdjson
#endif // __cpp_concepts
#endif // SIMDJSON_CONVERT_H
@@ -122,6 +122,7 @@ struct implementation_simdjson_result_base {
* the error() method returns a value that evaluates to false.
*/
simdjson_inline T&& value_unsafe() && noexcept;
protected:
/** users should never directly access first and second. **/
T first{}; /** Users should never directly access 'first'. **/
@@ -36,6 +36,9 @@ simdjson_inline array_iterator &array_iterator::operator++() noexcept {
return *this;
}
simdjson_inline bool array_iterator::at_end() const noexcept {
return iter.at_end();
}
} // namespace ondemand
} // namespace SIMDJSON_IMPLEMENTATION
} // namespace simdjson
@@ -72,7 +75,9 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::array_iterato
++(first);
return *this;
}
simdjson_inline bool simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::array_iterator>::at_end() const noexcept {
return !first.iter.is_valid() || first.at_end();
}
} // namespace simdjson
#endif // SIMDJSON_GENERIC_ONDEMAND_ARRAY_ITERATOR_INL_H
@@ -34,7 +34,8 @@ public:
*
* Part of the std::iterator interface.
*/
simdjson_inline simdjson_result<value> operator*() noexcept; // MUST ONLY BE CALLED ONCE PER ITERATION.
simdjson_inline simdjson_result<value>
operator*() noexcept; // MUST ONLY BE CALLED ONCE PER ITERATION.
/**
* Check if we are at the end of the JSON.
*
@@ -58,6 +59,11 @@ public:
*/
simdjson_inline array_iterator &operator++() noexcept;
/**
* Check if the array is at the end.
*/
[[nodiscard]] simdjson_inline bool at_end() const noexcept;
private:
value_iterator iter{};
@@ -76,7 +82,6 @@ namespace simdjson {
template<>
struct simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::array_iterator> : public SIMDJSON_IMPLEMENTATION::implementation_simdjson_result_base<SIMDJSON_IMPLEMENTATION::ondemand::array_iterator> {
public:
simdjson_inline simdjson_result(SIMDJSON_IMPLEMENTATION::ondemand::array_iterator &&value) noexcept; ///< @private
simdjson_inline simdjson_result(error_code error) noexcept; ///< @private
simdjson_inline simdjson_result() noexcept = default;
@@ -89,6 +94,8 @@ public:
simdjson_inline bool operator==(const simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::array_iterator> &) const noexcept;
simdjson_inline bool operator!=(const simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::array_iterator> &) const noexcept;
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::array_iterator> &operator++() noexcept;
[[nodiscard]] simdjson_inline bool at_end() const noexcept;
};
} // namespace simdjson
@@ -296,6 +296,11 @@ string_builder& operator<<(string_builder& b, const Z& z) {
}
} // namespace builder
} // namespace SIMDJSON_IMPLEMENTATION
// Alias the function template to 'to' in the global namespace
template <class Z>
simdjson_result<std::string> to_json(const Z &z, size_t initial_capacity = 1024) {
return SIMDJSON_IMPLEMENTATION::builder::to_json_string(z, initial_capacity);
}
} // namespace simdjson
#endif // SIMDJSON_STATIC_REFLECTION
@@ -218,6 +218,8 @@ simdjson_inline void json_iterator::assert_valid_position(token_position positio
#ifndef SIMDJSON_CLANG_VISUAL_STUDIO
SIMDJSON_ASSUME( position >= &parser->implementation->structural_indexes[0] );
SIMDJSON_ASSUME( position < &parser->implementation->structural_indexes[parser->implementation->n_structural_indexes] );
#else
(void)position; // Suppress unused parameter warning
#endif
}
@@ -11,7 +11,7 @@
#include <concepts>
#include <limits>
#if SIMDJSON_STATIC_REFLECTION
#include <experimental/meta>
#include <meta>
// #include <static_reflection> // for std::define_static_string - header not available yet
#endif
+5 -1
View File
@@ -8,7 +8,11 @@ RUN apt-get update && \
apt-get install -y --no-install-recommends ca-certificates gnupg \
build-essential cmake make python3 zlib1g wget subversion unzip ninja-build git linux-perf && \
rm -rf /var/lib/apt/lists/*
RUN git clone --depth=1 --branch p2996 https://github.com/bloomberg/clang-p2996.git /tmp/clang-source
ARG CLANG_COMMIT=d77eff1cbd78fd065668acf93b1f5f400d39134d
RUN git clone --depth=1 https://github.com/bloomberg/clang-p2996.git /tmp/clang-source && \
cd /tmp/clang-source && \
git fetch origin $CLANG_COMMIT --depth=1 && \
git checkout $CLANG_COMMIT
RUN cmake -S /tmp/clang-source/llvm -B /tmp/clang-source/build-llvm -DCMAKE_BUILD_TYPE=Release \
-DLLVM_ENABLE_ASSERTIONS=ON \
-DLLVM_UNREACHABLE_OPTIMIZE=ON \
+1
View File
@@ -64,6 +64,7 @@ cmake --build buildreflect --target benchmark_serialization_citm_catalog benchma
6. Run the tests...
```bash
cmake --build buildreflect
ctest --test-dir buildreflect --output-on-failure
```
+109 -45
View File
@@ -1,4 +1,4 @@
/* auto-generated on 2025-07-14 15:43:52 -0400. Do not edit! */
/* auto-generated on 2025-08-05 16:29:59 +0000. version 4.0.0 Do not edit! */
/* including simdjson.cpp: */
/* begin file simdjson.cpp */
#define SIMDJSON_SRC_SIMDJSON_CPP
@@ -577,17 +577,6 @@ double from_chars(const char *first, const char* end) noexcept;
// We assume by default static linkage
#define SIMDJSON_DLLIMPORTEXPORT
#endif
/**
* Workaround for the vcpkg package manager. Only vcpkg should
* ever touch the next line. The SIMDJSON_USING_LIBRARY macro is otherwise unused.
*/
#if SIMDJSON_USING_LIBRARY
#define SIMDJSON_DLLIMPORTEXPORT __declspec(dllimport)
#endif
/**
* End of workaround for the vcpkg package manager.
*/
#else
#define SIMDJSON_DLLIMPORTEXPORT
#endif
@@ -2444,6 +2433,18 @@ namespace std {
#define SIMDJSON_AVX512_ALLOWED 1
#endif
#ifndef __has_cpp_attribute
#define simdjson_lifetime_bound
#elif __has_cpp_attribute(msvc::lifetimebound)
#define simdjson_lifetime_bound [[msvc::lifetimebound]]
#elif __has_cpp_attribute(clang::lifetimebound)
#define simdjson_lifetime_bound [[clang::lifetimebound]]
#elif __has_cpp_attribute(lifetimebound)
#define simdjson_lifetime_bound [[lifetimebound]]
#else
#define simdjson_lifetime_bound
#endif
#endif // SIMDJSON_COMMON_DEFS_H
/* end file simdjson/common_defs.h */
/* skipped duplicate #include "simdjson/compiler_check.h" */
@@ -2908,7 +2909,6 @@ concept optional_type = requires(std::remove_cvref_t<T> obj) {
{ obj.value() } -> std::same_as<typename std::remove_cvref_t<T>::value_type&>;
requires requires(typename std::remove_cvref_t<T>::value_type &&val) {
obj.emplace(std::move(val));
obj = std::move(val);
{
obj.value_or(val)
} -> std::convertible_to<typename std::remove_cvref_t<T>::value_type>;
@@ -9170,6 +9170,7 @@ struct implementation_simdjson_result_base {
* the error() method returns a value that evaluates to false.
*/
simdjson_inline T&& value_unsafe() && noexcept;
protected:
/** users should never directly access first and second. **/
T first{}; /** Users should never directly access 'first'. **/
@@ -15609,6 +15610,7 @@ struct implementation_simdjson_result_base {
* the error() method returns a value that evaluates to false.
*/
simdjson_inline T&& value_unsafe() && noexcept;
protected:
/** users should never directly access first and second. **/
T first{}; /** Users should never directly access 'first'. **/
@@ -21903,6 +21905,7 @@ struct implementation_simdjson_result_base {
* the error() method returns a value that evaluates to false.
*/
simdjson_inline T&& value_unsafe() && noexcept;
protected:
/** users should never directly access first and second. **/
T first{}; /** Users should never directly access 'first'. **/
@@ -28354,6 +28357,7 @@ struct implementation_simdjson_result_base {
* the error() method returns a value that evaluates to false.
*/
simdjson_inline T&& value_unsafe() && noexcept;
protected:
/** users should never directly access first and second. **/
T first{}; /** Users should never directly access 'first'. **/
@@ -35164,6 +35168,7 @@ struct implementation_simdjson_result_base {
* the error() method returns a value that evaluates to false.
*/
simdjson_inline T&& value_unsafe() && noexcept;
protected:
/** users should never directly access first and second. **/
T first{}; /** Users should never directly access 'first'. **/
@@ -41796,6 +41801,7 @@ struct implementation_simdjson_result_base {
* the error() method returns a value that evaluates to false.
*/
simdjson_inline T&& value_unsafe() && noexcept;
protected:
/** users should never directly access first and second. **/
T first{}; /** Users should never directly access 'first'. **/
@@ -47874,6 +47880,7 @@ struct implementation_simdjson_result_base {
* the error() method returns a value that evaluates to false.
*/
simdjson_inline T&& value_unsafe() && noexcept;
protected:
/** users should never directly access first and second. **/
T first{}; /** Users should never directly access 'first'. **/
@@ -53544,6 +53551,7 @@ struct implementation_simdjson_result_base {
* the error() method returns a value that evaluates to false.
*/
simdjson_inline T&& value_unsafe() && noexcept;
protected:
/** users should never directly access first and second. **/
T first{}; /** Users should never directly access 'first'. **/
@@ -56568,10 +56576,78 @@ simdjson_inline void validate_utf8_character() {
idx += 4;
}
static const uint8_t CHAR_TYPE_SPACE = 1 << 0;
static const uint8_t CHAR_TYPE_OPERATOR = 1 << 1;
static const uint8_t CHAR_TYPE_ESC_ASCII = 1 << 2;
static const uint8_t CHAR_TYPE_NON_ASCII = 1 << 3;
const uint8_t char_table[256] = {
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
0x04, 0x05, 0x05, 0x04, 0x04, 0x05, 0x04, 0x04,
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x04, 0x02, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00,
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08
};
simdjson_inline bool char_is_type(uint8_t c, uint8_t type) {
return (char_table[c] & type);
}
simdjson_inline bool char_is_space(uint8_t c) {
return char_is_type(c, CHAR_TYPE_SPACE);
}
simdjson_inline bool char_is_operator(uint8_t c) {
return char_is_type(c, CHAR_TYPE_OPERATOR);
}
simdjson_inline bool char_is_space_or_operator(uint8_t c) {
return char_is_type(c, CHAR_TYPE_SPACE | CHAR_TYPE_OPERATOR);
}
simdjson_inline bool char_is_ascii_stop(uint8_t c) {
return char_is_type(c, CHAR_TYPE_ESC_ASCII | CHAR_TYPE_NON_ASCII);
}
// Returns true if the string is unclosed.
simdjson_inline bool validate_string() {
idx++; // skip first quote
while (idx < len && buf[idx] != '"') {
while (idx < len) {
do {
if (char_is_ascii_stop(buf[idx])) { break; }
idx++;
} while (idx < len);
if (idx >= len) { return true; }
if (buf[idx] == '"') {
return false;
}
if (buf[idx] == '\\') {
idx += 2;
} else if (simdjson_unlikely(buf[idx] & 0x80)) {
@@ -56585,43 +56661,31 @@ simdjson_inline bool validate_string() {
return false;
}
simdjson_inline bool is_whitespace_or_operator(uint8_t c) {
switch (c) {
case '{': case '}': case '[': case ']': case ',': case ':':
case ' ': case '\r': case '\n': case '\t':
return true;
default:
return false;
}
}
//
// Parse the entire input in STEP_SIZE-byte chunks.
//
simdjson_inline error_code scan() {
bool unclosed_string = false;
for (;idx<len;idx++) {
switch (buf[idx]) {
// String
case '"':
add_structural();
unclosed_string |= validate_string();
break;
// Operator
case '{': case '}': case '[': case ']': case ',': case ':':
add_structural();
break;
// Whitespace
case ' ': case '\r': case '\n': case '\t':
break;
// Primitive or invalid character (invalid characters will be checked in stage 2)
default:
// Anything else, add the structural and go until we find the next one
add_structural();
while (idx+1<len && !is_whitespace_or_operator(buf[idx+1])) {
idx++;
};
break;
do {
if (!char_is_space(buf[idx])) { break; }
idx++;
} while (idx < len);
if (idx >= len) { break; }
// String
if (buf[idx] == '"') {
add_structural();
unclosed_string |= validate_string();
// Operator
} else if (char_is_operator(buf[idx])) {
add_structural();
// Primitive or invalid character (invalid characters will be checked in stage 2)
} else {
// Anything else, add the structural and go until we find the next one
add_structural();
while (idx+1<len && !char_is_space_or_operator(buf[idx+1])) {
idx++;
};
}
}
// We pad beyond.
+3015 -287
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
@@ -77,6 +77,7 @@ namespace builder_tests {
Car c = {"Toyota", "Corolla", 2017, {30.0,30.2,30.513,30.79}};
append(sb, c);
std::string_view p{sb};
(void)p; // to avoid unused variable warning
TEST_SUCCEED();
}
bool car_test_exception2() {
@@ -85,12 +86,18 @@ namespace builder_tests {
Car c = {"Toyota", "Corolla", 2017, {30.0,30.2,30.513,30.79}};
sb << c;
std::string_view p{sb};
(void)p; // to avoid unused variable warning
TEST_SUCCEED();
}
void car_test_to_json_exception() {
bool car_test_to_json_exception() {
TEST_START();
Car c = {"Toyota", "Corolla", 2017, {30.0,30.2,30.513,30.79}};
std::string json = simdjson::builder::to_json_string(c);
std::string json = simdjson::to_json(c);
TEST_SUCCEED();
}
bool car_test_to_json_exception_value() {
TEST_START();
std::string json = simdjson::to_json(Car{"Toyota", "Corolla", 2017, {30.0,30.2,30.513,30.79}});
TEST_SUCCEED();
}
#endif // SIMDJSON_EXCEPTIONS
@@ -165,6 +172,7 @@ bool serialize_deserialize_x_y_z() {
car_test_exception() &&
car_test_exception2() &&
car_test_to_json_exception() &&
car_test_to_json_exception_value() &&
#endif // SIMDJSON_EXCEPTIONS
car_test() &&
serialize_deserialize_kid() &&
+3 -1
View File
@@ -132,7 +132,9 @@ if(
)
message(STATUS "compiler id: ${CMAKE_CXX_COMPILER_ID} version: ${CMAKE_CXX_COMPILER_VERSION}")
add_cpp_test(ranges_test LABELS dom acceptance per_implementation)
set_target_properties(ranges_test PROPERTIES CXX_STANDARD 20 CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF)
if(NOT SIMDJSON_STATIC_REFLECTION)
set_target_properties(ranges_test PROPERTIES CXX_STANDARD 20 CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF)
endif()
endif()
if(WIN32 AND BUILD_SHARED_LIBS)
+1
View File
@@ -33,6 +33,7 @@ add_cpp_test(ondemand_iterate_many_csv LABELS ondemand acceptance
add_cpp_test(ondemand_custom_types_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_custom_types_document_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_stl_types_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_convert_tests LABELS ondemand acceptance per_implementation)
if(NOT SIMDJSON_SANITIZE)
add_cpp_test(ondemand_cacheline LABELS ondemand acceptance per_implementation)
endif()
+336
View File
@@ -0,0 +1,336 @@
#include "simdjson.h"
#include "simdjson/convert.h"
#include "test_ondemand.h"
#include <ranges>
#include <string>
#include <vector>
#ifdef __cpp_lib_ranges
namespace convert_tests {
#if SIMDJSON_EXCEPTIONS && SIMDJSON_SUPPORTS_DESERIALIZATION
struct Car {
std::string make{};
std::string model{};
int year{};
std::vector<double> tire_pressure{};
friend simdjson::error_code tag_invoke(simdjson::deserialize_tag, auto &val,
Car &car) {
simdjson::ondemand::object obj;
auto error = val.get_object().get(obj);
if (error) {
return error;
}
// Instead of repeatedly obj["something"], we iterate through the object
// which we expect to be faster.
for (auto field : obj) {
simdjson::ondemand::raw_json_string key;
error = field.key().get(key);
if (error) {
return error;
}
if (key == "make") {
error = field.value().get_string(car.make);
if (error) {
return error;
}
} else if (key == "model") {
error = field.value().get_string(car.model);
if (error) {
return error;
}
} else if (key == "year") {
error = field.value().get(car.year);
if (error) {
return error;
}
} else if (key == "tire_pressure") {
error = field.value().get(car.tire_pressure);
if (error) {
return error;
}
}
}
return simdjson::SUCCESS;
}
};
static_assert(simdjson::custom_deserializable<std::unique_ptr<Car>>,
"It should be deserializable");
static_assert(std::input_or_output_iterator<simdjson::auto_iterator>,
"Must be a valid input iterator");
static_assert(std::semiregular<simdjson::auto_iterator>,
"Should be kinda regular");
// static_assert(std::ranges::__access::__member_end<simdjson::auto_parser<>>,
// "Must be a valid input iterator");
// static_assert(std::ranges::views::__adaptor::__is_range_adaptor_closure<
// simdjson::auto_parser<>>,
// "Parser need to be range adaptor closure.");
// static_assert(std::ranges::views::__adaptor::__adaptor_invocable<
// decltype(simdjson::to<Car>()),
// simdjson::auto_parser<>>,
// "I don't even know!");
static_assert(std::ranges::range<simdjson::auto_parser<>>,
"Parser need to be a range.");
static_assert(std::ranges::forward_range<simdjson::auto_parser<>>,
"Parser need to be an input range.");
static_assert(
requires(simdjson::auto_parser<> &parser) {
{ parser.begin() } -> std::input_or_output_iterator;
}, "Must be valid iterator.");
simdjson::padded_string json_car =
R"( {
"make": "Toyota",
"model": "Camry",
"year": 2018,
"tire_pressure": [ 40.1, 39.9 ]
} )"_padded;
simdjson::padded_string json_cars =
R"( [ { "make": "Toyota", "model": "Camry", "year": 2018,
"tire_pressure": [ 40.1, 39.9 ] },
{ "make": "Kia", "model": "Soul", "year": 2012,
"tire_pressure": [ 30.1, 31.0 ] },
{ "make": "Toyota", "model": "Tercel", "year": 1999,
"tire_pressure": [ 29.8, 30.0 ] }
])"_padded;
bool simple() {
TEST_START();
Car car = simdjson::from(json_car);
if (car.make != "Toyota" || car.model != "Camry" || car.year != 2018) {
return false;
}
TEST_SUCCEED();
}
bool broken() {
TEST_START();
simdjson::padded_string short_json_cars = R"( { "make )"_padded;
try {
Car car = simdjson::from(json_cars);
TEST_FAIL("Should not have succeeded");
} catch (...) {
TEST_SUCCEED();
}
TEST_SUCCEED();
}
bool simple_optional() {
TEST_START();
auto car = simdjson::from(json_car).optional<Car>();
if (!car.has_value() || car->make != "Toyota" || car->model != "Camry" ||
car->year != 2018) {
return false;
}
TEST_SUCCEED();
}
bool with_parser() {
TEST_START();
simdjson::ondemand::parser parser;
Car car = simdjson::from(parser, json_car);
if (car.make != "Toyota" || car.model != "Camry" || car.year != 2018) {
return false;
}
TEST_SUCCEED();
}
bool to_array() {
TEST_START();
auto parser = simdjson::from(json_cars);
for (auto val : parser.array()) {
Car car{};
if (auto const error = val.get(car)) {
std::cerr << simdjson::error_message(error) << std::endl;
return false;
}
if (car.year < 1998) {
std::cerr << car.make << " " << car.model << " " << car.year << std::endl;
return false;
}
}
TEST_SUCCEED();
}
bool to_array_shortcut() {
TEST_START();
simdjson::ondemand::parser parser;
for (auto val : simdjson::from(parser, json_cars)) {
Car car{};
if (auto const error = val.get(car)) {
std::cerr << simdjson::error_message(error) << std::endl;
return false;
}
if (car.year < 1998) {
std::cerr << car.make << " " << car.model << " " << car.year << std::endl;
return false;
}
}
TEST_SUCCEED();
}
bool to_bad_array() {
TEST_START();
auto parser = simdjson::from(json_car);
try {
auto array_result = parser.array();
// Check if array_result has an error
if (array_result.error() != simdjson::SUCCESS) {
// This is expected - trying to get array from an object should fail
if (array_result.error() != simdjson::INCORRECT_TYPE) {
std::cerr << "Expected INCORRECT_TYPE but got: " << array_result.error()
<< " (" << simdjson::error_message(array_result.error()) << ")" << std::endl;
return false;
}
// Got expected error, test passes
TEST_SUCCEED();
}
// If we get here without error, try to iterate
// This might throw when we try to use the array
for (auto val : array_result) {
static_cast<void>(val);
// Should not reach here - the JSON is an object, not an array
std::cerr << "Unexpectedly succeeded in iterating over non-array JSON" << std::endl;
return false;
}
// Also should not reach here
std::cerr << "array() succeeded on object JSON without throwing" << std::endl;
return false;
} catch (simdjson::simdjson_error &e) {
if (e.error() != simdjson::INCORRECT_TYPE) {
std::cerr << "Expected INCORRECT_TYPE but got: " << e.error() << " (" << simdjson::error_message(e.error()) << ")" << std::endl;
return false;
}
// Got expected exception, test passes
} catch (...) {
std::cerr << "Unexpected exception type" << std::endl;
return false;
}
TEST_SUCCEED();
}
bool test_basic_adaptor() {
TEST_START();
for (Car car : simdjson::from(json_cars) | simdjson::as<Car>()) {
if (car.year < 1998) {
return false;
}
}
TEST_SUCCEED();
}
bool test_no_errors() {
TEST_START();
auto cars = simdjson::from(json_cars) | simdjson::no_errors;
for (auto val : cars) {
Car car = val.get<Car>();
if (car.year < 1998) {
return false;
}
}
TEST_SUCCEED();
}
bool to_clean_array() {
TEST_START();
for (auto val : simdjson::from(json_cars) | simdjson::no_errors) {
Car car = val.get<Car>();
if (car.year < 1998) {
std::cerr << car.make << " " << car.model << " " << car.year << std::endl;
return false;
}
}
TEST_SUCCEED();
}
bool test_to_adaptor_basic() {
TEST_START();
// Test 1: Basic usage of to<T> with a value reference
simdjson::ondemand::parser parser;
auto doc_result = parser.iterate(json_car);
if (doc_result.error()) {
return false;
}
simdjson::ondemand::document doc = std::move(doc_result.value());
simdjson::simdjson_result<simdjson::ondemand::value> val = doc.get_value();
// to<T> converts a simdjson_result<value>& to T
Car car = simdjson::to<Car>(val);
if (car.make != "Toyota" || car.model != "Camry" || car.year != 2018) {
return false;
}
TEST_SUCCEED();
}
bool test_to_adaptor_with_single_value() {
TEST_START();
// Test 2: Using to<T> to convert individual values
simdjson::ondemand::parser parser;
auto doc_result = parser.iterate(json_car);
if (doc_result.error()) {
return false;
}
simdjson::ondemand::document doc = std::move(doc_result.value());
// Get individual field and convert it
auto obj_result = doc.get_object();
if (obj_result.error()) {
return false;
}
simdjson::ondemand::object obj = std::move(obj_result.value());
auto year_val = obj["year"];
int64_t year = simdjson::to<int64_t>(year_val);
if (year != 2018) {
return false;
}
TEST_SUCCEED();
}
bool test_to_vs_from_equivalence() {
TEST_START();
// Test 3: Verify that simdjson::to<> and simdjson::from behave equivalently
// Both are instances of to_adaptor - from is just to<void>
// These should produce identical auto_parser objects
auto parser1 = simdjson::from(json_car);
// simdjson::from is an alias for simdjson::to<void>
auto parser2 = simdjson::from(json_car); // Same as parser1
// Both should parse the same way
Car car1 = parser1;
Car car2 = parser2;
if (car1.make != car2.make || car1.model != car2.model || car1.year != car2.year) {
return false;
}
TEST_SUCCEED();
}
#endif // SIMDJSON_EXCEPTIONS
bool run() {
return
#if SIMDJSON_EXCEPTIONS && SIMDJSON_SUPPORTS_DESERIALIZATION
test_basic_adaptor() && broken() && simple() && simple_optional() && with_parser() && to_array() &&
to_array_shortcut() && to_bad_array() && test_no_errors() &&
to_clean_array() && test_to_adaptor_basic() &&
test_to_adaptor_with_single_value() && test_to_vs_from_equivalence() &&
#endif // SIMDJSON_EXCEPTIONS
true;
}
} // namespace convert_tests
int main(int argc, char *argv[]) {
return test_main(argc, argv, convert_tests::run);
}
#else
int main() { return 0; }
#endif