mirror of
https://github.com/simdjson/simdjson
synced 2026-06-08 17:27:07 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cd4a074653 | |||
| 0ab7e2d701 | |||
| 0a694273e2 |
@@ -1,26 +0,0 @@
|
||||
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)
|
||||
@@ -1,88 +0,0 @@
|
||||
#ifndef GLAZE_CITM_CATALOG_DATA_H
|
||||
#define GLAZE_CITM_CATALOG_DATA_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <glaze/glaze.hpp>
|
||||
|
||||
// Glaze-specific shadow types. We mirror the Rust serde struct here (rather
|
||||
// than the C++ CitmCatalog) so that fields the source JSON encodes as `null`
|
||||
// (e.g. CITMEvent.name) parse cleanly into std::optional. This matches what
|
||||
// the Rust benchmark does, and the resulting JSON output volume is therefore
|
||||
// directly comparable to the Rust numbers.
|
||||
|
||||
struct GlazeCITMPrice {
|
||||
uint64_t amount;
|
||||
uint64_t audienceSubCategoryId;
|
||||
uint64_t seatCategoryId;
|
||||
};
|
||||
|
||||
struct GlazeCITMArea {
|
||||
uint64_t areaId;
|
||||
std::vector<uint64_t> blockIds;
|
||||
};
|
||||
|
||||
struct GlazeCITMSeatCategory {
|
||||
std::vector<GlazeCITMArea> areas;
|
||||
uint64_t seatCategoryId;
|
||||
};
|
||||
|
||||
struct GlazeCITMPerformance {
|
||||
uint64_t id;
|
||||
uint64_t eventId;
|
||||
std::optional<std::string> logo;
|
||||
std::optional<std::string> name;
|
||||
std::vector<GlazeCITMPrice> prices;
|
||||
std::vector<GlazeCITMSeatCategory> seatCategories;
|
||||
std::optional<std::string> seatMapImage;
|
||||
uint64_t start;
|
||||
std::string venueCode;
|
||||
};
|
||||
|
||||
struct GlazeCITMEvent {
|
||||
uint64_t id;
|
||||
std::optional<std::string> name;
|
||||
std::optional<std::string> description;
|
||||
std::optional<std::string> logo;
|
||||
std::vector<uint64_t> subTopicIds;
|
||||
std::optional<std::string> subjectCode;
|
||||
std::optional<std::string> subtitle;
|
||||
std::vector<uint64_t> topicIds;
|
||||
};
|
||||
|
||||
struct GlazeCitmCatalog {
|
||||
std::map<std::string, GlazeCITMEvent> events;
|
||||
std::vector<GlazeCITMPerformance> performances;
|
||||
};
|
||||
|
||||
inline GlazeCitmCatalog glaze_deserialize_citm(const std::string &json_str) {
|
||||
GlazeCitmCatalog data;
|
||||
constexpr glz::opts opts{.error_on_unknown_keys = false};
|
||||
auto err = glz::read<opts>(data, json_str);
|
||||
if (err) {
|
||||
throw std::runtime_error("glaze citm parse error: " +
|
||||
glz::format_error(err, json_str));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
inline std::string glaze_serialize_citm(const GlazeCitmCatalog &data) {
|
||||
std::string out;
|
||||
// skip_null_members = false: emit `"field":null` for unset optionals so the
|
||||
// output has the same field count as simdjson's (which writes `"field":""`).
|
||||
// Glaze still produces 4-char `null` vs simdjson's 2-char `""`, so it's not
|
||||
// byte-identical, but the work-per-field is comparable.
|
||||
constexpr glz::opts opts{.skip_null_members = false};
|
||||
auto err = glz::write<opts>(data, out);
|
||||
if (err) {
|
||||
throw std::runtime_error("glaze citm write error");
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
#endif // GLAZE_CITM_CATALOG_DATA_H
|
||||
@@ -1,95 +0,0 @@
|
||||
// Standalone Glaze benchmark for CITM Catalog (see glaze_twitter_bench.cpp
|
||||
// header comment for context).
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include "../citm_catalog_benchmark/glaze_citm_catalog_data.h"
|
||||
#include "../benchmark_utils/benchmark_helper.h"
|
||||
|
||||
namespace {
|
||||
|
||||
std::string read_file(const std::string &filename) {
|
||||
printf("# Reading file %s\n", filename.c_str());
|
||||
constexpr size_t read_size = 65536;
|
||||
std::ifstream stream(filename, std::ios::binary);
|
||||
if (!stream) {
|
||||
std::cerr << "Could not open file: " << filename << std::endl;
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
stream.exceptions(std::ios_base::badbit);
|
||||
std::string out;
|
||||
std::string buf(read_size, '\0');
|
||||
while (stream.read(&buf[0], read_size)) {
|
||||
out.append(buf, 0, size_t(stream.gcount()));
|
||||
}
|
||||
out.append(buf, 0, size_t(stream.gcount()));
|
||||
return out;
|
||||
}
|
||||
|
||||
void bench_glaze_serialization(GlazeCitmCatalog &data) {
|
||||
std::string output = glaze_serialize_citm(data);
|
||||
size_t output_volume = output.size();
|
||||
printf("# output volume: %zu bytes\n", output_volume);
|
||||
|
||||
volatile size_t measured_volume = 0;
|
||||
pretty_print(1, output_volume, "bench_glaze",
|
||||
bench([&data, &measured_volume, &output_volume]() {
|
||||
std::string output = glaze_serialize_citm(data);
|
||||
measured_volume = output.size();
|
||||
if (measured_volume != output_volume) {
|
||||
printf("mismatch\n");
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
void bench_glaze_parsing(const std::string &json_str) {
|
||||
size_t input_volume = json_str.size();
|
||||
printf("# input volume: %zu bytes\n", input_volume);
|
||||
|
||||
volatile bool result = true;
|
||||
pretty_print(1, input_volume, "bench_glaze_parsing",
|
||||
bench([&json_str, &result]() {
|
||||
try {
|
||||
GlazeCitmCatalog data = glaze_deserialize_citm(json_str);
|
||||
result = true;
|
||||
} catch (...) {
|
||||
result = false;
|
||||
printf("parse error\n");
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
const char *json_file = std::getenv("CITM_JSON");
|
||||
if (!json_file) {
|
||||
json_file = "buildreflect/jsonexamples/citm_catalog.json";
|
||||
}
|
||||
if (argc > 1) {
|
||||
json_file = argv[1];
|
||||
}
|
||||
|
||||
std::string json_str = read_file(json_file);
|
||||
|
||||
const char *mode = std::getenv("BENCH_MODE");
|
||||
if (!mode) mode = "all";
|
||||
|
||||
GlazeCitmCatalog data = glaze_deserialize_citm(json_str);
|
||||
|
||||
if (std::string(mode) == "all" || std::string(mode) == "parse") {
|
||||
printf("\n=== Glaze CITM Parsing ===\n");
|
||||
bench_glaze_parsing(json_str);
|
||||
}
|
||||
if (std::string(mode) == "all" || std::string(mode) == "serialize") {
|
||||
printf("\n=== Glaze CITM Serialization ===\n");
|
||||
bench_glaze_serialization(data);
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
// Standalone Glaze benchmark for Twitter, built with g++ instead of the
|
||||
// p2996 clang fork (which crashes on Glaze's heavy template metaprogramming).
|
||||
// Reuses the same data structs and bench() helper as the in-tree benchmarks
|
||||
// so the throughput numbers are directly comparable.
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../twitter_benchmark/twitter_data.h"
|
||||
#include "../twitter_benchmark/glaze_twitter_data.h"
|
||||
#include "../benchmark_utils/benchmark_helper.h"
|
||||
|
||||
namespace {
|
||||
|
||||
std::string read_file(const std::string &filename) {
|
||||
printf("# Reading file %s\n", filename.c_str());
|
||||
constexpr size_t read_size = 4096;
|
||||
std::ifstream stream(filename);
|
||||
if (!stream) {
|
||||
std::cerr << "Could not open file: " << filename << std::endl;
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
stream.exceptions(std::ios_base::badbit);
|
||||
std::string out;
|
||||
std::string buf(read_size, '\0');
|
||||
while (stream.read(&buf[0], read_size)) {
|
||||
out.append(buf, 0, size_t(stream.gcount()));
|
||||
}
|
||||
out.append(buf, 0, size_t(stream.gcount()));
|
||||
return out;
|
||||
}
|
||||
|
||||
void bench_glaze_serialization(TwitterData &data) {
|
||||
std::string output = glaze_serialize(data);
|
||||
size_t output_volume = output.size();
|
||||
printf("# output volume: %zu bytes\n", output_volume);
|
||||
|
||||
volatile size_t measured_volume = 0;
|
||||
pretty_print(1, output_volume, "bench_glaze",
|
||||
bench([&data, &measured_volume, &output_volume]() {
|
||||
std::string output = glaze_serialize(data);
|
||||
measured_volume = output.size();
|
||||
if (measured_volume != output_volume) {
|
||||
printf("mismatch\n");
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
void bench_glaze_parsing(const std::string &json_str) {
|
||||
size_t input_volume = json_str.size();
|
||||
printf("# input volume: %zu bytes\n", input_volume);
|
||||
|
||||
volatile bool result = true;
|
||||
pretty_print(1, input_volume, "bench_glaze_parsing",
|
||||
bench([&json_str, &result]() {
|
||||
try {
|
||||
TwitterData data = glaze_deserialize(json_str);
|
||||
result = true;
|
||||
} catch (...) {
|
||||
result = false;
|
||||
printf("parse error\n");
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
const char *json_file = std::getenv("TWITTER_JSON");
|
||||
if (!json_file) {
|
||||
json_file = "buildreflect/jsonexamples/twitter.json";
|
||||
}
|
||||
if (argc > 1) {
|
||||
json_file = argv[1];
|
||||
}
|
||||
|
||||
std::string json_str = read_file(json_file);
|
||||
|
||||
const char *mode = std::getenv("BENCH_MODE");
|
||||
if (!mode) mode = "all";
|
||||
TwitterData data = glaze_deserialize(json_str);
|
||||
|
||||
if (std::string(mode) == "all" || std::string(mode) == "parse") {
|
||||
printf("\n=== Glaze Twitter Parsing ===\n");
|
||||
bench_glaze_parsing(json_str);
|
||||
}
|
||||
if (std::string(mode) == "all" || std::string(mode) == "serialize") {
|
||||
printf("\n=== Glaze Twitter Serialization ===\n");
|
||||
bench_glaze_serialization(data);
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
#ifndef GLAZE_TWITTER_DATA_H
|
||||
#define GLAZE_TWITTER_DATA_H
|
||||
|
||||
#include "twitter_data.h"
|
||||
#include <glaze/glaze.hpp>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
// Glaze auto-reflects aggregate types whose field names already match JSON
|
||||
// keys (snake_case here matches the JSON), so no glz::meta is required.
|
||||
|
||||
inline TwitterData glaze_deserialize(const std::string &json_str) {
|
||||
TwitterData data;
|
||||
constexpr glz::opts opts{.error_on_unknown_keys = false};
|
||||
auto err = glz::read<opts>(data, json_str);
|
||||
if (err) {
|
||||
throw std::runtime_error("glaze parse error: " + glz::format_error(err, json_str));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
inline std::string glaze_serialize(const TwitterData &data) {
|
||||
std::string out;
|
||||
auto err = glz::write_json(data, out);
|
||||
if (err) {
|
||||
throw std::runtime_error("glaze write error");
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
#endif // GLAZE_TWITTER_DATA_H
|
||||
@@ -103,9 +103,21 @@ 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;
|
||||
SIMDJSON_TRY(internal::parse_json_pointer_array_index(json_pointer, array_index, 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"
|
||||
|
||||
// Get the child
|
||||
auto child = array(tape).at(array_index);
|
||||
|
||||
@@ -227,18 +227,8 @@ 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::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
|
||||
if (std::isnan(d) || std::isinf(d)) {
|
||||
return 4; // "null" for invalid numbers
|
||||
#endif
|
||||
}
|
||||
// Rough estimate: up to 17 significant digits + sign + decimal point + exponent
|
||||
char buf[32];
|
||||
@@ -960,14 +950,6 @@ 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,7 +11,6 @@
|
||||
#include "simdjson/dom/object-inl.h"
|
||||
#include "simdjson/internal/tape_ref-inl.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
namespace simdjson {
|
||||
@@ -182,22 +181,6 @@ 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,9 +147,7 @@ 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 && str8ncmp_case_insensitive(src, "infinity") == 0) {
|
||||
return true;
|
||||
}
|
||||
if (len == 8) { return str8ncmp_case_insensitive(src, "infinity") == 0; }
|
||||
if (len > 3) {
|
||||
return (str3ncmp_case_insensitive(src, "inf")
|
||||
| jsoncharutils::is_not_structural_or_whitespace(src[3])) == 0;
|
||||
|
||||
@@ -21,113 +21,23 @@ 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(writer &w, const T &t) {
|
||||
simdjson_really_inline constexpr void atom(string_builder &b, const T &t) {
|
||||
auto it = t.begin();
|
||||
auto end = t.end();
|
||||
if (it == end) {
|
||||
if (!w.ensure(2)) return;
|
||||
std::memcpy(w.ptr + w.pos, "[]", 2);
|
||||
w.pos += 2;
|
||||
b.append_raw("[]");
|
||||
return;
|
||||
}
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = '[';
|
||||
atom(w, *it);
|
||||
b.append('[');
|
||||
atom(b, *it);
|
||||
++it;
|
||||
for (; it != end; ++it) {
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = ',';
|
||||
atom(w, *it);
|
||||
b.append(',');
|
||||
atom(b, *it);
|
||||
}
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = ']';
|
||||
b.append(']');
|
||||
}
|
||||
|
||||
template <class T>
|
||||
@@ -135,100 +45,37 @@ 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(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++] = '"';
|
||||
simdjson_really_inline constexpr void atom(string_builder &b, const T &t) {
|
||||
b.escape_and_append_with_quotes(t);
|
||||
}
|
||||
|
||||
template <concepts::string_view_keyed_map T>
|
||||
requires(!require_custom_serialization<T>)
|
||||
simdjson_really_inline constexpr void atom(writer &w, const T &m) {
|
||||
simdjson_really_inline constexpr void atom(string_builder &b, const T &m) {
|
||||
if (m.empty()) {
|
||||
if (!w.ensure(2)) return;
|
||||
std::memcpy(w.ptr + w.pos, "{}", 2);
|
||||
w.pos += 2;
|
||||
b.append_raw("{}");
|
||||
return;
|
||||
}
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = '{';
|
||||
b.append('{');
|
||||
bool first = true;
|
||||
for (const auto& [key, value] : m) {
|
||||
if (!first) {
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = ',';
|
||||
b.append(',');
|
||||
}
|
||||
first = false;
|
||||
// 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);
|
||||
// Keys must be convertible to string_view per the concept
|
||||
b.escape_and_append_with_quotes(key);
|
||||
b.append(':');
|
||||
atom(b, value);
|
||||
}
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = '}';
|
||||
b.append('}');
|
||||
}
|
||||
|
||||
|
||||
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(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);
|
||||
}
|
||||
simdjson_really_inline constexpr void atom(string_builder &b, const number_type t) {
|
||||
b.append(t);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
@@ -241,83 +88,70 @@ 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(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.
|
||||
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;
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = '{';
|
||||
b.append('{');
|
||||
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 (!w.ensure(rest_key_len)) return;
|
||||
if (i == 0) {
|
||||
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;
|
||||
}
|
||||
atom(w, t.[:dm:]);
|
||||
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 (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = '}';
|
||||
b.append('}');
|
||||
}
|
||||
|
||||
// Support for optional types (std::optional, etc.)
|
||||
template <concepts::optional_type T>
|
||||
requires(!require_custom_serialization<T>)
|
||||
simdjson_really_inline constexpr void atom(writer &w, const T &opt) {
|
||||
simdjson_really_inline constexpr void atom(string_builder &b, const T &opt) {
|
||||
if (opt) {
|
||||
atom(w, opt.value());
|
||||
atom(b, opt.value());
|
||||
} else {
|
||||
if (!w.ensure(4)) return;
|
||||
std::memcpy(w.ptr + w.pos, "null", 4);
|
||||
w.pos += 4;
|
||||
b.append_raw("null");
|
||||
}
|
||||
}
|
||||
|
||||
// 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(writer &w, const T &ptr) {
|
||||
simdjson_really_inline constexpr void atom(string_builder &b, const T &ptr) {
|
||||
if (ptr) {
|
||||
atom(w, *ptr);
|
||||
atom(b, *ptr);
|
||||
} else {
|
||||
if (!w.ensure(4)) return;
|
||||
std::memcpy(w.ptr + w.pos, "null", 4);
|
||||
w.pos += 4;
|
||||
b.append_raw("null");
|
||||
}
|
||||
}
|
||||
|
||||
// Support for enums - serialize as string representation using expand approach from P2996R12
|
||||
template <typename T>
|
||||
requires(std::is_enum_v<T> && !require_custom_serialization<T>)
|
||||
simdjson_really_inline void atom(writer &w, const T &e) {
|
||||
simdjson_really_inline void atom(string_builder &b, 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:]) {
|
||||
if (!w.ensure(enum_str_len)) return;
|
||||
std::memcpy(w.ptr + w.pos, enum_str, enum_str_len);
|
||||
w.pos += enum_str_len;
|
||||
b.append_raw(enum_str);
|
||||
return;
|
||||
}
|
||||
};
|
||||
// Fallback to integer if enum value not found
|
||||
atom(w, static_cast<std::underlying_type_t<T>>(e));
|
||||
atom(b, static_cast<std::underlying_type_t<T>>(e));
|
||||
#else
|
||||
// Fallback: serialize as integer if reflection not available
|
||||
atom(w, static_cast<std::underlying_type_t<T>>(e));
|
||||
atom(b, static_cast<std::underlying_type_t<T>>(e));
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -327,37 +161,28 @@ 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(writer &w, const T &container) {
|
||||
simdjson_really_inline constexpr void atom(string_builder &b, const T &container) {
|
||||
if (container.empty()) {
|
||||
if (!w.ensure(2)) return;
|
||||
std::memcpy(w.ptr + w.pos, "[]", 2);
|
||||
w.pos += 2;
|
||||
b.append_raw("[]");
|
||||
return;
|
||||
}
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = '[';
|
||||
b.append('[');
|
||||
bool first = true;
|
||||
for (const auto& item : container) {
|
||||
if (!first) {
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = ',';
|
||||
b.append(',');
|
||||
}
|
||||
first = false;
|
||||
atom(w, item);
|
||||
atom(b, item);
|
||||
}
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = ']';
|
||||
b.append(']');
|
||||
}
|
||||
|
||||
// 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.
|
||||
// append functions that delegate to atom functions for primitive types
|
||||
template <class T>
|
||||
requires(std::is_arithmetic_v<T> && !std::is_same_v<T, char>)
|
||||
simdjson_inline void append(string_builder &b, const T &t) {
|
||||
writer w(b);
|
||||
atom(w, t);
|
||||
w.sync();
|
||||
void append(string_builder &b, const T &t) {
|
||||
atom(b, t);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
@@ -365,26 +190,20 @@ 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_inline void append(string_builder &b, const T &t) {
|
||||
writer w(b);
|
||||
atom(w, t);
|
||||
w.sync();
|
||||
void append(string_builder &b, const T &t) {
|
||||
atom(b, t);
|
||||
}
|
||||
|
||||
template <concepts::optional_type T>
|
||||
requires(!require_custom_serialization<T>)
|
||||
simdjson_inline void append(string_builder &b, const T &t) {
|
||||
writer w(b);
|
||||
atom(w, t);
|
||||
w.sync();
|
||||
void append(string_builder &b, const T &t) {
|
||||
atom(b, t);
|
||||
}
|
||||
|
||||
template <concepts::smart_pointer T>
|
||||
requires(!require_custom_serialization<T>)
|
||||
simdjson_inline void append(string_builder &b, const T &t) {
|
||||
writer w(b);
|
||||
atom(w, t);
|
||||
w.sync();
|
||||
void append(string_builder &b, const T &t) {
|
||||
atom(b, t);
|
||||
}
|
||||
|
||||
template <concepts::appendable_containers T>
|
||||
@@ -392,18 +211,14 @@ 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_inline void append(string_builder &b, const T &t) {
|
||||
writer w(b);
|
||||
atom(w, t);
|
||||
w.sync();
|
||||
void append(string_builder &b, const T &t) {
|
||||
atom(b, t);
|
||||
}
|
||||
|
||||
template <concepts::string_view_keyed_map T>
|
||||
requires(!require_custom_serialization<T>)
|
||||
simdjson_inline void append(string_builder &b, const T &t) {
|
||||
writer w(b);
|
||||
atom(w, t);
|
||||
w.sync();
|
||||
void append(string_builder &b, const T &t) {
|
||||
atom(b, t);
|
||||
}
|
||||
|
||||
// works for struct
|
||||
@@ -417,19 +232,40 @@ 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>)
|
||||
simdjson_inline void append(string_builder &b, const Z &z) {
|
||||
writer w(b);
|
||||
atom(w, z);
|
||||
w.sync();
|
||||
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('}');
|
||||
}
|
||||
|
||||
// works for container that have begin() and end() iterators
|
||||
template <class Z>
|
||||
requires(concepts::container_but_not_string<Z> && !require_custom_serialization<Z>)
|
||||
simdjson_inline void append(string_builder &b, const Z &z) {
|
||||
writer w(b);
|
||||
atom(w, z);
|
||||
w.sync();
|
||||
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(']');
|
||||
}
|
||||
|
||||
template <class Z>
|
||||
@@ -468,9 +304,7 @@ 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) {
|
||||
writer w(b);
|
||||
if (!w.ensure(1)) { w.sync(); return; }
|
||||
w.ptr[w.pos++] = '{';
|
||||
b.append('{');
|
||||
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()));
|
||||
@@ -480,29 +314,21 @@ 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)) + ":");
|
||||
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;
|
||||
}
|
||||
b.append_raw(first ? first_key : rest_key);
|
||||
first = false;
|
||||
atom(w, obj.[:mem:]);
|
||||
|
||||
// Serialize the value
|
||||
atom(b, obj.[:mem:]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!w.ensure(1)) { w.sync(); return; }
|
||||
w.ptr[w.pos++] = '}';
|
||||
w.sync();
|
||||
b.append('}');
|
||||
}
|
||||
|
||||
template<constevalutil::fixed_string... FieldNames, typename T>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
#ifndef SIMDJSON_GENERIC_STRING_BUILDER_INL_H
|
||||
|
||||
@@ -711,29 +709,6 @@ 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));
|
||||
@@ -745,11 +720,6 @@ 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);
|
||||
}
|
||||
@@ -758,11 +728,6 @@ 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 = 1024;
|
||||
static constexpr size_t DEFAULT_INITIAL_CAPACITY = 262144;
|
||||
|
||||
/**
|
||||
* Append number (includes Booleans). Booleans are mapped to the strings
|
||||
@@ -245,26 +245,6 @@ 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 */
|
||||
inline error_code slow_float_parsing(simdjson_unused const uint8_t * src, double* answer) {
|
||||
static error_code slow_float_parsing(simdjson_unused const uint8_t * src, double* answer) {
|
||||
if (parse_float_fallback(src, answer)) {
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
@@ -135,9 +135,21 @@ 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;
|
||||
SIMDJSON_TRY(internal::parse_json_pointer_array_index(json_pointer, array_index, 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"
|
||||
// Get the child
|
||||
auto child = at(array_index);
|
||||
// If there is an error, it ends here
|
||||
|
||||
@@ -267,22 +267,6 @@ constexpr bool user_defined_type = (std::is_class_v<T>
|
||||
!concepts::appendable_containers<T>);
|
||||
|
||||
|
||||
// Compile-time predicate: does T have any std::optional member?
|
||||
// Used to decide whether single-pass dispatch is worth it.
|
||||
template <typename T>
|
||||
consteval bool struct_has_optional_member() {
|
||||
bool result = false;
|
||||
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)) {
|
||||
using FieldT = [: std::meta::type_of(mem) :];
|
||||
if constexpr (concepts::optional_type<FieldT>) {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -292,42 +276,25 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept {
|
||||
} else {
|
||||
SIMDJSON_TRY(val.get_object().get(obj));
|
||||
}
|
||||
if constexpr (struct_has_optional_member<T>()) {
|
||||
// Single-pass dispatch: walk each JSON object field once, dispatching
|
||||
// to the matching struct member via a compile-time-generated key
|
||||
// comparison chain (with length pre-filter). This avoids the O(K)
|
||||
// full-object scan that obj[key] does for *absent* optional fields.
|
||||
// Worth it when the struct has optionals because some are usually missing.
|
||||
for (auto field : obj) {
|
||||
std::string_view key;
|
||||
SIMDJSON_TRY(field.unescaped_key().get(key));
|
||||
bool matched = false;
|
||||
const size_t key_size = key.size();
|
||||
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 name = std::define_static_string(std::meta::identifier_of(mem));
|
||||
constexpr size_t name_size = name.size();
|
||||
if (!matched && key_size == name_size && key == name) {
|
||||
SIMDJSON_TRY(field.value().get(out.[:mem:]));
|
||||
matched = true;
|
||||
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 (concepts::optional_type<decltype(out.[:mem:])>) {
|
||||
// for optional members, it's ok if the key is missing
|
||||
auto error = obj[key].get(out.[:mem:]);
|
||||
if (error && error != NO_SUCH_FIELD) {
|
||||
if(error == NO_SUCH_FIELD) {
|
||||
out.[:mem:].reset();
|
||||
continue;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
};
|
||||
// Unmatched value is skipped automatically by object_iterator::operator++().
|
||||
(void)matched;
|
||||
}
|
||||
} else {
|
||||
// Per-field obj[key] dispatch: for structs with all-required fields,
|
||||
// this is O(1) per field when the JSON keys are in declaration order
|
||||
// (find_field_unordered's fast path). Beats single-pass on dense
|
||||
// structs (e.g. Twitter Status with 22 always-present fields).
|
||||
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));
|
||||
} else {
|
||||
// for non-optional members, the key must be present
|
||||
SIMDJSON_TRY(obj[key].get(out.[:mem:]));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
return simdjson::SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,55 +1,12 @@
|
||||
#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.
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
#include "simdjson.h"
|
||||
#include "test_builder.h"
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@@ -20,27 +17,28 @@ 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>);
|
||||
@@ -162,140 +160,6 @@ 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;
|
||||
@@ -595,15 +459,14 @@ 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();
|
||||
}
|
||||
|
||||
@@ -798,11 +661,6 @@ 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;
|
||||
|
||||
+2
-163
@@ -1,9 +1,7 @@
|
||||
#include "simdjson.h"
|
||||
#include "test_macros.h"
|
||||
#include "test_main.h"
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
|
||||
using namespace simdjson;
|
||||
@@ -27,10 +25,8 @@ bool parse_nan() {
|
||||
|
||||
bool parse_infinity() {
|
||||
TEST_START();
|
||||
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 "}) {
|
||||
for (auto json_str :
|
||||
{"infinity", "Infinity", "INFINITY", "inf", "Inf", "INF"}) {
|
||||
dom::parser parser;
|
||||
dom::element doc;
|
||||
ASSERT_SUCCESS(
|
||||
@@ -284,154 +280,6 @@ 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() //
|
||||
@@ -443,15 +291,6 @@ 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,7 +257,6 @@ 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;
|
||||
|
||||
@@ -509,7 +509,6 @@ 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() &&
|
||||
@@ -519,4 +518,4 @@ namespace json_pointer_tests {
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
return test_main(argc, argv, json_pointer_tests::run);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user