Compare commits

..

3 Commits

Author SHA1 Message Date
Francisco Geiman Thiesen cd4a074653 WIP: bump DEFAULT_INITIAL_CAPACITY to 256KB
Most CITM/Twitter realloc cost was due to growing from 1024 in 7-9 doublings
to reach the actual output size. Bumping the initial capacity to 256KB means
Twitter (82KB) fits in one allocation and CITM (496KB) only needs 1 growth.

Measured (TRUE A/B, 7 alternating rounds in single docker, with all prior
follow-ups in this branch + this cap bump):
  CITM:    4358 -> 4924 MB/s  (+13.0%)  - now ~2.3% AHEAD of Glaze
  Twitter: 6338 -> 8034 MB/s  (+26.8%)  - ~50% AHEAD of Glaze

Trade-off: 256KB upfront per string_builder instance. Reasonable for
high-perf JSON serialization but wasteful for tiny one-off messages.
Users serializing small payloads should pass a smaller initial_capacity
to the constructor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 16:13:26 -07:00
Francisco Geiman Thiesen 0ab7e2d701 WIP: template append_raw on size for compile-time-inlined memcpy
Add append_raw_n<N>(const char*) which propagates the key length as a
template parameter, letting the compiler emit a fully inlined memcpy
with a constant size (direct loads/stores) instead of the size-passed-
as-runtime-arg variant which sometimes fell back to a libc memcpy
dispatch.

Use it in the reflection struct atom for both first_key and rest_key
(both are compile-time constants from define_static_string).

Measured (TRUE A/B, 7 alternating rounds in single docker invocation,
two independent runs combined):
  CITM:    baseline ~4395 -> patched ~4791 MB/s  (+9%)
           Glaze    ~4810 MB/s -> simdjson now at PARITY with Glaze
                                  (within +/- 3% noise across runs)
  Twitter: still well ahead of Glaze, no change from baseline

Output is byte-identical, all reflection comprehensive tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 15:45:08 -07:00
Francisco Geiman Thiesen 0a694273e2 WIP: builder follow-up — inline remaining atoms + skip strlen on keys
Three small follow-ups on top of #2707, all aimed at closing the
remaining CITM gap:

  1. simdjson_really_inline on atom<map>, atom<smart_pointer>,
     atom<enum>. The previous PR missed these three. atom<map> in
     particular was visible at ~11% of CITM profile time before this
     change (CITM has events: std::map<string, CITMEvent>).

  2. struct atom passes the key size explicitly to append_raw(c, len)
     instead of going through append_raw(const char*) which calls
     std::strlen on every key (8 fields × 184 events on CITM).

  3. append_raw(const char*) uses std::char_traits<char>::length
     (constexpr) instead of std::strlen so the compiler can fold the
     length when the pointer is to a compile-time string.

Measured (TRUE A/B, 7 alternating rounds in single docker invocation):
  CITM:    baseline 4437 -> patched 4686 MB/s  (+5.6%)
  Twitter: within noise

Output is byte-identical to baseline. All static_reflection_comprehensive_tests
pass.

WIP — pushing for safekeeping while continuing to investigate the
remaining ~5% gap to Glaze on CITM. Not ready for PR yet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 15:34:00 -07:00
20 changed files with 165 additions and 1127 deletions
-26
View File
@@ -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
+13 -1
View File
@@ -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);
+1 -19
View File
@@ -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;
-17
View File
@@ -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
+1 -3
View File
@@ -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;
+97 -271
View File
@@ -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.
+1 -1
View File
@@ -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;
}
+13 -1
View File
@@ -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;
}
-43
View File
@@ -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.
+20 -162
View File
@@ -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
View File
@@ -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() //
;
}
-1
View File
@@ -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);
}
}