mirror of
https://github.com/simdjson/simdjson
synced 2026-06-08 17:27:07 +00:00
Add std::ranges support for On-Demand API (variant) (#2678)
* add std::ranges support for On-Demand API (#2382) Add zero-cost range wrappers (array_range, object_range) that satisfy std::ranges::input_range, enabling std::views::transform and other C++20 range adaptors with the On-Demand parser. Uses direct forwarding via simdjson_inline with no value buffering, avoiding the per-element overhead (~20%) of the previous approach. Guarded by SIMDJSON_SUPPORTS_RANGES. * fix: replace non-ASCII em dash in test comment The just_ascii CI check flags any non-ASCII characters in source files. * let us see what we get with this... * minor tweak * minor update * update doc --------- Co-authored-by: Justin Li <justin53@bu.edu>
This commit is contained in:
@@ -124,6 +124,7 @@ SIMDJSON_POP_DISABLE_WARNINGS
|
||||
#include "kostya/boostjson.h"
|
||||
|
||||
#include "large_random/simdjson_ondemand.h"
|
||||
#include "large_random/simdjson_ondemand_ranges.h"
|
||||
#if SIMDJSON_COMPETITION_ONDEMAND_UNORDERED
|
||||
#include "large_random/simdjson_ondemand_unordered.h"
|
||||
#endif // SIMDJSON_COMPETITION_ONDEMAND_UNORDERED
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS && SIMDJSON_SUPPORTS_RANGES
|
||||
|
||||
#include "large_random.h"
|
||||
|
||||
namespace large_random {
|
||||
|
||||
using namespace simdjson;
|
||||
|
||||
// Identical to simdjson_ondemand but uses get_range() for iteration.
|
||||
// Demonstrates that the ranges wrapper has zero per-element overhead.
|
||||
struct simdjson_ondemand_ranges {
|
||||
static constexpr diff_flags DiffFlags = diff_flags::NONE;
|
||||
|
||||
ondemand::parser parser{};
|
||||
|
||||
bool run(simdjson::padded_string &json, std::vector<point> &result) {
|
||||
auto doc = parser.iterate(json);
|
||||
for (auto coord_result : ondemand::get_range(doc.get_array())) {
|
||||
ondemand::object coord = coord_result;
|
||||
result.emplace_back(json_benchmark::point{coord.find_field("x"), coord.find_field("y"), coord.find_field("z")});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
BENCHMARK_TEMPLATE(large_random, simdjson_ondemand_ranges)->UseManualTime();
|
||||
|
||||
} // namespace large_random
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS && SIMDJSON_SUPPORTS_RANGES
|
||||
@@ -28,6 +28,9 @@ separate document](https://github.com/simdjson/simdjson/blob/master/doc/builder.
|
||||
- [UTF-8 validation (alone)](#utf-8-validation-alone)
|
||||
- [JSON Pointer](#json-pointer)
|
||||
- [JSONPath](#jsonpath)
|
||||
* [Using `at_path_with_wildcard` for JSONPath Queries (On-Demand)](#using-at_path_with_wildcard-for-jsonpath-queries-on-demand)
|
||||
+ [Example Usage](#example-usage)
|
||||
- [C++20 Ranges Support](#c20-ranges-support)
|
||||
- [Compile-Time JSONPath and JSON Pointer (C++26 Reflection)](#compile-time-jsonpath-and-json-pointer-c26-reflection)
|
||||
- [Error handling](#error-handling)
|
||||
* [Error handling examples without exceptions](#error-handling-examples-without-exceptions)
|
||||
@@ -1901,6 +1904,66 @@ if (!error) {
|
||||
|
||||
This function is particularly useful for extracting data from complex JSON structures with nested arrays and objects. By leveraging wildcards, you can simplify your queries and reduce the need for multiple iterations.
|
||||
|
||||
## C++20 Ranges Support
|
||||
|
||||
When compiling with C++20 (or later), you can use `std::ranges` with the On-Demand API
|
||||
via the `get_range()` helper. This enables use of range adaptors such as `std::views::transform`.
|
||||
|
||||
```cpp
|
||||
#include "simdjson.h"
|
||||
#include <ranges>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
auto json = R"([
|
||||
{ "name": "Alice", "age": 30 },
|
||||
{ "name": "Bob", "age": 25 },
|
||||
{ "name": "Carol", "age": 35 }
|
||||
])"_padded;
|
||||
|
||||
ondemand::parser parser;
|
||||
auto doc = parser.iterate(json);
|
||||
auto arr = doc.get_array();
|
||||
|
||||
// Use std::views::transform to extract names
|
||||
auto names = ondemand::get_range(arr)
|
||||
| std::views::transform([](auto elem) -> std::string {
|
||||
return std::string(std::string_view(elem["name"]));
|
||||
});
|
||||
|
||||
for (auto name : names) {
|
||||
std::cout << name << std::endl; // Alice, Bob, Carol
|
||||
}
|
||||
```
|
||||
|
||||
The `get_range()` and `get_key_value_range()` functions wrap an `ondemand::array`
|
||||
or `ondemand::object` in a `std::ranges::view` that satisfies `std::ranges::input_range`.
|
||||
They work with both exception and non-exception code:
|
||||
|
||||
```cpp
|
||||
// With exceptions:
|
||||
auto range = ondemand::get_range(doc.get_array());
|
||||
|
||||
// Without exceptions:
|
||||
ondemand::array arr;
|
||||
if (doc.get_array().get(arr) == SUCCESS) {
|
||||
auto range = ondemand::get_range(arr);
|
||||
for (auto elem : range) { /* ... */ }
|
||||
}
|
||||
```
|
||||
|
||||
Object iteration uses `get_key_value_range()` and yields `simdjson_result<ondemand::field>` elements:
|
||||
|
||||
```cpp
|
||||
auto obj = doc.get_object();
|
||||
for (auto field_result : ondemand::get_key_value_range(obj)) {
|
||||
std::cout << field_result.key() << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
The range wrappers are zero-cost: they forward directly to the underlying
|
||||
On-Demand iterators with no value buffering or extra per-element overhead.
|
||||
|
||||
## Compile-Time JSONPath and JSON Pointer (C++26 Reflection)
|
||||
|
||||
The simdjson library provides **compile-time validated** JSONPath and JSON Pointer accessors when using C++26 Static Reflection. These accessors validate paths against struct definitions at compile time and generate optimized code with zero runtime overhead. In some cases, we find that it is much faster. Furthermore, it is safer in the sense that the expression
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "simdjson/generic/ondemand/field.h"
|
||||
#include "simdjson/generic/ondemand/object.h"
|
||||
#include "simdjson/generic/ondemand/object_iterator.h"
|
||||
#include "simdjson/generic/ondemand/ranges.h"
|
||||
#include "simdjson/generic/ondemand/serialization.h"
|
||||
|
||||
// Deserialization for standard types
|
||||
@@ -39,6 +40,7 @@
|
||||
#include "simdjson/generic/ondemand/logger-inl.h"
|
||||
#include "simdjson/generic/ondemand/object-inl.h"
|
||||
#include "simdjson/generic/ondemand/object_iterator-inl.h"
|
||||
#include "simdjson/generic/ondemand/ranges-inl.h"
|
||||
#include "simdjson/generic/ondemand/parser-inl.h"
|
||||
#include "simdjson/generic/ondemand/raw_json_string-inl.h"
|
||||
#include "simdjson/generic/ondemand/token_iterator-inl.h"
|
||||
|
||||
@@ -40,6 +40,13 @@ class token_iterator;
|
||||
class value;
|
||||
class value_iterator;
|
||||
|
||||
#if SIMDJSON_SUPPORTS_RANGES
|
||||
class array_range;
|
||||
class array_range_iterator;
|
||||
class object_range;
|
||||
class object_range_iterator;
|
||||
#endif // SIMDJSON_SUPPORTS_RANGES
|
||||
|
||||
} // namespace ondemand
|
||||
} // namespace SIMDJSON_IMPLEMENTATION
|
||||
} // namespace simdjson
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
#ifndef SIMDJSON_GENERIC_ONDEMAND_RANGES_INL_H
|
||||
|
||||
#ifndef SIMDJSON_CONDITIONAL_INCLUDE
|
||||
#define SIMDJSON_GENERIC_ONDEMAND_RANGES_INL_H
|
||||
#include "simdjson/generic/ondemand/base.h"
|
||||
#include "simdjson/generic/ondemand/ranges.h"
|
||||
#include "simdjson/generic/ondemand/array-inl.h"
|
||||
#include "simdjson/generic/ondemand/array_iterator-inl.h"
|
||||
#include "simdjson/generic/ondemand/object-inl.h"
|
||||
#include "simdjson/generic/ondemand/object_iterator-inl.h"
|
||||
#endif // SIMDJSON_CONDITIONAL_INCLUDE
|
||||
|
||||
#if SIMDJSON_SUPPORTS_RANGES
|
||||
|
||||
namespace simdjson {
|
||||
namespace SIMDJSON_IMPLEMENTATION {
|
||||
namespace ondemand {
|
||||
|
||||
//
|
||||
// array_range_iterator
|
||||
//
|
||||
|
||||
simdjson_inline array_range_iterator::array_range_iterator(array_iterator iter) noexcept
|
||||
: iter_{iter} {}
|
||||
|
||||
simdjson_inline simdjson_result<value> array_range_iterator::operator*() const noexcept {
|
||||
return *iter_;
|
||||
}
|
||||
|
||||
simdjson_inline array_range_iterator& array_range_iterator::operator++() noexcept {
|
||||
++iter_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
|
||||
simdjson_inline void array_range_iterator::operator++(int) noexcept {
|
||||
++*this;
|
||||
}
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
|
||||
//
|
||||
// array_range
|
||||
//
|
||||
|
||||
simdjson_inline array_range::array_range(array& arr) noexcept {
|
||||
auto b = arr.begin();
|
||||
if (b.error()) { error_ = b.error(); return; }
|
||||
begin_ = b.value_unsafe();
|
||||
end_ = arr.end().value_unsafe();
|
||||
}
|
||||
|
||||
simdjson_inline array_range_iterator array_range::begin() noexcept {
|
||||
return array_range_iterator(begin_);
|
||||
}
|
||||
|
||||
simdjson_inline array_range_iterator array_range::end() noexcept {
|
||||
return array_range_iterator(end_);
|
||||
}
|
||||
|
||||
//
|
||||
// object_range_iterator
|
||||
//
|
||||
|
||||
simdjson_inline object_range_iterator::object_range_iterator(object_iterator iter) noexcept
|
||||
: iter_{iter} {}
|
||||
|
||||
simdjson_inline simdjson_result<field> object_range_iterator::operator*() const noexcept {
|
||||
return *iter_;
|
||||
}
|
||||
|
||||
simdjson_inline object_range_iterator& object_range_iterator::operator++() noexcept {
|
||||
++iter_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
|
||||
simdjson_inline void object_range_iterator::operator++(int) noexcept {
|
||||
++*this;
|
||||
}
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
|
||||
|
||||
//
|
||||
// object_range
|
||||
//
|
||||
|
||||
simdjson_inline object_range::object_range(object& obj) noexcept {
|
||||
auto b = obj.begin();
|
||||
if (b.error()) { error_ = b.error(); return; }
|
||||
begin_ = b.value_unsafe();
|
||||
end_ = obj.end().value_unsafe();
|
||||
}
|
||||
|
||||
simdjson_inline object_range_iterator object_range::begin() noexcept {
|
||||
return object_range_iterator(begin_);
|
||||
}
|
||||
|
||||
simdjson_inline object_range_iterator object_range::end() noexcept {
|
||||
return object_range_iterator(end_);
|
||||
}
|
||||
|
||||
//
|
||||
// Free functions
|
||||
//
|
||||
|
||||
simdjson_inline array_range get_range(array& arr) noexcept {
|
||||
return array_range(arr);
|
||||
}
|
||||
|
||||
simdjson_inline object_range get_key_value_range(object& obj) noexcept {
|
||||
return object_range(obj);
|
||||
}
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
simdjson_inline array_range get_range(simdjson_result<array> result) {
|
||||
return array_range(result.value());
|
||||
}
|
||||
|
||||
simdjson_inline object_range get_key_value_range(simdjson_result<object> result) {
|
||||
return object_range(result.value());
|
||||
}
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
|
||||
} // namespace ondemand
|
||||
} // namespace SIMDJSON_IMPLEMENTATION
|
||||
} // namespace simdjson
|
||||
|
||||
// Verify the range wrapper types satisfy the expected C++20 concepts.
|
||||
static_assert(std::input_iterator<simdjson::SIMDJSON_IMPLEMENTATION::ondemand::array_range_iterator>);
|
||||
static_assert(std::input_iterator<simdjson::SIMDJSON_IMPLEMENTATION::ondemand::object_range_iterator>);
|
||||
static_assert(std::ranges::input_range<simdjson::SIMDJSON_IMPLEMENTATION::ondemand::array_range>);
|
||||
static_assert(std::ranges::input_range<simdjson::SIMDJSON_IMPLEMENTATION::ondemand::object_range>);
|
||||
static_assert(std::ranges::view<simdjson::SIMDJSON_IMPLEMENTATION::ondemand::array_range>);
|
||||
static_assert(std::ranges::view<simdjson::SIMDJSON_IMPLEMENTATION::ondemand::object_range>);
|
||||
|
||||
#endif // SIMDJSON_SUPPORTS_RANGES
|
||||
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_RANGES_INL_H
|
||||
@@ -0,0 +1,182 @@
|
||||
#ifndef SIMDJSON_GENERIC_ONDEMAND_RANGES_H
|
||||
|
||||
#ifndef SIMDJSON_CONDITIONAL_INCLUDE
|
||||
#define SIMDJSON_GENERIC_ONDEMAND_RANGES_H
|
||||
#include "simdjson/generic/ondemand/base.h"
|
||||
#include "simdjson/generic/ondemand/array.h"
|
||||
#include "simdjson/generic/ondemand/array_iterator.h"
|
||||
#include "simdjson/generic/ondemand/object.h"
|
||||
#include "simdjson/generic/ondemand/object_iterator.h"
|
||||
#include "simdjson/generic/ondemand/field.h"
|
||||
#include "simdjson/generic/ondemand/value.h"
|
||||
#endif // SIMDJSON_CONDITIONAL_INCLUDE
|
||||
|
||||
#if SIMDJSON_SUPPORTS_RANGES
|
||||
|
||||
namespace simdjson {
|
||||
namespace SIMDJSON_IMPLEMENTATION {
|
||||
namespace ondemand {
|
||||
|
||||
/**
|
||||
* A ranges-compatible iterator adapter for JSON arrays.
|
||||
*
|
||||
* Wraps array_iterator to satisfy std::input_iterator by providing:
|
||||
* - const operator* (via mutable internal state)
|
||||
* - post-increment operator
|
||||
* - iterator_concept tag
|
||||
*
|
||||
* The mutable approach is standard for single-pass input iterators that
|
||||
* read from external sources (similar to std::istream_iterator).
|
||||
*/
|
||||
class array_range_iterator {
|
||||
public:
|
||||
using iterator_concept = std::input_iterator_tag;
|
||||
using value_type = simdjson_result<value>;
|
||||
using reference = simdjson_result<value>;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
|
||||
simdjson_inline array_range_iterator() noexcept = default;
|
||||
simdjson_inline explicit array_range_iterator(array_iterator iter) noexcept;
|
||||
|
||||
/**
|
||||
* Get the current element. Const-qualified for std::indirectly_readable;
|
||||
* internally delegates to the mutable wrapped iterator.
|
||||
*/
|
||||
simdjson_inline simdjson_result<value> operator*() const noexcept;
|
||||
simdjson_inline array_range_iterator& operator++() noexcept;
|
||||
SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
|
||||
simdjson_inline void operator++(int) noexcept;
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
/**
|
||||
* Comparison delegates to array_iterator::operator==, which checks
|
||||
* whether the underlying parser has finished the array (depth-based).
|
||||
*/
|
||||
simdjson_inline friend bool operator==(const array_range_iterator& a,
|
||||
const array_range_iterator& b) noexcept {
|
||||
return a.iter_ == b.iter_;
|
||||
}
|
||||
|
||||
private:
|
||||
mutable array_iterator iter_{};
|
||||
};
|
||||
|
||||
/**
|
||||
* A std::ranges::view over a JSON array.
|
||||
*
|
||||
* Wraps an ondemand::array and exposes begin()/end() that return
|
||||
* array_range_iterator (satisfying std::input_iterator), enabling
|
||||
* use with std::views::transform and other range adaptors.
|
||||
*
|
||||
* If the array's begin() returns an error (only possible under
|
||||
* SIMDJSON_DEVELOPMENT_CHECKS), the range will be empty and error()
|
||||
* will return the error code.
|
||||
*
|
||||
* Usage:
|
||||
* ondemand::parser parser;
|
||||
* auto doc = parser.iterate(json);
|
||||
* auto arr = doc.get_array().value();
|
||||
* for (auto elem : ondemand::get_range(arr)) { ... }
|
||||
*/
|
||||
class array_range {
|
||||
public:
|
||||
simdjson_inline array_range() noexcept = default;
|
||||
simdjson_inline explicit array_range(array& arr) noexcept;
|
||||
|
||||
simdjson_inline array_range_iterator begin() noexcept;
|
||||
simdjson_inline array_range_iterator end() noexcept;
|
||||
|
||||
/** Returns SUCCESS if the range was created successfully, or the error code otherwise. */
|
||||
simdjson_inline error_code error() const noexcept { return error_; }
|
||||
|
||||
private:
|
||||
array_iterator begin_{};
|
||||
array_iterator end_{};
|
||||
error_code error_{SUCCESS};
|
||||
};
|
||||
|
||||
/**
|
||||
* A ranges-compatible iterator adapter for JSON objects.
|
||||
*
|
||||
* Wraps object_iterator to satisfy std::input_iterator, yielding
|
||||
* simdjson_result<field> elements (key-value pairs).
|
||||
*/
|
||||
class object_range_iterator {
|
||||
public:
|
||||
using iterator_concept = std::input_iterator_tag;
|
||||
using value_type = simdjson_result<field>;
|
||||
using reference = simdjson_result<field>;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
|
||||
simdjson_inline object_range_iterator() noexcept = default;
|
||||
simdjson_inline explicit object_range_iterator(object_iterator iter) noexcept;
|
||||
|
||||
simdjson_inline simdjson_result<field> operator*() const noexcept;
|
||||
simdjson_inline object_range_iterator& operator++() noexcept;
|
||||
SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
|
||||
simdjson_inline void operator++(int) noexcept;
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
simdjson_inline friend bool operator==(const object_range_iterator& a,
|
||||
const object_range_iterator& b) noexcept {
|
||||
return a.iter_ == b.iter_;
|
||||
}
|
||||
|
||||
private:
|
||||
mutable object_iterator iter_{};
|
||||
};
|
||||
|
||||
/**
|
||||
* A std::ranges::view over a JSON object.
|
||||
*
|
||||
* Wraps an ondemand::object and exposes begin()/end() that return
|
||||
* object_range_iterator, enabling use with range adaptors.
|
||||
*
|
||||
* If the object's begin() returns an error, the range will be empty
|
||||
* and error() will return the error code.
|
||||
*/
|
||||
class object_range {
|
||||
public:
|
||||
simdjson_inline object_range() noexcept = default;
|
||||
simdjson_inline explicit object_range(object& obj) noexcept;
|
||||
|
||||
simdjson_inline object_range_iterator begin() noexcept;
|
||||
simdjson_inline object_range_iterator end() noexcept;
|
||||
|
||||
/** Returns SUCCESS if the range was created successfully, or the error code otherwise. */
|
||||
simdjson_inline error_code error() const noexcept { return error_; }
|
||||
|
||||
private:
|
||||
object_iterator begin_{};
|
||||
object_iterator end_{};
|
||||
error_code error_{SUCCESS};
|
||||
};
|
||||
|
||||
/** Get a std::ranges compatible view over a JSON array. */
|
||||
simdjson_inline array_range get_range(array& arr) noexcept;
|
||||
|
||||
/** Get a std::ranges compatible view over a JSON object (key-value pairs). */
|
||||
simdjson_inline object_range get_key_value_range(object& obj) noexcept;
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
/** Get a std::ranges compatible view, unwrapping the simdjson_result (throws on error). */
|
||||
simdjson_inline array_range get_range(simdjson_result<array> result);
|
||||
|
||||
/** Get a std::ranges compatible view, unwrapping the simdjson_result (throws on error). */
|
||||
simdjson_inline object_range get_key_value_range(simdjson_result<object> result);
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
|
||||
} // namespace ondemand
|
||||
} // namespace SIMDJSON_IMPLEMENTATION
|
||||
} // namespace simdjson
|
||||
|
||||
namespace std {
|
||||
namespace ranges {
|
||||
template<>
|
||||
inline constexpr bool enable_view<simdjson::SIMDJSON_IMPLEMENTATION::ondemand::array_range> = true;
|
||||
template<>
|
||||
inline constexpr bool enable_view<simdjson::SIMDJSON_IMPLEMENTATION::ondemand::object_range> = true;
|
||||
} // namespace ranges
|
||||
} // namespace std
|
||||
|
||||
#endif // SIMDJSON_SUPPORTS_RANGES
|
||||
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_RANGES_H
|
||||
@@ -127,14 +127,16 @@ endif()
|
||||
# 1. Visual Studio 2022 v17.6 or later
|
||||
# 2. GCC v14.0.0 or later (GCC v13.0.0 cannot handle pipe operator of lambda)
|
||||
# 3. Clang v15.0.0 or later (certain version C++ headers occur error when compiling)
|
||||
# 4. or if we are targeting C++20 or better
|
||||
if(
|
||||
(MSVC AND MSVC_VERSION LESS 1930) OR
|
||||
(MSVC AND MSVC_VERSION GREATER_EQUAL 1930) OR
|
||||
(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "14.0.0") OR
|
||||
(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "15.0.0")
|
||||
(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "15.0.0") OR
|
||||
(CMAKE_CXX_STANDARD GREATER_EQUAL 20)
|
||||
)
|
||||
message(STATUS "compiler id: ${CMAKE_CXX_COMPILER_ID} version: ${CMAKE_CXX_COMPILER_VERSION}")
|
||||
add_cpp_test(ranges_test LABELS dom acceptance per_implementation)
|
||||
if(NOT SIMDJSON_STATIC_REFLECTION)
|
||||
if(NOT SIMDJSON_STATIC_REFLECTION AND NOT CMAKE_CXX_STANDARD GREATER_EQUAL 20)
|
||||
set_target_properties(ranges_test PROPERTIES CXX_STANDARD 20 CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -45,6 +45,25 @@ if(NOT SIMDJSON_SANITIZE)
|
||||
add_cpp_test(ondemand_cacheline LABELS ondemand acceptance per_implementation)
|
||||
endif()
|
||||
|
||||
# Add the tests if we're on:
|
||||
# 1. Visual Studio 2022 v17.6 or later
|
||||
# 2. GCC v14.0.0 or later (GCC v13.0.0 cannot handle pipe operator of lambda)
|
||||
# 3. Clang v15.0.0 or later (certain version C++ headers occur error when compiling)
|
||||
# 4. or if we are targeting C++20 or better
|
||||
if(
|
||||
(MSVC AND MSVC_VERSION GREATER_EQUAL 1930) OR
|
||||
(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "14.0.0") OR
|
||||
(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "15.0.0") OR
|
||||
(CMAKE_CXX_STANDARD GREATER_EQUAL 20)
|
||||
)
|
||||
message(STATUS "compiler id: ${CMAKE_CXX_COMPILER_ID} version: ${CMAKE_CXX_COMPILER_VERSION}")
|
||||
add_cpp_test(ondemand_ranges_tests LABELS ondemand acceptance per_implementation)
|
||||
if(NOT SIMDJSON_STATIC_REFLECTION AND NOT CMAKE_CXX_STANDARD GREATER_EQUAL 20)
|
||||
set_target_properties(ondemand_ranges_tests PROPERTIES CXX_STANDARD 20 CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
if(HAVE_POSIX_FORK AND HAVE_POSIX_WAIT) # assert tests use fork and wait, which aren't on MSVC
|
||||
add_cpp_test(ondemand_assert_out_of_order_values LABELS assert per_implementation explicitonly ondemand)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,319 @@
|
||||
#include <iostream>
|
||||
#include "simdjson.h"
|
||||
#include "test_macros.h"
|
||||
#include "test_main.h"
|
||||
|
||||
#if SIMDJSON_SUPPORTS_RANGES
|
||||
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace simdjson;
|
||||
|
||||
namespace ondemand_ranges_tests {
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
bool array_get_range_basic() {
|
||||
TEST_START();
|
||||
auto json = R"([10, 20, 30])"_padded;
|
||||
ondemand::parser parser;
|
||||
auto doc = parser.iterate(json);
|
||||
auto arr = doc.get_array();
|
||||
auto range = ondemand::get_range(arr);
|
||||
|
||||
std::vector<int64_t> values;
|
||||
for (auto elem : range) {
|
||||
values.push_back(int64_t(elem));
|
||||
}
|
||||
ASSERT_EQUAL(values.size(), size_t(3));
|
||||
ASSERT_EQUAL(values[0], int64_t(10));
|
||||
ASSERT_EQUAL(values[1], int64_t(20));
|
||||
ASSERT_EQUAL(values[2], int64_t(30));
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool array_range_with_transform() {
|
||||
TEST_START();
|
||||
auto json = R"([1, 2, 3, 4, 5])"_padded;
|
||||
ondemand::parser parser;
|
||||
auto doc = parser.iterate(json);
|
||||
auto arr = doc.get_array();
|
||||
|
||||
auto doubled = ondemand::get_range(arr)
|
||||
| std::views::transform([](auto v) -> int64_t { return int64_t(v) * 2; });
|
||||
|
||||
std::vector<int64_t> values;
|
||||
for (auto val : doubled) {
|
||||
values.push_back(val);
|
||||
}
|
||||
ASSERT_EQUAL(values.size(), size_t(5));
|
||||
ASSERT_EQUAL(values[0], int64_t(2));
|
||||
ASSERT_EQUAL(values[1], int64_t(4));
|
||||
ASSERT_EQUAL(values[2], int64_t(6));
|
||||
ASSERT_EQUAL(values[3], int64_t(8));
|
||||
ASSERT_EQUAL(values[4], int64_t(10));
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool array_range_strings() {
|
||||
TEST_START();
|
||||
auto json = R"(["alpha", "beta", "gamma"])"_padded;
|
||||
ondemand::parser parser;
|
||||
auto doc = parser.iterate(json);
|
||||
auto arr = doc.get_array();
|
||||
|
||||
auto to_string = [](auto v) -> std::string {
|
||||
return std::string(std::string_view(v));
|
||||
};
|
||||
auto strings = ondemand::get_range(arr) | std::views::transform(to_string);
|
||||
|
||||
std::vector<std::string> values;
|
||||
for (auto s : strings) {
|
||||
values.push_back(s);
|
||||
}
|
||||
ASSERT_EQUAL(values.size(), size_t(3));
|
||||
ASSERT_TRUE(values[0] == "alpha");
|
||||
ASSERT_TRUE(values[1] == "beta");
|
||||
ASSERT_TRUE(values[2] == "gamma");
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool array_range_empty() {
|
||||
TEST_START();
|
||||
auto json = R"([])"_padded;
|
||||
ondemand::parser parser;
|
||||
auto doc = parser.iterate(json);
|
||||
auto arr = doc.get_array();
|
||||
auto range = ondemand::get_range(arr);
|
||||
|
||||
int count = 0;
|
||||
for (simdjson_unused auto elem : range) {
|
||||
count++;
|
||||
}
|
||||
ASSERT_EQUAL(count, 0);
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool array_range_nested() {
|
||||
TEST_START();
|
||||
auto json = R"([{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}])"_padded;
|
||||
ondemand::parser parser;
|
||||
auto doc = parser.iterate(json);
|
||||
auto arr = doc.get_array();
|
||||
|
||||
auto get_name = [](auto v) -> std::string {
|
||||
return std::string(std::string_view(v["name"]));
|
||||
};
|
||||
auto names = ondemand::get_range(arr) | std::views::transform(get_name);
|
||||
|
||||
std::vector<std::string> values;
|
||||
for (auto name : names) {
|
||||
values.push_back(name);
|
||||
}
|
||||
ASSERT_EQUAL(values.size(), size_t(2));
|
||||
ASSERT_TRUE(values[0] == "Alice");
|
||||
ASSERT_TRUE(values[1] == "Bob");
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool object_get_range_basic() {
|
||||
TEST_START();
|
||||
auto json = R"({"a": 1, "b": 2, "c": 3})"_padded;
|
||||
ondemand::parser parser;
|
||||
auto doc = parser.iterate(json);
|
||||
auto obj = doc.get_object();
|
||||
auto range = ondemand::get_key_value_range(obj);
|
||||
|
||||
std::vector<std::string> keys;
|
||||
std::vector<int64_t> vals;
|
||||
for (auto field_result : range) {
|
||||
keys.push_back(std::string(std::string_view(field_result.escaped_key())));
|
||||
vals.push_back(int64_t(field_result.value()));
|
||||
}
|
||||
ASSERT_EQUAL(keys.size(), size_t(3));
|
||||
ASSERT_TRUE(keys[0] == "a");
|
||||
ASSERT_TRUE(keys[1] == "b");
|
||||
ASSERT_TRUE(keys[2] == "c");
|
||||
ASSERT_EQUAL(vals[0], int64_t(1));
|
||||
ASSERT_EQUAL(vals[1], int64_t(2));
|
||||
ASSERT_EQUAL(vals[2], int64_t(3));
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool object_range_with_transform() {
|
||||
TEST_START();
|
||||
auto json = R"({"x": 10, "y": 20, "z": 30})"_padded;
|
||||
ondemand::parser parser;
|
||||
auto doc = parser.iterate(json);
|
||||
auto obj = doc.get_object();
|
||||
|
||||
auto get_key = [](auto field_result) -> std::string {
|
||||
return std::string(std::string_view(field_result.escaped_key()));
|
||||
};
|
||||
auto keys = ondemand::get_key_value_range(obj) | std::views::transform(get_key);
|
||||
|
||||
std::vector<std::string> values;
|
||||
for (auto k : keys) {
|
||||
values.push_back(k);
|
||||
}
|
||||
ASSERT_EQUAL(values.size(), size_t(3));
|
||||
ASSERT_TRUE(values[0] == "x");
|
||||
ASSERT_TRUE(values[1] == "y");
|
||||
ASSERT_TRUE(values[2] == "z");
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool object_range_empty() {
|
||||
TEST_START();
|
||||
auto json = R"({})"_padded;
|
||||
ondemand::parser parser;
|
||||
auto doc = parser.iterate(json);
|
||||
auto obj = doc.get_object();
|
||||
auto range = ondemand::get_key_value_range(obj);
|
||||
|
||||
int count = 0;
|
||||
for (simdjson_unused auto elem : range) {
|
||||
count++;
|
||||
}
|
||||
ASSERT_EQUAL(count, 0);
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool get_range_from_result() {
|
||||
TEST_START();
|
||||
auto json = R"([100, 200, 300])"_padded;
|
||||
ondemand::parser parser;
|
||||
auto doc = parser.iterate(json);
|
||||
|
||||
// get_range with simdjson_result<array> - unwraps automatically
|
||||
auto range = ondemand::get_range(doc.get_array());
|
||||
|
||||
std::vector<int64_t> values;
|
||||
for (auto elem : range) {
|
||||
values.push_back(int64_t(elem));
|
||||
}
|
||||
ASSERT_EQUAL(values.size(), size_t(3));
|
||||
ASSERT_EQUAL(values[0], int64_t(100));
|
||||
ASSERT_EQUAL(values[1], int64_t(200));
|
||||
ASSERT_EQUAL(values[2], int64_t(300));
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool object_range_key_iteration() {
|
||||
TEST_START();
|
||||
auto json = R"({"name": "Alice", "age": 30, "city": "New York"})"_padded;
|
||||
ondemand::parser parser;
|
||||
auto doc = parser.iterate(json);
|
||||
auto obj = doc.get_object();
|
||||
|
||||
// Test the specific pattern: iterating over field_result.key()
|
||||
std::vector<std::string> keys;
|
||||
for (auto field_result : ondemand::get_key_value_range(obj)) {
|
||||
keys.push_back(std::string(field_result.escaped_key().value()));
|
||||
}
|
||||
|
||||
ASSERT_EQUAL(keys.size(), size_t(3));
|
||||
bool has_name = false, has_age = false, has_city = false;
|
||||
for (const auto& key : keys) {
|
||||
if (key == "name") has_name = true;
|
||||
else if (key == "age") has_age = true;
|
||||
else if (key == "city") has_city = true;
|
||||
}
|
||||
ASSERT_TRUE(has_name);
|
||||
ASSERT_TRUE(has_age);
|
||||
ASSERT_TRUE(has_city);
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Verify that the types satisfy the expected C++20 concepts.
|
||||
bool concept_checks() {
|
||||
TEST_START();
|
||||
static_assert(std::input_iterator<ondemand::array_range_iterator>);
|
||||
static_assert(std::input_iterator<ondemand::object_range_iterator>);
|
||||
static_assert(std::ranges::input_range<ondemand::array_range>);
|
||||
static_assert(std::ranges::input_range<ondemand::object_range>);
|
||||
static_assert(std::ranges::view<ondemand::array_range>);
|
||||
static_assert(std::ranges::view<ondemand::object_range>);
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
|
||||
// These tests work without exceptions.
|
||||
bool array_range_noexcept_basic() {
|
||||
TEST_START();
|
||||
auto json = R"([1, 2, 3])"_padded;
|
||||
ondemand::parser parser;
|
||||
auto doc = parser.iterate(json);
|
||||
ondemand::array arr;
|
||||
ASSERT_SUCCESS(doc.get_array().get(arr));
|
||||
auto range = ondemand::get_range(arr);
|
||||
ASSERT_SUCCESS(range.error());
|
||||
|
||||
int count = 0;
|
||||
for (auto elem : range) {
|
||||
int64_t val;
|
||||
ASSERT_SUCCESS(elem.get_int64().get(val));
|
||||
count++;
|
||||
}
|
||||
ASSERT_EQUAL(count, 3);
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool object_range_noexcept_basic() {
|
||||
TEST_START();
|
||||
auto json = R"({"a": 1, "b": 2})"_padded;
|
||||
ondemand::parser parser;
|
||||
auto doc = parser.iterate(json);
|
||||
ondemand::object obj;
|
||||
ASSERT_SUCCESS(doc.get_object().get(obj));
|
||||
auto range = ondemand::get_key_value_range(obj);
|
||||
ASSERT_SUCCESS(range.error());
|
||||
|
||||
int count = 0;
|
||||
for (auto field_result : range) {
|
||||
simdjson_unused ondemand::field f;
|
||||
ASSERT_SUCCESS(std::move(field_result).get(f));
|
||||
count++;
|
||||
}
|
||||
ASSERT_EQUAL(count, 2);
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool run() {
|
||||
return
|
||||
array_range_noexcept_basic() &&
|
||||
object_range_noexcept_basic() &&
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
concept_checks() &&
|
||||
array_get_range_basic() &&
|
||||
array_range_with_transform() &&
|
||||
array_range_strings() &&
|
||||
array_range_empty() &&
|
||||
array_range_nested() &&
|
||||
object_get_range_basic() &&
|
||||
object_range_with_transform() &&
|
||||
object_range_empty() &&
|
||||
get_range_from_result() &&
|
||||
object_range_key_iteration() &&
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
true;
|
||||
}
|
||||
|
||||
} // namespace ondemand_ranges_tests
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
return test_main(argc, argv, ondemand_ranges_tests::run);
|
||||
}
|
||||
|
||||
#else // !SIMDJSON_SUPPORTS_RANGES
|
||||
|
||||
int main() {
|
||||
std::cout << "Ranges tests require C++20 ranges support, skipping." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // SIMDJSON_SUPPORTS_RANGES
|
||||
Reference in New Issue
Block a user