simdjson 5.0.2
Ridiculously Fast JSON
Loading...
Searching...
No Matches
json_builder.h
1#ifndef SIMDJSON_GENERIC_BUILDER_H
2
3#ifndef SIMDJSON_CONDITIONAL_INCLUDE
4#define SIMDJSON_GENERIC_STRING_BUILDER_H
5#include "simdjson/generic/builder/json_string_builder.h"
6#include "simdjson/concepts.h"
8#endif // SIMDJSON_CONDITIONAL_INCLUDE
9#if SIMDJSON_STATIC_REFLECTION
10
11#include <charconv>
12#include <cstring>
13#include <meta>
14#include <memory>
15#include <optional>
16#include <string_view>
17#include <type_traits>
18#include <utility>
19// #include <static_reflection> // for std::define_static_string - header not available yet
20
21namespace simdjson {
22namespace SIMDJSON_IMPLEMENTATION {
23namespace builder {
24
25// Forward-declare helpers defined in json_string_builder-inl.h so the
26// writer-based atom code below can call them (the -inl.h is not yet
27// included at the point this header is parsed; without these forwards,
28// name lookup falls back to the wrong outer namespace).
29namespace internal {
30simdjson_really_inline char *write_uint_jeaiii(char *p, uint64_t v) noexcept;
31simdjson_inline char *write_double(char *p, double v) noexcept;
32} // namespace internal
33simdjson_really_inline size_t write_string_escaped(const std::string_view input, char *out);
34
35SIMDJSON_PUSH_DISABLE_WARNINGS
36SIMDJSON_DISABLE_GCC_WARNING(-Warray-bounds)
37#if !defined(__clang__)
38SIMDJSON_DISABLE_GCC_WARNING(-Wstringop-overflow)
39#endif
40
41// =============================================================
42// `writer`: position-as-local hot-path writer used by the reflection
43// atom code below. Holds the buffer pointer, write position and
44// capacity in three fields that, once `writer` itself is a stack-local
45// in the caller and all atom() functions are inlined, become true
46// register-resident locals after SROA. Glaze achieves the same effect
47// by passing `B&& b, auto&& ix` through every helper. Holding `pos`
48// in a register (rather than as a member of string_builder) is what
49// breaks the strict-aliasing penalty on every char* write through the
50// buffer, which forces a reload of `b.position` and `b.capacity`
51// after every byte.
52//
53// basic_writer<false> (below) is the unchecked variant: the caller has
54// already reserved enough capacity for everything the write chain can
55// produce (see bound_detail::size_bound), so ensure() compiles away.
56// =============================================================
57template <bool Checked>
58struct basic_writer {
59 static constexpr bool checked = Checked;
60 char *ptr; // buffer pointer (refreshed after a grow)
61 size_t pos; // write position (local)
62 size_t cap; // capacity (refreshed after a grow)
63 string_builder &sb; // back-ref for grow / sync
64
65 // Snapshot string_builder state into a writer for the duration of
66 // a write chain.
67 simdjson_really_inline basic_writer(string_builder &builder) noexcept
68 : ptr(builder.unsafe_data())
69 , pos(builder.unsafe_position())
70 , cap(builder.unsafe_capacity())
71 , sb(builder) {}
72
73 // Write the local position back to the underlying string_builder.
74 // Caller is responsible for invoking before the writer is dropped
75 // (otherwise data is lost). Idempotent.
76 simdjson_really_inline void sync() noexcept {
77 sb.unsafe_set_position(pos);
78 }
79
80 // Ensure at least `n` more bytes of free capacity. Grows the
81 // underlying buffer if needed (rare path). Returns false on
82 // allocation failure.
83 simdjson_really_inline bool ensure(size_t n) noexcept {
84 // pos <= cap, and cap is the size of a live allocation, so pos + n
85 // cannot wrap when n is a small constant or a compile-time length.
86 // Callers passing a size derived from input (the string atoms) must
87 // bound it against pos themselves. Keep the `pos + n <= cap` form:
88 // `n <= cap - pos` is measurably slower once the serializer is inlined.
89 if (simdjson_likely(pos + n <= cap)) { return true; }
90 return grow_slow(n);
91 }
92
93 simdjson_never_inline bool grow_slow(size_t n) noexcept {
94 // Detect overflow.
95 // This is pedantic except maybe on 32-bit targets.
96 if (simdjson_unlikely(pos + n < pos)) return false;
97 sb.unsafe_set_position(pos);
98 // even if 2*capacity overflows, the (std::max) below will pick the needed value,
99 // so we do not need a separate overflow check here.
100 if (!sb.unsafe_grow((std::max)(cap * 2, pos + n))) {
101 // The string_builder freed its buffer and is now invalid (null buffer,
102 // zero capacity and position). Mirror that state so that every later
103 // ensure() fails too: callers only return from the current atom, and
104 // their callers keep writing.
105 ptr = nullptr;
106 pos = 0;
107 cap = 0;
108 return false;
109 }
110 ptr = sb.unsafe_data();
111 cap = sb.unsafe_capacity();
112 return true;
113 }
114};
115
116// The unchecked writer writes into a raw buffer that the caller sized with
117// serialized_size_bound: it never grows and needs no string_builder.
118template <>
119struct basic_writer<false> {
120 static constexpr bool checked = false;
121 char *ptr;
122 size_t pos;
123
124 simdjson_really_inline basic_writer(char *buffer, size_t position) noexcept
125 : ptr(buffer), pos(position) {}
126
127 simdjson_really_inline bool ensure(size_t) const noexcept { return true; }
128};
129
130using writer = basic_writer<true>;
131using unchecked_writer = basic_writer<false>;
132
133// Bytes reserved past the size bound for an unchecked writer: it may then
134// write a little past the end of what it produces (e.g., copy keys as whole
135// 16-byte blocks).
136inline constexpr size_t unchecked_slack = 64;
137
138consteval size_t padded_key_length(size_t length) {
139 return (length + 15) / 16 * 16;
140}
141
142// === Helper: invoke a string_builder member that writes variable-length
143// content (escape_and_append_with_quotes etc), syncing the writer's local
144// state before the call and reloading after. Used for string fields where
145// rewriting the entire SIMD escape path through the writer would be a much
146// bigger refactor. f may be user code (a with<Adapter> serializer) that
147// throws: the exception then propagates to the caller.
148template <class W, class F>
149simdjson_really_inline void call_through_string_builder(W &w, F &&f) noexcept(noexcept(f(w.sb))) {
150 w.sync();
151 f(w.sb);
152 w.ptr = w.sb.unsafe_data();
153 w.pos = w.sb.unsafe_position();
154 w.cap = w.sb.unsafe_capacity();
155}
156
157// Helpers implementing the serialization side of the annotations (see
158// simdjson/annotations.h) for reflected structures.
159namespace annotation_detail {
160
161// A member is serialized unless it is annotated with skip or skip_serializing.
162consteval bool is_serialized_member(std::meta::info dm) {
163 return !simdjson::detail::has_annotation(dm, ^^simdjson::detail::skip_tag)
164 && !simdjson::detail::has_annotation(dm, ^^simdjson::detail::skip_serializing_tag);
165}
166
167// False when the member has a skip_serializing_if<pred> annotation and
168// pred(value) is true.
169template <auto dm, typename V>
170simdjson_really_inline bool should_serialize(const V &value) {
171 constexpr std::meta::info skip_if_type = simdjson::detail::annotation_of_template(dm, ^^simdjson::detail::skip_serializing_if_t);
172 if constexpr (skip_if_type != std::meta::info{}) {
173 using skip_if = typename [: skip_if_type :];
174 return !skip_if::predicate(value);
175 } else {
176 (void)value;
177 return true;
178 }
179}
180
181// Serialize a member value, through its with<Adapter> annotation when the
182// adapter provides a serialize function.
183template <auto dm, class W, typename V>
184simdjson_really_inline void atom_member(W &w, const V &value) {
185 constexpr std::meta::info with_type = simdjson::detail::annotation_of_template(dm, ^^simdjson::detail::with_t);
186 if constexpr (with_type != std::meta::info{}) {
187 using adapter = typename [: with_type :]::adapter;
188 if constexpr (requires(string_builder &b) { adapter::serialize(b, value); }) {
189 call_through_string_builder(w, [&](string_builder &b) { adapter::serialize(b, value); });
190 } else {
191 atom(w, value);
192 }
193 } else {
194 atom(w, value);
195 }
196}
197
198// Write the "key":value pairs of the members of t (without the braces), each
199// preceded by a comma unless it is the first one. The members of a member
200// annotated with flatten are written in its place.
201template <class W, class T>
202simdjson_really_inline void atom_fields(W &w, const T &t, bool &first) {
203 // Per-field block: ensure key+value worst case, then write key + value
204 // through the writer's local pos. For arithmetic fields, the integer
205 // write happens directly via write_uint_jeaiii on w.ptr+w.pos, so pos
206 // never round-trips through memory.
207 template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
208 if constexpr (is_serialized_member(dm)) {
209 if (should_serialize<dm>(t.[:dm:])) {
210 if constexpr (simdjson::detail::has_annotation(dm, ^^simdjson::detail::flatten_tag)) {
211 static_assert(std::meta::is_class_type(simdjson::detail::flattened_type(dm)));
212 using flattened = std::remove_cvref_t<decltype(t.[:dm:])>;
213 static_assert(!concepts::container_but_not_string<flattened> && !concepts::string_view_keyed_map<flattened> &&
214 !concepts::appendable_containers<flattened> && !concepts::optional_type<flattened> &&
215 !concepts::smart_pointer<flattened> && !std::is_same_v<flattened, std::string> &&
216 !std::is_same_v<flattened, std::string_view> && !require_custom_serialization<flattened>,
217 "simdjson::flatten requires a member whose type is a structure serialized member by member");
218 atom_fields(w, t.[:dm:], first);
219 } else {
220 // Copy the key as whole 16-byte blocks from a zero-padded copy (one
221 // load and one store); ensure() reserves the padded length, and the
222 // unchecked writer has slack past its bound.
223 constexpr const char* key_name = simdjson::get_json_key_name<dm>();
224 constexpr size_t first_key_len = constevalutil::consteval_to_quoted_escaped(key_name).size() + 1;
225 constexpr size_t rest_key_len = first_key_len + 1;
226 constexpr auto first_key = std::define_static_string(
227 constevalutil::consteval_to_quoted_escaped(key_name) + ":" +
228 std::string(padded_key_length(first_key_len) - first_key_len, '\0'));
229 constexpr auto rest_key = std::define_static_string(
230 std::string(",") + constevalutil::consteval_to_quoted_escaped(key_name) + ":" +
231 std::string(padded_key_length(rest_key_len) - rest_key_len, '\0'));
232 if (!w.ensure(padded_key_length(rest_key_len))) { return; }
233 if (first) {
234 std::memcpy(w.ptr + w.pos, first_key, padded_key_length(first_key_len));
235 w.pos += first_key_len;
236 } else {
237 std::memcpy(w.ptr + w.pos, rest_key, padded_key_length(rest_key_len));
238 w.pos += rest_key_len;
239 }
240 first = false;
241 atom_member<dm>(w, t.[:dm:]);
242 }
243 }
244 }
245 };
246}
247
248} // namespace annotation_detail
249
250template <class W, class T>
251 requires(concepts::container_but_not_string<T> && !require_custom_serialization<T>)
252simdjson_really_inline constexpr void atom(W &w, const T &t) {
253 auto it = t.begin();
254 auto end = t.end();
255 if (it == end) {
256 if (!w.ensure(2)) return;
257 std::memcpy(w.ptr + w.pos, "[]", 2);
258 w.pos += 2;
259 return;
260 }
261 if (!w.ensure(1)) return;
262 w.ptr[w.pos++] = '[';
263 atom(w, *it);
264 ++it;
265 for (; it != end; ++it) {
266 if (!w.ensure(1)) return;
267 w.ptr[w.pos++] = ',';
268 atom(w, *it);
269 }
270 if (!w.ensure(1)) return;
271 w.ptr[w.pos++] = ']';
272}
273
274template <class W, class T>
275 requires(std::is_same_v<T, std::string> ||
276 std::is_same_v<T, std::string_view> ||
277 std::is_same_v<T, const char *> ||
278 std::is_same_v<T, char>)
279simdjson_really_inline constexpr void atom(W &w, const T &t) {
280 // Inline the escape path through the writer so we never round-trip
281 // pos through memory for string fields (Twitter is dominated by
282 // these -- sync/reload around each string was a real cost).
283 std::string_view input;
284 if constexpr (std::is_same_v<T, char>) {
285 input = std::string_view(&t, 1);
286 } else {
287 input = std::string_view(t);
288 }
289 // Worst-case escape: every byte expands to \uXXXX (6 chars), plus 2 quotes.
290 // Guard against w.pos + 2 + 6 * input.size() wrapping for huge inputs -- if
291 // it wrapped to a small value, ensure() would spuriously succeed and the
292 // subsequent escape would overflow the buffer. max - w.pos cannot wrap, and
293 // size < (max - pos) / 6 implies pos + 6 * size + 6 <= max.
294 // Note that this is pedantic except maybe on 32-bit targets.
295 if constexpr (W::checked) {
296 if (simdjson_unlikely(input.size() >= ((std::numeric_limits<size_t>::max)() - w.pos) / 6)) { return; }
297 if (!w.ensure(2 + 6 * input.size())) { return; }
298 }
299 w.ptr[w.pos++] = '"';
300 w.pos += write_string_escaped(input, w.ptr + w.pos);
301 w.ptr[w.pos++] = '"';
302}
303
304template <class W, concepts::string_view_keyed_map T>
305 requires(!require_custom_serialization<T>)
306simdjson_really_inline constexpr void atom(W &w, const T &m) {
307 if (m.empty()) {
308 if (!w.ensure(2)) return;
309 std::memcpy(w.ptr + w.pos, "{}", 2);
310 w.pos += 2;
311 return;
312 }
313 if (!w.ensure(1)) return;
314 w.ptr[w.pos++] = '{';
315 bool first = true;
316 for (const auto& [key, value] : m) {
317 if (!first) {
318 if (!w.ensure(1)) return;
319 w.ptr[w.pos++] = ',';
320 }
321 first = false;
322 // Keys must be convertible to string_view per the concept.
323 std::string_view key_sv(key);
324 // Guard against w.pos + 3 + 6 * key_sv.size() wrapping for huge keys, if
325 // it wrapped to a small value, ensure() would spuriously succeed and the
326 // subsequent escape would overflow the buffer. max - w.pos cannot wrap, and
327 // size < (max - pos) / 6 implies pos + 6 * size + 6 <= max.
328 // Note that this is pedantic except maybe on 32-bit targets.
329 if constexpr (W::checked) {
330 if (simdjson_unlikely(key_sv.size() >= ((std::numeric_limits<size_t>::max)() - w.pos) / 6)) { return; }
331 if (!w.ensure(2 + 6 * key_sv.size() + 1)) { return; }
332 }
333 w.ptr[w.pos++] = '"';
334 w.pos += write_string_escaped(key_sv, w.ptr + w.pos);
335 w.ptr[w.pos++] = '"';
336 w.ptr[w.pos++] = ':';
337 atom(w, value);
338 }
339 if (!w.ensure(1)) return;
340 w.ptr[w.pos++] = '}';
341}
342
343
344template<class W, typename number_type,
345 typename = typename std::enable_if<std::is_arithmetic<number_type>::value && !std::is_same_v<number_type, char>>::type>
346simdjson_really_inline constexpr void atom(W &w, const number_type t) {
347 // Booleans / floats: defer to string_builder (rare path; keeps writer hot
348 // path free of float-formatter machinery). For integers, write directly
349 // via jeaiii using local pos.
350 if constexpr (std::is_same_v<number_type, bool>) {
351 if (t) {
352 if (!w.ensure(4)) return;
353 std::memcpy(w.ptr + w.pos, "true", 4);
354 w.pos += 4;
355 } else {
356 if (!w.ensure(5)) return;
357 std::memcpy(w.ptr + w.pos, "false", 5);
358 w.pos += 5;
359 }
360 } else if constexpr (std::is_floating_point_v<number_type>) {
361 if constexpr (W::checked) {
362 call_through_string_builder(w, [&](string_builder &b) { b.append(t); });
363 } else {
364 w.pos = size_t(internal::write_double(w.ptr + w.pos, double(t)) - w.ptr);
365 }
366 } else if constexpr (std::is_unsigned_v<number_type>) {
367 if (!w.ensure(20)) return;
368 char *end = internal::write_uint_jeaiii(
369 w.ptr + w.pos, static_cast<uint64_t>(t));
370 w.pos = static_cast<size_t>(end - w.ptr);
371 } else {
372 // signed integral
373 if (!w.ensure(20)) return;
374 using U = typename std::make_unsigned<number_type>::type;
375 bool negative = t < 0;
376 U pv = negative ? U(0) - static_cast<U>(t) : static_cast<U>(t);
377 w.ptr[w.pos] = '-';
378 w.pos += negative;
379 char *end = internal::write_uint_jeaiii(
380 w.ptr + w.pos, static_cast<uint64_t>(pv));
381 w.pos = static_cast<size_t>(end - w.ptr);
382 }
383}
384
385template <class W, class T>
386 requires(std::is_class_v<T> && !concepts::container_but_not_string<T> &&
387 !concepts::string_view_keyed_map<T> &&
388 !concepts::optional_type<T> &&
389 !concepts::smart_pointer<T> &&
390 !concepts::appendable_containers<T> &&
391 !std::is_same_v<T, std::string> &&
392 !std::is_same_v<T, std::string_view> &&
393 !std::is_same_v<T, const char*> &&
394 !std::is_same_v<T, char> && !require_custom_serialization<T>)
395simdjson_really_inline constexpr void atom(W &w, const T &t) {
396 if constexpr (simdjson::detail::has_annotation(^^T, ^^simdjson::detail::transparent_tag)) {
397 // A transparent structure is serialized as its single member.
398 constexpr auto dm = simdjson::detail::transparent_member(^^T);
399 annotation_detail::atom_member<dm>(w, t.[:dm:]);
400 } else {
401 bool first = true;
402 if (!w.ensure(1)) { return; }
403 w.ptr[w.pos++] = '{';
404 annotation_detail::atom_fields(w, t, first);
405 if (!w.ensure(1)) { return; }
406 w.ptr[w.pos++] = '}';
407 }
408}
409
410// Support for optional types (std::optional, etc.)
411template <class W, concepts::optional_type T>
412 requires(!require_custom_serialization<T>)
413simdjson_really_inline constexpr void atom(W &w, const T &opt) {
414 if (opt) {
415 atom(w, opt.value());
416 } else {
417 if (!w.ensure(4)) return;
418 std::memcpy(w.ptr + w.pos, "null", 4);
419 w.pos += 4;
420 }
421}
422
423// Support for smart pointers (std::unique_ptr, std::shared_ptr, etc.)
424template <class W, concepts::smart_pointer T>
425 requires(!require_custom_serialization<T>)
426simdjson_really_inline constexpr void atom(W &w, const T &ptr) {
427 if (ptr) {
428 atom(w, *ptr);
429 } else {
430 if (!w.ensure(4)) return;
431 std::memcpy(w.ptr + w.pos, "null", 4);
432 w.pos += 4;
433 }
434}
435
436// Support for enums - serialize as string representation using expand approach from P2996R12
437template <class W, typename T>
438 requires(std::is_enum_v<T> && !require_custom_serialization<T>)
439simdjson_really_inline void atom(W &w, const T &e) {
440#if SIMDJSON_STATIC_REFLECTION
441 static constexpr auto enumerators = std::define_static_array(std::meta::enumerators_of(^^T));
442 template for (constexpr auto enum_val : enumerators) {
443 constexpr auto enum_str = std::define_static_string(constevalutil::consteval_to_quoted_escaped(simdjson::get_json_key_name<enum_val>()));
444 constexpr size_t enum_str_len = std::char_traits<char>::length(enum_str);
445 if (e == [:enum_val:]) {
446 if (!w.ensure(enum_str_len)) return;
447 std::memcpy(w.ptr + w.pos, enum_str, enum_str_len);
448 w.pos += enum_str_len;
449 return;
450 }
451 };
452 // Fallback to integer if enum value not found
453 atom(w, static_cast<std::underlying_type_t<T>>(e));
454#else
455 // Fallback: serialize as integer if reflection not available
456 atom(w, static_cast<std::underlying_type_t<T>>(e));
457#endif
458}
459
460// Support for appendable containers that don't have operator[] (sets, etc.)
461template <class W, concepts::appendable_containers T>
462 requires(!concepts::container_but_not_string<T> && !concepts::string_view_keyed_map<T> &&
463 !concepts::optional_type<T> && !concepts::smart_pointer<T> &&
464 !std::is_same_v<T, std::string> &&
465 !std::is_same_v<T, std::string_view> && !std::is_same_v<T, const char*> && !require_custom_serialization<T>)
466simdjson_really_inline constexpr void atom(W &w, const T &container) {
467 if (container.empty()) {
468 if (!w.ensure(2)) return;
469 std::memcpy(w.ptr + w.pos, "[]", 2);
470 w.pos += 2;
471 return;
472 }
473 if (!w.ensure(1)) return;
474 w.ptr[w.pos++] = '[';
475 bool first = true;
476 for (const auto& item : container) {
477 if (!first) {
478 if (!w.ensure(1)) return;
479 w.ptr[w.pos++] = ',';
480 }
481 first = false;
482 atom(w, item);
483 }
484 if (!w.ensure(1)) return;
485 w.ptr[w.pos++] = ']';
486}
487
488// =============================================================
489// Size bound: an upper bound on the number of bytes that atom(w, t) writes.
490// Computing it first lets append() reserve the capacity once and then run
491// the whole write chain through an unchecked_writer, without a capacity
492// check before every write. It mirrors the atom() overloads above.
493// =============================================================
494namespace bound_detail {
495
496// Whether size_bound covers everything that atom() writes for T: not when a
497// member is serialized by a with<Adapter> serializer, which writes an unknown
498// amount through the string_builder.
499template <class T>
500consteval bool is_bounded() {
501 if constexpr (require_custom_serialization<T>) {
502 return false;
503 } else if constexpr (std::is_same_v<T, std::string> || std::is_same_v<T, std::string_view> ||
504 std::is_same_v<T, const char *> || std::is_arithmetic_v<T> || std::is_enum_v<T>) {
505 return true;
506 } else if constexpr (concepts::optional_type<T> || concepts::smart_pointer<T>) {
507 return is_bounded<std::remove_cvref_t<decltype(*std::declval<const T &>())>>();
508 } else if constexpr (concepts::string_view_keyed_map<T>) {
509 return is_bounded<std::remove_cvref_t<typename T::mapped_type>>();
510 } else if constexpr (concepts::container_but_not_string<T> || concepts::appendable_containers<T>) {
511 return is_bounded<std::remove_cvref_t<std::ranges::range_value_t<T>>>();
512 } else {
513 bool bounded = true;
514 template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
515 if constexpr (annotation_detail::is_serialized_member(dm)) {
516 bounded = bounded && simdjson::detail::annotation_of_template(dm, ^^simdjson::detail::with_t) == std::meta::info{} &&
517 is_bounded<std::remove_cvref_t<decltype(std::declval<const T &>().[:dm:])>>();
518 }
519 };
520 return bounded;
521 }
522}
523
524template <class T>
525consteval size_t enum_bound() {
526 size_t bound = 20; // the integer fallback
527 template for (constexpr auto enum_val : std::define_static_array(std::meta::enumerators_of(^^T))) {
528 constexpr size_t len = std::char_traits<char>::length(std::define_static_string(
529 constevalutil::consteval_to_quoted_escaped(simdjson::get_json_key_name<enum_val>())));
530 bound = (std::max)(bound, len);
531 };
532 return bound;
533}
534
535template <class T>
536simdjson_really_inline size_t size_bound(const T &t) noexcept;
537
538// Bound for the "key":value pairs of a structure, commas included.
539template <class T>
540simdjson_really_inline size_t fields_bound(const T &t) noexcept {
541 size_t bound = 0;
542 template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
543 if constexpr (annotation_detail::is_serialized_member(dm)) {
544 if constexpr (simdjson::detail::has_annotation(dm, ^^simdjson::detail::flatten_tag)) {
545 bound += fields_bound(t.[:dm:]);
546 } else {
547 constexpr size_t rest_key_len = constevalutil::consteval_to_quoted_escaped(simdjson::get_json_key_name<dm>()).size() + 2;
548 bound += rest_key_len + size_bound(t.[:dm:]);
549 }
550 }
551 };
552 return bound;
553}
554
555template <class T>
556simdjson_really_inline size_t size_bound([[maybe_unused]] const T &t) noexcept {
557 if constexpr (std::is_same_v<T, char>) {
558 return 2 + 6;
559 } else if constexpr (std::is_same_v<T, std::string> || std::is_same_v<T, std::string_view> ||
560 std::is_same_v<T, const char *>) {
561 // Every byte may become \uXXXX, plus the quotes.
562 return 2 + 6 * std::string_view(t).size();
563 } else if constexpr (std::is_same_v<T, bool>) {
564 return 5;
565 } else if constexpr (std::is_floating_point_v<T>) {
566 return simdjson::internal::to_chars_buffer_size;
567 } else if constexpr (std::is_arithmetic_v<T>) {
568 return 20;
569 } else if constexpr (std::is_enum_v<T>) {
570 return enum_bound<T>();
571 } else if constexpr (concepts::optional_type<T> || concepts::smart_pointer<T>) {
572 return t ? size_bound(*t) : 4;
573 } else if constexpr (concepts::string_view_keyed_map<T>) {
574 size_t bound = 2;
575 for (const auto &[key, value] : t) {
576 // comma, quotes, colon
577 bound += 4 + 6 * std::string_view(key).size() + size_bound(value);
578 }
579 return bound;
580 } else if constexpr (concepts::container_but_not_string<T> || concepts::appendable_containers<T>) {
581 using value_type = std::remove_cvref_t<std::ranges::range_value_t<T>>;
582 if constexpr (std::is_arithmetic_v<value_type> && !std::is_same_v<value_type, char>) {
583 // A fixed bound per element: no need to visit them.
584 return 2 + size_t(std::ranges::distance(t)) * (1 + size_bound(value_type{}));
585 } else {
586 size_t bound = 2;
587#if defined(__GNUC__) && !defined(__clang__)
588#pragma GCC novector // a vector loop is slower on short containers
589#endif
590#pragma GCC unroll 4
591 for (const auto &item : t) {
592 bound += 1 + size_bound(item);
593 }
594 return bound;
595 }
596 } else if constexpr (simdjson::detail::has_annotation(^^T, ^^simdjson::detail::transparent_tag)) {
597 constexpr auto dm = simdjson::detail::transparent_member(^^T);
598 return size_bound(t.[:dm:]);
599 } else {
600 return 2 + fields_bound(t);
601 }
602}
603
604} // namespace bound_detail
605
606// Write t through an unchecked writer when its size bound is available,
607// reserving that many bytes first, and through the checked writer otherwise.
608template <class T>
609simdjson_really_inline void append_bounded(string_builder &b, const T &t) {
610 // On 32-bit systems, the bound could overflow: keep the checked writer.
611 if constexpr (sizeof(size_t) >= 8 && bound_detail::is_bounded<T>()) {
612 const size_t bound = bound_detail::size_bound(t) + unchecked_slack;
613 const size_t pos = b.unsafe_position();
614 // The bound is a sum of in-memory sizes times a small constant: it cannot
615 // overflow on a 64-bit system. Be pedantic elsewhere.
616 if (sizeof(size_t) >= 8 || bound <= (std::numeric_limits<size_t>::max)() - pos) {
617 const size_t cap = b.unsafe_capacity();
618 // Grow geometrically so that many small appends stay amortized.
619 if (pos + bound <= cap || b.unsafe_grow((std::max)(cap * 2, pos + bound))) {
620 unchecked_writer w(b.unsafe_data(), pos);
621 atom(w, t);
622 b.unsafe_set_position(w.pos);
623 }
624 return;
625 }
626 }
627 writer w(b);
628 atom(w, t);
629 w.sync();
630}
631
632// append() -- top-level entry. Each overload constructs a stack-local
633// writer, runs atom(w, t) through the inlined call chain, then syncs
634// the local position back into the string_builder.
635template <class T>
636 requires(std::is_arithmetic_v<T> && !std::is_same_v<T, char>)
637simdjson_inline void append(string_builder &b, const T &t) {
638 writer w(b);
639 atom(w, t);
640 w.sync();
641}
642
643template <class T>
644 requires(std::is_same_v<T, std::string> ||
645 std::is_same_v<T, std::string_view> ||
646 std::is_same_v<T, const char *> ||
647 std::is_same_v<T, char>)
648simdjson_inline void append(string_builder &b, const T &t) {
649 writer w(b);
650 atom(w, t);
651 w.sync();
652}
653
654template <concepts::optional_type T>
655 requires(!require_custom_serialization<T>)
656simdjson_inline void append(string_builder &b, const T &t) {
657 append_bounded(b, t);
658}
659
660template <concepts::smart_pointer T>
661 requires(!require_custom_serialization<T>)
662simdjson_inline void append(string_builder &b, const T &t) {
663 append_bounded(b, t);
664}
665
666template <concepts::appendable_containers T>
667 requires(!concepts::container_but_not_string<T> && !concepts::string_view_keyed_map<T> &&
668 !concepts::optional_type<T> && !concepts::smart_pointer<T> &&
669 !std::is_same_v<T, std::string> &&
670 !std::is_same_v<T, std::string_view> && !std::is_same_v<T, const char*> && !require_custom_serialization<T>)
671simdjson_inline void append(string_builder &b, const T &t) {
672 append_bounded(b, t);
673}
674
675template <concepts::string_view_keyed_map T>
676 requires(!require_custom_serialization<T>)
677simdjson_inline void append(string_builder &b, const T &t) {
678 append_bounded(b, t);
679}
680
681// works for struct
682template <class Z>
683 requires(std::is_class_v<Z> && !concepts::container_but_not_string<Z> &&
684 !concepts::string_view_keyed_map<Z> &&
685 !concepts::optional_type<Z> &&
686 !concepts::smart_pointer<Z> &&
687 !concepts::appendable_containers<Z> &&
688 !std::is_same_v<Z, std::string> &&
689 !std::is_same_v<Z, std::string_view> &&
690 !std::is_same_v<Z, const char*> &&
691 !std::is_same_v<Z, char> && !require_custom_serialization<Z>)
692simdjson_inline void append(string_builder &b, const Z &z) {
693 append_bounded(b, z);
694}
695
696// works for container that have begin() and end() iterators
697template <class Z>
698 requires(concepts::container_but_not_string<Z> && !require_custom_serialization<Z>)
699simdjson_inline void append(string_builder &b, const Z &z) {
700 append_bounded(b, z);
701}
702
703template <class Z>
704 requires (require_custom_serialization<Z>)
705void append(string_builder &b, const Z &z) {
706 b.append(z);
707}
708
709
710template <class Z>
711simdjson_warn_unused error_code to_json(const Z &z, std::string &s, size_t initial_capacity = string_builder::DEFAULT_INITIAL_CAPACITY) {
712 if constexpr (sizeof(size_t) >= 8 && bound_detail::is_bounded<Z>()) {
713 // Write straight into s, sized by the bound: no intermediate buffer, no copy.
714 (void)initial_capacity;
715 const size_t bound = bound_detail::size_bound(z) + unchecked_slack;
716 auto write = [&z](char *p) noexcept {
717 unchecked_writer w(p, 0);
718 atom(w, z);
719 return w.pos;
720 };
721#if defined(__cpp_lib_string_resize_and_overwrite) && __cpp_lib_string_resize_and_overwrite >= 202110L
722 s.resize_and_overwrite(bound, [&write](char *p, size_t) noexcept { return write(p); });
723#else
724 s.resize(bound);
725 s.resize(write(s.data()));
726#endif
727 return SUCCESS;
728 } else {
729 string_builder b(initial_capacity);
730 append(b, z);
731 std::string_view view;
732 if(auto e = b.view().get(view); e) { return e; }
733 s.assign(view);
734 return SUCCESS;
735 }
736}
737
738template <class Z>
739simdjson_warn_unused simdjson_result<std::string> to_json_string(const Z &z, size_t initial_capacity = string_builder::DEFAULT_INITIAL_CAPACITY) {
740 std::string s;
741 if(auto e = to_json(z, s, initial_capacity); e) { return e; }
742 return s;
743}
744
745template <class Z>
746string_builder& operator<<(string_builder& b, const Z& z) {
747 append(b, z);
748 return b;
749}
750
751// extract_from: Serialize only specific fields from a struct to JSON
752template<constevalutil::fixed_string... FieldNames, typename T>
753 requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0))
754void extract_from(string_builder &b, const T &obj) {
755 writer w(b);
756 if (!w.ensure(1)) { w.sync(); return; }
757 w.ptr[w.pos++] = '{';
758 bool first = true;
759 // Iterate through all members of T using reflection
760 static constexpr auto members = std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()));
761 template for (constexpr auto mem : members) {
762 if constexpr (std::meta::is_public(mem)) {
763 static constexpr std::string_view key = std::define_static_string(std::meta::identifier_of(mem));
764
765 // Only serialize this field if it's in our list of requested fields
766 if constexpr (((FieldNames.view() == key) || ...)) {
767 static constexpr auto first_key = std::define_static_string(
768 constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(mem)) + ":");
769 static constexpr auto rest_key = std::define_static_string(
770 std::string(",") + constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(mem)) + ":");
771 constexpr size_t first_key_len = std::char_traits<char>::length(first_key);
772 constexpr size_t rest_key_len = std::char_traits<char>::length(rest_key);
773 if (!w.ensure(rest_key_len)) { w.sync(); return; }
774 if (first) {
775 std::memcpy(w.ptr + w.pos, first_key, first_key_len);
776 w.pos += first_key_len;
777 } else {
778 std::memcpy(w.ptr + w.pos, rest_key, rest_key_len);
779 w.pos += rest_key_len;
780 }
781 first = false;
782 atom(w, obj.[:mem:]);
783 }
784 }
785 };
786
787 if (!w.ensure(1)) { w.sync(); return; }
788 w.ptr[w.pos++] = '}';
789 w.sync();
790}
791
792template<constevalutil::fixed_string... FieldNames, typename T>
793 requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0))
794simdjson_warn_unused simdjson_result<std::string> extract_from(const T &obj, size_t initial_capacity = string_builder::DEFAULT_INITIAL_CAPACITY) {
795 string_builder b(initial_capacity);
796 extract_from<FieldNames...>(b, obj);
797 std::string_view s;
798 if(auto e = b.view().get(s); e) { return e; }
799 return std::string(s);
800}
801
802SIMDJSON_POP_DISABLE_WARNINGS
803
804} // namespace builder
805} // namespace SIMDJSON_IMPLEMENTATION
806// Alias the function template to 'to' in the global namespace
807template <class Z>
808simdjson_warn_unused simdjson_result<std::string> to_json(const Z &z, size_t initial_capacity = SIMDJSON_IMPLEMENTATION::builder::string_builder::DEFAULT_INITIAL_CAPACITY) {
809 return SIMDJSON_IMPLEMENTATION::builder::to_json_string(z, initial_capacity);
810}
811template <class Z>
812simdjson_warn_unused error_code to_json(const Z &z, std::string &s, size_t initial_capacity = SIMDJSON_IMPLEMENTATION::builder::string_builder::DEFAULT_INITIAL_CAPACITY) {
813 return SIMDJSON_IMPLEMENTATION::builder::to_json(z, s, initial_capacity);
814}
815// Global namespace function for extract_from
816template<constevalutil::fixed_string... FieldNames, typename T>
817 requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0))
818simdjson_warn_unused simdjson_result<std::string> extract_from(const T &obj, size_t initial_capacity = SIMDJSON_IMPLEMENTATION::builder::string_builder::DEFAULT_INITIAL_CAPACITY) {
819 SIMDJSON_IMPLEMENTATION::builder::string_builder b(initial_capacity);
820 SIMDJSON_IMPLEMENTATION::builder::extract_from<FieldNames...>(b, obj);
821 std::string_view s;
822 if(auto e = b.view().get(s); e) { return e; }
823 return std::string(s);
824}
825
826} // namespace simdjson
827
828#endif // SIMDJSON_STATIC_REFLECTION
829
830#endif
Provides compile-time annotations for simdjson structures.
The top level simdjson namespace, containing everything the library provides.
Definition base.h:8
simdjson_result< std::string_view > to_json_string(SIMDJSON_IMPLEMENTATION::ondemand::document &x) noexcept
Create a string-view instance out of a document instance.
error_code
All possible errors returned by simdjson.
Definition error.h:19
@ SUCCESS
No error.
Definition error.h:20