mirror of
https://github.com/simdjson/simdjson
synced 2026-06-08 17:27:07 +00:00
Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| de1b38c459 | |||
| 3d08f995d0 | |||
| 135159012f | |||
| 936afe9aa1 | |||
| 1cc03882de | |||
| 9cd389ae84 | |||
| 64d5ee550f | |||
| 18cdecaf8e | |||
| c33e4441a0 | |||
| 732ca8cf70 | |||
| 2143ebe538 | |||
| df770af450 | |||
| 1985584de7 | |||
| 6a74cd41d6 | |||
| dcaad94b53 | |||
| 98fde69253 | |||
| dc485f845d | |||
| 79bbba3e3e | |||
| 1a29a5e53a | |||
| 57561f64f4 | |||
| 00563f9133 | |||
| 5db72ab9d3 | |||
| d3d99b8fc5 | |||
| 168ef58075 | |||
| fb8c575121 | |||
| 7732480b25 | |||
| 1ae93a70d4 | |||
| 396ca141fd | |||
| 8e2e9b8e02 | |||
| 599895e54d | |||
| 77a53d19a2 | |||
| 8c7cc2f963 | |||
| 43766fc7ef | |||
| c2f25ab455 | |||
| ec49efa5da | |||
| a16e5128fe | |||
| bd42858383 | |||
| 7d30ff2574 | |||
| 42c047a927 | |||
| 376ef7e8e9 | |||
| 0a23ebbfef | |||
| 9803884546 |
@@ -58,7 +58,7 @@ We support up-to-date 64-bit ARM and x64 FreeBSD, macOS, Windows and Linux syste
|
||||
* We do not support unreleased or experimental compilers. If you encounter an issue with a
|
||||
pre-release version of a compiler, do not report it as a bug to simdjson. However, we always
|
||||
invite contributions either in the form an analysis or of a code contribution.
|
||||
* Vendors (e.g., Apple and Microsoft) stop supporting old systems. Once a compiler system is no longer supported by its vendor, we no longer support it. We will gladly accept code contributions, but we do not consider it a *bug* if you have issues with an obsolete compiler systems. This policy extends to obsolete standard libraries, linkers and other build tools. Please do not report it as an issue. If you cannot resolve the issue yourself, we encourage you to reach out to the vendor for legacy support.
|
||||
* Vendors (e.g., Apple and Microsoft) stop supporting old systems. Once a compiler system is no longer supported by its vendor, we no longer support it. We will gladly accept code contributions, but we do not consider it a *bug* if you have issues with an obsolete compiler systems. This policy extends to obsolete standard libraries, linkers and other build tools. Please do not report it as an issue. If you cannot resolve the issue yourself, we encourage you to reach out to the vendor for legacy support. As of 2026, Windows 10 is no longer supported.
|
||||
|
||||
Under Windows, we support Visual Studio (both with LLVM and without). We do not support MinGW and other alternate compiler systems. Windows users should be aware that there [is a long-running bug with GCC under Windows](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54412).
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
name: gcc 16
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: 'gcc:16'
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
apt -y update
|
||||
apt -y --no-install-recommends install cmake ninja-build
|
||||
- name: Build and test
|
||||
run: |
|
||||
cmake -B build -D SIMDJSON_STATIC_REFLECTION=ON -DSIMDJSON_DEVELOPER_MODE=ON -GNinja
|
||||
cmake --build build
|
||||
ctest --test-dir build --parallel $(nproc)
|
||||
@@ -26,4 +26,4 @@ jobs:
|
||||
run: |
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_COMPETITION=OFF -B build
|
||||
cmake --build build -j=2
|
||||
ctest --output-on-failure --test-dir build
|
||||
ctest --output-on-failure --test-dir build -E ondemand_cacheline
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
name: Ubuntu 24.04 CI (CLANG 20)
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
ubuntu-build:
|
||||
if: >-
|
||||
! contains(toJSON(github.event.commits.*.message), '[skip ci]') &&
|
||||
! contains(toJSON(github.event.commits.*.message), '[skip github]')
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: dependencies/.cache
|
||||
key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
|
||||
- name: Install clang-20
|
||||
run: |
|
||||
sudo apt-get update -q -y
|
||||
sudo apt-get install -y clang-20
|
||||
- name: Use cmake
|
||||
run: |
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
CXX=clang++-20 cmake -DSIMDJSON_DEVELOPER_MODE=ON .. &&
|
||||
cmake --build . &&
|
||||
ctest --output-on-failure -LE explicitonly -j
|
||||
@@ -15,14 +15,12 @@ jobs:
|
||||
include:
|
||||
- {gen: Visual Studio 17 2022, arch: Win32, shared: ON, build_type: Release, memory_map: OFF, nan_inf: OFF}
|
||||
- {gen: Visual Studio 17 2022, arch: Win32, shared: OFF, build_type: Release, memory_map: OFF, nan_inf: OFF}
|
||||
- {gen: Visual Studio 17 2022, arch: x64, shared: ON, build_type: Release, memory_map: OFF, nan_inf: OFF}
|
||||
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Debug, memory_map: OFF, nan_inf: OFF}
|
||||
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Release, memory_map: OFF, nan_inf: OFF}
|
||||
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: RelWithDebInfo, memory_map: OFF, nan_inf: OFF}
|
||||
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Debug, memory_map: OFF, nan_inf: ON}
|
||||
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Release, memory_map: OFF, nan_inf: ON}
|
||||
# Exercise the opt-in Windows memory-file mapping path at least once in CI.
|
||||
- {gen: Visual Studio 17 2022, arch: x64, shared: ON, build_type: Release, memory_map: ON, nan_inf: OFF}
|
||||
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Debug, memory_map: ON, nan_inf: OFF}
|
||||
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Release, memory_map: ON, nan_inf: OFF}
|
||||
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: RelWithDebInfo, memory_map: ON, nan_inf: OFF}
|
||||
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Debug, memory_map: ON, nan_inf: ON}
|
||||
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Release, memory_map: ON, nan_inf: ON}
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -190,6 +190,7 @@ We distinguish between "bindings" (which just wrap the C++ code) and a port to a
|
||||
- [gemmaJSON](https://github.com/sainttttt/gemmaJSON): Nim JSON parser based on simdjson bindings.
|
||||
- [simdjson-java](https://github.com/simdjson/simdjson-java): Java port.
|
||||
- [mruby-fast-json](https://github.com/Asmod4n/mruby-fast-json): mruby binding with high API coverage.
|
||||
- [simdjson-dart](https://github.com/xaldarof/simdjson-dart): Dart bindings for the simdjson project.
|
||||
|
||||
About simdjson
|
||||
--------------
|
||||
@@ -200,7 +201,7 @@ CPU's multiple execution cores.
|
||||
|
||||
Our default front-end is called On-Demand, and we wrote a paper about it:
|
||||
|
||||
- John Keiser, Daniel Lemire, [On-Demand JSON: A Better Way to Parse Documents?](http://arxiv.org/abs/2312.17149), Software: Practice and Experience 54 (6), 2024.
|
||||
- John Keiser, Daniel Lemire, [On-Demand JSON: A Better Way to Parse Documents?](https://arxiv.org/abs/2312.17149), Software: Practice and Experience 54 (6), 2024.
|
||||
|
||||
Some people [enjoy reading the first (2019) simdjson paper](https://arxiv.org/abs/1902.08318): A description of the design
|
||||
and implementation of simdjson is in our research article:
|
||||
@@ -213,12 +214,12 @@ We have an in-depth paper focused on the UTF-8 validation:
|
||||
We also have an informal [blog post providing some background and context](https://branchfree.org/2019/02/25/paper-parsing-gigabytes-of-json-per-second/).
|
||||
|
||||
For the video inclined, we had a talk at QCon San Francisco 2019<br />
|
||||
[](http://www.youtube.com/watch?v=wlvKAT7SZIQ)<br />
|
||||
[](https://www.youtube.com/watch?v=wlvKAT7SZIQ)<br />
|
||||
(It was the best voted talk, we're kinda proud of it.)
|
||||
|
||||
We also had a CppCon 2025 talk. We show how C++26 reflection allows for one-line serialization (to_json(player)) or deserialization—without invasive macros or manual mapping—using nothing but the C++ standard library. Whether you’re a performance junkie or simply interested in the roadmap for the next decade of C++ development, watch our full talk!
|
||||
|
||||
[](http://www.youtube.com/watch?v=Mcgk3CxHYMs)<br />
|
||||
[](https://www.youtube.com/watch?v=Mcgk3CxHYMs)<br />
|
||||
|
||||
|
||||
|
||||
@@ -271,7 +272,7 @@ This code is made available under the [Apache License 2.0](https://www.apache.or
|
||||
|
||||
Under Windows, we build some tools using the windows/dirent_portable.h file (which is outside our library code): it is under the liberal (business-friendly) MIT license.
|
||||
|
||||
For compilers that do not support [C++17](https://en.wikipedia.org/wiki/C%2B%2B17), we bundle the string-view library which is published under the [Boost license](http://www.boost.org/LICENSE_1_0.txt). Like the Apache license, the Boost license is a permissive license allowing commercial redistribution.
|
||||
For compilers that do not support [C++17](https://en.wikipedia.org/wiki/C%2B%2B17), we bundle the string-view library which is published under the [Boost license](https://www.boost.org/LICENSE_1_0.txt). Like the Apache license, the Boost license is a permissive license allowing commercial redistribution.
|
||||
|
||||
For efficient number serialization, we bundle Florian Loitsch's implementation of the Grisu2 algorithm for binary to decimal floating-point numbers. The implementation was slightly modified by JSON for Modern C++ library. Both Florian Loitsch's implementation and JSON for Modern C++ are provided under the MIT license.
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ SIMDJSON_POP_DISABLE_WARNINGS
|
||||
#include "json2msgpack/boostjson.h"
|
||||
|
||||
#include "partial_tweets/simdjson_ondemand.h"
|
||||
#include "partial_tweets/simdjson_ondemand_key_selector.h"
|
||||
#include "partial_tweets/simdjson_dom.h"
|
||||
#include "partial_tweets/yyjson.h"
|
||||
#if SIMDJSON_COMPETITION_ONDEMAND_SAJSON
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS && SIMDJSON_SUPPORTS_CONCEPTS
|
||||
|
||||
#include "partial_tweets.h"
|
||||
|
||||
namespace partial_tweets {
|
||||
|
||||
using namespace simdjson;
|
||||
|
||||
struct simdjson_ondemand_key_selector {
|
||||
using StringType = std::string_view;
|
||||
|
||||
ondemand::parser parser{};
|
||||
|
||||
// Compile-time selectors — all PHF tables are static constexpr, so every
|
||||
// call to match_raw fully inlines.
|
||||
using tweet_sel_t = ondemand::key_selector<
|
||||
"created_at", "id", "text", "in_reply_to_status_id",
|
||||
"user", "retweet_count", "favorite_count">;
|
||||
using user_sel_t = ondemand::key_selector<"id", "screen_name">;
|
||||
|
||||
simdjson_inline uint64_t nullable_int(ondemand::value value) {
|
||||
if (value.is_null()) { return 0; }
|
||||
return value;
|
||||
}
|
||||
|
||||
simdjson_inline twitter_user<std::string_view> read_user(ondemand::object user) {
|
||||
twitter_user<std::string_view> out{};
|
||||
user.for_each<user_sel_t>([&](std::size_t i, ondemand::value v) {
|
||||
switch (i) {
|
||||
case 0: out.id = uint64_t(v); break; // "id"
|
||||
case 1: out.screen_name = std::string_view(v); break; // "screen_name"
|
||||
}
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
bool run(simdjson::padded_string &json, std::vector<tweet<std::string_view>> &result) {
|
||||
auto doc = parser.iterate(json);
|
||||
for (ondemand::object tw : doc.find_field("statuses")) {
|
||||
tweet<std::string_view> t{};
|
||||
tw.for_each<tweet_sel_t>([&](std::size_t i, ondemand::value v) {
|
||||
switch (i) {
|
||||
case 0: t.created_at = std::string_view(v); break; // "created_at"
|
||||
case 1: t.id = uint64_t(v); break; // "id"
|
||||
case 2: t.result = std::string_view(v); break; // "text"
|
||||
case 3: t.in_reply_to_status_id = nullable_int(v); break; // "in_reply_to_status_id"
|
||||
case 4: t.user = read_user(v); break; // "user"
|
||||
case 5: t.retweet_count = uint64_t(v); break; // "retweet_count"
|
||||
case 6: t.favorite_count = uint64_t(v); break; // "favorite_count"
|
||||
}
|
||||
});
|
||||
result.emplace_back(std::move(t));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
BENCHMARK_TEMPLATE(partial_tweets, simdjson_ondemand_key_selector)->UseManualTime();
|
||||
|
||||
} // namespace partial_tweets
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS && SIMDJSON_SUPPORTS_CONCEPTS
|
||||
@@ -61,22 +61,29 @@ struct yyjson_base {
|
||||
};
|
||||
|
||||
struct yyjson : yyjson_base {
|
||||
// The document owns the string memory that result's string_views point into,
|
||||
// so it must outlive each run() (the verification diff happens after run()
|
||||
// returns). Free it on the next run() / at destruction, not before the views
|
||||
// are read.
|
||||
yyjson_doc *doc{};
|
||||
~yyjson() { if (doc != nullptr) { yyjson_doc_free(doc); } }
|
||||
bool run(simdjson::padded_string &json, std::vector<tweet<std::string_view>> &result) {
|
||||
yyjson_doc *doc = yyjson_read(json.data(), json.size(), 0);
|
||||
bool b = yyjson_base::run(doc, result);
|
||||
yyjson_doc_free(doc);
|
||||
return b;
|
||||
if (doc != nullptr) { yyjson_doc_free(doc); doc = nullptr; }
|
||||
doc = yyjson_read(json.data(), json.size(), 0);
|
||||
return yyjson_base::run(doc, result);
|
||||
}
|
||||
};
|
||||
BENCHMARK_TEMPLATE(partial_tweets, yyjson)->UseManualTime();
|
||||
|
||||
#if SIMDJSON_COMPETITION_ONDEMAND_INSITU
|
||||
struct yyjson_insitu : yyjson_base {
|
||||
// See the note on yyjson above: the document must outlive result's views.
|
||||
yyjson_doc *doc{};
|
||||
~yyjson_insitu() { if (doc != nullptr) { yyjson_doc_free(doc); } }
|
||||
bool run(simdjson::padded_string &json, std::vector<tweet<std::string_view>> &result) {
|
||||
yyjson_doc *doc = yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0);
|
||||
bool b = yyjson_base::run(doc, result);
|
||||
yyjson_doc_free(doc);
|
||||
return b;
|
||||
if (doc != nullptr) { yyjson_doc_free(doc); doc = nullptr; }
|
||||
doc = yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0);
|
||||
return yyjson_base::run(doc, result);
|
||||
}
|
||||
};
|
||||
BENCHMARK_TEMPLATE(partial_tweets, yyjson_insitu)->UseManualTime();
|
||||
|
||||
+336
-10
@@ -23,7 +23,9 @@ separate document](https://github.com/simdjson/simdjson/blob/master/doc/builder.
|
||||
* [2. Use `tag_invoke` for custom types (C++20)](#2-use-tag_invoke-for-custom-types-c20)
|
||||
* [3. Using static reflection (C++26)](#3-using-static-reflection-c26)
|
||||
+ [Special cases](#special-cases)
|
||||
+ [Renaming and skipping fields with annotations](#renaming-and-skipping-fields-with-annotations)
|
||||
* [The simdjson::from shortcut (experimental, C++20)](#the-simdjsonfrom-shortcut-experimental-c20)
|
||||
* [Order-independent reflective deserialization (experimental)](#order-independent-reflective-deserialization-experimental)
|
||||
- [Minifying JSON strings without parsing](#minifying-json-strings-without-parsing)
|
||||
- [UTF-8 validation (alone)](#utf-8-validation-alone)
|
||||
- [JSON Pointer](#json-pointer)
|
||||
@@ -31,6 +33,7 @@ separate document](https://github.com/simdjson/simdjson/blob/master/doc/builder.
|
||||
* [Using `for_each_at_path_with_wildcard` for JSONPath Queries (On-Demand)](#using-for_each_at_path_with_wildcard-for-jsonpath-queries-on-demand)
|
||||
+ [Example Usage](#example-usage)
|
||||
- [C++20 Ranges Support](#c20-ranges-support)
|
||||
- [Key selectors](#key-selectors)
|
||||
- [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)
|
||||
@@ -42,6 +45,7 @@ separate document](https://github.com/simdjson/simdjson/blob/master/doc/builder.
|
||||
- [Newline-Delimited JSON (ndjson) and JSON lines](#newline-delimited-json-ndjson-and-json-lines)
|
||||
- [Parsing numbers inside strings](#parsing-numbers-inside-strings)
|
||||
- [Dynamic Number Types](#dynamic-number-types)
|
||||
- [Infinity and NaN support](#infinity-and-nan-support)
|
||||
- [Raw strings from keys](#raw-strings-from-keys)
|
||||
- [General direct access to the raw JSON string](#general-direct-access-to-the-raw-json-string)
|
||||
* [Raw JSON string for objects and arrays](#raw-json-string-for-objects-and-arrays)
|
||||
@@ -455,7 +459,7 @@ We also have a generic ephemeral type (`simdjson::ondemand::value`) which repres
|
||||
array or object, or scalar type (`double`, `uint64_t`, `int64_t`, `bool`, `null`, string) inside
|
||||
an array or an object. Both generic types (`simdjson::ondemand::document` and
|
||||
`simdjson::ondemand::value`) have a `type()` method returning a `json_type` value describing indicating the type (`json_type::array`, `json_type::object`, `json_type::number`, `json_type::string`,
|
||||
`json_type::boolean`, `json_type::null`, and `json_type::unknown` for unrecognized types). The `type()` method does not consume nor validate the value: e.g., you must still call `is_null()` to check that the value is a `null` even if `json_type::null` is returned. Starting with simdjson 4.0, we return `json_type::unknown` for bad tokens such as the `NaN` token in `{"key":NaN}`. A `json_type::unknown` type value indicates an error in the JSON document but you might still be able to proceed, see [General direct access to the raw JSON string](#general-direct-access-to-the-raw-json-string). A generic value (`simdjson::ondemand::value`)
|
||||
`json_type::boolean`, `json_type::null`, and `json_type::unknown` for unrecognized types). The `type()` method does not consume nor validate the value: e.g., you must still call `is_null()` to check that the value is a `null` even if `json_type::null` is returned. Starting with simdjson 4.0, we return `json_type::unknown` for bad tokens (such as the `NaN` token in `{"key":NaN}` when using the default strict parsing behavior). A `json_type::unknown` type value indicates an error in the JSON document but you might still be able to proceed, see [General direct access to the raw JSON string](#general-direct-access-to-the-raw-json-string). A generic value (`simdjson::ondemand::value`)
|
||||
is only valid temporarily, as soon as you access other values, other keys in objects, etc.
|
||||
it becomes invalid: you should therefore consume the value immediately by converting it to a
|
||||
scalar type, an array or an object.
|
||||
@@ -1444,7 +1448,9 @@ that are not made by Toyota.
|
||||
|
||||
|
||||
**Performance tip**: You will get better performance if you order the attributes (make, model)
|
||||
in the order they appear in the JSON document.
|
||||
in the order they appear in the JSON document. Alternatively, [key selectors](#key-selectors)
|
||||
let you extract a fixed, known set of fields in a single pass, independently of the
|
||||
order in which they appear in the document.
|
||||
|
||||
|
||||
### 3. Using static reflection (C++26)
|
||||
@@ -1523,7 +1529,9 @@ void f() {
|
||||
|
||||
|
||||
**Performance tip**: You will get better performance if you order the attributes (make, model)
|
||||
in the order they appear in the JSON document.
|
||||
in the order they appear in the JSON document. Alternatively, [key selectors](#key-selectors)
|
||||
let you extract a fixed, known set of fields in a single pass, independently of the
|
||||
order in which they appear in the document.
|
||||
|
||||
#### Special cases
|
||||
|
||||
@@ -1591,6 +1599,51 @@ You can also automatically serialize the `Car` instance to a JSON string, see
|
||||
our [Builder documentation](builder.md).
|
||||
|
||||
|
||||
#### Renaming and skipping fields with annotations
|
||||
|
||||
**This is experimental: the syntax may change slightly in the future.**
|
||||
|
||||
C++26 annotations provide a convenient way to customize (de)serialization
|
||||
without writing `tag_invoke` functions. You can rename the JSON key that
|
||||
corresponds to a C++ data member, or skip a member entirely.
|
||||
|
||||
The syntax is:
|
||||
|
||||
```cpp
|
||||
// rename cppFieldName (in C++) to json_key_name (in JSON)
|
||||
[[= simdjson::rename<"json_key_name">]] std::string cppFieldName;
|
||||
// do not serialize or deserialize this field
|
||||
[[= simdjson::skip]] int internalState;
|
||||
```
|
||||
|
||||
Full examples:
|
||||
|
||||
```cpp
|
||||
struct RenamedFields {
|
||||
[[= simdjson::rename<"first_name">]] std::string firstName = "";
|
||||
[[= simdjson::rename<"last_name">]] std::string lastName = "";
|
||||
int age = 0;
|
||||
};
|
||||
|
||||
struct SkippedField {
|
||||
std::string name = "";
|
||||
[[= simdjson::skip]] int internalCache = 0;
|
||||
};
|
||||
|
||||
struct MixedAnnotations {
|
||||
[[= simdjson::rename<"user_name">]] std::string userName = "";
|
||||
[[= simdjson::skip]] int sessionToken = 0;
|
||||
int age = 0;
|
||||
};
|
||||
```
|
||||
|
||||
- Serialization via `simdjson::to_json(r)` or `builder << r` will use the renamed
|
||||
keys and omit skipped fields.
|
||||
- Deserialization via `doc.get<RenamedFields>()` will map the JSON keys back
|
||||
to the C++ fields. Skipped fields are never written during deserialization
|
||||
(they keep their default-initialized value), and keys matching skipped fields
|
||||
in the JSON input are ignored.
|
||||
|
||||
|
||||
### The simdjson::from shortcut (experimental, C++20)
|
||||
|
||||
@@ -1631,6 +1684,56 @@ std::map<std::string, std::string> obj =
|
||||
|
||||
The `simdjson::from` construction is EXPERIMENTAL and subject to changes.
|
||||
|
||||
### Order-independent reflective deserialization (experimental)
|
||||
|
||||
> **Experimental and opt-in.** This feature is disabled by default. Enable it by
|
||||
> defining the macro `SIMDJSON_USE_KEY_SELECTOR_REFLECTION=1` before including
|
||||
> simdjson (e.g. as a compiler flag `-DSIMDJSON_USE_KEY_SELECTOR_REFLECTION=1`).
|
||||
> It requires C++26 static reflection (`SIMDJSON_STATIC_REFLECTION`).
|
||||
|
||||
By default, reflective deserialization (`doc.get<T>()` / `simdjson::from`) reads
|
||||
each struct member with an ordered field lookup. This is fastest when the JSON
|
||||
keys appear in the same order as the struct's members, which is the common case.
|
||||
|
||||
When you genuinely cannot rely on the JSON key order — for example when the data
|
||||
comes from a producer that emits members in an arbitrary or varying order — the
|
||||
ordered lookups degrade, because each out-of-order key forces a rescan of the
|
||||
object. For that situation simdjson offers an optional, order-independent
|
||||
strategy: when `SIMDJSON_USE_KEY_SELECTOR_REFLECTION=1` is defined, the
|
||||
reflective deserializer builds a compile-time [key selector](#key-selectors)
|
||||
from the struct's members and walks each object **once** with
|
||||
`object::for_each`, classifying every key through a perfect hash regardless of
|
||||
its position.
|
||||
|
||||
```cpp
|
||||
// Compile this translation unit with -DSIMDJSON_USE_KEY_SELECTOR_REFLECTION=1
|
||||
#include "simdjson.h"
|
||||
using namespace simdjson;
|
||||
|
||||
struct Tweet {
|
||||
uint64_t id;
|
||||
std::string text;
|
||||
uint64_t retweet_count;
|
||||
};
|
||||
|
||||
// Keys here are NOT in declaration order, yet deserialization succeeds.
|
||||
auto json = R"({ "retweet_count": 7, "id": 12345, "text": "hello" })"_padded;
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc = parser.iterate(json);
|
||||
Tweet t;
|
||||
auto error = doc.get(t); // uses the key-selector path under the macro
|
||||
```
|
||||
|
||||
**This is not a universal speedup — run your own benchmarks before adopting it.**
|
||||
In our measurements the key-selector path is roughly on par with the default on
|
||||
small structs (e.g. the Twitter user/tweet objects) but **slower** on documents
|
||||
dominated by many small nested objects (e.g. the CITM catalog), because walking
|
||||
every field of every object and hashing each key costs more than ordered,
|
||||
short-circuiting lookups when the keys *are* in order. Only consider enabling the
|
||||
macro when (a) your inputs really do present keys out of order, and (b) your own
|
||||
benchmarks on your own data show a win. Otherwise, leave it off.
|
||||
|
||||
|
||||
Minifying JSON strings without parsing
|
||||
----------------------
|
||||
|
||||
@@ -1963,6 +2066,168 @@ for (auto field_result : ondemand::get_key_value_range(obj)) {
|
||||
The range wrappers are zero-cost: they forward directly to the underlying
|
||||
On-Demand iterators with no value buffering or extra per-element overhead.
|
||||
|
||||
## Key selectors
|
||||
|
||||
> **Experimental.** Key selectors are an experimental feature: the API may change
|
||||
> in a future release.
|
||||
|
||||
When you need to extract a known, fixed set of fields from a JSON object and you
|
||||
do not care about the order in which they appear in the document, *key selectors*
|
||||
let you do it in a single pass: each selected key is mapped to a small integer
|
||||
index, and you dispatch on that index (typically with a `switch`) instead of
|
||||
repeatedly looking up keys by string.
|
||||
|
||||
**Requirements.** Key selectors rely on C++20 features (concepts and class-type
|
||||
non-type template parameters), so they are only available when simdjson is
|
||||
compiled in C++20 mode or later. When that support is present, the macro
|
||||
`SIMDJSON_SUPPORTS_CONCEPTS` is defined.
|
||||
|
||||
Key selection works with hashing.
|
||||
A *hash function* maps keys (here, JSON field names such as `"id"` or `"name"`)
|
||||
to small integers. A *perfect* hash function is one that, for a fixed and known
|
||||
set of keys, maps each key to a distinct slot with no collisions, so a single
|
||||
hash computation plus one comparison suffices to identify a key, there is no
|
||||
probing and no collision chains.
|
||||
|
||||
Because the set of keys is known at compile time, the simdjson library builds the perfect
|
||||
hash function during compilation (using `consteval`). All of its lookup tables
|
||||
become `static constexpr` data, which the compiler treats as constants at every
|
||||
call site and can fully inline. At run time, recognizing a field name reduces to:
|
||||
scan its length, compute a hash from a couple of bytes, and perform one
|
||||
length-and-bytes comparison, branch-light and SIMD-accelerated.
|
||||
|
||||
You declare a selector type from a list of string literals:
|
||||
|
||||
```cpp
|
||||
using sel = simdjson::ondemand::key_selector<"id", "name", "email">;
|
||||
```
|
||||
|
||||
`sel` is a stateless type. Each key is assigned a fixed index, in declaration
|
||||
order: `"id"` is 0, `"name"` is 1, `"email"` is 2. The type exposes a couple of
|
||||
compile-time helpers:
|
||||
|
||||
- `sel::size()` — the number of keys in the selector (here, 3);
|
||||
- `sel::key_at(i)` — the key text at index `i`.
|
||||
|
||||
You then walk an object with `object::for_each<sel>(callback)`. The callback is
|
||||
invoked once for each field whose key belongs to the selector, **in JSON order**,
|
||||
with two arguments: the selector index of the matched key, and the field's value
|
||||
(an `ondemand::value` that you must consume inside the callback, before the next
|
||||
field is visited, as usual with On Demand). Fields whose keys are not in the
|
||||
selector are skipped without being parsed; duplicate keys are ignored after the
|
||||
first match; and iteration stops as soon as every selector key has matched or the
|
||||
object ends. The `for_each` call returns a `simdjson::error_code` (`SUCCESS`, or the first
|
||||
error encountered while walking the object). Your callback may itself return a
|
||||
`simdjson::error_code`: when it does, the walk stops at the first non-`SUCCESS`
|
||||
result and `for_each` returns it. That is the recommended way to report a
|
||||
value-parsing error (e.g. a type mismatch) from inside the callback.
|
||||
|
||||
Because the selector index is a small integer, a `switch` is the natural way to
|
||||
dispatch on the matched field.
|
||||
|
||||
|
||||
Key selectors are subject to a few compile-time restrictions:
|
||||
|
||||
- Key length. We currently limit keys to at most 31 characters long.
|
||||
A longer key produces a compile-time error. This limitations could be
|
||||
eased in the future but we expect longer keys to be unusual.
|
||||
- Number of keys. The hard limit is 255 keys, but the compile-time perfect-hash
|
||||
construction may fail (again, a compile-time error) for large or awkward key sets,
|
||||
and compilation time grows with the number of keys. For compilation speed,
|
||||
you may use precompiled headers if you have dozens of keys.
|
||||
- Key contents. Keys must be distinct, non-empty, and must not contain a
|
||||
backslash, a double quote, or a null byte. Matching is performed against the
|
||||
raw, unescaped JSON key bytes, so a selector key has to equal the key exactly
|
||||
as it appears in the document (no JSON escape processing is applied).
|
||||
|
||||
This example uses exceptions (see [Disabling exceptions](#disabling-exceptions)
|
||||
for the error-code style). The `"age"` field is not selected, so it is skipped.
|
||||
|
||||
```cpp
|
||||
using namespace simdjson;
|
||||
auto json = R"({ "name": "Daniel", "age": 42, "city": "Montreal" })"_padded;
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc = parser.iterate(json);
|
||||
ondemand::object obj = doc.get_object();
|
||||
|
||||
using fields = ondemand::key_selector<"name", "city">;
|
||||
|
||||
std::string_view name, city;
|
||||
obj.for_each<fields>([&](std::size_t i, ondemand::value v) {
|
||||
switch (i) {
|
||||
case 0: name = std::string_view(v); break; // "name"
|
||||
case 1: city = std::string_view(v); break; // "city"
|
||||
}
|
||||
});
|
||||
// name == "Daniel", city == "Montreal"
|
||||
```
|
||||
|
||||
Key selectors work on any object and we do not have
|
||||
to include all keys. Here the `"verified"` field is skipped.
|
||||
|
||||
```cpp
|
||||
using namespace simdjson;
|
||||
auto json = R"({ "user": { "id": 1186275104, "screen_name": "ayuu0123", "verified": false } })"_padded;
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc = parser.iterate(json);
|
||||
ondemand::object user = doc["user"].get_object();
|
||||
|
||||
using user_fields = ondemand::key_selector<"id", "screen_name">;
|
||||
|
||||
uint64_t id = 0;
|
||||
std::string_view handle;
|
||||
user.for_each<user_fields>([&](std::size_t i, ondemand::value v) {
|
||||
switch (i) {
|
||||
case 0: id = uint64_t(v); break; // "id"
|
||||
case 1: handle = std::string_view(v); break; // "screen_name"
|
||||
}
|
||||
});
|
||||
// id == 1186275104, handle == "ayuu0123"
|
||||
```
|
||||
|
||||
|
||||
A selected value can be any JSON value, including a nested object. Because the
|
||||
value is consumed inside the callback, you can simply turn it into an
|
||||
`ondemand::object` and call `for_each` again with another selector. As always
|
||||
with On Demand, the inner object must be fully consumed before the outer
|
||||
iteration continues, which the nested `for_each` does for you.
|
||||
|
||||
```cpp
|
||||
using namespace simdjson;
|
||||
auto json = R"({
|
||||
"id": 42,
|
||||
"author": { "name": "Daniel", "handle": "lemire" },
|
||||
"title": "On Demand"
|
||||
})"_padded;
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc = parser.iterate(json);
|
||||
ondemand::object obj = doc.get_object();
|
||||
|
||||
using post_fields = ondemand::key_selector<"id", "author", "title">;
|
||||
using author_fields = ondemand::key_selector<"name", "handle">;
|
||||
|
||||
uint64_t id = 0;
|
||||
std::string_view title, author_name, author_handle;
|
||||
obj.for_each<post_fields>([&](std::size_t i, ondemand::value v) {
|
||||
switch (i) {
|
||||
case 0: id = uint64_t(v); break; // "id"
|
||||
case 1: { // "author" is itself an object
|
||||
ondemand::object author = v.get_object();
|
||||
author.for_each<author_fields>([&](std::size_t j, ondemand::value av) {
|
||||
switch (j) {
|
||||
case 0: author_name = std::string_view(av); break; // "name"
|
||||
case 1: author_handle = std::string_view(av); break; // "handle"
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 2: title = std::string_view(v); break; // "title"
|
||||
}
|
||||
});
|
||||
// id == 42, title == "On Demand", author_name == "Daniel", author_handle == "lemire"
|
||||
```
|
||||
|
||||
|
||||
## 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
|
||||
@@ -2647,7 +2912,7 @@ Market: btce Price: 432.89 Volume: 8561.06
|
||||
*/
|
||||
```
|
||||
|
||||
Finally, here is an example dealing with errors where the user wants to convert the string `"Infinity"`(`"change"` key) to a float with infinity value.
|
||||
Finally, here is an example dealing with errors where the user wants to convert the string `"Infinity"`(`"change"` key) to a float with infinity value when using the default strict parsing behavior:
|
||||
|
||||
```cpp
|
||||
ondemand::parser parser;
|
||||
@@ -2662,7 +2927,7 @@ if (error) {
|
||||
error = value.get_string().get(view);
|
||||
if (error) { /* Handle error */ }
|
||||
else if (view == "Infinity") {
|
||||
d = std::numeric_limits::infinity();
|
||||
d = std::numeric_limits<double>::infinity();
|
||||
}
|
||||
else { /* Handle wrong value */ }
|
||||
}
|
||||
@@ -2670,6 +2935,15 @@ if (error) {
|
||||
It is also important to note that when dealing an invalid number inside a string, simdjson will report a `NUMBER_ERROR` error if the string begins with a number whereas simdjson
|
||||
will report an `INCORRECT_TYPE` error otherwise.
|
||||
|
||||
When `SIMDJSON_ENABLE_NAN_INF` is enabled, simdjson can parse `"Infinity"`, `"-Infinity"`, and `"NaN"` from a string using `get_double_in_string` without needing extra error handling:
|
||||
|
||||
```cpp
|
||||
ondemand::parser parser;
|
||||
auto doc = parser.iterate(json);
|
||||
// Get "change"/"Infinity" key/value pair as a double.
|
||||
double d = doc["ticker"]["change"].get_double_in_string();
|
||||
```
|
||||
|
||||
The `*_in_string` methods can also be called on a single document instance:
|
||||
e.g., when your document consist solely of a quoted number.
|
||||
|
||||
@@ -2809,6 +3083,53 @@ This code prints the following:
|
||||
'99999999999999999999999 '
|
||||
```
|
||||
|
||||
|
||||
Infinity and NaN support
|
||||
------------------------------
|
||||
|
||||
The JSON specification does not support `Infinity` and `NaN` literals. However, some engineers use literal `Infinity` and `NaN` tokens when serializing floating-point values to JSON.
|
||||
|
||||
The simdjson library achieves maximum JSON parsing performance by adhering to a strict interpretation of the JSON specification. Therefore strict parsing is enabled by default - `Infinity` and `NaN` literals are not parsed as valid JSON.
|
||||
|
||||
Users can opt-in to parsing `Infinity`, `-Infinity`, and `NaN` as `double` values by setting the `SIMDJSON_ENABLE_NAN_INF` flag to `ON` when building simdjson: `cmake -B build -D SIMDJSON_ENABLE_NAN_INF=ON` and setting `SIMDJSON_ENABLE_NAN_INF` to 1 before including `"simdjson.h"`. When enabled, `Infinity`, `-Infinity`, `Inf`, `-Inf`, and `NaN` literals are case-insensitively matched and parsed as `double`:
|
||||
|
||||
```cpp
|
||||
// The SIMDJSON_ENABLE_NAN_INF flag also needs to be set before including simdjson.h
|
||||
#define SIMDJSON_ENABLE_NAN_INF 1
|
||||
#include "simdjson.h"
|
||||
using namespace simdjson;
|
||||
|
||||
// ...
|
||||
|
||||
ondemand::parser parser;
|
||||
auto inf_nan_literals = "[Infinity, -Infinity, NaN, inf, -inf, nan]"_padded;
|
||||
ondemand::document doc = parser.iterate(inf_nan_literals);
|
||||
|
||||
// Parse and iterate through the array of literal inf/nan values.
|
||||
for (ondemand::value val: doc.get_array()) {
|
||||
ondemand::number num = val.get_number();
|
||||
ondemand::number_type t = num.get_number_type();
|
||||
|
||||
if (t == ondemand::number_type::floating_point_number) {
|
||||
std::cout << "Parsed floating-point number: " << num.get_double() << std::endl;
|
||||
} else {
|
||||
std::cout << "Failed to parse." << std::endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
produces the following output:
|
||||
|
||||
```
|
||||
Parsed floating-point number: inf
|
||||
Parsed floating-point number: -inf
|
||||
Parsed floating-point number: nan
|
||||
Parsed floating-point number: inf
|
||||
Parsed floating-point number: -inf
|
||||
Parsed floating-point number: nan
|
||||
```
|
||||
|
||||
|
||||
Raw strings from keys
|
||||
-----------
|
||||
|
||||
@@ -2907,7 +3228,7 @@ std::string_view noquote(std::string_view v) { return {v.data()+1, v.find_last_o
|
||||
|
||||
|
||||
The `raw_json_token()` method can enable you to provide fallbacks when parsing fails.
|
||||
Consider the following example.
|
||||
Consider the following example under the default strict parsing behavior (`NaN` parsing is disabled):
|
||||
|
||||
```cpp
|
||||
padded_string json = "{\"key\": NaN}"_padded;
|
||||
@@ -2929,6 +3250,9 @@ Consider the following example.
|
||||
The NaN is not supported in JSON. However, in the On-Demand API, you can check
|
||||
the string corresponding to the JSON token and determine how to handle it.
|
||||
|
||||
In this particular case, `SIMDJSON_ENABLE_NAN_INF` can be enabled to parse `Infinity` and `NaN` tokens.
|
||||
However other tokens will still need to be handled via the `raw_json_token()` method.
|
||||
|
||||
### Raw JSON string for objects and arrays
|
||||
|
||||
If your value is an array or an object, `raw_json_token()` returns effectively a single
|
||||
@@ -2952,8 +3276,10 @@ simdjson::ondemand::array arr = doc.get_array();
|
||||
string_view token = arr.raw_json(); // gives you `[1,2,3]`
|
||||
```
|
||||
|
||||
Because `raw_json()` consumes to object or the array, if you want to both have
|
||||
access to the raw string, and also use the array or object, you should call `reset()`.
|
||||
Because `raw_json()` consumes the object or the array, if you want both the raw
|
||||
substring and later field access on the same instance, call `reset()` on that
|
||||
object or array (as below). To re-parse the whole document from the start,
|
||||
use `document::rewind()` instead (see [Rewinding](#rewinding)).
|
||||
|
||||
```cpp
|
||||
simdjson::ondemand::parser parser;
|
||||
@@ -2961,7 +3287,7 @@ simdjson::padded_string docdata = R"({"value":123})"_padded;
|
||||
simdjson::ondemand::document doc = parser.iterate(docdata);
|
||||
simdjson::ondemand::object obj = doc.get_object();
|
||||
string_view token = obj.raw_json(); // gives you `{"value":123}`
|
||||
obj.reset(); // revise the object
|
||||
obj.reset(); // re-open the same object for further iteration
|
||||
uint64_t x = obj["value"]; // gives me 123
|
||||
```
|
||||
|
||||
@@ -3502,7 +3828,7 @@ Performance tips
|
||||
std::string_view year = data["year"];
|
||||
std::string_view rating = data["rating"];
|
||||
```
|
||||
- You will get better performance if you seek the keys in the order in which they appear in the document. So if processing `{"a":1, "b":2, "c":3}`, do `value1 = data["a"]; value2 = data["b"]; value3 data["c"];` and not `value2 = data["b"]; value1 = data["a"]; value3 data["c"];`. Of course, it is not always possible to know for sure in which order the keys appear.
|
||||
- You will get better performance if you seek the keys in the order in which they appear in the document. So if processing `{"a":1, "b":2, "c":3}`, do `value1 = data["a"]; value2 = data["b"]; value3 data["c"];` and not `value2 = data["b"]; value1 = data["a"]; value3 data["c"];`. Of course, it is not always possible to know for sure in which order the keys appear. See also [key selectors](#key-selectors), which extract a fixed, known set of fields in a single pass regardless of their order in the document.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ speed and high convenience.
|
||||
* [Overview: string_builder](#overview--string-builder)
|
||||
* [Example: string_builder](#example--string-builder)
|
||||
* [C++26 static reflection](#c--26-static-reflection)
|
||||
+ [Renaming and skipping fields with annotations](#renaming-and-skipping-fields-with-annotations)
|
||||
+ [Without `string_buffer` instance](#without--string-buffer--instance)
|
||||
+ [Without `string_buffer` instance but with explicit error handling](#without--string-buffer--instance-but-with-explicit-error-handling)
|
||||
+ [Pretty formatted (fractured JSON)](#pretty-formatted-fractured-json)
|
||||
@@ -260,6 +261,48 @@ automatically. In most cases, it should work automatically:
|
||||
```
|
||||
|
||||
|
||||
#### Renaming and skipping fields with annotations
|
||||
|
||||
|
||||
**This is experimental: the syntax may change slightly in the future.**
|
||||
|
||||
When using C++26 static reflection for automatic serialization (and deserialization),
|
||||
you can annotate your struct members to rename the corresponding JSON keys or to
|
||||
exclude fields from the JSON representation.
|
||||
|
||||
|
||||
The syntax is:
|
||||
|
||||
```cpp
|
||||
// rename cppFieldName (in C++) to json_key_name (in JSON)
|
||||
[[= simdjson::rename<"json_key_name">]] std::string cppFieldName;
|
||||
// do not serialize or deserialize this field
|
||||
[[= simdjson::skip]] int internalState;
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```cpp
|
||||
struct Person {
|
||||
[[= simdjson::rename<"first_name">]] std::string firstName = "";
|
||||
[[= simdjson::rename<"last_name">]] std::string lastName = "";
|
||||
[[= simdjson::skip]] int internalCache = 0;
|
||||
int age = 0;
|
||||
};
|
||||
```
|
||||
|
||||
Serialization then produces:
|
||||
|
||||
```cpp
|
||||
Person p{"Alice", "Smith", 999, 30};
|
||||
std::string json = simdjson::to_json(p);
|
||||
// json == R"({"first_name":"Alice","last_name":"Smith","age":30})"
|
||||
// Note: internalCache is omitted entirely.
|
||||
```
|
||||
|
||||
The `skip` annotation also affects deserialization: the field keeps its default value
|
||||
and any corresponding key in the input JSON is ignored.
|
||||
|
||||
### Without `string_buffer` instance
|
||||
|
||||
In some instances, you might want to create a string directly from your own data type.
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
#ifndef SIMDJSON_ANNOTATIONS_H
|
||||
#define SIMDJSON_ANNOTATIONS_H
|
||||
|
||||
/**
|
||||
* @file annotations.h
|
||||
* @brief Provides compile-time annotations for simdjson structures.
|
||||
* This header defines annotations that can be applied to data members of structures
|
||||
* to control how they are serialized/deserialized with simdjson.
|
||||
* This is currently experimental and subject to change (syntax and semantics may evolve).
|
||||
*/
|
||||
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
|
||||
#include <meta>
|
||||
#include <string_view>
|
||||
|
||||
namespace simdjson {
|
||||
|
||||
// Structural compile-time string — char array avoids the pointer-based
|
||||
// 'reflect_constant failed' that occurs with const char* / string_view members.
|
||||
template <size_t N>
|
||||
struct fixed_string {
|
||||
char data[N];
|
||||
|
||||
consteval fixed_string(const char (&s)[N]) noexcept {
|
||||
for (size_t i = 0; i < N; ++i) { data[i] = s[i]; }
|
||||
}
|
||||
|
||||
consteval std::string_view view() const noexcept { return {data, N - 1}; }
|
||||
|
||||
consteval bool operator==(const fixed_string&) const noexcept = default;
|
||||
};
|
||||
|
||||
// Usage: [[= simdjson::rename<"first_name">]] std::string firstName;
|
||||
namespace detail {
|
||||
template <fixed_string Name>
|
||||
struct rename_t {
|
||||
static constexpr auto name = Name;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
template <fixed_string Name>
|
||||
inline constexpr detail::rename_t<Name> rename{};
|
||||
|
||||
// Usage: [[= simdjson::skip]] int internalCache;
|
||||
namespace detail {
|
||||
struct skip_tag {};
|
||||
} // namespace detail
|
||||
|
||||
inline constexpr detail::skip_tag skip{};
|
||||
|
||||
// Returns the JSON key for a reflected data member.
|
||||
template <auto dm>
|
||||
consteval const char* get_json_key_name() {
|
||||
template for (constexpr auto ann :
|
||||
std::define_static_array(std::meta::annotations_of(dm))) {
|
||||
constexpr auto ann_type = std::meta::type_of(ann);
|
||||
if constexpr (std::meta::has_template_arguments(ann_type) &&
|
||||
std::meta::template_of(ann_type) == ^^detail::rename_t) {
|
||||
constexpr auto args =
|
||||
std::define_static_array(std::meta::template_arguments_of(ann_type));
|
||||
return std::define_static_string([:args[0]:].view());
|
||||
}
|
||||
}
|
||||
return std::define_static_string(std::meta::identifier_of(dm));
|
||||
}
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_STATIC_REFLECTION
|
||||
#endif // SIMDJSON_ANNOTATIONS_H
|
||||
@@ -103,21 +103,9 @@ inline simdjson_result<element> array::at_pointer(std::string_view json_pointer)
|
||||
// We don't support this, because we're returning a real element, not a position.
|
||||
if (json_pointer == "-") { return INDEX_OUT_OF_BOUNDS; }
|
||||
|
||||
// Read the array index
|
||||
size_t array_index = 0;
|
||||
size_t i;
|
||||
for (i = 0; i < json_pointer.length() && json_pointer[i] != '/'; i++) {
|
||||
uint8_t digit = uint8_t(json_pointer[i] - '0');
|
||||
// Check for non-digit in array index. If it's there, we're trying to get a field in an object
|
||||
if (digit > 9) { return INCORRECT_TYPE; }
|
||||
array_index = array_index*10 + digit;
|
||||
}
|
||||
|
||||
// 0 followed by other digits is invalid
|
||||
if (i > 1 && json_pointer[0] == '0') { return INVALID_JSON_POINTER; } // "JSON pointer array index has other characters after 0"
|
||||
|
||||
// Empty string is invalid; so is a "/" with no digits before it
|
||||
if (i == 0) { return INVALID_JSON_POINTER; } // "Empty string in JSON pointer array index"
|
||||
SIMDJSON_TRY(internal::parse_json_pointer_array_index(json_pointer, array_index, i));
|
||||
|
||||
// Get the child
|
||||
auto child = array(tape).at(array_index);
|
||||
|
||||
@@ -72,7 +72,7 @@ inline element_metrics structure_analyzer::analyze_scalar(const dom::element& el
|
||||
metrics.complexity = 0;
|
||||
metrics.child_count = 0;
|
||||
metrics.can_inline = true;
|
||||
metrics.recommended_layout = layout_mode::INLINE;
|
||||
metrics.recommended_layout = layout_mode::single_line;
|
||||
|
||||
switch (elem.type()) {
|
||||
case dom::element_type::STRING: {
|
||||
@@ -158,16 +158,16 @@ inline element_metrics structure_analyzer::analyze_array(const dom::array& arr,
|
||||
|
||||
// Decide layout
|
||||
if (metrics.child_count == 0) {
|
||||
metrics.recommended_layout = layout_mode::INLINE;
|
||||
metrics.recommended_layout = layout_mode::single_line;
|
||||
} else if (metrics.can_inline) {
|
||||
metrics.recommended_layout = layout_mode::INLINE;
|
||||
metrics.recommended_layout = layout_mode::single_line;
|
||||
} else if (metrics.is_uniform_array && !metrics.common_keys.empty()) {
|
||||
metrics.recommended_layout = layout_mode::TABLE;
|
||||
metrics.recommended_layout = layout_mode::table;
|
||||
} else if (current_opts_->enable_compact_multiline &&
|
||||
max_child_complexity <= current_opts_->max_compact_array_complexity) {
|
||||
metrics.recommended_layout = layout_mode::COMPACT_MULTILINE;
|
||||
metrics.recommended_layout = layout_mode::compact_multiline;
|
||||
} else {
|
||||
metrics.recommended_layout = layout_mode::EXPANDED;
|
||||
metrics.recommended_layout = layout_mode::expanded;
|
||||
}
|
||||
|
||||
return metrics;
|
||||
@@ -206,9 +206,9 @@ inline element_metrics structure_analyzer::analyze_object(const dom::object& obj
|
||||
|
||||
// Objects use inline or expanded (no table/compact for objects)
|
||||
if (metrics.child_count == 0 || metrics.can_inline) {
|
||||
metrics.recommended_layout = layout_mode::INLINE;
|
||||
metrics.recommended_layout = layout_mode::single_line;
|
||||
} else {
|
||||
metrics.recommended_layout = layout_mode::EXPANDED;
|
||||
metrics.recommended_layout = layout_mode::expanded;
|
||||
}
|
||||
|
||||
return metrics;
|
||||
@@ -227,8 +227,18 @@ inline size_t structure_analyzer::estimate_string_length(std::string_view s) con
|
||||
}
|
||||
|
||||
inline size_t structure_analyzer::estimate_number_length(double d) const {
|
||||
if (std::isnan(d) || std::isinf(d)) {
|
||||
if (!std::isfinite(d)) {
|
||||
#if SIMDJSON_ENABLE_NAN_INF
|
||||
if (std::isnan(d)) {
|
||||
return 3; // "NaN"
|
||||
} else if (d < 0) {
|
||||
return 9; // "-Infinity"
|
||||
} else {
|
||||
return 8; // "Infinity"
|
||||
}
|
||||
#else
|
||||
return 4; // "null" for invalid numbers
|
||||
#endif
|
||||
}
|
||||
// Rough estimate: up to 17 significant digits + sign + decimal point + exponent
|
||||
char buf[32];
|
||||
@@ -346,28 +356,28 @@ inline layout_mode structure_analyzer::decide_layout(const element_metrics& metr
|
||||
size_t depth,
|
||||
size_t available_width) const {
|
||||
if (metrics.child_count == 0) {
|
||||
return layout_mode::INLINE;
|
||||
return layout_mode::single_line;
|
||||
}
|
||||
|
||||
// Check inline feasibility
|
||||
size_t indent_width = depth * current_opts_->indent_spaces;
|
||||
if (metrics.can_inline &&
|
||||
metrics.estimated_inline_len + indent_width <= available_width) {
|
||||
return layout_mode::INLINE;
|
||||
return layout_mode::single_line;
|
||||
}
|
||||
|
||||
// Check table mode
|
||||
if (metrics.is_uniform_array && !metrics.common_keys.empty()) {
|
||||
return layout_mode::TABLE;
|
||||
return layout_mode::table;
|
||||
}
|
||||
|
||||
// Check compact multiline
|
||||
if (current_opts_->enable_compact_multiline &&
|
||||
metrics.complexity <= current_opts_->max_compact_array_complexity + 1) {
|
||||
return layout_mode::COMPACT_MULTILINE;
|
||||
return layout_mode::compact_multiline;
|
||||
}
|
||||
|
||||
return layout_mode::EXPANDED;
|
||||
return layout_mode::expanded;
|
||||
}
|
||||
|
||||
//
|
||||
@@ -378,7 +388,7 @@ inline fractured_formatter::fractured_formatter(const fractured_json_options& op
|
||||
: options_(opts), column_widths_{} {}
|
||||
|
||||
simdjson_inline void fractured_formatter::print_newline() {
|
||||
if (current_layout_ == layout_mode::INLINE) {
|
||||
if (current_layout_ == layout_mode::single_line) {
|
||||
return; // No newlines in inline mode
|
||||
}
|
||||
one_char('\n');
|
||||
@@ -386,7 +396,7 @@ simdjson_inline void fractured_formatter::print_newline() {
|
||||
}
|
||||
|
||||
simdjson_inline void fractured_formatter::print_indents(size_t depth) {
|
||||
if (current_layout_ == layout_mode::INLINE) {
|
||||
if (current_layout_ == layout_mode::single_line) {
|
||||
return; // No indentation in inline mode
|
||||
}
|
||||
for (size_t i = 0; i < depth * options_.indent_spaces; i++) {
|
||||
@@ -533,16 +543,16 @@ inline void fractured_string_builder::format_array(const dom::array& arr,
|
||||
const element_metrics& metrics,
|
||||
size_t depth) {
|
||||
switch (metrics.recommended_layout) {
|
||||
case layout_mode::INLINE:
|
||||
case layout_mode::single_line:
|
||||
format_array_inline(arr, metrics);
|
||||
break;
|
||||
case layout_mode::COMPACT_MULTILINE:
|
||||
case layout_mode::compact_multiline:
|
||||
format_array_compact_multiline(arr, metrics, depth);
|
||||
break;
|
||||
case layout_mode::TABLE:
|
||||
case layout_mode::table:
|
||||
format_array_as_table(arr, metrics, depth);
|
||||
break;
|
||||
case layout_mode::EXPANDED:
|
||||
case layout_mode::expanded:
|
||||
default:
|
||||
format_array_expanded(arr, metrics, depth);
|
||||
break;
|
||||
@@ -552,7 +562,7 @@ inline void fractured_string_builder::format_array(const dom::array& arr,
|
||||
inline void fractured_string_builder::format_array_inline(const dom::array& arr,
|
||||
const element_metrics& metrics) {
|
||||
layout_mode prev_layout = format_.get_layout_mode();
|
||||
format_.set_layout_mode(layout_mode::INLINE);
|
||||
format_.set_layout_mode(layout_mode::single_line);
|
||||
|
||||
format_.start_array();
|
||||
|
||||
@@ -613,7 +623,7 @@ inline void fractured_string_builder::format_array_compact_multiline(const dom::
|
||||
|
||||
// Format element inline
|
||||
layout_mode prev_layout = format_.get_layout_mode();
|
||||
format_.set_layout_mode(layout_mode::INLINE);
|
||||
format_.set_layout_mode(layout_mode::single_line);
|
||||
const element_metrics& child_metrics = (child_idx < metrics.children.size())
|
||||
? metrics.children[child_idx] : element_metrics{};
|
||||
format_element(elem, child_metrics, depth + 1);
|
||||
@@ -709,7 +719,7 @@ inline void fractured_string_builder::format_array_as_table(const dom::array& ar
|
||||
// Write value
|
||||
if (found) {
|
||||
layout_mode prev_layout = format_.get_layout_mode();
|
||||
format_.set_layout_mode(layout_mode::INLINE);
|
||||
format_.set_layout_mode(layout_mode::single_line);
|
||||
const element_metrics& value_metrics = (field_idx < row_metrics.children.size())
|
||||
? row_metrics.children[field_idx] : element_metrics{};
|
||||
format_element(value, value_metrics, depth + 1);
|
||||
@@ -778,7 +788,7 @@ inline void fractured_string_builder::format_array_expanded(const dom::array& ar
|
||||
inline void fractured_string_builder::format_object(const dom::object& obj,
|
||||
const element_metrics& metrics,
|
||||
size_t depth) {
|
||||
if (metrics.recommended_layout == layout_mode::INLINE || metrics.can_inline) {
|
||||
if (metrics.recommended_layout == layout_mode::single_line || metrics.can_inline) {
|
||||
format_object_inline(obj, metrics);
|
||||
} else {
|
||||
format_object_expanded(obj, metrics, depth);
|
||||
@@ -788,7 +798,7 @@ inline void fractured_string_builder::format_object(const dom::object& obj,
|
||||
inline void fractured_string_builder::format_object_inline(const dom::object& obj,
|
||||
const element_metrics& metrics) {
|
||||
layout_mode prev_layout = format_.get_layout_mode();
|
||||
format_.set_layout_mode(layout_mode::INLINE);
|
||||
format_.set_layout_mode(layout_mode::single_line);
|
||||
|
||||
format_.start_object();
|
||||
|
||||
@@ -950,6 +960,14 @@ inline size_t fractured_string_builder::measure_value_length(const dom::element&
|
||||
case dom::element_type::DOUBLE: {
|
||||
double val;
|
||||
if (elem.get_double().get(val) == SUCCESS) {
|
||||
#if SIMDJSON_ENABLE_NAN_INF
|
||||
if (!std::isfinite(val)) {
|
||||
if (std::isnan(val))
|
||||
return 3; // "NaN"
|
||||
// "-Infinity" (9) or "Infinity" (8)
|
||||
return val < 0 ? 9 : 8;
|
||||
}
|
||||
#endif
|
||||
char buf[32];
|
||||
int len = snprintf(buf, sizeof(buf), "%.17g", val);
|
||||
return len > 0 ? static_cast<size_t>(len) : 1;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "simdjson/dom/object-inl.h"
|
||||
#include "simdjson/internal/tape_ref-inl.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
namespace simdjson {
|
||||
@@ -181,6 +182,22 @@ simdjson_inline void base_formatter<formatter>::number(int64_t x) {
|
||||
|
||||
template <class formatter>
|
||||
simdjson_inline void base_formatter<formatter>::number(double x) {
|
||||
#if SIMDJSON_ENABLE_NAN_INF
|
||||
if (simdjson_unlikely(!std::isfinite(x))) {
|
||||
if (std::isnan(x)) {
|
||||
char const *s = "NaN";
|
||||
chars(s, s + 3);
|
||||
} else {
|
||||
if (x < 0) {
|
||||
one_char('-');
|
||||
}
|
||||
char const *s = "Infinity";
|
||||
chars(s, s + 8);
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
char number_buffer[24];
|
||||
// Currently, passing the nullptr to the second argument is
|
||||
// safe because our implementation does not check the second
|
||||
|
||||
@@ -147,7 +147,9 @@ simdjson_inline bool is_valid_inf_in_string(const uint8_t *src) {
|
||||
simdjson_warn_unused
|
||||
simdjson_inline bool is_valid_inf_atom(const uint8_t *src, size_t len) {
|
||||
if (len > 8) { return is_valid_inf_atom(src); }
|
||||
if (len == 8) { return str8ncmp_case_insensitive(src, "infinity") == 0; }
|
||||
if (len == 8 && str8ncmp_case_insensitive(src, "infinity") == 0) {
|
||||
return true;
|
||||
}
|
||||
if (len > 3) {
|
||||
return (str3ncmp_case_insensitive(src, "inf")
|
||||
| jsoncharutils::is_not_structural_or_whitespace(src[3])) == 0;
|
||||
|
||||
@@ -10,5 +10,6 @@
|
||||
// Otherwise, amalgamation will fail.
|
||||
#include "simdjson/concepts.h"
|
||||
#include "simdjson/dom/fractured_json.h"
|
||||
#include "simdjson/annotations.h"
|
||||
|
||||
#endif // SIMDJSON_GENERIC_BUILDER_DEPENDENCIES_H
|
||||
@@ -4,6 +4,7 @@
|
||||
#define SIMDJSON_GENERIC_STRING_BUILDER_H
|
||||
#include "simdjson/generic/builder/json_string_builder.h"
|
||||
#include "simdjson/concepts.h"
|
||||
#include "simdjson/annotations.h"
|
||||
#endif // SIMDJSON_CONDITIONAL_INCLUDE
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
|
||||
@@ -21,23 +22,113 @@ namespace simdjson {
|
||||
namespace SIMDJSON_IMPLEMENTATION {
|
||||
namespace builder {
|
||||
|
||||
// Forward-declare helpers defined in json_string_builder-inl.h so the
|
||||
// writer-based atom code below can call them (the -inl.h is not yet
|
||||
// included at the point this header is parsed; without these forwards,
|
||||
// name lookup falls back to the wrong outer namespace).
|
||||
namespace internal {
|
||||
simdjson_really_inline char *write_uint_jeaiii(char *p, uint64_t v) noexcept;
|
||||
} // namespace internal
|
||||
inline size_t write_string_escaped(const std::string_view input, char *out);
|
||||
|
||||
// =============================================================
|
||||
// `writer`: position-as-local hot-path writer used by the reflection
|
||||
// atom code below. Holds the buffer pointer, write position and
|
||||
// capacity in three fields that, once `writer` itself is a stack-local
|
||||
// in the caller and all atom() functions are inlined, become true
|
||||
// register-resident locals after SROA. Glaze achieves the same effect
|
||||
// by passing `B&& b, auto&& ix` through every helper. Holding `pos`
|
||||
// in a register (rather than as a member of string_builder) is what
|
||||
// breaks the strict-aliasing penalty on every char* write through the
|
||||
// buffer, which forces a reload of `b.position` and `b.capacity`
|
||||
// after every byte.
|
||||
// =============================================================
|
||||
struct writer {
|
||||
char *ptr; // buffer pointer (refreshed after a grow)
|
||||
size_t pos; // write position (local)
|
||||
size_t cap; // capacity (refreshed after a grow)
|
||||
string_builder &sb; // back-ref for grow / sync
|
||||
|
||||
// Snapshot string_builder state into a writer for the duration of
|
||||
// a write chain.
|
||||
simdjson_really_inline writer(string_builder &builder) noexcept
|
||||
: ptr(builder.unsafe_data())
|
||||
, pos(builder.unsafe_position())
|
||||
, cap(builder.unsafe_capacity())
|
||||
, sb(builder) {}
|
||||
|
||||
// Write the local position back to the underlying string_builder.
|
||||
// Caller is responsible for invoking before the writer is dropped
|
||||
// (otherwise data is lost). Idempotent.
|
||||
simdjson_really_inline void sync() noexcept {
|
||||
sb.unsafe_set_position(pos);
|
||||
}
|
||||
|
||||
// Ensure at least `n` more bytes of free capacity. Grows the
|
||||
// underlying buffer if needed (rare path). Returns false on
|
||||
// allocation failure.
|
||||
simdjson_really_inline bool ensure(size_t n) noexcept {
|
||||
// Use subtraction (relying on the pos <= cap invariant) so a huge n
|
||||
// cannot wrap pos + n to a small value that spuriously passes the test.
|
||||
// This is pedantic except maybe on 32-bit targets.
|
||||
if (simdjson_likely(n <= cap - pos)) return true;
|
||||
return grow_slow(n);
|
||||
}
|
||||
|
||||
// Slow path of ensure(). Out-of-line via simdjson_inline (not
|
||||
// simdjson_really_inline) to keep the hot path short.
|
||||
simdjson_inline bool grow_slow(size_t n) noexcept {
|
||||
// Detect overflow.
|
||||
// This is pedantic except maybe on 32-bit targets.
|
||||
if (simdjson_unlikely(pos + n < pos)) return false;
|
||||
sb.unsafe_set_position(pos);
|
||||
// even if 2*capacity overflows, the (std::max) below will pick the needed value,
|
||||
// so we do not need a separate overflow check here.
|
||||
if (!sb.unsafe_grow((std::max)(cap * 2, pos + n))) {
|
||||
return false;
|
||||
}
|
||||
ptr = sb.unsafe_data();
|
||||
cap = sb.unsafe_capacity();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// === Helper: invoke a string_builder member that writes variable-length
|
||||
// content (escape_and_append_with_quotes etc), syncing the writer's local
|
||||
// state before the call and reloading after. Used for string fields where
|
||||
// rewriting the entire SIMD escape path through the writer would be a much
|
||||
// bigger refactor.
|
||||
template <class F>
|
||||
simdjson_really_inline void call_through_string_builder(writer &w, F &&f) noexcept {
|
||||
w.sync();
|
||||
f(w.sb);
|
||||
w.ptr = w.sb.unsafe_data();
|
||||
w.pos = w.sb.unsafe_position();
|
||||
w.cap = w.sb.unsafe_capacity();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
requires(concepts::container_but_not_string<T> && ! concepts::optional_type<T> && !require_custom_serialization<T>)
|
||||
simdjson_really_inline constexpr void atom(string_builder &b, const T &t) {
|
||||
simdjson_really_inline constexpr void atom(writer &w, const T &t) {
|
||||
auto it = t.begin();
|
||||
auto end = t.end();
|
||||
if (it == end) {
|
||||
b.append_raw("[]");
|
||||
if (!w.ensure(2)) return;
|
||||
std::memcpy(w.ptr + w.pos, "[]", 2);
|
||||
w.pos += 2;
|
||||
return;
|
||||
}
|
||||
b.append('[');
|
||||
atom(b, *it);
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = '[';
|
||||
atom(w, *it);
|
||||
++it;
|
||||
for (; it != end; ++it) {
|
||||
b.append(',');
|
||||
atom(b, *it);
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = ',';
|
||||
atom(w, *it);
|
||||
}
|
||||
b.append(']');
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = ']';
|
||||
}
|
||||
|
||||
template <class T>
|
||||
@@ -45,37 +136,100 @@ template <class T>
|
||||
std::is_same_v<T, std::string_view> ||
|
||||
std::is_same_v<T, const char *> ||
|
||||
std::is_same_v<T, char>)
|
||||
simdjson_really_inline constexpr void atom(string_builder &b, const T &t) {
|
||||
b.escape_and_append_with_quotes(t);
|
||||
simdjson_really_inline constexpr void atom(writer &w, const T &t) {
|
||||
// Inline the escape path through the writer so we never round-trip
|
||||
// pos through memory for string fields (Twitter is dominated by
|
||||
// these — sync/reload around each string was a real cost).
|
||||
std::string_view input;
|
||||
if constexpr (std::is_same_v<T, char>) {
|
||||
input = std::string_view(&t, 1);
|
||||
} else {
|
||||
input = std::string_view(t);
|
||||
}
|
||||
// Worst-case escape: every byte expands to \uXXXX (6 chars), plus 2 quotes.
|
||||
// Guard against 2 + 6 * input.size() wrapping for huge inputs — if it
|
||||
// wrapped to a small value, ensure() would spuriously succeed and the
|
||||
// subsequent escape would overflow the buffer.
|
||||
// Note that this is pedantic except maybe on 32-bit targets.
|
||||
if (simdjson_unlikely(input.size() > ((std::numeric_limits<size_t>::max)() - 2) / 6)) { return; }
|
||||
if (!w.ensure(2 + 6 * input.size())) { return; }
|
||||
w.ptr[w.pos++] = '"';
|
||||
w.pos += write_string_escaped(input, w.ptr + w.pos);
|
||||
w.ptr[w.pos++] = '"';
|
||||
}
|
||||
|
||||
template <concepts::string_view_keyed_map T>
|
||||
requires(!require_custom_serialization<T>)
|
||||
simdjson_really_inline constexpr void atom(string_builder &b, const T &m) {
|
||||
simdjson_really_inline constexpr void atom(writer &w, const T &m) {
|
||||
if (m.empty()) {
|
||||
b.append_raw("{}");
|
||||
if (!w.ensure(2)) return;
|
||||
std::memcpy(w.ptr + w.pos, "{}", 2);
|
||||
w.pos += 2;
|
||||
return;
|
||||
}
|
||||
b.append('{');
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = '{';
|
||||
bool first = true;
|
||||
for (const auto& [key, value] : m) {
|
||||
if (!first) {
|
||||
b.append(',');
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = ',';
|
||||
}
|
||||
first = false;
|
||||
// Keys must be convertible to string_view per the concept
|
||||
b.escape_and_append_with_quotes(key);
|
||||
b.append(':');
|
||||
atom(b, value);
|
||||
// Keys must be convertible to string_view per the concept.
|
||||
std::string_view key_sv(key);
|
||||
// Guard against 3 + 6 * key_sv.size() wrapping for huge keys, if it
|
||||
// wrapped to a small value, ensure() would spuriously succeed and the
|
||||
// subsequent escape would overflow the buffer.
|
||||
// Note that this is pedantic except maybe on 32-bit targets.
|
||||
if (simdjson_unlikely(key_sv.size() > ((std::numeric_limits<size_t>::max)() - 3) / 6)) { return; }
|
||||
if (!w.ensure(2 + 6 * key_sv.size() + 1)) { return; }
|
||||
w.ptr[w.pos++] = '"';
|
||||
w.pos += write_string_escaped(key_sv, w.ptr + w.pos);
|
||||
w.ptr[w.pos++] = '"';
|
||||
w.ptr[w.pos++] = ':';
|
||||
atom(w, value);
|
||||
}
|
||||
b.append('}');
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = '}';
|
||||
}
|
||||
|
||||
|
||||
template<typename number_type,
|
||||
typename = typename std::enable_if<std::is_arithmetic<number_type>::value && !std::is_same_v<number_type, char>>::type>
|
||||
simdjson_really_inline constexpr void atom(string_builder &b, const number_type t) {
|
||||
b.append(t);
|
||||
simdjson_really_inline constexpr void atom(writer &w, const number_type t) {
|
||||
// Booleans / floats: defer to string_builder (rare path; keeps writer hot
|
||||
// path free of float-formatter machinery). For integers, write directly
|
||||
// via jeaiii using local pos.
|
||||
if constexpr (std::is_same_v<number_type, bool>) {
|
||||
if (t) {
|
||||
if (!w.ensure(4)) return;
|
||||
std::memcpy(w.ptr + w.pos, "true", 4);
|
||||
w.pos += 4;
|
||||
} else {
|
||||
if (!w.ensure(5)) return;
|
||||
std::memcpy(w.ptr + w.pos, "false", 5);
|
||||
w.pos += 5;
|
||||
}
|
||||
} else if constexpr (std::is_floating_point_v<number_type>) {
|
||||
call_through_string_builder(w, [&](string_builder &b) { b.append(t); });
|
||||
} else if constexpr (std::is_unsigned_v<number_type>) {
|
||||
if (!w.ensure(20)) return;
|
||||
char *end = internal::write_uint_jeaiii(
|
||||
w.ptr + w.pos, static_cast<uint64_t>(t));
|
||||
w.pos = static_cast<size_t>(end - w.ptr);
|
||||
} else {
|
||||
// signed integral
|
||||
if (!w.ensure(20)) return;
|
||||
using U = typename std::make_unsigned<number_type>::type;
|
||||
bool negative = t < 0;
|
||||
U pv = negative ? U(0) - static_cast<U>(t) : static_cast<U>(t);
|
||||
w.ptr[w.pos] = '-';
|
||||
w.pos += negative;
|
||||
char *end = internal::write_uint_jeaiii(
|
||||
w.ptr + w.pos, static_cast<uint64_t>(pv));
|
||||
w.pos = static_cast<size_t>(end - w.ptr);
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
@@ -88,70 +242,86 @@ template <class T>
|
||||
!std::is_same_v<T, std::string_view> &&
|
||||
!std::is_same_v<T, const char*> &&
|
||||
!std::is_same_v<T, char> && !require_custom_serialization<T>)
|
||||
simdjson_really_inline constexpr void atom(string_builder &b, const T &t) {
|
||||
// Coalesce the per-field separator+key+colon writes into a single
|
||||
// append_raw, so each field does one capacity_check + one memcpy instead of
|
||||
// three. The leading-comma variant is selected at runtime by the compile-time
|
||||
// peeled `i` counter, which clang folds away after the template-for unroll.
|
||||
int i = 0;
|
||||
b.append('{');
|
||||
simdjson_really_inline constexpr void atom(writer &w, const T &t) {
|
||||
// Per-field block: ensure key+value worst case, then write key + value
|
||||
// through the writer's local pos. For arithmetic fields, the integer
|
||||
// write happens directly via write_uint_jeaiii on w.ptr+w.pos, so pos
|
||||
// never round-trips through memory.
|
||||
bool first = true;
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = '{';
|
||||
template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
|
||||
constexpr auto first_key = std::define_static_string(
|
||||
constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(dm)) + ":");
|
||||
constexpr auto rest_key = std::define_static_string(
|
||||
std::string(",") + constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(dm)) + ":");
|
||||
// Pass size as template parameter so memcpy is fully inlined with
|
||||
// a compile-time-constant size.
|
||||
constexpr size_t first_key_len = std::char_traits<char>::length(first_key);
|
||||
constexpr size_t rest_key_len = std::char_traits<char>::length(rest_key);
|
||||
if (i == 0) b.template append_raw_n<first_key_len>(first_key);
|
||||
else b.template append_raw_n<rest_key_len>(rest_key);
|
||||
atom(b, t.[:dm:]);
|
||||
i++;
|
||||
if constexpr (std::meta::annotations_of_with_type(dm, ^^simdjson::detail::skip_tag).empty()) {
|
||||
constexpr const char* key_name = simdjson::get_json_key_name<dm>();
|
||||
constexpr auto first_key = std::define_static_string(
|
||||
constevalutil::consteval_to_quoted_escaped(key_name) + ":");
|
||||
constexpr auto rest_key = std::define_static_string(
|
||||
std::string(",") + constevalutil::consteval_to_quoted_escaped(key_name) + ":");
|
||||
constexpr size_t first_key_len = std::char_traits<char>::length(first_key);
|
||||
constexpr size_t rest_key_len = std::char_traits<char>::length(rest_key);
|
||||
if (!w.ensure(rest_key_len)) return;
|
||||
if (first) {
|
||||
std::memcpy(w.ptr + w.pos, first_key, first_key_len);
|
||||
w.pos += first_key_len;
|
||||
} else {
|
||||
std::memcpy(w.ptr + w.pos, rest_key, rest_key_len);
|
||||
w.pos += rest_key_len;
|
||||
}
|
||||
first = false;
|
||||
atom(w, t.[:dm:]);
|
||||
}
|
||||
};
|
||||
b.append('}');
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = '}';
|
||||
}
|
||||
|
||||
// Support for optional types (std::optional, etc.)
|
||||
template <concepts::optional_type T>
|
||||
requires(!require_custom_serialization<T>)
|
||||
simdjson_really_inline constexpr void atom(string_builder &b, const T &opt) {
|
||||
simdjson_really_inline constexpr void atom(writer &w, const T &opt) {
|
||||
if (opt) {
|
||||
atom(b, opt.value());
|
||||
atom(w, opt.value());
|
||||
} else {
|
||||
b.append_raw("null");
|
||||
if (!w.ensure(4)) return;
|
||||
std::memcpy(w.ptr + w.pos, "null", 4);
|
||||
w.pos += 4;
|
||||
}
|
||||
}
|
||||
|
||||
// Support for smart pointers (std::unique_ptr, std::shared_ptr, etc.)
|
||||
template <concepts::smart_pointer T>
|
||||
requires(!require_custom_serialization<T>)
|
||||
simdjson_really_inline constexpr void atom(string_builder &b, const T &ptr) {
|
||||
simdjson_really_inline constexpr void atom(writer &w, const T &ptr) {
|
||||
if (ptr) {
|
||||
atom(b, *ptr);
|
||||
atom(w, *ptr);
|
||||
} else {
|
||||
b.append_raw("null");
|
||||
if (!w.ensure(4)) return;
|
||||
std::memcpy(w.ptr + w.pos, "null", 4);
|
||||
w.pos += 4;
|
||||
}
|
||||
}
|
||||
|
||||
// Support for enums - serialize as string representation using expand approach from P2996R12
|
||||
template <typename T>
|
||||
requires(std::is_enum_v<T> && !require_custom_serialization<T>)
|
||||
simdjson_really_inline void atom(string_builder &b, const T &e) {
|
||||
simdjson_really_inline void atom(writer &w, const T &e) {
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
static constexpr auto enumerators = std::define_static_array(std::meta::enumerators_of(^^T));
|
||||
template for (constexpr auto enum_val : enumerators) {
|
||||
constexpr auto enum_str = std::define_static_string(constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(enum_val)));
|
||||
constexpr size_t enum_str_len = std::char_traits<char>::length(enum_str);
|
||||
if (e == [:enum_val:]) {
|
||||
b.append_raw(enum_str);
|
||||
if (!w.ensure(enum_str_len)) return;
|
||||
std::memcpy(w.ptr + w.pos, enum_str, enum_str_len);
|
||||
w.pos += enum_str_len;
|
||||
return;
|
||||
}
|
||||
};
|
||||
// Fallback to integer if enum value not found
|
||||
atom(b, static_cast<std::underlying_type_t<T>>(e));
|
||||
atom(w, static_cast<std::underlying_type_t<T>>(e));
|
||||
#else
|
||||
// Fallback: serialize as integer if reflection not available
|
||||
atom(b, static_cast<std::underlying_type_t<T>>(e));
|
||||
atom(w, static_cast<std::underlying_type_t<T>>(e));
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -161,28 +331,37 @@ template <concepts::appendable_containers 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*> && !require_custom_serialization<T>)
|
||||
simdjson_really_inline constexpr void atom(string_builder &b, const T &container) {
|
||||
simdjson_really_inline constexpr void atom(writer &w, const T &container) {
|
||||
if (container.empty()) {
|
||||
b.append_raw("[]");
|
||||
if (!w.ensure(2)) return;
|
||||
std::memcpy(w.ptr + w.pos, "[]", 2);
|
||||
w.pos += 2;
|
||||
return;
|
||||
}
|
||||
b.append('[');
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = '[';
|
||||
bool first = true;
|
||||
for (const auto& item : container) {
|
||||
if (!first) {
|
||||
b.append(',');
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = ',';
|
||||
}
|
||||
first = false;
|
||||
atom(b, item);
|
||||
atom(w, item);
|
||||
}
|
||||
b.append(']');
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = ']';
|
||||
}
|
||||
|
||||
// append functions that delegate to atom functions for primitive types
|
||||
// append() — top-level entry. Each overload constructs a stack-local
|
||||
// writer, runs atom(w, t) through the inlined call chain, then syncs
|
||||
// the local position back into the string_builder.
|
||||
template <class T>
|
||||
requires(std::is_arithmetic_v<T> && !std::is_same_v<T, char>)
|
||||
void append(string_builder &b, const T &t) {
|
||||
atom(b, t);
|
||||
simdjson_inline void append(string_builder &b, const T &t) {
|
||||
writer w(b);
|
||||
atom(w, t);
|
||||
w.sync();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
@@ -190,20 +369,26 @@ template <class T>
|
||||
std::is_same_v<T, std::string_view> ||
|
||||
std::is_same_v<T, const char *> ||
|
||||
std::is_same_v<T, char>)
|
||||
void append(string_builder &b, const T &t) {
|
||||
atom(b, t);
|
||||
simdjson_inline void append(string_builder &b, const T &t) {
|
||||
writer w(b);
|
||||
atom(w, t);
|
||||
w.sync();
|
||||
}
|
||||
|
||||
template <concepts::optional_type T>
|
||||
requires(!require_custom_serialization<T>)
|
||||
void append(string_builder &b, const T &t) {
|
||||
atom(b, t);
|
||||
simdjson_inline void append(string_builder &b, const T &t) {
|
||||
writer w(b);
|
||||
atom(w, t);
|
||||
w.sync();
|
||||
}
|
||||
|
||||
template <concepts::smart_pointer T>
|
||||
requires(!require_custom_serialization<T>)
|
||||
void append(string_builder &b, const T &t) {
|
||||
atom(b, t);
|
||||
simdjson_inline void append(string_builder &b, const T &t) {
|
||||
writer w(b);
|
||||
atom(w, t);
|
||||
w.sync();
|
||||
}
|
||||
|
||||
template <concepts::appendable_containers T>
|
||||
@@ -211,14 +396,18 @@ template <concepts::appendable_containers 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*> && !require_custom_serialization<T>)
|
||||
void append(string_builder &b, const T &t) {
|
||||
atom(b, t);
|
||||
simdjson_inline void append(string_builder &b, const T &t) {
|
||||
writer w(b);
|
||||
atom(w, t);
|
||||
w.sync();
|
||||
}
|
||||
|
||||
template <concepts::string_view_keyed_map T>
|
||||
requires(!require_custom_serialization<T>)
|
||||
void append(string_builder &b, const T &t) {
|
||||
atom(b, t);
|
||||
simdjson_inline void append(string_builder &b, const T &t) {
|
||||
writer w(b);
|
||||
atom(w, t);
|
||||
w.sync();
|
||||
}
|
||||
|
||||
// works for struct
|
||||
@@ -232,40 +421,19 @@ template <class Z>
|
||||
!std::is_same_v<Z, std::string_view> &&
|
||||
!std::is_same_v<Z, const char*> &&
|
||||
!std::is_same_v<Z, char> && !require_custom_serialization<Z>)
|
||||
void append(string_builder &b, const Z &z) {
|
||||
// Same coalescing as the atom() overload above.
|
||||
int i = 0;
|
||||
b.append('{');
|
||||
template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^Z, std::meta::access_context::unchecked()))) {
|
||||
constexpr auto first_key = std::define_static_string(
|
||||
constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(dm)) + ":");
|
||||
constexpr auto rest_key = std::define_static_string(
|
||||
std::string(",") + constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(dm)) + ":");
|
||||
b.append_raw(i == 0 ? first_key : rest_key);
|
||||
atom(b, z.[:dm:]);
|
||||
i++;
|
||||
};
|
||||
b.append('}');
|
||||
simdjson_inline void append(string_builder &b, const Z &z) {
|
||||
writer w(b);
|
||||
atom(w, z);
|
||||
w.sync();
|
||||
}
|
||||
|
||||
// works for container that have begin() and end() iterators
|
||||
template <class Z>
|
||||
requires(concepts::container_but_not_string<Z> && !require_custom_serialization<Z>)
|
||||
void append(string_builder &b, const Z &z) {
|
||||
auto it = z.begin();
|
||||
auto end = z.end();
|
||||
if (it == end) {
|
||||
b.append_raw("[]");
|
||||
return;
|
||||
}
|
||||
b.append('[');
|
||||
atom(b, *it);
|
||||
++it;
|
||||
for (; it != end; ++it) {
|
||||
b.append(',');
|
||||
atom(b, *it);
|
||||
}
|
||||
b.append(']');
|
||||
simdjson_inline void append(string_builder &b, const Z &z) {
|
||||
writer w(b);
|
||||
atom(w, z);
|
||||
w.sync();
|
||||
}
|
||||
|
||||
template <class Z>
|
||||
@@ -304,7 +472,9 @@ string_builder& operator<<(string_builder& b, const Z& z) {
|
||||
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) {
|
||||
b.append('{');
|
||||
writer w(b);
|
||||
if (!w.ensure(1)) { w.sync(); return; }
|
||||
w.ptr[w.pos++] = '{';
|
||||
bool first = true;
|
||||
// Iterate through all members of T using reflection
|
||||
static constexpr auto members = std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()));
|
||||
@@ -314,21 +484,29 @@ void extract_from(string_builder &b, const T &obj) {
|
||||
|
||||
// Only serialize this field if it's in our list of requested fields
|
||||
if constexpr (((FieldNames.view() == key) || ...)) {
|
||||
// Same coalescing as the atom() / append() struct overloads.
|
||||
static constexpr auto first_key = std::define_static_string(
|
||||
constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(mem)) + ":");
|
||||
static constexpr auto rest_key = std::define_static_string(
|
||||
std::string(",") + constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(mem)) + ":");
|
||||
b.append_raw(first ? first_key : rest_key);
|
||||
constexpr size_t first_key_len = std::char_traits<char>::length(first_key);
|
||||
constexpr size_t rest_key_len = std::char_traits<char>::length(rest_key);
|
||||
if (!w.ensure(rest_key_len)) { w.sync(); return; }
|
||||
if (first) {
|
||||
std::memcpy(w.ptr + w.pos, first_key, first_key_len);
|
||||
w.pos += first_key_len;
|
||||
} else {
|
||||
std::memcpy(w.ptr + w.pos, rest_key, rest_key_len);
|
||||
w.pos += rest_key_len;
|
||||
}
|
||||
first = false;
|
||||
|
||||
// Serialize the value
|
||||
atom(b, obj.[:mem:]);
|
||||
atom(w, obj.[:mem:]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
b.append('}');
|
||||
if (!w.ensure(1)) { w.sync(); return; }
|
||||
w.ptr[w.pos++] = '}';
|
||||
w.sync();
|
||||
}
|
||||
|
||||
template<constevalutil::fixed_string... FieldNames, typename T>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
#ifndef SIMDJSON_GENERIC_STRING_BUILDER_INL_H
|
||||
|
||||
@@ -171,6 +173,35 @@ simdjson_inline bool fast_needs_escaping(std::string_view view) {
|
||||
}
|
||||
return _mm_movemask_epi8(running) != 0;
|
||||
}
|
||||
#elif SIMDJSON_EXPERIMENTAL_HAS_LSX
|
||||
simdjson_inline bool fast_needs_escaping(std::string_view view) {
|
||||
if (view.size() < 16) {
|
||||
return simple_needs_escaping(view);
|
||||
}
|
||||
size_t i = 0;
|
||||
__m128i running = __lsx_vreplgr2vr_b(0);
|
||||
__m128i v34 = __lsx_vreplgr2vr_b(34);
|
||||
__m128i v92 = __lsx_vreplgr2vr_b(92);
|
||||
__m128i v32 = __lsx_vreplgr2vr_b(32);
|
||||
|
||||
for (; i + 15 < view.size(); i += 16) {
|
||||
__m128i word =
|
||||
__lsx_vld(reinterpret_cast<const char *>(view.data() + i), 0);
|
||||
__m128i chunk = __lsx_vseq_b(word, v34);
|
||||
chunk = __lsx_vor_v(chunk, __lsx_vseq_b(word, v92));
|
||||
chunk = __lsx_vor_v(chunk, __lsx_vslt_bu(word, v32));
|
||||
running = __lsx_vor_v(running, chunk);
|
||||
}
|
||||
if (i < view.size()) {
|
||||
__m128i word = __lsx_vld(
|
||||
reinterpret_cast<const char *>(view.data() + view.length() - 16), 0);
|
||||
__m128i chunk = __lsx_vseq_b(word, v34);
|
||||
chunk = __lsx_vor_v(chunk, __lsx_vseq_b(word, v92));
|
||||
chunk = __lsx_vor_v(chunk, __lsx_vslt_bu(word, v32));
|
||||
running = __lsx_vor_v(running, chunk);
|
||||
}
|
||||
return !__lsx_bz_v(running);
|
||||
}
|
||||
#elif SIMDJSON_EXPERIMENTAL_HAS_PPC64
|
||||
simdjson_inline bool fast_needs_escaping(std::string_view view) {
|
||||
if (view.size() < 16) {
|
||||
@@ -709,6 +740,29 @@ simdjson_inline void string_builder::append(number_type v) noexcept {
|
||||
else SIMDJSON_IF_CONSTEXPR(std::is_floating_point<number_type>::value) {
|
||||
constexpr size_t max_number_size = 24;
|
||||
if (capacity_check(max_number_size)) {
|
||||
#if SIMDJSON_ENABLE_NAN_INF
|
||||
// Check if the input might be NaN or infinity
|
||||
if (simdjson_unlikely(!std::isfinite(v))) {
|
||||
if (std::isnan(v)) {
|
||||
constexpr char nan_literal[] = "NaN";
|
||||
constexpr size_t nan_len = sizeof(nan_literal) - 1;
|
||||
|
||||
std::memcpy(buffer.get() + position, nan_literal, nan_len);
|
||||
position += nan_len;
|
||||
} else {
|
||||
constexpr char inf_literal[] = "Infinity";
|
||||
constexpr size_t inf_len = sizeof(inf_literal) - 1;
|
||||
if (v < 0) {
|
||||
buffer.get()[position] = '-';
|
||||
++position;
|
||||
}
|
||||
std::memcpy(buffer.get() + position, inf_literal, inf_len);
|
||||
position += inf_len;
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// We could specialize for float.
|
||||
char *end = simdjson::internal::to_chars(buffer.get() + position, nullptr,
|
||||
double(v));
|
||||
@@ -720,6 +774,11 @@ simdjson_inline void string_builder::append(number_type v) noexcept {
|
||||
simdjson_inline void
|
||||
string_builder::escape_and_append(std::string_view input) noexcept {
|
||||
// escaping might turn a control character into \x00xx so 6 characters.
|
||||
// Guard against size_t overflow in the multiplication below.
|
||||
if (input.size() > (std::numeric_limits<size_t>::max)() / 6) {
|
||||
set_valid(false);
|
||||
return;
|
||||
}
|
||||
if (capacity_check(6 * input.size())) {
|
||||
position += write_string_escaped(input, buffer.get() + position);
|
||||
}
|
||||
@@ -728,6 +787,11 @@ string_builder::escape_and_append(std::string_view input) noexcept {
|
||||
simdjson_inline void
|
||||
string_builder::escape_and_append_with_quotes(std::string_view input) noexcept {
|
||||
// escaping might turn a control character into \x00xx so 6 characters.
|
||||
// Guard against size_t overflow in the arithmetic below.
|
||||
if (input.size() > ((std::numeric_limits<size_t>::max)() - 2) / 6) {
|
||||
set_valid(false);
|
||||
return;
|
||||
}
|
||||
if (capacity_check(2 + 6 * input.size())) {
|
||||
buffer.get()[position++] = '"';
|
||||
position += write_string_escaped(input, buffer.get() + position);
|
||||
|
||||
@@ -52,7 +52,7 @@ class string_builder {
|
||||
public:
|
||||
simdjson_inline string_builder(size_t initial_capacity = DEFAULT_INITIAL_CAPACITY);
|
||||
|
||||
static constexpr size_t DEFAULT_INITIAL_CAPACITY = 262144;
|
||||
static constexpr size_t DEFAULT_INITIAL_CAPACITY = 1024;
|
||||
|
||||
/**
|
||||
* Append number (includes Booleans). Booleans are mapped to the strings
|
||||
@@ -245,6 +245,26 @@ requires (!std::is_convertible<R, std::string_view>::value && !concepts::optiona
|
||||
*/
|
||||
simdjson_inline size_t size() const noexcept;
|
||||
|
||||
// ============================================================
|
||||
// Internal hooks for the position-as-local writer in json_builder.h.
|
||||
// These exist so the reflection atom code can hold buffer pointer,
|
||||
// position and capacity in registers across long write chains rather
|
||||
// than reloading them after every char* write (strict aliasing
|
||||
// forces those reloads when accessed via members of *this). User
|
||||
// code should NOT call these directly.
|
||||
// ============================================================
|
||||
simdjson_inline char *unsafe_data() noexcept { return buffer.get(); }
|
||||
simdjson_inline size_t unsafe_position() const noexcept { return position; }
|
||||
simdjson_inline size_t unsafe_capacity() const noexcept { return capacity; }
|
||||
simdjson_inline void unsafe_set_position(size_t p) noexcept { position = p; }
|
||||
/// Make capacity available for at least `n` more bytes after the current
|
||||
/// position. Returns false if the allocation failed.
|
||||
simdjson_inline bool unsafe_grow(size_t needed_total_capacity) noexcept {
|
||||
grow_buffer(needed_total_capacity);
|
||||
return is_valid;
|
||||
}
|
||||
simdjson_inline bool unsafe_is_valid() const noexcept { return is_valid; }
|
||||
|
||||
private:
|
||||
/**
|
||||
* Returns true if we can write at least upcoming_bytes bytes.
|
||||
|
||||
@@ -497,7 +497,7 @@ simdjson_inline size_t significant_digits(const uint8_t * start_digits, size_t d
|
||||
} // unnamed namespace
|
||||
|
||||
/** @private */
|
||||
static error_code slow_float_parsing(simdjson_unused const uint8_t * src, double* answer) {
|
||||
inline error_code slow_float_parsing(simdjson_unused const uint8_t * src, double* answer) {
|
||||
if (parse_float_fallback(src, answer)) {
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "simdjson/generic/ondemand/document.h"
|
||||
#include "simdjson/generic/ondemand/document_stream.h"
|
||||
#include "simdjson/generic/ondemand/field.h"
|
||||
#include "simdjson/generic/ondemand/key_selector.h"
|
||||
#include "simdjson/generic/ondemand/object.h"
|
||||
#include "simdjson/generic/ondemand/object_iterator.h"
|
||||
#include "simdjson/generic/ondemand/ranges.h"
|
||||
|
||||
@@ -135,21 +135,9 @@ inline simdjson_result<value> array::at_pointer(std::string_view json_pointer) n
|
||||
// We don't support this, because we're returning a real element, not a position.
|
||||
if (json_pointer == "-") { return INDEX_OUT_OF_BOUNDS; }
|
||||
|
||||
// Read the array index
|
||||
size_t array_index = 0;
|
||||
size_t i;
|
||||
for (i = 0; i < json_pointer.length() && json_pointer[i] != '/'; i++) {
|
||||
uint8_t digit = uint8_t(json_pointer[i] - '0');
|
||||
// Check for non-digit in array index. If it's there, we're trying to get a field in an object
|
||||
if (digit > 9) { return INCORRECT_TYPE; }
|
||||
array_index = array_index*10 + digit;
|
||||
}
|
||||
|
||||
// 0 followed by other digits is invalid
|
||||
if (i > 1 && json_pointer[0] == '0') { return INVALID_JSON_POINTER; } // "JSON pointer array index has other characters after 0"
|
||||
|
||||
// Empty string is invalid; so is a "/" with no digits before it
|
||||
if (i == 0) { return INVALID_JSON_POINTER; } // "Empty string in JSON pointer array index"
|
||||
SIMDJSON_TRY(internal::parse_json_pointer_array_index(json_pointer, array_index, i));
|
||||
// Get the child
|
||||
auto child = at(array_index);
|
||||
// If there is an error, it ends here
|
||||
|
||||
@@ -10,9 +10,13 @@
|
||||
// Otherwise, amalgamation will fail.
|
||||
#include "simdjson/dom/base.h" // for MINIMAL_DOCUMENT_CAPACITY
|
||||
#include "simdjson/implementation.h"
|
||||
#include "simdjson/base.h"
|
||||
#include "simdjson/common_defs.h"
|
||||
#include "simdjson/constevalutil.h"
|
||||
#include "simdjson/padded_string.h"
|
||||
#include "simdjson/padded_string_view.h"
|
||||
#include "simdjson/internal/dom_parser_implementation.h"
|
||||
#include "simdjson/jsonpathutil.h"
|
||||
#include "simdjson/annotations.h"
|
||||
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_DEPENDENCIES_H
|
||||
@@ -24,6 +24,9 @@ simdjson_inline json_iterator::json_iterator(json_iterator &&other) noexcept
|
||||
_depth{other._depth},
|
||||
_root{other._root},
|
||||
_streaming{other._streaming}
|
||||
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
, _allow_incomplete_json{other._allow_incomplete_json}
|
||||
#endif
|
||||
{
|
||||
other.parser = nullptr;
|
||||
}
|
||||
@@ -35,6 +38,9 @@ simdjson_inline json_iterator &json_iterator::operator=(json_iterator &&other) n
|
||||
_depth = other._depth;
|
||||
_root = other._root;
|
||||
_streaming = other._streaming;
|
||||
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
_allow_incomplete_json = other._allow_incomplete_json;
|
||||
#endif
|
||||
other.parser = nullptr;
|
||||
return *this;
|
||||
}
|
||||
@@ -61,7 +67,8 @@ simdjson_inline json_iterator::json_iterator(const uint8_t *buf, ondemand::parse
|
||||
_string_buf_loc{parser->string_buf.get()},
|
||||
_depth{1},
|
||||
_root{parser->implementation->structural_indexes.get()},
|
||||
_streaming{streaming}
|
||||
_streaming{streaming},
|
||||
_allow_incomplete_json{true}
|
||||
|
||||
{
|
||||
logger::log_headers();
|
||||
@@ -193,6 +200,17 @@ simdjson_inline bool json_iterator::streaming() const noexcept {
|
||||
return _streaming;
|
||||
}
|
||||
|
||||
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
simdjson_inline bool json_iterator::allow_incomplete_json() const noexcept {
|
||||
return _allow_incomplete_json;
|
||||
}
|
||||
|
||||
simdjson_inline size_t json_iterator::remaining_input_length(const uint8_t *json) const noexcept {
|
||||
const uint8_t *end = token.buf + parser->_document_len;
|
||||
return json < end ? size_t(end - json) : 0;
|
||||
}
|
||||
#endif // SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
|
||||
simdjson_inline token_position json_iterator::root_position() const noexcept {
|
||||
return _root;
|
||||
}
|
||||
@@ -450,4 +468,4 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::json_iterator
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_JSON_ITERATOR_INL_H
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_JSON_ITERATOR_INL_H
|
||||
|
||||
@@ -60,6 +60,9 @@ protected:
|
||||
* value of this attribute.
|
||||
*/
|
||||
bool _streaming{false};
|
||||
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
bool _allow_incomplete_json{false};
|
||||
#endif
|
||||
|
||||
public:
|
||||
simdjson_inline json_iterator() noexcept = default;
|
||||
@@ -84,6 +87,10 @@ public:
|
||||
* start_root_array() and start_root_object().
|
||||
*/
|
||||
simdjson_inline bool streaming() const noexcept;
|
||||
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
simdjson_inline bool allow_incomplete_json() const noexcept;
|
||||
simdjson_inline size_t remaining_input_length(const uint8_t *json) const noexcept;
|
||||
#endif // SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
|
||||
/**
|
||||
* Get the root value iterator
|
||||
@@ -335,4 +342,4 @@ public:
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_JSON_ITERATOR_H
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_JSON_ITERATOR_H
|
||||
|
||||
@@ -0,0 +1,930 @@
|
||||
#ifndef SIMDJSON_GENERIC_ONDEMAND_KEY_SELECTOR_H
|
||||
#define SIMDJSON_GENERIC_ONDEMAND_KEY_SELECTOR_H
|
||||
|
||||
#ifndef SIMDJSON_CONDITIONAL_INCLUDE
|
||||
#include "simdjson/base.h"
|
||||
#include "simdjson/common_defs.h"
|
||||
#include "simdjson/constevalutil.h"
|
||||
#include "simdjson/generic/ondemand/raw_json_string.h"
|
||||
#endif // SIMDJSON_CONDITIONAL_INCLUDE
|
||||
|
||||
#include <array>
|
||||
#include <string_view>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#if defined(__aarch64__) || defined(__ARM_NEON)
|
||||
#include <arm_neon.h>
|
||||
#define SIMDJSON_KEY_SELECTOR_HAS_NEON 1
|
||||
#else
|
||||
#define SIMDJSON_KEY_SELECTOR_HAS_NEON 0
|
||||
#endif
|
||||
#if defined(__SSE2__)
|
||||
#include <emmintrin.h>
|
||||
#define SIMDJSON_KEY_SELECTOR_HAS_SSE2 1
|
||||
#else
|
||||
#define SIMDJSON_KEY_SELECTOR_HAS_SSE2 0
|
||||
#endif
|
||||
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS
|
||||
|
||||
namespace simdjson {
|
||||
namespace SIMDJSON_IMPLEMENTATION {
|
||||
namespace ondemand {
|
||||
|
||||
namespace key_selector_detail {
|
||||
|
||||
// ============================================================================
|
||||
// Compile-time perfect-hash generator.
|
||||
//
|
||||
// This is a port of the ConstexprCore perfect-hash generator
|
||||
// (https://github.com/ConstexprCore/perfect_hash). It scales to ~100 keys at
|
||||
// compile time by determining association values one (position, character)
|
||||
// symbol at a time (gperf-style) instead of an exhaustive offset search, and
|
||||
// falls back to a Hash-and-Displace construction for large/awkward key sets.
|
||||
//
|
||||
// Only flat tables survive to runtime; the lookup is a few additions plus a
|
||||
// single SIMD key comparison (see match_raw below).
|
||||
// ============================================================================
|
||||
|
||||
// Maximum number of character positions the gperf hash may combine.
|
||||
static constexpr std::size_t MAX_POSITIONS = 16;
|
||||
// Sentinel "position" meaning "the last character of the key".
|
||||
static constexpr std::size_t LAST_CHAR = std::size_t(-1);
|
||||
// Runtime-encoded sentinels (stored in uint8 tables).
|
||||
static constexpr std::uint8_t POS_LAST_CHAR = 0xFF; // positions_[i] == last char
|
||||
static constexpr std::uint8_t HD_MODE = 0xFF; // num_positions == H&D mode
|
||||
// Flags stored in positions[2] in H&D mode to select the key-hash variant.
|
||||
static constexpr std::size_t HD_HASH_2BYTE_FLAG = 2;
|
||||
static constexpr std::size_t HD_HASH_4BYTE_FLAG = 4;
|
||||
|
||||
constexpr std::size_t next_power_of_2(std::size_t n) noexcept {
|
||||
if (n == 0) { return 1; }
|
||||
std::size_t p = 1;
|
||||
while (p < n) { p <<= 1; }
|
||||
return p;
|
||||
}
|
||||
|
||||
// Character at a given position (LAST_CHAR means last character), or 256 if out
|
||||
// of bounds.
|
||||
constexpr std::size_t char_at(std::string_view key, std::size_t pos) noexcept {
|
||||
if (pos == LAST_CHAR) {
|
||||
if (key.empty()) { return 256; }
|
||||
return static_cast<unsigned char>(key[key.size() - 1]);
|
||||
}
|
||||
if (pos >= key.size()) { return 256; }
|
||||
return static_cast<unsigned char>(key[pos]);
|
||||
}
|
||||
|
||||
// Count key pairs that a set of positions fails to distinguish. Keys whose
|
||||
// lengths differ modulo the table size are separated by the length term in the
|
||||
// hash, so they need no position coverage.
|
||||
template <std::size_t N>
|
||||
consteval std::size_t count_undistinguished_pairs(
|
||||
const std::array<std::string_view, N>& keys,
|
||||
const std::size_t* positions,
|
||||
std::size_t num_positions,
|
||||
std::size_t modulus) {
|
||||
std::size_t count = 0;
|
||||
for (std::size_t i = 0; i < N; ++i) {
|
||||
for (std::size_t j = i + 1; j < N; ++j) {
|
||||
if (keys[i].size() % modulus != keys[j].size() % modulus) { continue; }
|
||||
bool distinguished = false;
|
||||
for (std::size_t p = 0; p < num_positions; ++p) {
|
||||
if (char_at(keys[i], positions[p]) != char_at(keys[j], positions[p])) {
|
||||
distinguished = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!distinguished) { ++count; }
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
template <std::size_t N>
|
||||
consteval bool positions_distinguish(
|
||||
const std::array<std::string_view, N>& keys,
|
||||
const std::size_t* positions,
|
||||
std::size_t num_positions,
|
||||
std::size_t modulus) {
|
||||
return count_undistinguished_pairs<N>(keys, positions, num_positions, modulus) == 0;
|
||||
}
|
||||
|
||||
// Number of distinct (length % modulus, char_at(key, pos)) pairs at a position.
|
||||
template <std::size_t N>
|
||||
consteval std::size_t discriminating_power(
|
||||
const std::array<std::string_view, N>& keys,
|
||||
std::size_t pos,
|
||||
std::size_t modulus) {
|
||||
struct pair { std::size_t len_mod; std::size_t ch; };
|
||||
std::array<pair, N> pairs{};
|
||||
for (std::size_t i = 0; i < N; ++i) {
|
||||
pairs[i] = {keys[i].size() % modulus, char_at(keys[i], pos)};
|
||||
}
|
||||
std::size_t count = 0;
|
||||
for (std::size_t i = 0; i < N; ++i) {
|
||||
bool dup = false;
|
||||
for (std::size_t j = 0; j < i; ++j) {
|
||||
if (pairs[i].len_mod == pairs[j].len_mod && pairs[i].ch == pairs[j].ch) {
|
||||
dup = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!dup) { ++count; }
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
template <std::size_t N>
|
||||
consteval std::size_t max_key_length(const std::array<std::string_view, N>& keys) {
|
||||
std::size_t m = 0;
|
||||
for (std::size_t i = 0; i < N; ++i) {
|
||||
if (keys[i].size() > m) { m = keys[i].size(); }
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
// Bounded backtracking DFS for a minimal set of distinguishing positions.
|
||||
template <std::size_t N>
|
||||
consteval bool backtracking_search(
|
||||
const std::array<std::string_view, N>& keys,
|
||||
const std::size_t* candidates,
|
||||
std::size_t num_candidates,
|
||||
std::size_t* positions,
|
||||
std::size_t& num_positions_out,
|
||||
std::size_t& budget,
|
||||
std::size_t modulus) {
|
||||
constexpr std::size_t MAX_DEPTH = 8;
|
||||
std::size_t breadth = num_candidates < 20 ? num_candidates : 20;
|
||||
|
||||
struct frame { std::size_t depth; std::size_t next_ci; std::size_t parent_count; };
|
||||
std::array<frame, MAX_DEPTH + 1> stack{};
|
||||
std::size_t sp = 0;
|
||||
|
||||
std::size_t initial_count = count_undistinguished_pairs<N>(keys, positions, 0, modulus);
|
||||
if (budget > 0) { --budget; }
|
||||
if (initial_count == 0) { num_positions_out = 0; return true; }
|
||||
|
||||
stack[0] = {0, 0, initial_count};
|
||||
|
||||
while (budget > 0) {
|
||||
if (sp > MAX_DEPTH) {
|
||||
if (sp == 0) { break; }
|
||||
--sp;
|
||||
++stack[sp].next_ci;
|
||||
continue;
|
||||
}
|
||||
auto& f = stack[sp];
|
||||
if (f.next_ci >= breadth) {
|
||||
if (sp == 0) { break; }
|
||||
--sp;
|
||||
++stack[sp].next_ci;
|
||||
continue;
|
||||
}
|
||||
positions[sp] = candidates[f.next_ci];
|
||||
--budget;
|
||||
std::size_t new_count = count_undistinguished_pairs<N>(keys, positions, sp + 1, modulus);
|
||||
if (new_count == 0) { num_positions_out = sp + 1; return true; }
|
||||
if (new_count < f.parent_count && sp + 1 < MAX_DEPTH) {
|
||||
stack[sp + 1] = {sp + 1, f.next_ci + 1, new_count};
|
||||
++sp;
|
||||
} else {
|
||||
++f.next_ci;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Phase 1: select character positions that distinguish all colliding pairs.
|
||||
template <std::size_t N>
|
||||
consteval std::size_t select_positions(
|
||||
const std::array<std::string_view, N>& keys,
|
||||
std::array<std::size_t, MAX_POSITIONS>& positions,
|
||||
std::size_t modulus) {
|
||||
if (positions_distinguish<N>(keys, positions.data(), 0, modulus)) { return 0; }
|
||||
|
||||
std::size_t max_len = max_key_length(keys);
|
||||
constexpr std::size_t MAX_CANDIDATES = 256;
|
||||
std::array<std::size_t, MAX_CANDIDATES> candidates{};
|
||||
std::array<std::size_t, MAX_CANDIDATES> powers{};
|
||||
std::size_t num_candidates = 0;
|
||||
for (std::size_t p = 0; p < max_len && num_candidates < MAX_CANDIDATES - 1; ++p) {
|
||||
candidates[num_candidates] = p;
|
||||
powers[num_candidates] = discriminating_power(keys, p, modulus);
|
||||
++num_candidates;
|
||||
}
|
||||
if (num_candidates < MAX_CANDIDATES) {
|
||||
candidates[num_candidates] = LAST_CHAR;
|
||||
powers[num_candidates] = discriminating_power(keys, LAST_CHAR, modulus);
|
||||
++num_candidates;
|
||||
}
|
||||
for (std::size_t i = 0; i < num_candidates; ++i) {
|
||||
for (std::size_t j = i + 1; j < num_candidates; ++j) {
|
||||
if (powers[j] > powers[i]) {
|
||||
auto tc = candidates[i]; candidates[i] = candidates[j]; candidates[j] = tc;
|
||||
auto tp = powers[i]; powers[i] = powers[j]; powers[j] = tp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
positions[0] = candidates[0];
|
||||
if (positions_distinguish<N>(keys, positions.data(), 1, modulus)) { return 1; }
|
||||
|
||||
positions[0] = 0;
|
||||
positions[1] = LAST_CHAR;
|
||||
if (positions_distinguish<N>(keys, positions.data(), 2, modulus)) { return 2; }
|
||||
|
||||
{
|
||||
std::size_t budget = 5000;
|
||||
std::size_t num_found = 0;
|
||||
if (backtracking_search<N>(keys, candidates.data(), num_candidates,
|
||||
positions.data(), num_found, budget, modulus)) {
|
||||
return num_found;
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t num_pos = 0;
|
||||
for (std::size_t ci = 0; ci < num_candidates && num_pos < MAX_POSITIONS; ++ci) {
|
||||
bool already = false;
|
||||
for (std::size_t p = 0; p < num_pos; ++p) {
|
||||
if (positions[p] == candidates[ci]) { already = true; break; }
|
||||
}
|
||||
if (already) { continue; }
|
||||
positions[num_pos] = candidates[ci];
|
||||
++num_pos;
|
||||
if (positions_distinguish<N>(keys, positions.data(), num_pos, modulus)) { return num_pos; }
|
||||
}
|
||||
|
||||
throw "Failed to find distinguishing positions for perfect hash";
|
||||
}
|
||||
|
||||
// Result of PHF computation. A max-sized slot_to_key array lets the same struct
|
||||
// type carry any chosen table size.
|
||||
template <std::size_t N>
|
||||
struct phf_result {
|
||||
// Allow up to 8x the minimum table size. Sparser tables solve faster.
|
||||
static constexpr std::size_t MAX_TABLE_SIZE = next_power_of_2(N) * 8;
|
||||
std::size_t table_size{};
|
||||
std::array<std::array<std::size_t, 256>, MAX_POSITIONS> asso_values{};
|
||||
std::size_t num_positions{};
|
||||
std::array<std::size_t, MAX_POSITIONS> positions{};
|
||||
std::array<std::size_t, MAX_TABLE_SIZE> slot_to_key{};
|
||||
};
|
||||
|
||||
// Partition-based asso_values search (gperf-style). Determines asso_values one
|
||||
// (position, character) symbol at a time; never revisits a value. Equivalence
|
||||
// classes (keys sharing the same undetermined symbols) keep the search cheap.
|
||||
template <std::size_t N, std::size_t M>
|
||||
consteval bool try_generate_gperf(
|
||||
const std::array<std::string_view, N>& keys,
|
||||
std::array<std::array<std::size_t, 256>, MAX_POSITIONS>& asso_values,
|
||||
std::size_t& num_positions,
|
||||
std::array<std::size_t, MAX_POSITIONS>& positions,
|
||||
std::array<std::size_t, M>& slot_to_key) {
|
||||
num_positions = select_positions<N>(keys, positions, M);
|
||||
|
||||
for (std::size_t p = 0; p < MAX_POSITIONS; ++p) {
|
||||
for (std::size_t c = 0; c < 256; ++c) { asso_values[p][c] = 0; }
|
||||
}
|
||||
|
||||
if (num_positions == 0) {
|
||||
for (std::size_t i = 0; i < M; ++i) { slot_to_key[i] = N; }
|
||||
for (std::size_t i = 0; i < N; ++i) {
|
||||
std::size_t slot = keys[i].size() % M;
|
||||
if (slot_to_key[slot] != N) { return false; }
|
||||
slot_to_key[slot] = i;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::array<std::array<std::size_t, MAX_POSITIONS>, N> kchars{};
|
||||
for (std::size_t k = 0; k < N; ++k) {
|
||||
for (std::size_t p = 0; p < num_positions; ++p) {
|
||||
kchars[k][p] = char_at(keys[k], positions[p]);
|
||||
}
|
||||
}
|
||||
|
||||
struct sym_t { std::size_t pos; std::size_t ch; std::size_t freq; };
|
||||
constexpr std::size_t MAX_SYMS = MAX_POSITIONS * 256;
|
||||
std::array<sym_t, MAX_SYMS> syms{};
|
||||
std::size_t nsyms = 0;
|
||||
for (std::size_t p = 0; p < num_positions; ++p) {
|
||||
std::array<std::size_t, 256> freq{};
|
||||
for (std::size_t k = 0; k < N; ++k) {
|
||||
std::size_t c = kchars[k][p];
|
||||
if (c < 256) { freq[c]++; }
|
||||
}
|
||||
for (std::size_t c = 0; c < 256; ++c) {
|
||||
if (freq[c] > 0) { syms[nsyms++] = {p, c, freq[c]}; }
|
||||
}
|
||||
}
|
||||
for (std::size_t i = 0; i < nsyms; ++i) {
|
||||
for (std::size_t j = i + 1; j < nsyms; ++j) {
|
||||
if (syms[j].freq > syms[i].freq) {
|
||||
auto tmp = syms[i]; syms[i] = syms[j]; syms[j] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::array<std::size_t, N> phash{};
|
||||
for (std::size_t k = 0; k < N; ++k) { phash[k] = keys[k].size(); }
|
||||
|
||||
// Equivalence-class signatures: sig[k] = XOR of a per-symbol salt over the
|
||||
// key's undetermined symbols. Updated incrementally as symbols are fixed.
|
||||
std::array<std::array<std::size_t, 256>, MAX_POSITIONS> salt{};
|
||||
{
|
||||
std::size_t s = 0x9e3779b97f4a7c15ULL;
|
||||
for (std::size_t p = 0; p < num_positions; ++p) {
|
||||
for (std::size_t c = 0; c < 256; ++c) {
|
||||
s = s * 6364136223846793005ULL + 1442695040888963407ULL;
|
||||
salt[p][c] = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::array<std::size_t, N> sig{};
|
||||
for (std::size_t k = 0; k < N; ++k) {
|
||||
std::size_t s = 0;
|
||||
for (std::size_t p = 0; p < num_positions; ++p) {
|
||||
std::size_t c = kchars[k][p];
|
||||
if (c < 256) { s ^= salt[p][c]; }
|
||||
}
|
||||
sig[k] = s;
|
||||
}
|
||||
std::array<std::size_t, N> order{};
|
||||
for (std::size_t k = 0; k < N; ++k) { order[k] = k; }
|
||||
|
||||
std::array<std::size_t, M> slot_gen{};
|
||||
std::size_t gen = 0;
|
||||
|
||||
std::size_t search_limit = next_power_of_2(M);
|
||||
if (search_limit < 32) { search_limit = 32; }
|
||||
|
||||
for (std::size_t si = 0; si < nsyms; ++si) {
|
||||
std::size_t sp = syms[si].pos;
|
||||
std::size_t sc = syms[si].ch;
|
||||
|
||||
std::size_t sp_salt = salt[sp][sc];
|
||||
for (std::size_t k = 0; k < N; ++k) {
|
||||
if (kchars[k][sp] == sc) { sig[k] ^= sp_salt; }
|
||||
}
|
||||
|
||||
for (std::size_t i = 1; i < N; ++i) {
|
||||
std::size_t x = order[i];
|
||||
std::size_t xs = sig[x];
|
||||
std::size_t j = i;
|
||||
while (j > 0 && sig[order[j - 1]] > xs) {
|
||||
order[j] = order[j - 1];
|
||||
--j;
|
||||
}
|
||||
order[j] = x;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
for (std::size_t v = 0; v < search_limit && !found; ++v) {
|
||||
bool collision = false;
|
||||
std::size_t ci = 0;
|
||||
while (ci < N && !collision) {
|
||||
std::size_t class_sig = sig[order[ci]];
|
||||
std::size_t cj = ci;
|
||||
while (cj < N && sig[order[cj]] == class_sig) { ++cj; }
|
||||
if (cj - ci > 1) {
|
||||
++gen;
|
||||
for (std::size_t x = ci; x < cj; ++x) {
|
||||
std::size_t k = order[x];
|
||||
std::size_t h = phash[k];
|
||||
if (kchars[k][sp] == sc) { h += v; }
|
||||
h %= M;
|
||||
if (slot_gen[h] == gen) { collision = true; break; }
|
||||
slot_gen[h] = gen;
|
||||
}
|
||||
}
|
||||
ci = cj;
|
||||
}
|
||||
if (!collision) {
|
||||
asso_values[sp][sc] = v;
|
||||
for (std::size_t k = 0; k < N; ++k) {
|
||||
if (kchars[k][sp] == sc) { phash[k] += v; }
|
||||
}
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found) { return false; }
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < M; ++i) { slot_to_key[i] = N; }
|
||||
for (std::size_t i = 0; i < N; ++i) {
|
||||
std::size_t slot = phash[i] % M;
|
||||
if (slot_to_key[slot] != N) { return false; }
|
||||
slot_to_key[slot] = i;
|
||||
}
|
||||
std::size_t filled = 0;
|
||||
for (std::size_t i = 0; i < M; ++i) {
|
||||
if (slot_to_key[i] != N) { ++filled; }
|
||||
}
|
||||
return filled == N;
|
||||
}
|
||||
|
||||
template <std::size_t N, std::size_t M>
|
||||
consteval bool try_compute_phf(const std::array<std::string_view, N>& keys, phf_result<N>& result) {
|
||||
static_assert(M <= phf_result<N>::MAX_TABLE_SIZE, "Table size M exceeds maximum");
|
||||
std::array<std::array<std::size_t, 256>, MAX_POSITIONS> asso{};
|
||||
std::size_t npos{};
|
||||
std::array<std::size_t, MAX_POSITIONS> pos{};
|
||||
std::array<std::size_t, M> s2k{};
|
||||
if (try_generate_gperf<N, M>(keys, asso, npos, pos, s2k)) {
|
||||
result.table_size = M;
|
||||
result.asso_values = asso;
|
||||
result.num_positions = npos;
|
||||
result.positions = pos;
|
||||
for (std::size_t i = 0; i < M; ++i) { result.slot_to_key[i] = s2k[i]; }
|
||||
for (std::size_t i = M; i < phf_result<N>::MAX_TABLE_SIZE; ++i) { result.slot_to_key[i] = N; }
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <std::size_t N, std::size_t M, std::size_t MaxM>
|
||||
consteval bool try_gperf_po2(const std::array<std::string_view, N>& keys, phf_result<N>& result) {
|
||||
if (try_compute_phf<N, M>(keys, result)) { return true; }
|
||||
constexpr std::size_t NextM = M * 2;
|
||||
if constexpr (NextM <= MaxM) { return try_gperf_po2<N, NextM, MaxM>(keys, result); }
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- Hash-and-Displace fallback --------------------------------------------
|
||||
|
||||
constexpr std::size_t hd_bucket_hash(std::string_view key) noexcept {
|
||||
std::size_t c0 = key.empty() ? 0 : static_cast<unsigned char>(key[0]);
|
||||
std::size_t c1 = key.empty() ? 0 : static_cast<unsigned char>(key[key.size() - 1]);
|
||||
return (c0 + c1 * 3 + key.size() * 17) & 0xFF;
|
||||
}
|
||||
constexpr std::size_t hd_safe_char(const char* p, std::size_t len, std::size_t idx) noexcept {
|
||||
std::size_t has = static_cast<std::size_t>(idx < len);
|
||||
std::size_t si = idx & (std::size_t{0} - has);
|
||||
return static_cast<unsigned char>(p[si]) & (std::size_t{0} - has);
|
||||
}
|
||||
constexpr std::size_t hd_key_hash_2(std::string_view key) noexcept {
|
||||
std::size_t kc = key.size();
|
||||
kc = kc * 31 + static_cast<unsigned char>(key[0]);
|
||||
kc = kc * 31 + hd_safe_char(key.data(), key.size(), 1);
|
||||
return kc;
|
||||
}
|
||||
constexpr std::size_t hd_key_hash_4(std::string_view key) noexcept {
|
||||
std::size_t kc = key.size();
|
||||
kc = kc * 31 + static_cast<unsigned char>(key[0]);
|
||||
kc = kc * 31 + hd_safe_char(key.data(), key.size(), 1);
|
||||
kc = kc * 31 + hd_safe_char(key.data(), key.size(), 2);
|
||||
kc = kc * 31 + hd_safe_char(key.data(), key.size(), 3);
|
||||
return kc;
|
||||
}
|
||||
|
||||
template <std::size_t N, std::size_t M>
|
||||
consteval bool try_hash_and_displace(
|
||||
const std::array<std::string_view, N>& keys,
|
||||
std::array<std::array<std::size_t, 256>, MAX_POSITIONS>& asso_values,
|
||||
std::size_t& num_positions,
|
||||
std::array<std::size_t, MAX_POSITIONS>& positions,
|
||||
std::array<std::size_t, M>& slot_to_key) {
|
||||
num_positions = select_positions<N>(keys, positions, M);
|
||||
|
||||
if (num_positions == 0) {
|
||||
for (std::size_t i = 0; i < 256; ++i) { asso_values[0][i] = 0; }
|
||||
for (std::size_t i = 0; i < M; ++i) { slot_to_key[i] = N; }
|
||||
for (std::size_t i = 0; i < N; ++i) {
|
||||
std::size_t slot = keys[i].size() % M;
|
||||
if (slot_to_key[slot] != N) { return false; }
|
||||
slot_to_key[slot] = i;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < 256; ++i) { asso_values[0][i] = 0; }
|
||||
num_positions = HD_MODE; // sentinel for H&D mode
|
||||
positions[0] = 0;
|
||||
positions[1] = LAST_CHAR;
|
||||
|
||||
std::array<std::size_t, N> key_bucket{};
|
||||
for (std::size_t i = 0; i < N; ++i) { key_bucket[i] = hd_bucket_hash(keys[i]); }
|
||||
|
||||
struct bucket_info { std::size_t ch; std::size_t count; };
|
||||
std::array<bucket_info, N> buckets{};
|
||||
std::size_t num_buckets = 0;
|
||||
for (std::size_t i = 0; i < N; ++i) {
|
||||
std::size_t bk = key_bucket[i];
|
||||
bool found = false;
|
||||
for (std::size_t b = 0; b < num_buckets; ++b) {
|
||||
if (buckets[b].ch == bk) { ++buckets[b].count; found = true; break; }
|
||||
}
|
||||
if (!found) { buckets[num_buckets++] = {bk, 1}; }
|
||||
}
|
||||
for (std::size_t i = 0; i < num_buckets; ++i) {
|
||||
for (std::size_t j = i + 1; j < num_buckets; ++j) {
|
||||
if (buckets[j].count > buckets[i].count) {
|
||||
auto tmp = buckets[i]; buckets[i] = buckets[j]; buckets[j] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto try_placement = [&](auto key_hash_fn) -> bool {
|
||||
for (std::size_t i = 0; i < M; ++i) { slot_to_key[i] = N; }
|
||||
for (std::size_t i = 0; i < 256; ++i) { asso_values[0][i] = 0; }
|
||||
for (std::size_t b = 0; b < num_buckets; ++b) {
|
||||
std::size_t ch = buckets[b].ch;
|
||||
std::array<std::size_t, N> bucket_keys{};
|
||||
std::size_t bk_count = 0;
|
||||
for (std::size_t i = 0; i < N; ++i) {
|
||||
if (key_bucket[i] == ch) { bucket_keys[bk_count++] = i; }
|
||||
}
|
||||
bool placed = false;
|
||||
std::size_t max_d = M < 255 ? M : 255;
|
||||
for (std::size_t d = 0; d < max_d; ++d) {
|
||||
bool ok = true;
|
||||
std::array<std::size_t, N> bucket_slots{};
|
||||
for (std::size_t k = 0; k < bk_count; ++k) {
|
||||
std::size_t slot = (d + key_hash_fn(keys[bucket_keys[k]])) % M;
|
||||
if (slot_to_key[slot] != N) { ok = false; break; }
|
||||
for (std::size_t k2 = 0; k2 < k; ++k2) {
|
||||
if (bucket_slots[k2] == slot) { ok = false; break; }
|
||||
}
|
||||
if (!ok) { break; }
|
||||
bucket_slots[k] = slot;
|
||||
}
|
||||
if (ok) {
|
||||
asso_values[0][ch] = d;
|
||||
for (std::size_t k = 0; k < bk_count; ++k) {
|
||||
slot_to_key[bucket_slots[k]] = bucket_keys[k];
|
||||
}
|
||||
placed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!placed) { return false; }
|
||||
}
|
||||
std::size_t filled = 0;
|
||||
for (std::size_t i = 0; i < M; ++i) {
|
||||
if (slot_to_key[i] != N) { ++filled; }
|
||||
}
|
||||
return filled == N;
|
||||
};
|
||||
|
||||
if (try_placement([](std::string_view k) { return hd_key_hash_2(k); })) {
|
||||
positions[2] = HD_HASH_2BYTE_FLAG;
|
||||
return true;
|
||||
}
|
||||
if (try_placement([](std::string_view k) { return hd_key_hash_4(k); })) {
|
||||
positions[2] = HD_HASH_4BYTE_FLAG;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <std::size_t N, std::size_t M>
|
||||
consteval bool try_compute_phf_hd(const std::array<std::string_view, N>& keys, phf_result<N>& result) {
|
||||
static_assert(M <= phf_result<N>::MAX_TABLE_SIZE, "Table size M exceeds maximum");
|
||||
std::array<std::array<std::size_t, 256>, MAX_POSITIONS> asso{};
|
||||
std::size_t npos{};
|
||||
std::array<std::size_t, MAX_POSITIONS> pos{};
|
||||
std::array<std::size_t, M> s2k{};
|
||||
if (try_hash_and_displace<N, M>(keys, asso, npos, pos, s2k)) {
|
||||
result.table_size = M;
|
||||
result.asso_values = asso;
|
||||
result.num_positions = npos;
|
||||
result.positions = pos;
|
||||
for (std::size_t i = 0; i < M; ++i) { result.slot_to_key[i] = s2k[i]; }
|
||||
for (std::size_t i = M; i < phf_result<N>::MAX_TABLE_SIZE; ++i) { result.slot_to_key[i] = N; }
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <std::size_t N, std::size_t M>
|
||||
consteval phf_result<N> compute_phf_hd_po2(const std::array<std::string_view, N>& keys) {
|
||||
static_assert(M <= phf_result<N>::MAX_TABLE_SIZE, "Table size M exceeds maximum");
|
||||
phf_result<N> result{};
|
||||
if (try_compute_phf_hd<N, M>(keys, result)) { return result; }
|
||||
constexpr std::size_t NextM = M * 2;
|
||||
if constexpr (NextM <= phf_result<N>::MAX_TABLE_SIZE) {
|
||||
return compute_phf_hd_po2<N, NextM>(keys);
|
||||
} else {
|
||||
throw "Hash-and-Displace: failed to find valid table size";
|
||||
}
|
||||
}
|
||||
|
||||
// Compute a perfect hash for `keys`: try gperf at power-of-two sizes (capped so
|
||||
// the runtime tables stay within uint8 indices), then fall back to H&D.
|
||||
template <std::size_t N>
|
||||
consteval phf_result<N> compute_phf(const std::array<std::string_view, N>& keys) {
|
||||
constexpr std::size_t StartM = next_power_of_2(N);
|
||||
constexpr std::size_t GPERF_MAX_TABLE =
|
||||
phf_result<N>::MAX_TABLE_SIZE < 256 ? phf_result<N>::MAX_TABLE_SIZE : 256;
|
||||
if constexpr (StartM <= GPERF_MAX_TABLE) {
|
||||
phf_result<N> result{};
|
||||
if (try_gperf_po2<N, StartM, GPERF_MAX_TABLE>(keys, result)) { return result; }
|
||||
}
|
||||
return compute_phf_hd_po2<N, StartM>(keys);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Runtime tables (flat, uint8) derived from a phf_result.
|
||||
// ============================================================================
|
||||
|
||||
template <std::size_t N, std::size_t TableSize, std::size_t MaxKeyLen>
|
||||
struct phf_data {
|
||||
std::array<std::array<std::uint8_t, 256>, MAX_POSITIONS> asso_values{};
|
||||
std::array<std::uint8_t, MAX_POSITIONS> positions{};
|
||||
std::uint8_t num_positions{};
|
||||
std::uint8_t hd_hash_variant{}; // 2 or 4 (H&D only)
|
||||
std::array<std::uint8_t, TableSize> slot_to_key{};
|
||||
// slot_key_bytes[s] holds the key stored at slot s, zero-padded to a 16-byte
|
||||
// multiple so the SIMD comparison can read a whole register.
|
||||
std::array<std::array<char, ((MaxKeyLen + 15) / 16) * 16>, TableSize> slot_key_bytes{};
|
||||
std::array<std::uint8_t, TableSize> slot_key_len{};
|
||||
};
|
||||
|
||||
template <std::size_t N>
|
||||
constexpr std::size_t compute_max_key_len(const std::array<std::string_view, N>& keys) noexcept {
|
||||
std::size_t m = 0;
|
||||
for (std::size_t i = 0; i < N; ++i) { if (keys[i].size() > m) { m = keys[i].size(); } }
|
||||
return m;
|
||||
}
|
||||
|
||||
// Validate keys and build the runtime tables from the computed perfect hash.
|
||||
template <std::size_t N, std::size_t TableSize, std::size_t MaxKeyLen>
|
||||
consteval phf_data<N, TableSize, MaxKeyLen>
|
||||
build_phf_data(const std::array<std::string_view, N>& keys, const phf_result<N>& result) {
|
||||
for (std::size_t i = 0; i < N; ++i) {
|
||||
if (keys[i].empty()) { throw "empty keys are not allowed in key_selector"; }
|
||||
if (keys[i].size() > MaxKeyLen) { throw "key length exceeds MaxKeyLen"; }
|
||||
for (char c : keys[i]) {
|
||||
if (c == '\\') { throw "backslash not allowed in key_selector keys"; }
|
||||
if (c == '"') { throw "quote not allowed in key_selector keys"; }
|
||||
if (c == '\0') { throw "null byte not allowed in key_selector keys"; }
|
||||
}
|
||||
for (std::size_t j = i + 1; j < N; ++j) {
|
||||
if (keys[i] == keys[j]) { throw "duplicate keys in key_selector"; }
|
||||
}
|
||||
}
|
||||
|
||||
phf_data<N, TableSize, MaxKeyLen> out{};
|
||||
|
||||
if (result.num_positions == HD_MODE) {
|
||||
// H&D mode: single displacement table in asso_values[0].
|
||||
for (std::size_t c = 0; c < 256; ++c) {
|
||||
out.asso_values[0][c] = static_cast<std::uint8_t>(result.asso_values[0][c]);
|
||||
}
|
||||
out.num_positions = static_cast<std::uint8_t>(HD_MODE);
|
||||
out.hd_hash_variant = static_cast<std::uint8_t>(result.positions[2]);
|
||||
} else {
|
||||
for (std::size_t pi = 0; pi < result.num_positions; ++pi) {
|
||||
for (std::size_t c = 0; c < 256; ++c) {
|
||||
out.asso_values[pi][c] = static_cast<std::uint8_t>(result.asso_values[pi][c] % TableSize);
|
||||
}
|
||||
}
|
||||
out.num_positions = static_cast<std::uint8_t>(result.num_positions);
|
||||
for (std::size_t i = 0; i < result.num_positions; ++i) {
|
||||
out.positions[i] = (result.positions[i] == LAST_CHAR)
|
||||
? POS_LAST_CHAR
|
||||
: static_cast<std::uint8_t>(result.positions[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (std::size_t s = 0; s < TableSize; ++s) {
|
||||
out.slot_to_key[s] = static_cast<std::uint8_t>(result.slot_to_key[s]);
|
||||
}
|
||||
|
||||
for (std::size_t s = 0; s < TableSize; ++s) {
|
||||
std::size_t ki = result.slot_to_key[s];
|
||||
if (ki < N) {
|
||||
auto k = keys[ki];
|
||||
out.slot_key_len[s] = static_cast<std::uint8_t>(k.size());
|
||||
for (std::size_t c = 0; c < k.size(); ++c) { out.slot_key_bytes[s][c] = k[c]; }
|
||||
} else {
|
||||
out.slot_key_len[s] = 0; // empty slot: no length can match
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// --- SIMD runtime primitives ------------------------------------------------
|
||||
|
||||
// Scan for the terminating '"' starting at p. Returns its byte offset (= key
|
||||
// length). Caller guarantees SIMDJSON_PADDING bytes past the JSON buffer, so the
|
||||
// load is safe.
|
||||
template <std::size_t MaxKeyLen>
|
||||
simdjson_really_inline std::size_t scan_key_length(const char* p) noexcept {
|
||||
// The SIMD paths scan only two 16-byte blocks (offsets 0..31), so a key
|
||||
// whose closing quote sits at offset 32 would be missed. Cap at 31 to keep
|
||||
// SIMD and scalar builds in agreement.
|
||||
static_assert(MaxKeyLen <= 31, "MaxKeyLen must be <= 31 for current SIMD implementations");
|
||||
#if SIMDJSON_KEY_SELECTOR_HAS_NEON
|
||||
uint8x16_t v0 = vld1q_u8(reinterpret_cast<const uint8_t*>(p));
|
||||
uint8x16_t cmp0 = vceqq_u8(v0, vdupq_n_u8('"'));
|
||||
uint64_t m0 = vget_lane_u64(
|
||||
vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(cmp0), 4)), 0);
|
||||
if constexpr (MaxKeyLen < 16) {
|
||||
if (simdjson_likely(m0 != 0)) return std::size_t(__builtin_ctzll(m0)) >> 2;
|
||||
return MaxKeyLen + 1;
|
||||
} else {
|
||||
uint8x16_t v1 = vld1q_u8(reinterpret_cast<const uint8_t*>(p) + 16);
|
||||
uint8x16_t cmp1 = vceqq_u8(v1, vdupq_n_u8('"'));
|
||||
uint64_t m1 = vget_lane_u64(
|
||||
vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(cmp1), 4)), 0);
|
||||
if (simdjson_likely(m0 != 0)) return std::size_t(__builtin_ctzll(m0)) >> 2;
|
||||
if (m1 != 0) return 16 + (std::size_t(__builtin_ctzll(m1)) >> 2);
|
||||
return MaxKeyLen + 1;
|
||||
}
|
||||
#elif SIMDJSON_KEY_SELECTOR_HAS_SSE2
|
||||
__m128i v0 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(p));
|
||||
__m128i cmp0 = _mm_cmpeq_epi8(v0, _mm_set1_epi8('"'));
|
||||
unsigned m0 = static_cast<unsigned>(_mm_movemask_epi8(cmp0));
|
||||
if constexpr (MaxKeyLen < 16) {
|
||||
if (simdjson_likely(m0 != 0)) return std::size_t(__builtin_ctz(m0));
|
||||
return MaxKeyLen + 1;
|
||||
} else {
|
||||
__m128i v1 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(p + 16));
|
||||
__m128i cmp1 = _mm_cmpeq_epi8(v1, _mm_set1_epi8('"'));
|
||||
unsigned m1 = static_cast<unsigned>(_mm_movemask_epi8(cmp1));
|
||||
if (simdjson_likely(m0 != 0)) return std::size_t(__builtin_ctz(m0));
|
||||
if (m1 != 0) return 16 + std::size_t(__builtin_ctz(m1));
|
||||
return MaxKeyLen + 1;
|
||||
}
|
||||
#else
|
||||
for (std::size_t i = 0; i <= MaxKeyLen; ++i)
|
||||
if (p[i] == '"') return i;
|
||||
return MaxKeyLen + 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Byte-equal of p[0..len) against stored[0..len). stored is zero-padded past
|
||||
// `len`. Input is read over 16 or 32 bytes (padded JSON buffer guaranteed).
|
||||
template <std::size_t MaxKeyLen>
|
||||
simdjson_really_inline bool compare_key_bytes(
|
||||
const char* p, const char* stored, std::size_t len) noexcept {
|
||||
alignas(16) static constexpr uint8_t idx16[16] =
|
||||
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
|
||||
if constexpr (MaxKeyLen <= 16) {
|
||||
#if SIMDJSON_KEY_SELECTOR_HAS_NEON
|
||||
uint8x16_t vp = vld1q_u8(reinterpret_cast<const uint8_t*>(p));
|
||||
uint8x16_t vs = vld1q_u8(reinterpret_cast<const uint8_t*>(stored));
|
||||
uint8x16_t mask = vcltq_u8(vld1q_u8(idx16), vdupq_n_u8(static_cast<uint8_t>(len)));
|
||||
uint8x16_t diff = veorq_u8(vandq_u8(vp, mask), vs);
|
||||
// A 32-bit-lane horizontal max is enough to decide "all bytes equal"
|
||||
// (diff is zero iff every 32-bit word is zero) and is cheaper than a
|
||||
// byte-wide reduction.
|
||||
return vmaxvq_u32(vreinterpretq_u32_u8(diff)) == 0;
|
||||
#elif SIMDJSON_KEY_SELECTOR_HAS_SSE2
|
||||
__m128i vp = _mm_loadu_si128(reinterpret_cast<const __m128i*>(p));
|
||||
__m128i vs = _mm_loadu_si128(reinterpret_cast<const __m128i*>(stored));
|
||||
__m128i idx = _mm_load_si128(reinterpret_cast<const __m128i*>(idx16));
|
||||
__m128i mask = _mm_cmplt_epi8(idx, _mm_set1_epi8(static_cast<char>(len)));
|
||||
__m128i eq = _mm_cmpeq_epi8(_mm_and_si128(vp, mask), vs);
|
||||
return _mm_movemask_epi8(eq) == 0xFFFF;
|
||||
#else
|
||||
for (std::size_t i = 0; i < len; ++i)
|
||||
if (p[i] != stored[i]) return false;
|
||||
return true;
|
||||
#endif
|
||||
} else if constexpr (MaxKeyLen <= 32) {
|
||||
alignas(16) static constexpr uint8_t idx32_hi[16] =
|
||||
{16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31};
|
||||
#if SIMDJSON_KEY_SELECTOR_HAS_NEON
|
||||
uint8x16_t vp_lo = vld1q_u8(reinterpret_cast<const uint8_t*>(p));
|
||||
uint8x16_t vp_hi = vld1q_u8(reinterpret_cast<const uint8_t*>(p) + 16);
|
||||
uint8x16_t vs_lo = vld1q_u8(reinterpret_cast<const uint8_t*>(stored));
|
||||
uint8x16_t vs_hi = vld1q_u8(reinterpret_cast<const uint8_t*>(stored) + 16);
|
||||
uint8x16_t lenv = vdupq_n_u8(static_cast<uint8_t>(len));
|
||||
uint8x16_t m_lo = vcltq_u8(vld1q_u8(idx16), lenv);
|
||||
uint8x16_t m_hi = vcltq_u8(vld1q_u8(idx32_hi), lenv);
|
||||
uint8x16_t d_lo = veorq_u8(vandq_u8(vp_lo, m_lo), vs_lo);
|
||||
uint8x16_t d_hi = veorq_u8(vandq_u8(vp_hi, m_hi), vs_hi);
|
||||
return vmaxvq_u32(vreinterpretq_u32_u8(vorrq_u8(d_lo, d_hi))) == 0;
|
||||
#elif SIMDJSON_KEY_SELECTOR_HAS_SSE2
|
||||
__m128i vp_lo = _mm_loadu_si128(reinterpret_cast<const __m128i*>(p));
|
||||
__m128i vp_hi = _mm_loadu_si128(reinterpret_cast<const __m128i*>(p + 16));
|
||||
__m128i vs_lo = _mm_loadu_si128(reinterpret_cast<const __m128i*>(stored));
|
||||
__m128i vs_hi = _mm_loadu_si128(reinterpret_cast<const __m128i*>(stored + 16));
|
||||
__m128i lenv = _mm_set1_epi8(static_cast<char>(len));
|
||||
__m128i m_lo = _mm_cmplt_epi8(_mm_load_si128(reinterpret_cast<const __m128i*>(idx16)), lenv);
|
||||
__m128i m_hi = _mm_cmplt_epi8(_mm_load_si128(reinterpret_cast<const __m128i*>(idx32_hi)), lenv);
|
||||
__m128i eq_lo = _mm_cmpeq_epi8(_mm_and_si128(vp_lo, m_lo), vs_lo);
|
||||
__m128i eq_hi = _mm_cmpeq_epi8(_mm_and_si128(vp_hi, m_hi), vs_hi);
|
||||
return (_mm_movemask_epi8(eq_lo) & _mm_movemask_epi8(eq_hi)) == 0xFFFF;
|
||||
#else
|
||||
for (std::size_t i = 0; i < len; ++i)
|
||||
if (p[i] != stored[i]) return false;
|
||||
return true;
|
||||
#endif
|
||||
} else {
|
||||
for (std::size_t i = 0; i < len; ++i)
|
||||
if (p[i] != stored[i]) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace key_selector_detail
|
||||
|
||||
/**
|
||||
* Stateless, compile-time key selector.
|
||||
*
|
||||
* Usage:
|
||||
* using sel_t = key_selector<"id", "text", "user">;
|
||||
* std::size_t i = sel_t::match_raw(raw_key); // returns sel_t::size() on miss
|
||||
*
|
||||
* The perfect hash is built at compile time (gperf-style, with a
|
||||
* Hash-and-Displace fallback) and only flat tables survive to runtime. All
|
||||
* tables are static constexpr, so the lookup fully inlines.
|
||||
*
|
||||
* Limitations:
|
||||
* - Each key must be at most 31 characters long (and no longer than
|
||||
* SIMDJSON_PADDING). Longer keys trigger a compile-time error.
|
||||
* - The number of keys should be moderate. The hard limit is 255 keys;
|
||||
* compilation time grows with the number of keys, so prefer a few dozen at
|
||||
* most per selector.
|
||||
* - Keys must be distinct, non-empty, and free of backslash, double-quote and
|
||||
* null bytes (matching is done against the raw, unescaped JSON key bytes).
|
||||
*/
|
||||
template <constevalutil::fixed_string... Keys>
|
||||
struct key_selector {
|
||||
static constexpr std::size_t N = sizeof...(Keys);
|
||||
static_assert(N > 0, "key_selector requires at least one key");
|
||||
static_assert(N <= 255,"key_selector supports at most 255 keys");
|
||||
|
||||
static constexpr std::array<std::string_view, N> keys{ Keys.view()... };
|
||||
static constexpr std::size_t max_key_len = key_selector_detail::compute_max_key_len<N>(keys);
|
||||
static_assert(max_key_len <= SIMDJSON_PADDING,
|
||||
"key longer than SIMDJSON_PADDING is not supported");
|
||||
// The SIMD key-length scan covers offsets 0..31 only; a 32-character key's
|
||||
// closing quote lands at offset 32 and would be silently missed on
|
||||
// NEON/SSE2 while still matching in scalar builds. Cap at 31 so the result
|
||||
// is identical across implementations.
|
||||
static_assert(max_key_len <= 31,
|
||||
"key_selector keys must be at most 31 characters long");
|
||||
|
||||
static constexpr auto result = key_selector_detail::compute_phf<N>(keys);
|
||||
static constexpr std::size_t table_size = result.table_size;
|
||||
|
||||
static constexpr auto phf =
|
||||
key_selector_detail::build_phf_data<N, table_size, max_key_len>(keys, result);
|
||||
|
||||
static constexpr std::size_t size() noexcept { return N; }
|
||||
|
||||
/**
|
||||
* Look up a JSON key. rjs must point just after an opening quote in a padded
|
||||
* simdjson buffer. Returns the selector index in [0, N) on match, or N on miss.
|
||||
*/
|
||||
static simdjson_really_inline std::size_t match_raw(raw_json_string rjs) noexcept {
|
||||
const char* p = rjs.raw();
|
||||
std::size_t len = key_selector_detail::scan_key_length<max_key_len>(p);
|
||||
if (len == 0 || len > max_key_len) { return N; }
|
||||
|
||||
std::size_t slot;
|
||||
if (phf.num_positions == key_selector_detail::HD_MODE) {
|
||||
// Hash-and-Displace: bucket displacement + per-key hash.
|
||||
std::string_view key(p, len);
|
||||
std::size_t bucket = key_selector_detail::hd_bucket_hash(key);
|
||||
std::size_t kh = (phf.hd_hash_variant == key_selector_detail::HD_HASH_2BYTE_FLAG)
|
||||
? key_selector_detail::hd_key_hash_2(key)
|
||||
: key_selector_detail::hd_key_hash_4(key);
|
||||
slot = (phf.asso_values[0][bucket] + kh) & (table_size - 1);
|
||||
} else {
|
||||
// gperf: h = len + sum of asso_values over the selected positions.
|
||||
// positions / num_positions / asso_values are compile-time constants,
|
||||
// so this loop fully unrolls. The idx < len guard mirrors the
|
||||
// generator's char_at()-> 256 -> skip behavior for out-of-range
|
||||
// positions (required: arbitrary positions may exceed a key's length).
|
||||
std::size_t h = len;
|
||||
for (std::uint8_t i = 0; i < phf.num_positions; ++i) {
|
||||
std::uint8_t pos = phf.positions[i];
|
||||
std::size_t idx = (pos == key_selector_detail::POS_LAST_CHAR)
|
||||
? (len - std::size_t{1})
|
||||
: static_cast<std::size_t>(pos);
|
||||
if (idx < len) {
|
||||
h += phf.asso_values[i][static_cast<unsigned char>(p[idx])];
|
||||
}
|
||||
}
|
||||
slot = h & (table_size - 1);
|
||||
}
|
||||
|
||||
std::uint8_t ki = phf.slot_to_key[slot];
|
||||
if (ki >= N) { return N; }
|
||||
if (phf.slot_key_len[slot] != len) { return N; }
|
||||
if (!key_selector_detail::compare_key_bytes<max_key_len>(
|
||||
p, phf.slot_key_bytes[slot].data(), len)) { return N; }
|
||||
return ki;
|
||||
}
|
||||
|
||||
/** Return the key text at selector index i (i in [0, N)). */
|
||||
static constexpr std::string_view key_at(std::size_t i) noexcept {
|
||||
return keys[i];
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ondemand
|
||||
} // namespace SIMDJSON_IMPLEMENTATION
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_SUPPORTS_CONCEPTS
|
||||
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_KEY_SELECTOR_H
|
||||
@@ -63,6 +63,42 @@ simdjson_inline simdjson_result<value> object::find_field(const std::string_view
|
||||
return value(iter.child());
|
||||
}
|
||||
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS
|
||||
template <typename Selector, typename Func>
|
||||
simdjson_inline error_code object::for_each(Func&& on_match) noexcept {
|
||||
auto first = this->begin();
|
||||
if (first.error()) { return first.error(); }
|
||||
object_iterator it = first.value_unsafe();
|
||||
object_iterator last{};
|
||||
std::array<bool, Selector::size()> seen{};
|
||||
std::size_t matched = 0;
|
||||
while (it != last) {
|
||||
auto field_res = *it;
|
||||
if (field_res.error()) { return field_res.error(); }
|
||||
field f = field_res.value_unsafe();
|
||||
std::size_t idx = Selector::match_raw(f.key());
|
||||
if (idx < Selector::size() && !seen[idx]) {
|
||||
seen[idx] = true;
|
||||
value matched_value = f.value();
|
||||
// The callback may return either void or an error_code. When it returns an
|
||||
// error_code, we stop at the first non-SUCCESS result and propagate it so
|
||||
// the caller can surface value-parse errors (for example, a type mismatch
|
||||
// on a matched field). A void-returning callback is responsible for
|
||||
// handling its own errors.
|
||||
if constexpr (std::is_same_v<decltype(on_match(idx, matched_value)), error_code>) {
|
||||
error_code e = on_match(idx, matched_value);
|
||||
if (e) { return e; }
|
||||
} else {
|
||||
on_match(idx, matched_value);
|
||||
}
|
||||
if (++matched >= Selector::size()) { break; }
|
||||
}
|
||||
++it;
|
||||
}
|
||||
return SUCCESS;
|
||||
}
|
||||
#endif
|
||||
|
||||
simdjson_inline simdjson_result<object> object::start(value_iterator &iter) noexcept {
|
||||
SIMDJSON_TRY( iter.start_object().error() );
|
||||
return object(iter);
|
||||
@@ -326,6 +362,7 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> simdjs
|
||||
return std::forward<SIMDJSON_IMPLEMENTATION::ondemand::object>(first).find_field(key);
|
||||
}
|
||||
|
||||
|
||||
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::object>::at_pointer(std::string_view json_pointer) noexcept {
|
||||
if (error()) { return error(); }
|
||||
return first.at_pointer(json_pointer);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "simdjson/generic/ondemand/base.h"
|
||||
#include "simdjson/generic/implementation_simdjson_result_base.h"
|
||||
#include "simdjson/generic/ondemand/value_iterator.h"
|
||||
#include "simdjson/generic/ondemand/key_selector.h"
|
||||
#include <vector>
|
||||
#if SIMDJSON_STATIC_REFLECTION && SIMDJSON_SUPPORTS_CONCEPTS
|
||||
#include "simdjson/generic/ondemand/json_string_builder.h" // for constevalutil::fixed_string
|
||||
@@ -122,6 +123,38 @@ public:
|
||||
/** @overload simdjson_inline simdjson_result<value> find_field_unordered(std::string_view key) & noexcept; */
|
||||
simdjson_inline simdjson_result<value> operator[](std::string_view key) && noexcept;
|
||||
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS
|
||||
/**
|
||||
* Walk this object once and invoke on_match(selector_index, value) for each
|
||||
* field whose key is in the compile-time key_selector Selector, in JSON order
|
||||
* (first occurrence of a duplicate key wins). Iteration stops once all
|
||||
* Selector::size() keys have matched or the object ends. The value is consumed
|
||||
* in place, so this is a low-overhead way to extract a known set of fields
|
||||
* regardless of their order in the JSON.
|
||||
*
|
||||
* Usage:
|
||||
* using sel_t = ondemand::key_selector<"id", "text", "user">;
|
||||
* obj.for_each<sel_t>([&](std::size_t i, ondemand::value v) {
|
||||
* switch (i) { case 0: ...; case 1: ...; }
|
||||
* });
|
||||
*
|
||||
* Limitations (see key_selector): each key must be at most 31 characters long,
|
||||
* and the number of keys should be moderate (hard limit 255; a handful is
|
||||
* best, as the compile-time perfect hash may fail or slow compilation for
|
||||
* large key sets). They keys must be distinct, non-empty, and free of backslash, double-quote and
|
||||
* null bytes.
|
||||
*
|
||||
* The callback may return either void or an error_code. When it returns an
|
||||
* error_code, the walk stops at the first non-SUCCESS result and that error is
|
||||
* returned, which lets the callback surface value-parse errors.
|
||||
*
|
||||
* @returns SUCCESS, or the first error encountered while walking the object
|
||||
* (including any error returned by the callback).
|
||||
*/
|
||||
template <typename Selector, typename Func>
|
||||
simdjson_inline error_code for_each(Func&& on_match) noexcept;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Get the value associated with the given JSON pointer. We use the RFC 6901
|
||||
* https://tools.ietf.org/html/rfc6901 standard, interpreting the current node
|
||||
|
||||
@@ -55,6 +55,7 @@ simdjson_warn_unused simdjson_inline simdjson_result<document> parser::iterate(p
|
||||
if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; }
|
||||
|
||||
json.remove_utf8_bom();
|
||||
_document_len = json.length();
|
||||
|
||||
// Allocate if needed
|
||||
if (capacity() < json.length() || !string_buf) {
|
||||
@@ -71,6 +72,7 @@ simdjson_warn_unused simdjson_inline simdjson_result<document> parser::iterate_a
|
||||
if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; }
|
||||
|
||||
json.remove_utf8_bom();
|
||||
_document_len = json.length();
|
||||
|
||||
// Allocate if needed
|
||||
if (capacity() < json.length() || !string_buf) {
|
||||
@@ -312,4 +314,4 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::parser>::simd
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_PARSER_INL_H
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_PARSER_INL_H
|
||||
|
||||
@@ -431,6 +431,7 @@ private:
|
||||
size_t _capacity{0};
|
||||
size_t _max_capacity;
|
||||
size_t _max_depth{DEFAULT_MAX_DEPTH};
|
||||
size_t _document_len{0};
|
||||
std::unique_ptr<uint8_t[]> string_buf{};
|
||||
|
||||
#if SIMDJSON_DEVELOPMENT_CHECKS
|
||||
|
||||
@@ -6,12 +6,14 @@
|
||||
#include "simdjson/generic/ondemand/object.h"
|
||||
#include "simdjson/generic/ondemand/array.h"
|
||||
#include "simdjson/generic/ondemand/base.h"
|
||||
#include "simdjson/annotations.h"
|
||||
#endif // SIMDJSON_CONDITIONAL_INCLUDE
|
||||
|
||||
#include <concepts>
|
||||
#include <limits>
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
#include <meta>
|
||||
#include <vector>
|
||||
// #include <static_reflection> // for std::define_static_string - header not available yet
|
||||
#endif
|
||||
|
||||
@@ -267,6 +269,96 @@ constexpr bool user_defined_type = (std::is_class_v<T>
|
||||
!concepts::appendable_containers<T>);
|
||||
|
||||
|
||||
#if defined(SIMDJSON_USE_KEY_SELECTOR_REFLECTION) && SIMDJSON_USE_KEY_SELECTOR_REFLECTION
|
||||
|
||||
// Experimental: deserialize a reflected struct using a compile-time key_selector
|
||||
// and a single object::for_each pass (perfect-hash key matching) instead of one
|
||||
// obj[key] lookup per member. Enable with -DSIMDJSON_USE_KEY_SELECTOR_REFLECTION=1.
|
||||
namespace key_selector_reflection_detail {
|
||||
|
||||
// A member participates if it is public, non-const, and not annotated to skip.
|
||||
consteval bool is_eligible_member(std::meta::info mem) {
|
||||
return !std::meta::is_const(mem) && std::meta::is_public(mem)
|
||||
&& std::meta::annotations_of_with_type(mem, ^^simdjson::detail::skip_tag).empty();
|
||||
}
|
||||
|
||||
// JSON key name for `mem`, as a constevalutil::fixed_string usable as an NTTP.
|
||||
template <auto mem>
|
||||
consteval auto member_key_fixed_string() {
|
||||
constexpr std::string_view key{ simdjson::get_json_key_name<mem>() };
|
||||
char buffer[key.size() + 1] = {};
|
||||
for (std::size_t i = 0; i < key.size(); ++i) { buffer[i] = key[i]; }
|
||||
return constevalutil::fixed_string<key.size() + 1>(buffer);
|
||||
}
|
||||
|
||||
// key_selector template arguments (one fixed_string per eligible member), in
|
||||
// declaration order.
|
||||
template <typename T>
|
||||
consteval std::vector<std::meta::info> selector_key_args() {
|
||||
std::vector<std::meta::info> args;
|
||||
template for (constexpr auto mem : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
|
||||
if constexpr (is_eligible_member(mem)) {
|
||||
args.push_back(std::meta::reflect_constant(member_key_fixed_string<mem>()));
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
// key_selector whose keys are exactly T's eligible members (index i <-> i-th).
|
||||
template <typename T>
|
||||
using selector_for = typename [: std::meta::substitute(
|
||||
^^SIMDJSON_IMPLEMENTATION::ondemand::key_selector, selector_key_args<T>()) :];
|
||||
|
||||
} // namespace key_selector_reflection_detail
|
||||
|
||||
template <typename T, typename ValT>
|
||||
requires(user_defined_type<T> && std::is_class_v<T>)
|
||||
error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept {
|
||||
SIMDJSON_IMPLEMENTATION::ondemand::object obj;
|
||||
if constexpr (std::is_same_v<std::remove_cvref_t<ValT>, SIMDJSON_IMPLEMENTATION::ondemand::object>) {
|
||||
obj = val;
|
||||
} else {
|
||||
SIMDJSON_TRY(val.get_object().get(obj));
|
||||
}
|
||||
using selector = key_selector_reflection_detail::selector_for<T>;
|
||||
std::array<bool, selector::size()> seen_member{};
|
||||
// Single pass over the object: each field whose key matches a member yields its
|
||||
// selector index, which we map back to the corresponding member. The callback
|
||||
// returns an error_code so that a value-parse error (e.g. a type mismatch on a
|
||||
// matched field) is propagated by for_each instead of being silently dropped.
|
||||
error_code walk_error = obj.template for_each<selector>(
|
||||
[&](std::size_t matched_index, SIMDJSON_IMPLEMENTATION::ondemand::value field_value) -> error_code {
|
||||
std::size_t counter = 0;
|
||||
error_code field_error = SUCCESS;
|
||||
template for (constexpr auto mem : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
|
||||
if constexpr (key_selector_reflection_detail::is_eligible_member(mem)) {
|
||||
if (matched_index == counter) {
|
||||
seen_member[counter] = true;
|
||||
field_error = field_value.get(out.[:mem:]);
|
||||
}
|
||||
++counter;
|
||||
}
|
||||
}
|
||||
return field_error;
|
||||
});
|
||||
if (walk_error) { return walk_error; }
|
||||
// Required (non-optional) members must be present: a missing one is reported as
|
||||
// NO_SUCH_FIELD, mirroring the ordered obj[key] path. Optional members may be
|
||||
// absent.
|
||||
std::size_t check_counter = 0;
|
||||
template for (constexpr auto mem : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
|
||||
if constexpr (key_selector_reflection_detail::is_eligible_member(mem)) {
|
||||
if constexpr (!concepts::optional_type<decltype(out.[:mem:])>) {
|
||||
if (!seen_member[check_counter]) { return NO_SUCH_FIELD; }
|
||||
}
|
||||
++check_counter;
|
||||
}
|
||||
}
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
template <typename T, typename ValT>
|
||||
requires(user_defined_type<T> && std::is_class_v<T>)
|
||||
error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept {
|
||||
@@ -277,8 +369,9 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept {
|
||||
SIMDJSON_TRY(val.get_object().get(obj));
|
||||
}
|
||||
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));
|
||||
if constexpr (!std::meta::is_const(mem) && std::meta::is_public(mem)
|
||||
&& std::meta::annotations_of_with_type(mem, ^^simdjson::detail::skip_tag).empty()) {
|
||||
constexpr std::string_view key = simdjson::get_json_key_name<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:]);
|
||||
@@ -298,6 +391,8 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept {
|
||||
return simdjson::SUCCESS;
|
||||
}
|
||||
|
||||
#endif // SIMDJSON_USE_KEY_SELECTOR_REFLECTION
|
||||
|
||||
// Support for enum deserialization - deserialize from string representation using expand approach from P2996R12
|
||||
template <typename T, typename ValT>
|
||||
requires(std::is_enum_v<T>)
|
||||
|
||||
@@ -15,6 +15,27 @@ namespace simdjson {
|
||||
namespace SIMDJSON_IMPLEMENTATION {
|
||||
namespace ondemand {
|
||||
|
||||
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
simdjson_inline bool raw_json_string_is_quote_terminated(const uint8_t *json, uint32_t max_len) noexcept {
|
||||
bool escaping{false};
|
||||
for (uint32_t i = 1; i < max_len; i++) {
|
||||
switch (json[i]) {
|
||||
case '"':
|
||||
if (!escaping) { return true; }
|
||||
escaping = false;
|
||||
break;
|
||||
case '\\':
|
||||
escaping = !escaping;
|
||||
break;
|
||||
default:
|
||||
escaping = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif // SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
|
||||
simdjson_inline value_iterator::value_iterator(
|
||||
json_iterator *json_iter,
|
||||
depth_t depth,
|
||||
@@ -530,6 +551,15 @@ simdjson_warn_unused simdjson_inline simdjson_result<std::string_view> value_ite
|
||||
simdjson_warn_unused simdjson_inline simdjson_result<raw_json_string> value_iterator::get_raw_json_string() noexcept {
|
||||
auto json = peek_scalar("string");
|
||||
if (*json != '"') { return incorrect_type_error("Not a string"); }
|
||||
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
if (_json_iter->allow_incomplete_json()) {
|
||||
const size_t remaining_input_length = _json_iter->remaining_input_length(json);
|
||||
const uint32_t max_len = remaining_input_length < peek_start_length() ? uint32_t(remaining_input_length) : peek_start_length();
|
||||
if (!raw_json_string_is_quote_terminated(json, max_len)) {
|
||||
return STRING_ERROR;
|
||||
}
|
||||
}
|
||||
#endif // SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
advance_scalar("string");
|
||||
return raw_json_string(json+1);
|
||||
}
|
||||
@@ -677,6 +707,15 @@ simdjson_warn_unused simdjson_inline simdjson_result<raw_json_string> value_iter
|
||||
auto json = peek_scalar("string");
|
||||
if (*json != '"') { return incorrect_type_error("Not a string"); }
|
||||
if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; }
|
||||
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
if (_json_iter->allow_incomplete_json()) {
|
||||
const size_t remaining_input_length = _json_iter->remaining_input_length(json);
|
||||
const uint32_t max_len = remaining_input_length < peek_root_length() ? uint32_t(remaining_input_length) : peek_root_length();
|
||||
if (!raw_json_string_is_quote_terminated(json, max_len)) {
|
||||
return STRING_ERROR;
|
||||
}
|
||||
}
|
||||
#endif // SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
advance_scalar("string");
|
||||
return raw_json_string(json+1);
|
||||
}
|
||||
@@ -1104,4 +1143,4 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value_iterato
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_VALUE_ITERATOR_INL_H
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_VALUE_ITERATOR_INL_H
|
||||
|
||||
@@ -76,7 +76,7 @@ public:
|
||||
|
||||
private:
|
||||
fractured_json_options options_;
|
||||
layout_mode current_layout_ = layout_mode::EXPANDED;
|
||||
layout_mode current_layout_ = layout_mode::expanded;
|
||||
size_t current_depth_ = 0;
|
||||
size_t current_line_length_ = 0;
|
||||
|
||||
|
||||
@@ -20,10 +20,10 @@ namespace internal {
|
||||
* Layout mode for fractured JSON formatting.
|
||||
*/
|
||||
enum class layout_mode {
|
||||
INLINE, // Single line: [1, 2, 3] or {"a": 1}
|
||||
COMPACT_MULTILINE, // Multiple items per line with breaks
|
||||
TABLE, // Tabular format for arrays of similar objects
|
||||
EXPANDED // Traditional multi-line with indentation
|
||||
single_line, // Single line: [1, 2, 3] or {"a": 1}
|
||||
compact_multiline, // Multiple items per line with breaks
|
||||
table, // Tabular format for arrays of similar objects
|
||||
expanded // Traditional multi-line with indentation
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -50,7 +50,7 @@ struct element_metrics {
|
||||
std::vector<std::string> common_keys{};
|
||||
|
||||
/** Recommended layout mode based on analysis */
|
||||
layout_mode recommended_layout = layout_mode::EXPANDED;
|
||||
layout_mode recommended_layout = layout_mode::expanded;
|
||||
|
||||
/** Child metrics for arrays and objects (in order of iteration) */
|
||||
std::vector<element_metrics> children{};
|
||||
|
||||
@@ -1,12 +1,55 @@
|
||||
#ifndef SIMDJSON_JSONPATHUTIL_H
|
||||
#define SIMDJSON_JSONPATHUTIL_H
|
||||
|
||||
#include "simdjson/error.h"
|
||||
#include <string>
|
||||
#include "simdjson/common_defs.h"
|
||||
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
|
||||
namespace simdjson {
|
||||
namespace internal {
|
||||
/**
|
||||
* Parses the next JSON Pointer array index token.
|
||||
*
|
||||
* The caller passes a pointer fragment with no leading '/', such as "123/foo".
|
||||
* On success, array_index receives the parsed index and token_length receives
|
||||
* the number of bytes consumed before the next '/' or the end of the fragment.
|
||||
*/
|
||||
simdjson_inline error_code parse_json_pointer_array_index(std::string_view json_pointer,
|
||||
size_t &array_index,
|
||||
size_t &token_length) noexcept {
|
||||
array_index = 0;
|
||||
token_length = 0;
|
||||
|
||||
for (; token_length < json_pointer.length() && json_pointer[token_length] != '/';
|
||||
token_length++) {
|
||||
uint8_t digit = uint8_t(json_pointer[token_length] - '0');
|
||||
// Check for non-digit in array index. If it's there, we're trying to get a field in an object.
|
||||
if (digit > 9) {
|
||||
return INCORRECT_TYPE;
|
||||
}
|
||||
// 0 followed by other digits is invalid.
|
||||
if (token_length > 0 && json_pointer[0] == '0') {
|
||||
return INVALID_JSON_POINTER;
|
||||
}
|
||||
if (array_index >
|
||||
(((std::numeric_limits<size_t>::max)() - digit) / 10)) {
|
||||
return INDEX_OUT_OF_BOUNDS;
|
||||
}
|
||||
array_index = array_index * 10 + digit;
|
||||
}
|
||||
|
||||
// Empty string is invalid; so is a "/" with no digits before it.
|
||||
if (token_length == 0) {
|
||||
return INVALID_JSON_POINTER;
|
||||
}
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
} // namespace internal
|
||||
|
||||
/**
|
||||
* Converts JSONPath to JSON Pointer.
|
||||
* @param json_path The JSONPath string to be converted.
|
||||
|
||||
@@ -17,7 +17,7 @@ public:
|
||||
simdjson_inline implementation() : simdjson::implementation(
|
||||
"rvv_vls",
|
||||
"RISC-V V extension",
|
||||
0
|
||||
internal::instruction_set::RVV_VLS
|
||||
) {}
|
||||
simdjson_warn_unused error_code create_dom_parser_implementation(
|
||||
size_t capacity,
|
||||
|
||||
@@ -25,7 +25,7 @@ if (Python3_Interpreter_FOUND AND (NOT WIN32))
|
||||
AMALGAMATE_SOURCE_PATH=${PROJECT_SOURCE_DIR}/src
|
||||
AMALGAMATE_INPUT_PATH=${PROJECT_SOURCE_DIR}/include
|
||||
AMALGAMATE_OUTPUT_PATH=${CMAKE_CURRENT_BINARY_DIR}
|
||||
${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/amalgamate.py
|
||||
${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/amalgamate.py --quiet
|
||||
#
|
||||
# This is the best way I could find to make amalgamation trigger whenever source files or
|
||||
# header files change: since the "simdjson" library has to get rebuilt when that happens, we
|
||||
|
||||
+161
-37
@@ -11,6 +11,7 @@ import re
|
||||
import shutil
|
||||
import datetime
|
||||
import json
|
||||
import argparse
|
||||
from typing import Dict, List, Optional, Set, TextIO, Union, cast
|
||||
|
||||
# Pre-compile regex patterns for performance
|
||||
@@ -30,6 +31,18 @@ if sys.version_info < (3, 0):
|
||||
sys.stdout.write("Sorry, requires Python 3.x or better\n")
|
||||
sys.exit(1)
|
||||
|
||||
# Parse command-line arguments.
|
||||
arg_parser = argparse.ArgumentParser(description="Creates the amalgamated source files.")
|
||||
arg_parser.add_argument("--quiet", action="store_true",
|
||||
help="Suppress informational output (errors and warnings are still printed).")
|
||||
args = arg_parser.parse_args()
|
||||
QUIET = args.quiet
|
||||
|
||||
def log(*log_args, **log_kwargs):
|
||||
"""Print informational output unless --quiet was passed."""
|
||||
if not QUIET:
|
||||
print(*log_args, **log_kwargs)
|
||||
|
||||
rules = """
|
||||
|
||||
Amalgamation Rules for simdjson
|
||||
@@ -99,11 +112,11 @@ If adding a new implementation, edit the IMPLEMENTATIONS list in this script.
|
||||
|
||||
SCRIPTPATH = os.path.dirname(os.path.abspath(sys.argv[0]))
|
||||
PROJECTPATH = os.path.dirname(SCRIPTPATH)
|
||||
print(f"SCRIPTPATH={SCRIPTPATH} PROJECTPATH={PROJECTPATH}")
|
||||
log(f"SCRIPTPATH={SCRIPTPATH} PROJECTPATH={PROJECTPATH}")
|
||||
|
||||
|
||||
print("We are about to amalgamate all simdjson files into one source file.")
|
||||
print("See https://www.sqlite.org/amalgamation.html and https://en.wikipedia.org/wiki/Single_Compilation_Unit for rationale.")
|
||||
log("We are about to amalgamate all simdjson files into one source file.")
|
||||
log("See https://www.sqlite.org/amalgamation.html and https://en.wikipedia.org/wiki/Single_Compilation_Unit for rationale.")
|
||||
if "AMALGAMATE_SOURCE_PATH" not in os.environ:
|
||||
AMALGAMATE_SOURCE_PATH = os.path.join(PROJECTPATH, "src")
|
||||
else:
|
||||
@@ -265,37 +278,93 @@ class SimdjsonFile:
|
||||
return self.filename == 'dependencies.h'
|
||||
|
||||
def add_include(self, include: 'SimdjsonFile'):
|
||||
print(f" Adding include: {self} includes {include}")
|
||||
log(f" Adding include: {self} includes {include}")
|
||||
if self.is_conditional_include:
|
||||
# If I have a dependency file, I can only include something that has a dependency file.
|
||||
if not include.is_conditional_include:
|
||||
dep = self.dependency_file
|
||||
dep_hint = f" and add it to the dependency file '{dep}'" if dep else ""
|
||||
if dep:
|
||||
step2 = (
|
||||
f" 2. Add '{include}' to the dependency manifest so the amalgamator\n"
|
||||
f" emits it before any file that needs it:\n\n"
|
||||
f" // in '{dep}'\n"
|
||||
f" #include \"{include}\"\n\n"
|
||||
)
|
||||
else:
|
||||
step2 = (
|
||||
f" 2. '{self}' has no associated dependencies.h file, so the\n"
|
||||
f" amalgamator cannot be told to emit '{include}' earlier.\n"
|
||||
f" Either give '{self}' a dependencies.h, or restructure so\n"
|
||||
f" the include is not needed here.\n\n"
|
||||
)
|
||||
raise AssertionError(
|
||||
f"Error: Amalgamated file '{self}' is trying to include '{include}', "
|
||||
f"but '{include}' is not an amalgamated file.\n\n"
|
||||
f"FIX: Wrap the #include \"{include}\" in a conditional block:\n\n"
|
||||
f" #ifndef SIMDJSON_CONDITIONAL_INCLUDE\n"
|
||||
f" #include \"{include}\"\n"
|
||||
f" #endif // SIMDJSON_CONDITIONAL_INCLUDE\n\n"
|
||||
f"This makes the include editor-only (skipped during amalgamation){dep_hint}.\n\n"
|
||||
f"During amalgamation, '{include}' is already included earlier in the "
|
||||
f"amalgamated output, so it does not need to be included again.\n\n"
|
||||
f"{rules}"
|
||||
f"Error: amalgamated file '{self}' includes non-amalgamated header '{include}'.\n\n"
|
||||
f" Why: '{self}' is stitched into the single-header output. Top-level\n"
|
||||
f" headers like '{include}' are already emitted earlier in that\n"
|
||||
f" output, so re-including them inside an amalgamated file would\n"
|
||||
f" either duplicate content or break include ordering.\n\n"
|
||||
f"FIX (two steps):\n\n"
|
||||
f" 1. In '{self}', wrap the include so it is skipped during amalgamation\n"
|
||||
f" but still visible to your editor/IDE:\n\n"
|
||||
f" #ifndef SIMDJSON_CONDITIONAL_INCLUDE\n"
|
||||
f" #include \"{include}\"\n"
|
||||
f" #endif // SIMDJSON_CONDITIONAL_INCLUDE\n\n"
|
||||
f" Place it alongside the existing '#ifndef SIMDJSON_CONDITIONAL_INCLUDE'\n"
|
||||
f" block near the top of the file, if there is one.\n\n"
|
||||
f"{step2}"
|
||||
f"After both edits, re-run:\n\n"
|
||||
f" python3 singleheader/amalgamate.py\n\n"
|
||||
f"See HACKING.md for the full amalgamation rules.\n"
|
||||
)
|
||||
# TODO make sure we only include amalgamated files that are guaranteed to be included with us (or before us)
|
||||
# if include.amalgamator_file:
|
||||
# assert include.amalgamator_file == self, f"{self} cannot include {include}: it should be included from {include.amalgamator_file} instead."
|
||||
else:
|
||||
assert include.is_amalgamator or not include.is_conditional_include, f"Error: Free dependency file '{self}' is trying to include '{include}', which is an amalgamated file. Free dependency files (top-level headers) can only include amalgamator files or other free files, not amalgamated files directly. This prevents improper layering. Move the include to an amalgamator or restructure dependencies. {rules}"
|
||||
if not (include.is_amalgamator or not include.is_conditional_include):
|
||||
raise AssertionError(
|
||||
f"Error: free (top-level) header '{self}' includes amalgamated file '{include}'.\n\n"
|
||||
f" Why: top-level headers are emitted once into the single-header output\n"
|
||||
f" and form the public API surface. Amalgamated files are emitted\n"
|
||||
f" multiple times (once per CPU implementation), so including one\n"
|
||||
f" from the top level would either duplicate them or pin them to a\n"
|
||||
f" single implementation.\n\n"
|
||||
f"FIX: move the include to an amalgamator file (e.g. simdjson/arm64.h,\n"
|
||||
f" simdjson/generic/amalgamated.h) or, if you actually need the\n"
|
||||
f" declarations at the top level, factor them out into a top-level\n"
|
||||
f" header that '{self}' can include instead.\n\n"
|
||||
f"After editing, re-run:\n\n"
|
||||
f" python3 singleheader/amalgamate.py\n\n"
|
||||
f"See HACKING.md for the full amalgamation rules.\n"
|
||||
)
|
||||
|
||||
self.includes.append(include)
|
||||
include.included_from.add(self)
|
||||
|
||||
def add_editor_only_include(self, include: 'SimdjsonFile'):
|
||||
assert self.is_conditional_include, f"Error: File '{self}' uses '#ifndef SIMDJSON_CONDITIONAL_INCLUDE', but '{self}' is not an amalgamated file. Conditional include blocks are only allowed in amalgamated files (those with dependencies). Remove the conditional block or ensure the file is amalgamated. {rules}"
|
||||
if not include.is_conditional_include:
|
||||
assert self.dependency_file, f"Error: In '{self}', editor-only include of '{include}' requires a dependency file, but '{self}' has none. Ensure '{self}' has an associated dependencies.h file. {rules}"
|
||||
if not self.is_conditional_include:
|
||||
raise AssertionError(
|
||||
f"Error: '{self}' uses '#ifndef SIMDJSON_CONDITIONAL_INCLUDE',\n"
|
||||
f" but '{self}' is not an amalgamated file (it has no\n"
|
||||
f" associated dependencies.h).\n\n"
|
||||
f" Why: conditional-include blocks only make sense in amalgamated files,\n"
|
||||
f" because the SIMDJSON_CONDITIONAL_INCLUDE macro is only defined\n"
|
||||
f" while the amalgamator is emitting them.\n\n"
|
||||
f"FIX: either remove the '#ifndef SIMDJSON_CONDITIONAL_INCLUDE' block from\n"
|
||||
f" '{self}' (and include the headers unconditionally), or — if '{self}'\n"
|
||||
f" really should be amalgamated — give it an associated dependencies.h\n"
|
||||
f" file under the same directory.\n\n"
|
||||
f"See HACKING.md for the full amalgamation rules.\n"
|
||||
)
|
||||
if not include.is_conditional_include and not self.dependency_file:
|
||||
raise AssertionError(
|
||||
f"Error: '{self}' editor-only-includes '{include}', but '{self}' has no\n"
|
||||
f" associated dependencies.h, so the amalgamator has nowhere to record\n"
|
||||
f" that '{include}' must be emitted earlier in the single-header output.\n\n"
|
||||
f"FIX: add a 'dependencies.h' file in the same directory as '{self}',\n"
|
||||
f" and list '{include}' inside it:\n\n"
|
||||
f" #include \"{include}\"\n\n"
|
||||
f"See HACKING.md for the full amalgamation rules.\n"
|
||||
)
|
||||
# TODO make sure we only include amalgamated files that are guaranteed to be included with us (or before us)
|
||||
# elif include.amalgamator_file:
|
||||
# assert self.is_amalgamated_before(self.amalgamator_file), f"{self} cannot include {include}: it should be included from {include.amalgamator_file} instead."
|
||||
@@ -310,11 +379,40 @@ class SimdjsonFile:
|
||||
if file.dependency_file == self:
|
||||
for editor_only_include in file.editor_only_includes:
|
||||
if not editor_only_include.is_conditional_include:
|
||||
assert editor_only_include in self.includes, f"Error: Dependency file '{self}' is missing an include for '{editor_only_include}', which is editor-only included in '{file}'. Add '{editor_only_include}' to '{self}' to ensure completeness. {rules}"
|
||||
if editor_only_include not in self.includes:
|
||||
raise AssertionError(
|
||||
f"Error: dependency manifest '{self}' is missing\n"
|
||||
f" '{editor_only_include}'.\n\n"
|
||||
f" It is editor-only-included by '{file}'\n"
|
||||
f" (inside a '#ifndef SIMDJSON_CONDITIONAL_INCLUDE' block),\n"
|
||||
f" which means the amalgamator does NOT see that include —\n"
|
||||
f" so unless '{self}' lists it, '{editor_only_include}' will\n"
|
||||
f" never be emitted in the single-header build.\n\n"
|
||||
f"FIX: add this line to '{self}':\n\n"
|
||||
f" #include \"{editor_only_include}\"\n\n"
|
||||
f"Then re-run:\n\n"
|
||||
f" python3 singleheader/amalgamate.py\n\n"
|
||||
f"See HACKING.md for the full amalgamation rules.\n"
|
||||
)
|
||||
if editor_only_include in extra_include_set:
|
||||
extra_include_set.remove(editor_only_include)
|
||||
|
||||
assert len(extra_include_set) == 0, f"Error: Dependency file '{self}' includes {extra_include_set}, which are not used in any amalgamated files. Remove these unnecessary includes to clean up dependencies. {rules}"
|
||||
if len(extra_include_set) > 0:
|
||||
extras = sorted(str(f) for f in extra_include_set)
|
||||
bullet_list = "\n".join(f" #include \"{e}\"" for e in extras)
|
||||
raise AssertionError(
|
||||
f"Error: dependency manifest '{self}' lists includes that no\n"
|
||||
f" amalgamated file actually needs:\n\n"
|
||||
f"{bullet_list}\n\n"
|
||||
f" Why: every include in a dependencies.h file should correspond to a\n"
|
||||
f" '#ifndef SIMDJSON_CONDITIONAL_INCLUDE' block in some amalgamated\n"
|
||||
f" sibling file. Stale entries here pull unused headers into the\n"
|
||||
f" single-header build.\n\n"
|
||||
f"FIX: either delete the unused entries from '{self}', or, if they were\n"
|
||||
f" supposed to support a real consumer, add the matching editor-only\n"
|
||||
f" '#ifndef SIMDJSON_CONDITIONAL_INCLUDE' block to that consumer.\n\n"
|
||||
f"See HACKING.md for the full amalgamation rules.\n"
|
||||
)
|
||||
|
||||
class SimdjsonRepository:
|
||||
def __init__(self, project_path: str, relative_roots: List[RelativeRoot]):
|
||||
@@ -366,12 +464,28 @@ class SimdjsonRepository:
|
||||
used_files = set([file.include_path for file in self if file.root == root])
|
||||
all_files.difference_update(used_files)
|
||||
all_files.difference_update(DEPRECATED_FILES)
|
||||
assert len(all_files) == 0, f"Error: The following files in '{root}' are not used in the amalgamation: {sorted(all_files)}. All .h and .cpp files must be included or added to DEPRECATED_FILES. Check for missing includes or deprecate unused files."
|
||||
if len(all_files) > 0:
|
||||
bullet_list = "\n".join(f" {root}/{f}" for f in sorted(all_files))
|
||||
raise AssertionError(
|
||||
f"Error: the following files under '{root}/' exist on disk but are not\n"
|
||||
f" reachable from any #include chain seen by the amalgamator:\n\n"
|
||||
f"{bullet_list}\n\n"
|
||||
f" Why: every .h/.cpp file in the tree must either be transitively included\n"
|
||||
f" from the amalgamation roots, or explicitly listed in DEPRECATED_FILES\n"
|
||||
f" in singleheader/amalgamate.py. This catches files that were forgotten\n"
|
||||
f" after a rename, or were added without a corresponding #include.\n\n"
|
||||
f"FIX: pick one for each file above:\n"
|
||||
f" - Add an #include for it in the appropriate amalgamator/dependencies file.\n"
|
||||
f" - If the file is genuinely obsolete, delete it.\n"
|
||||
f" - If the file is a deprecated public header kept only for source\n"
|
||||
f" compatibility, add it to the DEPRECATED_FILES set in\n"
|
||||
f" singleheader/amalgamate.py.\n"
|
||||
)
|
||||
|
||||
class Amalgamator:
|
||||
@classmethod
|
||||
def amalgamate(cls, output_path: str, filename: str, roots: List[RelativeRoot], timestamp: str, version: str):
|
||||
print(f"Creating {output_path}")
|
||||
log(f"Creating {output_path}")
|
||||
fid = open(output_path, 'w')
|
||||
print(f"/* auto-generated on {timestamp}. version {version} Do not edit! */", file=fid)
|
||||
amalgamator = cls(fid, SimdjsonRepository(PROJECTPATH, roots))
|
||||
@@ -425,7 +539,17 @@ class Amalgamator:
|
||||
|
||||
def write_file(self, file: SimdjsonFile):
|
||||
# Detect cyclic dependencies
|
||||
assert file not in self.include_stack, f"Error: Cyclic include detected: {self.include_stack} -> {file}. Remove the circular dependency by restructuring includes."
|
||||
if file in self.include_stack:
|
||||
cycle_start = self.include_stack.index(file)
|
||||
chain = self.include_stack[cycle_start:] + [file]
|
||||
arrow_chain = "\n -> ".join(str(f) for f in chain)
|
||||
raise AssertionError(
|
||||
f"Error: cyclic include detected:\n\n"
|
||||
f" {arrow_chain}\n\n"
|
||||
f"FIX: break the cycle by moving the shared declarations into a separate\n"
|
||||
f" header that both ends of the cycle can include, or by forward-declaring\n"
|
||||
f" instead of including.\n"
|
||||
)
|
||||
self.include_stack.append(file)
|
||||
|
||||
file.processed = False
|
||||
@@ -577,12 +701,12 @@ class Amalgamator:
|
||||
try:
|
||||
proc = subprocess.run(['git', 'show', '-s', '--format=%ci', 'HEAD'],
|
||||
stdout=subprocess.PIPE)
|
||||
print("the commandline is {}".format(proc.args))
|
||||
log("the commandline is {}".format(proc.args))
|
||||
timestamp = proc.stdout.decode('utf-8').strip()
|
||||
except:
|
||||
print("git not found, timestamp based on current time")
|
||||
timestamp = str(datetime.datetime.now())
|
||||
print(f"timestamp is {timestamp}")
|
||||
log(f"timestamp is {timestamp}")
|
||||
|
||||
os.makedirs(AMALGAMATE_OUTPUT_PATH, exist_ok=True)
|
||||
AMAL_H = os.path.join(AMALGAMATE_OUTPUT_PATH, "simdjson.h")
|
||||
@@ -625,7 +749,7 @@ if not validate_implementations():
|
||||
print("Validation failed. Please update IMPLEMENTATIONS list in amalgamate.py.")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("implementation validated")
|
||||
log("implementation validated")
|
||||
Amalgamator.amalgamate(AMAL_H, "simdjson.h", ['include'], timestamp, version).validate_all_files_used('include')
|
||||
Amalgamator.amalgamate(AMAL_C, "simdjson.cpp", ['src', 'include'], timestamp, version).validate_all_files_used('src')
|
||||
|
||||
@@ -642,20 +766,20 @@ def create_zip():
|
||||
outdir = AMALGAMATE_OUTPUT_PATH
|
||||
|
||||
path = os.path.join(outdir, "singleheader.zip")
|
||||
print(f"Creating {path}")
|
||||
log(f"Creating {path}")
|
||||
with zipfile.ZipFile(path, 'w') as zf:
|
||||
for name in ["simdjson.cpp", "simdjson.h"]:
|
||||
source = os.path.join(outdir, name)
|
||||
print(f"Adding {source}")
|
||||
log(f"Adding {source}")
|
||||
zf.write(source, name)
|
||||
print(f"Created {path}")
|
||||
log(f"Created {path}")
|
||||
create_zip()
|
||||
print("Done with all files generation.")
|
||||
log("Done with all files generation.")
|
||||
|
||||
print(f"Files have been written to directory: {AMALGAMATE_OUTPUT_PATH}/")
|
||||
print(subprocess.run(['ls', '-la', AMAL_C, AMAL_H, DEMOCPP, README],
|
||||
stdout=subprocess.PIPE).stdout.decode('utf-8').strip())
|
||||
print("Done with all files generation.")
|
||||
log(f"Files have been written to directory: {AMALGAMATE_OUTPUT_PATH}/")
|
||||
log(subprocess.run(['ls', '-la', AMAL_C, AMAL_H, DEMOCPP, README],
|
||||
stdout=subprocess.PIPE).stdout.decode('utf-8').strip())
|
||||
log("Done with all files generation.")
|
||||
|
||||
|
||||
|
||||
@@ -664,7 +788,7 @@ print("Done with all files generation.")
|
||||
# Instructions to create demo
|
||||
#
|
||||
|
||||
print("\nGiving final instructions:")
|
||||
log("\nGiving final instructions:")
|
||||
with open(README) as r:
|
||||
for line in r:
|
||||
print(line)
|
||||
log(line)
|
||||
|
||||
@@ -8,6 +8,7 @@ if(SIMDJSON_STATIC_REFLECTION)
|
||||
add_cpp_test(static_reflection_edge_cases_tests LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(static_reflection_enum_tests LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(static_reflection_fractured_json_tests LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(static_reflection_annotations_tests LABELS ondemand acceptance per_implementation)
|
||||
endif(SIMDJSON_STATIC_REFLECTION)
|
||||
# Copy the simdjson dll into the tests directory
|
||||
if(MSVC AND BUILD_SHARED_LIBS)
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#include "simdjson.h"
|
||||
#include "test_builder.h"
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@@ -17,28 +20,27 @@ struct Car {
|
||||
std::vector<double> tire_pressure;
|
||||
}; // Car
|
||||
|
||||
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS
|
||||
struct Car2549 {
|
||||
std::string make;
|
||||
std::string model;
|
||||
int64_t year;
|
||||
std::vector<float> tire_pressure;
|
||||
std::string make;
|
||||
std::string model;
|
||||
int64_t year;
|
||||
std::vector<float> tire_pressure;
|
||||
};
|
||||
namespace simdjson {
|
||||
// we intentionally pass by non-const reference to car.
|
||||
template <typename builder_type>
|
||||
void tag_invoke(serialize_tag, builder_type& builder, Car2549& 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();
|
||||
}
|
||||
// we intentionally pass by non-const reference to car.
|
||||
template <typename builder_type>
|
||||
void tag_invoke(serialize_tag, builder_type &builder, Car2549 &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
|
||||
|
||||
static_assert(simdjson::require_custom_serialization<Car2549>);
|
||||
@@ -160,6 +162,140 @@ bool append_float() {
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
#if SIMDJSON_ENABLE_NAN_INF
|
||||
bool append_nan() {
|
||||
TEST_START();
|
||||
simdjson::builder::string_builder sb;
|
||||
sb.append(std::numeric_limits<double>::quiet_NaN());
|
||||
std::string_view p;
|
||||
ASSERT_SUCCESS(sb.view().get(p));
|
||||
ASSERT_EQUAL(p, "NaN");
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool append_positive_infinity() {
|
||||
TEST_START();
|
||||
simdjson::builder::string_builder sb;
|
||||
sb.append(std::numeric_limits<double>::infinity());
|
||||
std::string_view p;
|
||||
ASSERT_SUCCESS(sb.view().get(p));
|
||||
ASSERT_EQUAL(p, "Infinity");
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool append_negative_infinity() {
|
||||
TEST_START();
|
||||
simdjson::builder::string_builder sb;
|
||||
sb.append(-std::numeric_limits<double>::infinity());
|
||||
std::string_view p;
|
||||
ASSERT_SUCCESS(sb.view().get(p));
|
||||
ASSERT_EQUAL(p, "-Infinity");
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool append_float_nan_inf() {
|
||||
TEST_START();
|
||||
{
|
||||
simdjson::builder::string_builder sb;
|
||||
sb.append(std::numeric_limits<float>::quiet_NaN());
|
||||
std::string_view p;
|
||||
ASSERT_SUCCESS(sb.view().get(p));
|
||||
ASSERT_EQUAL(p, "NaN");
|
||||
}
|
||||
{
|
||||
simdjson::builder::string_builder sb;
|
||||
sb.append(std::numeric_limits<float>::infinity());
|
||||
std::string_view p;
|
||||
ASSERT_SUCCESS(sb.view().get(p));
|
||||
ASSERT_EQUAL(p, "Infinity");
|
||||
}
|
||||
{
|
||||
simdjson::builder::string_builder sb;
|
||||
sb.append(-std::numeric_limits<float>::infinity());
|
||||
std::string_view p;
|
||||
ASSERT_SUCCESS(sb.view().get(p));
|
||||
ASSERT_EQUAL(p, "-Infinity");
|
||||
}
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool nan_inf_in_array() {
|
||||
TEST_START();
|
||||
simdjson::builder::string_builder sb;
|
||||
sb.start_array();
|
||||
sb.append(1.5);
|
||||
sb.append_comma();
|
||||
sb.append(std::numeric_limits<double>::quiet_NaN());
|
||||
sb.append_comma();
|
||||
sb.append(std::numeric_limits<double>::infinity());
|
||||
sb.append_comma();
|
||||
sb.append(-std::numeric_limits<double>::infinity());
|
||||
sb.append_comma();
|
||||
sb.append(2.5);
|
||||
sb.end_array();
|
||||
std::string_view p;
|
||||
ASSERT_SUCCESS(sb.view().get(p));
|
||||
ASSERT_EQUAL(p, "[1.5,NaN,Infinity,-Infinity,2.5]");
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool nan_inf_in_object() {
|
||||
TEST_START();
|
||||
simdjson::builder::string_builder sb;
|
||||
sb.start_object();
|
||||
sb.append_key_value("a", std::numeric_limits<double>::quiet_NaN());
|
||||
sb.append_comma();
|
||||
sb.append_key_value("b", std::numeric_limits<double>::infinity());
|
||||
sb.append_comma();
|
||||
sb.append_key_value("c", -std::numeric_limits<double>::infinity());
|
||||
sb.end_object();
|
||||
std::string_view p;
|
||||
ASSERT_SUCCESS(sb.view().get(p));
|
||||
ASSERT_EQUAL(p, "{\"a\":NaN,\"b\":Infinity,\"c\":-Infinity}");
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool nan_inf_roundtrip() {
|
||||
TEST_START();
|
||||
simdjson::builder::string_builder sb;
|
||||
sb.start_array();
|
||||
sb.append(std::numeric_limits<double>::quiet_NaN());
|
||||
sb.append_comma();
|
||||
sb.append(std::numeric_limits<double>::infinity());
|
||||
sb.append_comma();
|
||||
sb.append(-std::numeric_limits<double>::infinity());
|
||||
sb.end_array();
|
||||
std::string_view p;
|
||||
ASSERT_SUCCESS(sb.view().get(p));
|
||||
|
||||
simdjson::padded_string output{p};
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::dom::element doc;
|
||||
ASSERT_SUCCESS(parser.parse(output).get(doc));
|
||||
simdjson::dom::array arr;
|
||||
ASSERT_SUCCESS(doc.get_array().get(arr));
|
||||
|
||||
std::array<double, 3> expected{
|
||||
std::numeric_limits<double>::quiet_NaN(),
|
||||
std::numeric_limits<double>::infinity(),
|
||||
-std::numeric_limits<double>::infinity(),
|
||||
};
|
||||
size_t index = 0;
|
||||
for (auto val : arr) {
|
||||
double parsed;
|
||||
ASSERT_SUCCESS(val.get_double().get(parsed));
|
||||
if (std::isnan(expected[index])) {
|
||||
ASSERT_TRUE(std::isnan(parsed));
|
||||
} else {
|
||||
ASSERT_EQUAL(parsed, expected[index]);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
ASSERT_EQUAL(index, expected.size());
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
#endif // SIMDJSON_ENABLE_NAN_INF
|
||||
|
||||
bool append_null() {
|
||||
TEST_START();
|
||||
simdjson::builder::string_builder sb;
|
||||
@@ -459,14 +595,15 @@ bool car_test() {
|
||||
bool issue2549() {
|
||||
TEST_START();
|
||||
simdjson::builder::string_builder sb;
|
||||
Car2549 c = { "Toyota", "Corolla", 2017, {1.0f,2.0f,3.0f} };
|
||||
Car2549 c = {"Toyota", "Corolla", 2017, {1.0f, 2.0f, 3.0f}};
|
||||
sb.start_object();
|
||||
sb.append_key_value("car", c);
|
||||
sb.end_object();
|
||||
std::string_view p;
|
||||
auto result = sb.view().get(p);
|
||||
ASSERT_SUCCESS(result);
|
||||
ASSERT_EQUAL(p, "{\"car\":{\"make\":\"Toyota\",\"model\":\"Corolla\",\"year\":2017,\"tire_pressure\":[1.0,2.0,3.0]}}");
|
||||
ASSERT_EQUAL(p, "{\"car\":{\"make\":\"Toyota\",\"model\":\"Corolla\","
|
||||
"\"year\":2017,\"tire_pressure\":[1.0,2.0,3.0]}}");
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
@@ -661,6 +798,11 @@ bool run() {
|
||||
issue2549() && car_test_template() && serialize_optional() &&
|
||||
#endif
|
||||
append_char() && append_integer() && append_float() && append_null() &&
|
||||
#if SIMDJSON_ENABLE_NAN_INF
|
||||
append_nan() && append_positive_infinity() &&
|
||||
append_negative_infinity() && append_float_nan_inf() &&
|
||||
nan_inf_in_array() && nan_inf_in_object() && nan_inf_roundtrip() &&
|
||||
#endif
|
||||
clear() && escape_and_append() && escape_and_append_with_quotes() &&
|
||||
append_raw() && raw_with_length() && string_convertion() &&
|
||||
buffer_growth() && unicode_validation() && true;
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
#include "simdjson.h"
|
||||
#include "test_builder.h"
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
using namespace simdjson;
|
||||
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
|
||||
struct RenamedFields {
|
||||
[[= simdjson::rename<"first_name">]] std::string firstName = "";
|
||||
[[= simdjson::rename<"last_name">]] std::string lastName = "";
|
||||
int age = 0;
|
||||
};
|
||||
|
||||
struct SkippedField {
|
||||
std::string name = "";
|
||||
[[= simdjson::skip]] int internalCache = 0;
|
||||
};
|
||||
|
||||
struct MixedAnnotations {
|
||||
[[= simdjson::rename<"user_name">]] std::string userName = "";
|
||||
[[= simdjson::skip]] int sessionToken = 0;
|
||||
int age = 0;
|
||||
};
|
||||
|
||||
#endif // SIMDJSON_STATIC_REFLECTION
|
||||
|
||||
namespace annotation_tests {
|
||||
|
||||
bool rename_serialize_test() {
|
||||
TEST_START();
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
RenamedFields r{"Alice", "Smith", 30};
|
||||
std::string out;
|
||||
ASSERT_SUCCESS(simdjson::to_json(r).get(out));
|
||||
ASSERT_EQUAL(out, "{\"first_name\":\"Alice\",\"last_name\":\"Smith\",\"age\":30}");
|
||||
#endif
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool rename_deserialize_test() {
|
||||
TEST_START();
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
std::string json = R"({"first_name":"Bob","last_name":"Jones","age":25})";
|
||||
simdjson::ondemand::parser parser;
|
||||
simdjson::ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(simdjson::pad(json)).get(doc));
|
||||
RenamedFields r;
|
||||
ASSERT_SUCCESS(doc.get<RenamedFields>().get(r));
|
||||
ASSERT_EQUAL(r.firstName, "Bob");
|
||||
ASSERT_EQUAL(r.lastName, "Jones");
|
||||
ASSERT_EQUAL(r.age, 25);
|
||||
#endif
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool rename_roundtrip_test() {
|
||||
TEST_START();
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
RenamedFields original{"Carol", "White", 40};
|
||||
std::string json;
|
||||
ASSERT_SUCCESS(simdjson::to_json(original).get(json));
|
||||
simdjson::ondemand::parser parser;
|
||||
simdjson::ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(simdjson::pad(json)).get(doc));
|
||||
RenamedFields result;
|
||||
ASSERT_SUCCESS(doc.get<RenamedFields>().get(result));
|
||||
ASSERT_EQUAL(result.firstName, original.firstName);
|
||||
ASSERT_EQUAL(result.lastName, original.lastName);
|
||||
ASSERT_EQUAL(result.age, original.age);
|
||||
#endif
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool skip_serialize_test() {
|
||||
TEST_START();
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
SkippedField s{"Alice", 999};
|
||||
std::string out;
|
||||
ASSERT_SUCCESS(simdjson::to_json(s).get(out));
|
||||
ASSERT_EQUAL(out, "{\"name\":\"Alice\"}");
|
||||
#endif
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool skip_deserialize_ignores_field_test() {
|
||||
TEST_START();
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
// The key "internalCache" is present in JSON but the field is annotated skip -
|
||||
// it should be ignored and the field should keep its default value.
|
||||
std::string json = R"({"name":"Carol","internalCache":999})";
|
||||
simdjson::ondemand::parser parser;
|
||||
simdjson::ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(simdjson::pad(json)).get(doc));
|
||||
SkippedField s;
|
||||
ASSERT_SUCCESS(doc.get<SkippedField>().get(s));
|
||||
ASSERT_EQUAL(s.name, "Carol");
|
||||
ASSERT_EQUAL(s.internalCache, 0);
|
||||
#endif
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool mixed_annotations_serialize_test() {
|
||||
TEST_START();
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
MixedAnnotations m{"dave", 12345, 28};
|
||||
std::string out;
|
||||
ASSERT_SUCCESS(simdjson::to_json(m).get(out));
|
||||
// sessionToken must not appear; userName must appear as "user_name"
|
||||
ASSERT_EQUAL(out, "{\"user_name\":\"dave\",\"age\":28}");
|
||||
#endif
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool mixed_annotations_deserialize_test() {
|
||||
TEST_START();
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
std::string json = R"({"user_name":"eve","age":35})";
|
||||
simdjson::ondemand::parser parser;
|
||||
simdjson::ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(simdjson::pad(json)).get(doc));
|
||||
MixedAnnotations m;
|
||||
ASSERT_SUCCESS(doc.get<MixedAnnotations>().get(m));
|
||||
ASSERT_EQUAL(m.userName, "eve");
|
||||
ASSERT_EQUAL(m.age, 35);
|
||||
ASSERT_EQUAL(m.sessionToken, 0);
|
||||
#endif
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool run_all() {
|
||||
return rename_serialize_test()
|
||||
&& rename_deserialize_test()
|
||||
&& rename_roundtrip_test()
|
||||
&& skip_serialize_test()
|
||||
&& skip_deserialize_ignores_field_test()
|
||||
&& mixed_annotations_serialize_test()
|
||||
&& mixed_annotations_deserialize_test();
|
||||
}
|
||||
|
||||
} // namespace annotation_tests
|
||||
|
||||
int main() {
|
||||
return annotation_tests::run_all() ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
+163
-2
@@ -1,7 +1,9 @@
|
||||
#include "simdjson.h"
|
||||
#include "test_macros.h"
|
||||
#include "test_main.h"
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
|
||||
using namespace simdjson;
|
||||
@@ -25,8 +27,10 @@ bool parse_nan() {
|
||||
|
||||
bool parse_infinity() {
|
||||
TEST_START();
|
||||
for (auto json_str :
|
||||
{"infinity", "Infinity", "INFINITY", "inf", "Inf", "INF"}) {
|
||||
for (auto json_str : {"infinity", "Infinity", "INFINITY", "inf", "Inf", "INF",
|
||||
// Check that 'Inf' parses correctly even when padded to
|
||||
// the same length as 'Infinity'
|
||||
"inf ", "Inf ", "INF "}) {
|
||||
dom::parser parser;
|
||||
dom::element doc;
|
||||
ASSERT_SUCCESS(
|
||||
@@ -280,6 +284,154 @@ bool reject_truncated_atoms() {
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// DOM printer (to_string / minify / prettify) tests. When NaN/Infinity
|
||||
// parsing is enabled, the writer must emit the same literals on output so
|
||||
// that round-tripping through the parser preserves the value.
|
||||
|
||||
bool print_nan() {
|
||||
TEST_START();
|
||||
dom::parser parser;
|
||||
dom::element doc;
|
||||
ASSERT_SUCCESS(parser.parse("NaN"_padded).get(doc));
|
||||
ASSERT_EQUAL(simdjson::to_string(doc), "NaN");
|
||||
ASSERT_EQUAL(simdjson::minify(doc), "NaN");
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool print_infinity() {
|
||||
TEST_START();
|
||||
dom::parser parser;
|
||||
dom::element doc;
|
||||
ASSERT_SUCCESS(parser.parse("Infinity"_padded).get(doc));
|
||||
ASSERT_EQUAL(simdjson::to_string(doc), "Infinity");
|
||||
ASSERT_EQUAL(simdjson::minify(doc), "Infinity");
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool print_negative_infinity() {
|
||||
TEST_START();
|
||||
dom::parser parser;
|
||||
dom::element doc;
|
||||
ASSERT_SUCCESS(parser.parse("-Infinity"_padded).get(doc));
|
||||
ASSERT_EQUAL(simdjson::to_string(doc), "-Infinity");
|
||||
ASSERT_EQUAL(simdjson::minify(doc), "-Infinity");
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool print_nan_in_array() {
|
||||
TEST_START();
|
||||
dom::parser parser;
|
||||
dom::element doc;
|
||||
ASSERT_SUCCESS(
|
||||
parser.parse("[1.5, NaN, Infinity, -Infinity, 2.5]"_padded).get(doc));
|
||||
ASSERT_EQUAL(simdjson::to_string(doc), "[1.5,NaN,Infinity,-Infinity,2.5]");
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool print_nan_in_object() {
|
||||
TEST_START();
|
||||
dom::parser parser;
|
||||
dom::element doc;
|
||||
ASSERT_SUCCESS(
|
||||
parser.parse(R"({"a": NaN, "b": Infinity, "c": -Infinity})"_padded)
|
||||
.get(doc));
|
||||
ASSERT_EQUAL(simdjson::to_string(doc),
|
||||
"{\"a\":NaN,\"b\":Infinity,\"c\":-Infinity}");
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool print_roundtrip() {
|
||||
TEST_START();
|
||||
dom::parser parser;
|
||||
dom::element doc;
|
||||
ASSERT_SUCCESS(parser.parse("[NaN, Infinity, -Infinity]"_padded).get(doc));
|
||||
std::string serialized = simdjson::to_string(doc);
|
||||
|
||||
dom::parser parser2;
|
||||
dom::element doc2;
|
||||
ASSERT_SUCCESS(parser2.parse(padded_string(serialized)).get(doc2));
|
||||
dom::array arr;
|
||||
ASSERT_SUCCESS(doc2.get_array().get(arr));
|
||||
|
||||
std::array<double, 3> expected{
|
||||
std::numeric_limits<double>::quiet_NaN(),
|
||||
std::numeric_limits<double>::infinity(),
|
||||
-std::numeric_limits<double>::infinity(),
|
||||
};
|
||||
size_t index = 0;
|
||||
for (auto val : arr) {
|
||||
double parsed;
|
||||
ASSERT_SUCCESS(val.get_double().get(parsed));
|
||||
if (std::isnan(expected[index])) {
|
||||
ASSERT_TRUE(std::isnan(parsed));
|
||||
} else {
|
||||
ASSERT_EQUAL(parsed, expected[index]);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
ASSERT_EQUAL(index, expected.size());
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// FracturedJson aligns values into columns in table mode. The column width
|
||||
// is driven by the estimator for unseen elements and by measure_value_length
|
||||
// for the chosen cells; if either undercounts NaN/Infinity, column 1's
|
||||
// padding won't match column 2's emitted width and rows visibly misalign.
|
||||
// Force table mode with min_table_rows = 2 and max_inline_length = 0.
|
||||
|
||||
bool table_aligns_nan() {
|
||||
TEST_START();
|
||||
dom::parser parser;
|
||||
dom::element doc;
|
||||
ASSERT_SUCCESS(
|
||||
parser.parse(R"([{"a": 1, "b": 1},{"a": NaN, "b": 1}])"_padded).get(doc));
|
||||
fractured_json_options opts;
|
||||
opts.min_table_rows = 2;
|
||||
opts.max_inline_length = 0;
|
||||
ASSERT_EQUAL(simdjson::fractured_json(doc, opts),
|
||||
"[\n"
|
||||
" { \"a\": 1 , \"b\": 1 },\n"
|
||||
" { \"a\": NaN, \"b\": 1 }\n"
|
||||
"]");
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool table_aligns_inf() {
|
||||
TEST_START();
|
||||
dom::parser parser;
|
||||
dom::element doc;
|
||||
ASSERT_SUCCESS(
|
||||
parser.parse(R"([{"a": 1, "b": 1},{"a": Infinity, "b": 1}])"_padded)
|
||||
.get(doc));
|
||||
fractured_json_options opts;
|
||||
opts.min_table_rows = 2;
|
||||
opts.max_inline_length = 0;
|
||||
ASSERT_EQUAL(simdjson::fractured_json(doc, opts),
|
||||
"[\n"
|
||||
" { \"a\": 1 , \"b\": 1 },\n"
|
||||
" { \"a\": Infinity, \"b\": 1 }\n"
|
||||
"]");
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool table_aligns_neg_inf() {
|
||||
TEST_START();
|
||||
dom::parser parser;
|
||||
dom::element doc;
|
||||
ASSERT_SUCCESS(
|
||||
parser.parse(R"([{"a": 1, "b": 1},{"a": -Infinity, "b": 1}])"_padded)
|
||||
.get(doc));
|
||||
fractured_json_options opts;
|
||||
opts.min_table_rows = 2;
|
||||
opts.max_inline_length = 0;
|
||||
ASSERT_EQUAL(simdjson::fractured_json(doc, opts),
|
||||
"[\n"
|
||||
" { \"a\": 1 , \"b\": 1 },\n"
|
||||
" { \"a\": -Infinity, \"b\": 1 }\n"
|
||||
"]");
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool run() {
|
||||
return parse_nan() //
|
||||
&& parse_infinity() //
|
||||
@@ -291,6 +443,15 @@ bool run() {
|
||||
&& reject_trailing_junk() //
|
||||
&& reject_similar_prefix() //
|
||||
&& reject_truncated_atoms() //
|
||||
&& print_nan() //
|
||||
&& print_infinity() //
|
||||
&& print_negative_infinity() //
|
||||
&& print_nan_in_array() //
|
||||
&& print_nan_in_object() //
|
||||
&& print_roundtrip() //
|
||||
&& table_aligns_nan() //
|
||||
&& table_aligns_inf() //
|
||||
&& table_aligns_neg_inf() //
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
@@ -257,6 +257,7 @@ int main() {
|
||||
&& json_pointer_failure_test(TEST_JSON, "/~01abc", NO_SUCH_FIELD) // Test that we don't try to compare the literal key
|
||||
&& json_pointer_failure_test(TEST_JSON, "/~1~001abc/01", INVALID_JSON_POINTER) // Leading 0 in integer index
|
||||
&& json_pointer_failure_test(TEST_JSON, "/~1~001abc/", INVALID_JSON_POINTER) // Empty index to array
|
||||
&& json_pointer_failure_test(TEST_JSON, "/~1~001abc/18446744073709551616", INDEX_OUT_OF_BOUNDS) // Overflowed index
|
||||
&& json_pointer_failure_test(TEST_JSON, "/~1~001abc/-", INDEX_OUT_OF_BOUNDS) // End index is always out of bounds
|
||||
) {
|
||||
std::cout << "Success!" << std::endl;
|
||||
|
||||
@@ -26,6 +26,7 @@ add_cpp_test(ondemand_nan_inf_tests LABELS ondemand acceptance
|
||||
add_cpp_test(ondemand_number_tests LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(ondemand_number_in_string_tests LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(ondemand_object_tests LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(ondemand_object_find_field_tests LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(ondemand_object_error_tests LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(ondemand_ordering_tests LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(ondemand_parse_api_tests LABELS ondemand acceptance per_implementation)
|
||||
@@ -37,13 +38,17 @@ add_cpp_test(ondemand_wrong_type_error_tests LABELS ondemand acceptance
|
||||
add_cpp_test(ondemand_iterate_many_csv LABELS ondemand acceptance per_implementation)
|
||||
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)
|
||||
# Reflective deserialization through the experimental key-selector path. The
|
||||
# SIMDJSON_USE_KEY_SELECTOR_REFLECTION macro is defined at the top of the test
|
||||
# source itself, so this is an ordinary test target.
|
||||
add_cpp_test(ondemand_key_selector_reflection_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)
|
||||
add_cpp_test(ondemand_unknown_tests LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(ondemand_wildcard_tests LABELS ondemand acceptance per_implementation)
|
||||
if(NOT SIMDJSON_SANITIZE)
|
||||
add_cpp_test(ondemand_padded_input LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(ondemand_cacheline LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(ondemand_padded_input LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(ondemand_cacheline LABELS ondemand acceptance per_implementation)
|
||||
endif()
|
||||
|
||||
# Add the tests if we're on:
|
||||
|
||||
@@ -243,6 +243,28 @@ namespace json_path_tests {
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
bool incomplete_string_value_with_allow_incomplete_json() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
std::string_view val;
|
||||
|
||||
auto incomplete_string_value = R"({"key":")"_padded;
|
||||
auto complete_string_before_incomplete_value = R"({"key":"ok","incomplete":")"_padded;
|
||||
|
||||
ASSERT_SUCCESS(parser.iterate_allow_incomplete_json(incomplete_string_value).get(doc));
|
||||
ASSERT_ERROR(doc.at_path(".key").get_string().get(val), simdjson::STRING_ERROR);
|
||||
|
||||
ASSERT_SUCCESS(parser.iterate_allow_incomplete_json(complete_string_before_incomplete_value).get(doc));
|
||||
ASSERT_SUCCESS(doc.at_path(".key").get_string().get(val));
|
||||
ASSERT_EQUAL(val, "ok");
|
||||
|
||||
ASSERT_SUCCESS(parser.iterate_allow_incomplete_json(complete_string_before_incomplete_value).get(doc));
|
||||
ASSERT_ERROR(doc.at_path(".incomplete").get_string().get(val), simdjson::STRING_ERROR);
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
#endif
|
||||
|
||||
bool many_json_paths_object_array() {
|
||||
TEST_START();
|
||||
@@ -442,6 +464,9 @@ namespace json_path_tests {
|
||||
many_json_paths_object_array() &&
|
||||
many_json_paths_with_prefix() &&
|
||||
run_broken_tests() &&
|
||||
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
incomplete_string_value_with_allow_incomplete_json() &&
|
||||
#endif
|
||||
json_path_invalidation() &&
|
||||
demo_test() &&
|
||||
demo_relative_path() &&
|
||||
|
||||
@@ -509,6 +509,7 @@ namespace json_pointer_tests {
|
||||
run_failure_test(TEST_JSON, "/~01abc", NO_SUCH_FIELD) &&
|
||||
run_failure_test(TEST_JSON, "/~1~001abc/01", INVALID_JSON_POINTER) &&
|
||||
run_failure_test(TEST_JSON, "/~1~001abc/", INVALID_JSON_POINTER) &&
|
||||
run_failure_test(TEST_JSON, "/~1~001abc/18446744073709551616", INDEX_OUT_OF_BOUNDS) &&
|
||||
run_failure_test(TEST_JSON, "/~1~001abc/-", INDEX_OUT_OF_BOUNDS) &&
|
||||
many_json_pointers() &&
|
||||
document_as_scalar() &&
|
||||
@@ -518,4 +519,4 @@ namespace json_pointer_tests {
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
return test_main(argc, argv, json_pointer_tests::run);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
// Exercises the experimental key-selector-based reflective deserializer.
|
||||
//
|
||||
// We enable the option here (before including simdjson) so the reflective
|
||||
// tag_invoke uses a compile-time key_selector + object::for_each single pass
|
||||
// instead of one obj[key] lookup per member. The point of the option is to
|
||||
// deserialize structs correctly even when the JSON keys are NOT in declaration
|
||||
// order, so the tests below deliberately scramble the key order.
|
||||
#define SIMDJSON_USE_KEY_SELECTOR_REFLECTION 1
|
||||
#include "simdjson.h"
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include "test_ondemand.h"
|
||||
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace simdjson;
|
||||
|
||||
// Default member initializers keep -Weffc++ happy; reflective deserialization
|
||||
// overwrites these fields from the JSON.
|
||||
struct User {
|
||||
uint64_t id = 0;
|
||||
std::string screen_name{};
|
||||
std::optional<std::string> location{};
|
||||
bool verified = false;
|
||||
};
|
||||
|
||||
struct Tweet {
|
||||
uint64_t id = 0;
|
||||
std::string text{};
|
||||
uint64_t retweet_count = 0;
|
||||
User user{};
|
||||
};
|
||||
|
||||
struct Flat {
|
||||
uint64_t id = 0;
|
||||
std::string text{};
|
||||
uint64_t retweet_count = 0;
|
||||
};
|
||||
|
||||
bool flat_out_of_order() {
|
||||
TEST_START();
|
||||
// Keys deliberately out of declaration order.
|
||||
auto json = R"({ "retweet_count": 7, "id": 12345, "text": "hello world" })"_padded;
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(json).get(doc));
|
||||
Flat f;
|
||||
ASSERT_SUCCESS(doc.get(f));
|
||||
ASSERT_EQUAL(f.id, 12345);
|
||||
ASSERT_EQUAL(f.text, "hello world");
|
||||
ASSERT_EQUAL(f.retweet_count, 7);
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool nested_out_of_order() {
|
||||
TEST_START();
|
||||
// Both the outer and the nested object have scrambled key order, and the
|
||||
// optional "location" is present here.
|
||||
auto json = R"({
|
||||
"user": {
|
||||
"verified": true,
|
||||
"location": "Montreal",
|
||||
"screen_name": "ayuu0123",
|
||||
"id": 1186275104
|
||||
},
|
||||
"retweet_count": 3,
|
||||
"text": "nested",
|
||||
"id": 99
|
||||
})"_padded;
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(json).get(doc));
|
||||
Tweet t;
|
||||
ASSERT_SUCCESS(doc.get(t));
|
||||
ASSERT_EQUAL(t.id, 99);
|
||||
ASSERT_EQUAL(t.text, "nested");
|
||||
ASSERT_EQUAL(t.retweet_count, 3);
|
||||
ASSERT_EQUAL(t.user.id, 1186275104);
|
||||
ASSERT_EQUAL(t.user.screen_name, "ayuu0123");
|
||||
ASSERT_TRUE(t.user.location.has_value());
|
||||
ASSERT_EQUAL(t.user.location.value(), "Montreal");
|
||||
ASSERT_TRUE(t.user.verified);
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool optional_missing() {
|
||||
TEST_START();
|
||||
// "location" is absent: the optional member must be left empty, not error.
|
||||
auto json = R"({
|
||||
"user": { "id": 7, "screen_name": "nobody", "verified": false },
|
||||
"id": 1, "text": "t", "retweet_count": 0
|
||||
})"_padded;
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(json).get(doc));
|
||||
Tweet t;
|
||||
ASSERT_SUCCESS(doc.get(t));
|
||||
ASSERT_EQUAL(t.user.id, 7);
|
||||
ASSERT_EQUAL(t.user.screen_name, "nobody");
|
||||
ASSERT_FALSE(t.user.location.has_value());
|
||||
ASSERT_FALSE(t.user.verified);
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool run() {
|
||||
return flat_out_of_order() &&
|
||||
nested_out_of_order() &&
|
||||
optional_missing() &&
|
||||
true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
return test_main(argc, argv, run);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int main() { return 0; }
|
||||
|
||||
#endif
|
||||
@@ -173,6 +173,177 @@ namespace object_tests {
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS
|
||||
bool object_find_field_key_selector() {
|
||||
TEST_START();
|
||||
auto json = R"({ "name": "John", "age": 30, "city": "New York" })"_padded;
|
||||
using sel_t = ondemand::key_selector<"name", "age", "city">;
|
||||
|
||||
// The selector maps keys to compile-time indices.
|
||||
ASSERT_EQUAL(sel_t::size(), 3);
|
||||
ASSERT_EQUAL(sel_t::key_at(0), "name");
|
||||
ASSERT_EQUAL(sel_t::key_at(1), "age");
|
||||
ASSERT_EQUAL(sel_t::key_at(2), "city");
|
||||
|
||||
SUBTEST("ondemand::object with key_selector for_each", test_ondemand_doc(json, [&](auto doc_result) {
|
||||
ondemand::object object;
|
||||
ASSERT_SUCCESS( doc_result.get(object) );
|
||||
|
||||
std::array<bool, 3> seen{};
|
||||
std::string_view name_val{};
|
||||
uint64_t age_val{};
|
||||
std::string_view city_val{};
|
||||
// for_each only yields indices in [0, size()), so index is always < 3 here.
|
||||
ASSERT_SUCCESS( object.for_each<sel_t>([&](std::size_t index, ondemand::value v) {
|
||||
seen[index] = true;
|
||||
switch (index) {
|
||||
case 0: { std::string_view s; if (!v.get(s)) { name_val = s; } break; }
|
||||
case 1: { uint64_t n; if (!v.get(n)) { age_val = n; } break; }
|
||||
case 2: { std::string_view s; if (!v.get(s)) { city_val = s; } break; }
|
||||
}
|
||||
}) );
|
||||
|
||||
ASSERT_TRUE(seen[0] && seen[1] && seen[2]);
|
||||
ASSERT_EQUAL(name_val, "John");
|
||||
ASSERT_EQUAL(age_val, 30);
|
||||
ASSERT_EQUAL(city_val, "New York");
|
||||
|
||||
return true;
|
||||
}));
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Regression test for the for_each callback error contract: when the callback
|
||||
// returns an error_code, for_each stops at the first non-SUCCESS result and
|
||||
// returns it (so a value-parse error is no longer silently dropped).
|
||||
bool object_for_each_callback_error() {
|
||||
TEST_START();
|
||||
using sel_t = ondemand::key_selector<"id", "name">;
|
||||
auto json = R"({ "id": 7, "name": "Daniel" })"_padded;
|
||||
|
||||
// Happy path: an error_code-returning callback extracts the values and
|
||||
// for_each returns SUCCESS.
|
||||
SUBTEST("error_code callback success", test_ondemand_doc(json, [&](auto doc_result) {
|
||||
ondemand::object object;
|
||||
ASSERT_SUCCESS( doc_result.get(object) );
|
||||
uint64_t id{};
|
||||
std::string_view name{};
|
||||
ASSERT_SUCCESS( object.for_each<sel_t>([&](std::size_t index, ondemand::value v) -> simdjson::error_code {
|
||||
switch (index) {
|
||||
case 0: { return v.get(id); }
|
||||
case 1: { return v.get(name); }
|
||||
default: { return SUCCESS; }
|
||||
}
|
||||
}) );
|
||||
ASSERT_EQUAL(id, 7);
|
||||
ASSERT_EQUAL(name, "Daniel");
|
||||
return true;
|
||||
}));
|
||||
|
||||
// Error path: a value-parse error inside the callback (here, reading the
|
||||
// integer "id" as a string) is propagated by for_each.
|
||||
SUBTEST("error_code callback propagates error", test_ondemand_doc(json, [&](auto doc_result) {
|
||||
ondemand::object object;
|
||||
ASSERT_SUCCESS( doc_result.get(object) );
|
||||
ASSERT_ERROR( object.for_each<sel_t>([&](std::size_t index, ondemand::value v) -> simdjson::error_code {
|
||||
std::string_view s;
|
||||
if (index == 0) { return v.get(s); } // wrong type on purpose
|
||||
return SUCCESS;
|
||||
}), INCORRECT_TYPE );
|
||||
return true;
|
||||
}));
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS && SIMDJSON_SUPPORTS_CONCEPTS
|
||||
// Mirrors doc/basics.md "Key selectors", Example 1 (top-level fields).
|
||||
bool key_selector_example_toplevel() {
|
||||
TEST_START();
|
||||
auto json = R"({ "name": "Daniel", "age": 42, "city": "Montreal" })"_padded;
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc = parser.iterate(json);
|
||||
ondemand::object obj = doc.get_object();
|
||||
|
||||
using fields = ondemand::key_selector<"name", "city">;
|
||||
|
||||
std::string_view name, city;
|
||||
obj.for_each<fields>([&](std::size_t i, ondemand::value v) {
|
||||
switch (i) {
|
||||
case 0: name = std::string_view(v); break; // "name"
|
||||
case 1: city = std::string_view(v); break; // "city"
|
||||
}
|
||||
});
|
||||
ASSERT_EQUAL(name, "Daniel");
|
||||
ASSERT_EQUAL(city, "Montreal");
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Mirrors doc/basics.md "Key selectors", Example 2 (nested object).
|
||||
bool key_selector_example_nested() {
|
||||
TEST_START();
|
||||
auto json = R"({ "user": { "id": 1186275104, "screen_name": "ayuu0123", "verified": false } })"_padded;
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc = parser.iterate(json);
|
||||
ondemand::object user = doc.find_field("user").get_object();
|
||||
|
||||
using user_fields = ondemand::key_selector<"id", "screen_name">;
|
||||
|
||||
uint64_t id = 0;
|
||||
std::string_view handle;
|
||||
user.for_each<user_fields>([&](std::size_t i, ondemand::value v) {
|
||||
switch (i) {
|
||||
case 0: id = uint64_t(v); break; // "id"
|
||||
case 1: handle = std::string_view(v); break; // "screen_name"
|
||||
}
|
||||
});
|
||||
ASSERT_EQUAL(id, 1186275104);
|
||||
ASSERT_EQUAL(handle, "ayuu0123");
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
// Mirrors doc/basics.md "Key selectors", Example 3 (a selected value that is
|
||||
// itself an object, processed with a nested for_each).
|
||||
bool key_selector_example_inner_object() {
|
||||
TEST_START();
|
||||
auto json = R"({
|
||||
"id": 42,
|
||||
"author": { "name": "Daniel", "handle": "lemire" },
|
||||
"title": "On Demand"
|
||||
})"_padded;
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc = parser.iterate(json);
|
||||
ondemand::object obj = doc.get_object();
|
||||
|
||||
using post_fields = ondemand::key_selector<"id", "author", "title">;
|
||||
using author_fields = ondemand::key_selector<"name", "handle">;
|
||||
|
||||
uint64_t id = 0;
|
||||
std::string_view title, author_name, author_handle;
|
||||
obj.for_each<post_fields>([&](std::size_t i, ondemand::value v) {
|
||||
switch (i) {
|
||||
case 0: id = uint64_t(v); break; // "id"
|
||||
case 1: { // "author" is itself an object
|
||||
ondemand::object author = v.get_object();
|
||||
author.for_each<author_fields>([&](std::size_t j, ondemand::value av) {
|
||||
switch (j) {
|
||||
case 0: author_name = std::string_view(av); break; // "name"
|
||||
case 1: author_handle = std::string_view(av); break; // "handle"
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 2: title = std::string_view(v); break; // "title"
|
||||
}
|
||||
});
|
||||
ASSERT_EQUAL(id, 42);
|
||||
ASSERT_EQUAL(title, "On Demand");
|
||||
ASSERT_EQUAL(author_name, "Daniel");
|
||||
ASSERT_EQUAL(author_handle, "lemire");
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
#endif
|
||||
|
||||
bool run() {
|
||||
return
|
||||
object_find_field_unordered() &&
|
||||
@@ -181,6 +352,15 @@ namespace object_tests {
|
||||
object_find_field() &&
|
||||
document_object_find_field() &&
|
||||
value_object_find_field() &&
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS
|
||||
object_find_field_key_selector() &&
|
||||
object_for_each_callback_error() &&
|
||||
#endif
|
||||
#if SIMDJSON_EXCEPTIONS && SIMDJSON_SUPPORTS_CONCEPTS
|
||||
key_selector_example_toplevel() &&
|
||||
key_selector_example_nested() &&
|
||||
key_selector_example_inner_object() &&
|
||||
#endif
|
||||
true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user