simdjson 5.0.2
Ridiculously Fast JSON
Loading...
Searching...
No Matches
compile_time_json-inl.h
Go to the documentation of this file.
1
10#ifndef SIMDJSON_GENERIC_COMPILE_TIME_JSON_INL_H
11
12#if SIMDJSON_STATIC_REFLECTION
13
15#include "simdjson/internal/fast_float.h"
16#include <array>
17#include <cstdint>
18#include <meta>
19#include <limits>
20#include <string_view>
21
22#include <algorithm>
23#include <array>
24#include <charconv>
25#include <cstdint>
26#include <expected>
27#include <meta>
28#include <string>
29#include <string_view>
30#include <vector>
31
32#define simdjson_consteval_error(...) \
33 { \
34 std::abort(); \
35 }
36
37namespace simdjson {
38namespace compile_time {
39
48namespace number_parsing {
49
50// Parses a JSON float starting at src. Returns the value and the number of
51// characters consumed.
52//
53// Eisel-Lemire needs somewhere to fall back to: a mantissa of more than 19
54// significant digits, or an input where the truncated product cannot decide the
55// rounding, has to be finished by a slower exact method. A constant expression
56// cannot call the runtime one in src/from_chars.cpp, so we use fast_float, which
57// is correctly rounded in a constant expression and carries its own fallback.
58consteval std::pair<double, size_t> parse_double(const char *src,
59 const char *end) {
60 double value = 0;
61 auto answer = simdjson_fast_float::from_chars_advanced(
62 src, end, value,
64 simdjson_fast_float::chars_format::json});
65 if (answer.ec == std::errc::invalid_argument) {
66 simdjson_consteval_error("Invalid float value");
67 }
68 // fast_float reports result_out_of_range at either edge of the format. An
69 // underflow to zero is a value like any other; an overflow to infinity is not
70 // representable in JSON and was an error here before, so it stays one.
71 // (Comparing against max/lowest rather than calling std::isinf, which is not
72 // usable in a constant expression.)
73 if (value > (std::numeric_limits<double>::max)() ||
74 value < std::numeric_limits<double>::lowest()) {
75 simdjson_consteval_error("Overflow while computing the float value");
76 }
77 return {value, size_t(answer.ptr - src)};
78}
79
80} // namespace number_parsing
81
82consteval auto make_data_member_options(auto&& name_str) {
83 std::meta::data_member_options options{};
84 options.name = std::forward<decltype(name_str)>(name_str);
85 return options;
86}
87
88// JSON string may contain embedded nulls, and C++26 reflection does not yet
89// support std::string_view as a data member type. As a workaround, we define
90// a custom type that holds a const char* and a size.
91template <char... Vals> struct fixed_json_string {
92 // Statically-allocated array to hold the characters and a null terminator
93 static constexpr char inner_data[] = {Vals..., '\0'};
94
95 // Constant for the length of the string view (excluding the null terminator)
96 static constexpr std::size_t inner_size = sizeof...(Vals);
97
98 // The std::string_view over the data.
99 // We use data and size to avoid including the null terminator in the view.
100 static constexpr std::string_view view = {inner_data, inner_size};
101
102 constexpr operator std::string_view() { return view; }
103 constexpr const char *c_str() { return view.data(); }
104 constexpr const char *data() { return view.data(); }
105 constexpr size_t size() { return view.size(); }
106};
107
108consteval std::meta::info to_fixed_json_string(std::string_view in) {
109 std::vector<std::meta::info> Args = {};
110 for (char c : in) {
111 Args.push_back(std::meta::reflect_constant(c));
112 }
113 return std::meta::substitute(^^fixed_json_string, Args);
114}
115
119template <std::meta::info... meta_info> struct type_builder {
120 struct constructed_type;
121 consteval {
122 std::meta::define_aggregate(^^constructed_type, {
123 meta_info...});
124 }
125};
126
133template <std::meta::info... meta_info>
134using class_type = type_builder<meta_info...>::constructed_type;
135
142template <typename T, auto... Vs> constexpr T construct_from = T{Vs...};
143
144
145// in JSON, there are only a few whitespace characters that are allowed
146// outside of objects, arrays, strings, and numbers.
147[[nodiscard]] constexpr bool is_whitespace(char c) {
148 return c == ' ' || c == '\n' || c == '\t' || c == '\r';
149};
150
151[[nodiscard]] constexpr std::string_view trim_whitespace(std::string_view str) {
152 size_t start = 0;
153 size_t end = str.size();
154
155 while (start < end && is_whitespace(str[start])) {
156 start++;
157 }
158 while (end > start && is_whitespace(str[end - 1])) {
159 end--;
160 }
161 return str.substr(start, end - start);
162}
163
164// Forward declaration
165consteval std::pair<std::meta::info, size_t>
166parse_json_object_impl(std::string_view json);
167
171
172// Parses a JSON number from a string view.
173// Returns the number of characters consumed and the parsed value
174// as a variant of int64_t, uint64_t, or double.
175[[nodiscard]] consteval size_t
176parse_number(std::string_view json,
177 std::variant<int64_t, uint64_t, double> &out) {
178 if (json.empty()) {
179 simdjson_consteval_error("Empty string is not a valid JSON number");
180 }
181 if (json[0] == '+') {
182 simdjson_consteval_error("Invalid number: leading '+' sign is not allowed");
183 }
184 // Compute the range and determine the type.
185 auto it = json.begin();
186 if (it != json.end() && (*it == '-')) {
187 it++;
188 }
189 while (it != json.end() && (*it >= '0' && *it <= '9')) {
190 it++;
191 }
192 bool is_float = false;
193 if (it != json.end() && (*it == '.' || *it == 'e' || *it == 'E')) {
194 is_float = true;
195 if (*it == '.') {
196 it++;
197 while (it != json.end() && (*it >= '0' && *it <= '9')) {
198 it++;
199 }
200 }
201 if (it != json.end() && (*it == 'e' || *it == 'E')) {
202 it++;
203 if (it != json.end() && (*it == '+' || *it == '-')) {
204 it++;
205 }
206 while (it != json.end() && (*it >= '0' && *it <= '9')) {
207 it++;
208 }
209 }
210 }
211 size_t scope = it - json.begin();
212
213 bool is_negative = json[0] == '-';
214 // Note that we consider -0 to be an integer unless it has a decimal point or
215 // exponent.
216 if (is_float) {
217 auto [value, offset] =
218 number_parsing::parse_double(json.data(), json.data() + json.size());
219 if (offset != scope) {
220 simdjson_consteval_error(
221 "Internal error: cannot agree on the character range of the float");
222 }
223 out = value;
224 return offset;
225 } else if (is_negative) {
226 int64_t int_value = 0;
227 std::from_chars_result res =
228 std::from_chars(json.data(), json.data() + json.size(), int_value);
229 if (res.ec == std::errc()) {
230 out = int_value;
231 if (static_cast<std::size_t>(res.ptr - json.data()) != scope) {
232 simdjson_consteval_error(
233 "Internal error: cannot agree on the character range of the float");
234 }
235 return (res.ptr - json.data());
236 } else {
237 simdjson_consteval_error("Invalid integer value");
238 }
239 } else {
240 uint64_t uint_value = 0;
241 std::from_chars_result res =
242 std::from_chars(json.data(), json.data() + json.size(), uint_value);
243 if (res.ec == std::errc()) {
244 out = uint_value;
245 if (static_cast<std::size_t>(res.ptr - json.data()) != scope) {
246 simdjson_consteval_error(
247 "Internal error: cannot agree on the character range of the float");
248 }
249 return (res.ptr - json.data());
250 } else {
251 simdjson_consteval_error("Invalid unsigned integer value");
252 }
253 }
254}
255
259
260// parse a JSON string value, handling escape sequences and validating UTF-8
261// Returns the created string and the number of characters consumed.
262// Note that the number of characters consumed includes the surrounding quotes.
263// The number of bytes written to out differs from the number of characters
264// consumed in general because of escape sequences and UTF-8 encoding.
265[[nodiscard]] consteval std::pair<std::string, size_t>
266parse_string(std::string_view json) {
267 auto cursor = json.begin();
268 auto end = json.end();
269 std::string out;
270
271 // Expect starting quote
272 if (cursor == end || *(cursor++) != '"') {
273 simdjson_consteval_error("Expected opening quote for string");
274 }
275 // Notice that the quote is not appended.
276
277 // Process \uXXXX escape sequences, writes out to out.
278 // Returns true on success, false on error.
279 auto process_escaped_unicode = [&] [[nodiscard]] () -> bool {
280 // Processing \uXXXX escape sequence is a bit complicated, so we
281 // define helper lambdas here.
282 //
283 // consume the XXXX in \uXXXX and return the corresponding code point.
284 // In case of error, a value greater than 0xFFFF is returned.
285 // The caller should check!
286 auto hex_to_u32 = [&] [[nodiscard]] () -> uint32_t {
287 auto digit = [](uint8_t c) -> uint32_t {
288 if (c >= '0' && c <= '9')
289 return c - '0';
290 if (c >= 'A' && c <= 'F')
291 return c - 'A' + 10;
292 if (c >= 'a' && c <= 'f')
293 return c - 'a' + 10;
294 return 0xFFFFFFFF;
295 };
296 if (end - cursor < 4) {
297 return 0xFFFFFFFF;
298 }
299 uint32_t d0 = digit(*cursor++);
300 uint32_t d1 = digit(*cursor++);
301 uint32_t d2 = digit(*cursor++);
302 uint32_t d3 = digit(*cursor++);
303
304 return (d0 << 12) | (d1 << 8) | (d2 << 4) | d3;
305 };
306 // Write code point as UTF-8 into out.
307 // The caller must ensure that the code point is valid,
308 // i.e., not in the surrogate range or greater than 0x10FFFF.
309 auto codepoint_to_utf8 = [&out] [[nodiscard]] (uint32_t cp) -> bool {
310 // the high and low surrogates U+D800 through U+DFFF are invalid code
311 // points
312 if (cp > 0x10FFFF || (cp >= 0xD800 && cp <= 0xDFFF)) {
313 return false;
314 }
315 if (cp <= 0x7F) {
316 out.push_back(uint8_t(cp));
317 } else if (cp <= 0x7FF) {
318 out.push_back(uint8_t((cp >> 6) + 192));
319 out.push_back(uint8_t((cp & 63) + 128));
320 } else if (cp <= 0xFFFF) {
321 out.push_back(uint8_t((cp >> 12) + 224));
322 out.push_back(uint8_t(((cp >> 6) & 63) + 128));
323 out.push_back(uint8_t((cp & 63) + 128));
324 } else if (cp <= 0x10FFFF) {
325 out.push_back(uint8_t((cp >> 18) + 240));
326 out.push_back(uint8_t(((cp >> 12) & 63) + 128));
327 out.push_back(uint8_t(((cp >> 6) & 63) + 128));
328 out.push_back(uint8_t((cp & 63) + 128));
329 } else {
330 return false;
331 }
332 return true;
333 };
334 // We begin the implementation of process_escaped_unicode here.
335 // The substitution_code_point is used for invalid sequences.
336 // (This is U+FFFD, the Unicode replacement character.)
337 constexpr uint32_t substitution_code_point = 0xfffd;
338 // We need at least 4 characters for the hex digits
339 if (end - cursor < 4) {
340 return false;
341 }
342 // Consume the 4 hex digits
343 uint32_t code_point = hex_to_u32();
344 // Check for validity
345 if (code_point > 0xFFFF) {
346 return false;
347 }
348 // If we have a high surrogate, we need to process a low surrogate
349 if (code_point >= 0xd800 && code_point < 0xdc00) {
350 // We need at least 6 more characters for \uXXXX, if they are NOT
351 // present, we have an error (isolated high surrogate), which we
352 // tolerate by substituting the substitution_code_point.
353 if (end - cursor < 6 || *cursor != '\\' ||
354 *(cursor + 1) != 'u') {
355 code_point = substitution_code_point;
356 } else { // we have \u following the high surrogate
357 cursor += 2; // skip \u
358 uint32_t code_point_2 = hex_to_u32();
359 if (code_point_2 > 0xFFFF) {
360 return false;
361 }
362 uint32_t low_bit = code_point_2 - 0xdc00;
363 if (low_bit >> 10) {
364 // Not a valid low surrogate
365 code_point = substitution_code_point;
366 } else {
367 code_point = ((code_point - 0xd800) << 10 | low_bit) + 0x10000;
368 }
369 }
370 } else if (code_point >= 0xdc00 && code_point < 0xe000) {
371 // Isolated low surrogate, invalid
372 code_point = substitution_code_point;
373 }
374 // Now we have the final code point, write it as UTF-8 to out.
375 return codepoint_to_utf8(code_point);
376 };
377
378 while (cursor != end && *cursor != '"') {
379 // If we find the end of input before closing quote, it's an error
380 if (cursor == end) {
381 simdjson_consteval_error("Unterminated string");
382 }
383 // capture the next character and move forward
384 char c = *(cursor++);
385 // The one case where we don't want to append to the string directly
386 // is when the string contains escape sequences.
387
388 if (c == '\\') {
389 // Assume that it is the start of an escape sequence
390 size_t how_many_backslashes = 1;
391 while (cursor != end && *(cursor) == '\\') {
392 cursor++;
393 how_many_backslashes++;
394 }
395 size_t backslashes_to_add = how_many_backslashes / 2;
396 // Append the backslashes
397 for (size_t i = 0; i < backslashes_to_add; i++) {
398 out += '\\';
399 }
400 // If we have an odd number of backslashes, we have an escape sequence
401 // besides the backslashes we have already added.
402 if (how_many_backslashes % 2 == 1) {
403 // If we have an odd number of backslashes, we must be in an escape
404 // sequence check that what follows is a valid escape character
405 if (cursor == end) {
406 simdjson_consteval_error("Truncated escape sequence in string");
407 }
408 char next_char = *cursor;
409 cursor++;
410 switch (next_char) {
411 case '"':
412 out += '"';
413 break;
414 case '/':
415 out += '/';
416 break;
417 case 'b':
418 out += '\b';
419 break;
420 case 'f':
421 out += '\f';
422 break;
423 case 'n':
424 out += '\n';
425 break;
426 case 'r':
427 out += '\r';
428 break;
429 case 't':
430 out += '\t';
431 break;
432 case 'u':
433 if (!process_escaped_unicode()) {
434 simdjson_consteval_error(
435 "Invalid unicode escape sequence in string");
436 }
437 break;
438 default:
439 simdjson_consteval_error("Invalid escape character in string");
440 }
441 }
442 continue; // continue to next iteration
443 }
444 // Handle escape sequences and UTF-8 validation. We do not process
445 // the escape sequences here, just validate them.
446 out += c;
447 if (static_cast<unsigned char>(c) < 0x20) {
448 simdjson_consteval_error("Invalid control character in string");
449 }
450 if (static_cast<unsigned char>(c) >= 0x80) {
451 // We have a non-ASCII character inside a string
452 // We must validate it.
453 uint8_t first_byte = static_cast<uint8_t>(c);
454
455 if ((first_byte & 0b11100000) == 0b11000000) {
456 if (cursor == end) {
457 simdjson_consteval_error("Truncated UTF-8 sequence in string");
458 }
459
460 char second_byte = *cursor;
461 out += second_byte;
462 ++cursor;
463 if ((static_cast<uint8_t>(second_byte) & 0b11000000) != 0b10000000) {
464 simdjson_consteval_error("Invalid UTF-8 continuation byte in string");
465 }
466 // range check
467 uint32_t code_point = (first_byte & 0b00011111) << 6 |
468 (static_cast<uint8_t>(second_byte) & 0b00111111);
469 if ((code_point < 0x80) || (0x7ff < code_point)) {
470 simdjson_consteval_error("Invalid UTF-8 code point in string");
471 }
472 } else if ((first_byte & 0b11110000) == 0b11100000) {
473 if (cursor == end) {
474 simdjson_consteval_error("Truncated UTF-8 sequence in string");
475 }
476 char second_byte = *cursor;
477 ++cursor;
478 out += second_byte;
479 if ((static_cast<uint8_t>(second_byte) & 0b11000000) != 0b10000000) {
480 simdjson_consteval_error("Invalid UTF-8 continuation byte in string");
481 }
482 if (cursor == end) {
483 simdjson_consteval_error("Truncated UTF-8 sequence in string");
484 }
485 char third_byte = *cursor;
486 ++cursor;
487 out += third_byte;
488 if ((static_cast<uint8_t>(third_byte) & 0b11000000) != 0b10000000) {
489 simdjson_consteval_error("Invalid UTF-8 continuation byte in string");
490 }
491 // range check
492 uint32_t code_point = (first_byte & 0b00001111) << 12 |
493 (static_cast<uint8_t>(second_byte) & 0b00111111)
494 << 6 |
495 (static_cast<uint8_t>(third_byte) & 0b00111111);
496 if ((code_point < 0x800) || (0xffff < code_point) ||
497 (0xd7ff < code_point && code_point < 0xe000)) {
498 simdjson_consteval_error("Invalid UTF-8 code point in string");
499 }
500 } else if ((first_byte & 0b11111000) == 0b11110000) { // 0b11110000
501 if (cursor == end) {
502 simdjson_consteval_error("Truncated UTF-8 sequence in string");
503 }
504 char second_byte = *cursor;
505 ++cursor;
506 out += second_byte;
507 if (cursor == end) {
508 simdjson_consteval_error("Truncated UTF-8 sequence in string");
509 }
510 char third_byte = *cursor;
511 ++cursor;
512 out += third_byte;
513 if (cursor == end) {
514 simdjson_consteval_error("Truncated UTF-8 sequence in string");
515 }
516 char fourth_byte = *cursor;
517 ++cursor;
518 out += fourth_byte;
519 if ((static_cast<uint8_t>(second_byte) & 0b11000000) != 0b10000000) {
520 simdjson_consteval_error("Invalid UTF-8 continuation byte in string");
521 }
522 if ((static_cast<uint8_t>(third_byte) & 0b11000000) != 0b10000000) {
523 simdjson_consteval_error("Invalid UTF-8 continuation byte in string");
524 }
525 if ((static_cast<uint8_t>(fourth_byte) & 0b11000000) != 0b10000000) {
526 simdjson_consteval_error("Invalid UTF-8 continuation byte in string");
527 }
528 // range check
529 uint32_t code_point =
530 (first_byte & 0b00000111) << 18 |
531 (static_cast<uint8_t>(second_byte) & 0b00111111) << 12 |
532 (static_cast<uint8_t>(third_byte) & 0b00111111) << 6 |
533 (static_cast<uint8_t>(fourth_byte) & 0b00111111);
534 if (code_point <= 0xffff || 0x10ffff < code_point) {
535 simdjson_consteval_error("Invalid UTF-8 code point in string");
536 }
537 } else {
538 // we have a continuation
539 simdjson_consteval_error("Invalid UTF-8 continuation byte in string");
540 }
541 continue;
542 }
543 }
544 if (cursor == end) {
545 simdjson_consteval_error("Unterminated string");
546 }
547 if (*cursor != '"') {
548 simdjson_consteval_error("Internal error: expected closing quote");
549 }
550 cursor++; // consume the closing quote
551 // We get here if and only if we have seen the closing quote.
552
553 return {out, size_t(cursor - json.begin())};
554}
555
559
560// Parses a JSON array and returns a std::meta::info representing the array as
561// well as the number of characters consumed.
562consteval std::pair<std::meta::info, size_t>
563parse_json_array_impl(const std::string_view json) {
564 size_t consumed = 0;
565 auto cursor = json.begin();
566 auto end = json.end();
567 auto is_whitespace = [](char c) {
568 return c == ' ' || c == '\n' || c == '\t' || c == '\r';
569 };
570
571 auto skip_whitespace = [&]() -> void {
572 while (cursor != end && is_whitespace(*cursor))
573 cursor++;
574 };
575
576 auto expect_consume = [&] [[nodiscard]] (char c) -> bool {
577 skip_whitespace();
578 if (cursor == end || *(cursor++) != c) {
579 return false;
580 };
581 return true;
582 };
583
584 if (!expect_consume('[')) {
585 simdjson_consteval_error("Expected '['");
586 }
587
588 std::vector<std::meta::info> values = {^^void};
589 skip_whitespace();
590 if (cursor != end && *cursor == ']') {
591 if (!expect_consume(']')) {
592 simdjson_consteval_error("Expected ']'");
593 }
594 // Empty array - use int as placeholder type since void doesn't work
595 auto array_type = std::meta::substitute(
596 ^^std::array, {
597 ^^int, std::meta::reflect_constant(0uz)});
598 values[0] = array_type;
599 consumed = size_t(cursor - json.begin());
600 if (json[consumed - 1] != ']') {
601 simdjson_consteval_error("Expected ']'");
602 }
603 return {std::meta::substitute(^^construct_from, values), consumed};
604 }
605 while (cursor != end && *cursor != ']') {
606 char c = *cursor;
607 switch (c) {
608 case '{': {
609 std::string_view value(cursor, end);
610 auto [parsed, object_size] = parse_json_object_impl(value);
611 if (*(cursor + object_size - 1) != '}') {
612 simdjson_consteval_error("Expected '}'");
613 }
614 values.push_back(parsed);
615 cursor += object_size;
616 break;
617 }
618 case '[': {
619 std::string_view value(cursor, end);
620 auto [parsed, array_size] = parse_json_array_impl(value);
621 if (*(cursor + array_size - 1) != ']') {
622 simdjson_consteval_error("Expected ']'");
623 }
624 values.push_back(parsed);
625 cursor += array_size;
626 break;
627 }
628 case '"': {
629 auto res = parse_string(std::string_view(cursor, end));
630 cursor += res.second;
631 for (char ch : res.first) {
632 if (ch == '\0') {
633 simdjson_consteval_error(
634 "Field string values cannot contain embedded nulls");
635 }
636 }
637 values.push_back(std::meta::reflect_constant_string(res.first));
638 break;
639 }
640 case 't': {
641 if (end - cursor < 4 || std::string_view(cursor, 4) != "true") {
642 simdjson_consteval_error("Invalid value");
643 }
644 cursor += 4;
645 values.push_back(std::meta::reflect_constant(true));
646 break;
647 }
648 case 'f': {
649 if (end - cursor < 5 || std::string_view(cursor, 5) != "false") {
650 simdjson_consteval_error("Invalid value");
651 }
652 cursor += 5;
653 values.push_back(std::meta::reflect_constant(false));
654 break;
655 }
656 case 'n': {
657 if (end - cursor < 4 || std::string_view(cursor, 4) != "null") {
658 simdjson_consteval_error("Invalid value");
659 }
660 cursor += 4;
661 values.push_back(std::meta::reflect_constant(nullptr));
662 break;
663 }
664 // We deliberately do not include '+' here, as per JSON spec
665 case '-':
666 case '0':
667 case '1':
668 case '2':
669 case '3':
670 case '4':
671 case '5':
672 case '6':
673 case '7':
674 case '8':
675 case '9': {
676 std::string_view suffix = std::string_view(cursor, end);
677 std::variant<int64_t, uint64_t, double> out;
678 size_t r = parse_number(suffix, out);
679 cursor += r;
680 if (std::holds_alternative<int64_t>(out)) {
681 int64_t int_value = std::get<int64_t>(out);
682 values.push_back(std::meta::reflect_constant(int_value));
683 } else if (std::holds_alternative<uint64_t>(out)) {
684 uint64_t uint_value = std::get<uint64_t>(out);
685 values.push_back(std::meta::reflect_constant(uint_value));
686 } else {
687 double float_value = std::get<double>(out);
688 values.push_back(std::meta::reflect_constant(float_value));
689 }
690 break;
691 }
692 default:
693 simdjson_consteval_error("Invalid character starting value");
694 }
695 skip_whitespace();
696 if (cursor != end && *cursor == ',') {
697 ++cursor;
698 skip_whitespace();
699 }
700 }
701
702 if (!expect_consume(']')) {
703 simdjson_consteval_error("Expected ']'");
704 }
705 std::size_t count = values.size() - 1;
706 // We assume all elements have the same type as the first element.
707 // However, if the array is heterogeneous, we should use std::variant.
708 auto elem_type = std::meta::type_of(values[1]);
709 // String literals reflected via reflect_constant_string have type const
710 // char[N], but when passed as template auto parameters they decay to
711 // const char*. Use const char* as the element type so that
712 // construct_from can aggregate-initialize the array.
713 if (std::meta::is_array_type(elem_type) &&
714 std::meta::remove_all_extents(elem_type) == ^^const char) {
715 elem_type = ^^const char *;
716 }
717 auto array_type = std::meta::substitute(
718 ^^std::array,
719 {
720 elem_type, std::meta::reflect_constant(count)});
721
722 // Create array instance with values
723 values[0] = array_type;
724 consumed = size_t(cursor - json.begin());
725 if (json[consumed - 1] != ']') {
726 simdjson_consteval_error("Expected ']'");
727 }
728 return {std::meta::substitute(^^construct_from, values), consumed};
729}
730
734
735// Parses a JSON object and returns a std::meta::info representing the object
736// type as well as the number of characters consumed.
737consteval std::pair<std::meta::info, size_t>
738parse_json_object_impl(std::string_view json) {
739 size_t consumed = 0;
740 auto cursor = json.begin();
741 auto end = json.end();
742
743 auto skip_whitespace = [&]() -> void {
744 while (cursor != end && is_whitespace(*cursor))
745 cursor++;
746 };
747
748 auto expect_consume = [&] [[nodiscard]] (char c) -> bool {
749 skip_whitespace();
750 if (cursor == end || *(cursor++) != c) {
751 return false;
752 };
753 return true;
754 };
755
756 if (!expect_consume('{')) {
757 simdjson_consteval_error("Expected '{'");
758 }
759
760 std::vector<std::meta::info> members;
761 std::vector<std::meta::info> values = {^^void};
762
763 while (cursor != end && *cursor != '}') {
764 skip_whitespace();
765 // Not all strings can be identifiers, but in JSON field names can be any
766 // string. Thus we may have a problem if the field name contains characters
767 // not allowed in identifiers. Let the standard library handle that case.
768 auto field = parse_string(std::string_view(cursor, end));
769 std::string field_name = field.first;
770 cursor += field.second;
771 if (!expect_consume(':')) {
772 simdjson_consteval_error("Expected ':'");
773 }
774 skip_whitespace();
775 if (cursor == end) {
776 simdjson_consteval_error("Expected value after colon");
777 }
778 char c = *cursor;
779 switch (c) {
780 case '{': {
781 std::string_view value(cursor, end);
782 auto [parsed, object_size] = parse_json_object_impl(value);
783 if (*(cursor + object_size - 1) != '}') {
784 simdjson_consteval_error("Expected '}'");
785 }
786 cursor += object_size;
787 auto dms = std::meta::data_member_spec(std::meta::type_of(parsed), make_data_member_options(field_name));
788 members.push_back(std::meta::reflect_constant(dms));
789 values.push_back(parsed);
790
791 break;
792 }
793 case '[': {
794 std::string_view value(cursor, end);
795 auto [parsed, array_size] = parse_json_array_impl(value);
796 auto dms = std::meta::data_member_spec(std::meta::type_of(parsed), make_data_member_options(field_name));
797 members.push_back(std::meta::reflect_constant(dms));
798 values.push_back(parsed);
799 if (*(cursor + array_size - 1) != ']') {
800 simdjson_consteval_error("Expected ']'");
801 }
802 cursor += array_size;
803
804 break;
805 }
806 case '"': {
807 auto res = parse_string(std::string_view(cursor, end));
808 std::string_view value = res.first;
809 cursor += res.second;
810 for (char ch : value) {
811 if (ch == '\0') {
812 simdjson_consteval_error(
813 "Field string values cannot contain embedded nulls");
814 }
815 }
816 auto dms =
817 std::meta::data_member_spec(^^const char *, make_data_member_options(field_name));
818 members.push_back(std::meta::reflect_constant(dms));
819 values.push_back(std::meta::reflect_constant_string(value));
820 break;
821 }
822 case 't': {
823 if (end - cursor < 4 || std::string_view(cursor, 4) != "true") {
824 simdjson_consteval_error("Invalid value");
825 }
826 cursor += 4;
827
828 auto dms = std::meta::data_member_spec(^^bool, make_data_member_options(field_name));
829 members.push_back(std::meta::reflect_constant(dms));
830 values.push_back(std::meta::reflect_constant(true));
831 break;
832 }
833 case 'f': {
834 if (end - cursor < 5 || std::string_view(cursor, 5) != "false") {
835 simdjson_consteval_error("Invalid value");
836 }
837 cursor += 5;
838
839 auto dms = std::meta::data_member_spec(^^bool, make_data_member_options(field_name));
840 members.push_back(std::meta::reflect_constant(dms));
841 values.push_back(std::meta::reflect_constant(false));
842 break;
843 }
844 case 'n': {
845 if (end - cursor < 4 || std::string_view(cursor, 4) != "null") {
846 simdjson_consteval_error("Invalid value");
847 }
848 cursor += 4;
849
850 auto dms = std::meta::data_member_spec(^^std::nullptr_t, make_data_member_options(field_name));
851 members.push_back(std::meta::reflect_constant(dms));
852 values.push_back(std::meta::reflect_constant(nullptr));
853 break;
854 }
855 // We deliberately do not include '+' here, as per JSON spec
856 case '-':
857 case '0':
858 case '1':
859 case '2':
860 case '3':
861 case '4':
862 case '5':
863 case '6':
864 case '7':
865 case '8':
866 case '9': {
867 std::string_view suffix = std::string_view(cursor, end);
868 std::variant<int64_t, uint64_t, double> out;
869 size_t r = parse_number(suffix, out);
870 cursor += r;
871 if (std::holds_alternative<int64_t>(out)) {
872 int64_t int_value = std::get<int64_t>(out);
873 auto dms =
874 std::meta::data_member_spec(^^int64_t, make_data_member_options(field_name));
875 members.push_back(std::meta::reflect_constant(dms));
876 values.push_back(std::meta::reflect_constant(int_value));
877 } else if (std::holds_alternative<uint64_t>(out)) {
878 uint64_t uint_value = std::get<uint64_t>(out);
879 auto dms =
880 std::meta::data_member_spec(^^uint64_t, make_data_member_options(field_name));
881 members.push_back(std::meta::reflect_constant(dms));
882 values.push_back(std::meta::reflect_constant(uint_value));
883 } else {
884 double float_value = std::get<double>(out);
885 auto dms =
886 std::meta::data_member_spec(^^double, make_data_member_options(field_name));
887 members.push_back(std::meta::reflect_constant(dms));
888 values.push_back(std::meta::reflect_constant(float_value));
889 }
890 break;
891 }
892 default:
893 simdjson_consteval_error("Invalid character starting value");
894 }
895 skip_whitespace();
896 if (cursor == end) {
897 simdjson_consteval_error("Expected '}' or ','");
898 }
899 if (*cursor == ',') {
900 ++cursor;
901 skip_whitespace();
902 } else if (*cursor != '}') {
903 simdjson_consteval_error("Expected '}'");
904 }
905 }
906
907 if (!expect_consume('}')) {
908 simdjson_consteval_error("Expected '}'");
909 }
910 values[0] = std::meta::substitute(^^class_type, members);
911 consumed = size_t(cursor - json.begin());
912 if (json[consumed - 1] != '}') {
913 simdjson_consteval_error("Expected '}'");
914 }
915 return {std::meta::substitute(^^construct_from, values), consumed};
916}
917
924template <constevalutil::fixed_string json_str> consteval auto parse_json() {
925 constexpr std::string_view json = trim_whitespace(json_str.view());
926 static_assert(!json.empty(), "JSON string cannot be empty");
927 /*static_assert(json.front() != '{' && json.front() != '[',
928 "Only JSON objects and arrays are supported at the top level, this "
929 "limitation will be lifted in the future.");*/
930
931 if constexpr (json.front() == '[') {
932 return [: parse_json_array_impl(json).first :];
933 } else {
934 return [: parse_json_object_impl(json).first :];
935 }
936}
937
938} // namespace compile_time
939} // namespace simdjson
940
941#endif // SIMDJSON_STATIC_REFLECTION
942#endif // SIMDJSON_GENERIC_COMPILE_TIME_JSON_INL_H
Compile-time JSON parsing using C++26 reflection with std::meta::substitute()
The top level simdjson namespace, containing everything the library provides.
Definition base.h:8