GMP 0.4.0
Generative Metaprogramming library for C++
Loading...
Searching...
No Matches
json.hpp
Go to the documentation of this file.
1// ___ __ __ ___
2// / __| \/ | _ \ GMP(Generative Metaprogramming)
3// | (_ | |\/| | _/ version 0.4.0
4// \___|_| |_|_| https://github.com/lkimuk/gmp
5//
6// SPDX-FileCopyrightText: 2023-2026 Miles Li <https://www.cppmore.com/>
7// SPDX-License-Identifier: MIT
8//
9// This file is part of the GMP (Generative Metaprogramming) library.
10// Full project source: https://github.com/lkimuk/gmp
11
12#ifndef GMP_SERIALIZATION_JSON_HPP_
13#define GMP_SERIALIZATION_JSON_HPP_
14
15#include <charconv>
16#include <cmath>
17#include <concepts>
18#include <cstdint>
19#include <limits>
20#include <string>
21#include <string_view>
22#include <type_traits>
23#include <variant>
24#include <vector>
25
26#include <gmp/serialization/detail/text.hpp>
27#include <gmp/serialization/detail/write_value.hpp>
29
30namespace gmp {
31
32namespace detail {
33
34class json_parser {
35public:
36 json_parser(std::string_view input, json_read_options options)
37 : input_(input), options_(options) {}
38
40 if (input_.size() > options_.max_input_size) {
42 "JSON input exceeds the configured size limit");
43 }
44 skip_whitespace();
45 auto value = parse_value(0);
46 if (!value) {
47 return value.error();
48 }
49 skip_whitespace();
50 if (!at_end()) {
52 "unexpected characters after the JSON value");
53 }
54 return value;
55 }
56
57private:
58 [[nodiscard]] bool at_end() const noexcept {
59 return position_ >= input_.size();
60 }
61
62 [[nodiscard]] char peek() const noexcept {
63 return at_end() ? '\0' : input_[position_];
64 }
65
66 serialization_error error(serialization_errc code, std::string message) const {
67 return make_serialization_error(code, std::move(message), {}, position_);
68 }
69
70 void skip_whitespace() noexcept {
71 while (!at_end()) {
72 const char c = peek();
73 if (c != ' ' && c != '\t' && c != '\n' && c != '\r') {
74 break;
75 }
76 ++position_;
77 }
78 }
79
80 bool consume(char expected) noexcept {
81 if (peek() != expected) {
82 return false;
83 }
84 ++position_;
85 return true;
86 }
87
88 bool consume_literal(std::string_view literal) noexcept {
89 if (input_.substr(position_, literal.size()) != literal) {
90 return false;
91 }
92 position_ += literal.size();
93 return true;
94 }
95
96 serialization_result<serialization_value> parse_value(std::size_t depth) {
97 skip_whitespace();
98 const auto start = position_;
99 auto value = parse_value_impl(depth);
100 if (value) {
101 value->source_offset(start);
102 }
103 return value;
104 }
105
106 serialization_result<serialization_value> parse_value_impl(std::size_t depth) {
107 if (depth > options_.max_depth) {
108 return error(serialization_errc::depth_limit_exceeded, "maximum JSON nesting depth exceeded");
109 }
110 switch (peek()) {
111 case 'n':
112 if (!consume_literal("null")) {
113 return error(serialization_errc::invalid_syntax, "invalid null literal");
114 }
115 return serialization_value(nullptr);
116 case 't':
117 if (!consume_literal("true")) {
118 return error(serialization_errc::invalid_syntax, "invalid boolean literal");
119 }
120 return serialization_value(true);
121 case 'f':
122 if (!consume_literal("false")) {
123 return error(serialization_errc::invalid_syntax, "invalid boolean literal");
124 }
125 return serialization_value(false);
126 case '"': {
127 auto string = parse_string();
128 if (!string) {
129 return string.error();
130 }
131 return serialization_value(std::move(*string));
132 }
133 case '[':
134 if (depth >= options_.max_depth) {
136 "maximum JSON nesting depth exceeded");
137 }
138 return parse_array(depth + 1);
139 case '{':
140 if (depth >= options_.max_depth) {
142 "maximum JSON nesting depth exceeded");
143 }
144 return parse_object(depth + 1);
145 default:
146 if (peek() == '-' || (peek() >= '0' && peek() <= '9')) {
147 return parse_number();
148 }
149 if (at_end()) {
150 return error(serialization_errc::unexpected_end, "expected a JSON value");
151 }
152 return error(serialization_errc::invalid_syntax, "invalid JSON value");
153 }
154 }
155
156 serialization_result<serialization_value> parse_array(std::size_t depth) {
157 consume('[');
158 skip_whitespace();
160 if (consume(']')) {
161 return serialization_value(std::move(array));
162 }
163 for (;;) {
164 if (array.size() >= options_.max_container_size) {
166 "JSON array exceeds the configured size limit");
167 }
168 auto item = parse_value(depth);
169 if (!item) {
170 return item.error();
171 }
172 array.emplace_back(std::move(*item));
173 skip_whitespace();
174 if (consume(']')) {
175 break;
176 }
177 if (!consume(',')) {
178 return error(serialization_errc::invalid_syntax, "expected ',' or ']' in array");
179 }
180 skip_whitespace();
181 }
182 return serialization_value(std::move(array));
183 }
184
185 serialization_result<serialization_value> parse_object(std::size_t depth) {
186 consume('{');
187 skip_whitespace();
189 if (consume('}')) {
190 return serialization_value(std::move(object));
191 }
192 for (;;) {
193 if (object.size() >= options_.max_container_size) {
195 "JSON object exceeds the configured size limit");
196 }
197 if (peek() != '"') {
198 return error(serialization_errc::invalid_syntax, "expected a string object key");
199 }
200 auto key = parse_string();
201 if (!key) {
202 return key.error();
203 }
204 skip_whitespace();
205 if (!consume(':')) {
206 return error(serialization_errc::invalid_syntax, "expected ':' after object key");
207 }
208 auto value = parse_value(depth);
209 if (!value) {
210 return value.error();
211 }
212 object.emplace_back(std::move(*key), std::move(*value));
213 skip_whitespace();
214 if (consume('}')) {
215 break;
216 }
217 if (!consume(',')) {
218 return error(serialization_errc::invalid_syntax, "expected ',' or '}' in object");
219 }
220 skip_whitespace();
221 }
222 return serialization_value(std::move(object));
223 }
224
226 if (input_.size() - position_ < 4) {
227 return error(serialization_errc::unexpected_end, "incomplete Unicode escape");
228 }
229 std::uint32_t value = 0;
230 for (unsigned i = 0; i < 4; ++i) {
231 const char c = input_[position_++];
232 value <<= 4;
233 if (c >= '0' && c <= '9') {
234 value |= static_cast<std::uint32_t>(c - '0');
235 } else if (c >= 'a' && c <= 'f') {
236 value |= static_cast<std::uint32_t>(c - 'a' + 10);
237 } else if (c >= 'A' && c <= 'F') {
238 value |= static_cast<std::uint32_t>(c - 'A' + 10);
239 } else {
240 return error(serialization_errc::invalid_escape, "invalid Unicode escape");
241 }
242 }
243 return value;
244 }
245
246 serialization_result<std::string> parse_string() {
247 consume('"');
248 std::string output;
249 while (!at_end()) {
250 const unsigned char c = static_cast<unsigned char>(input_[position_++]);
251 if (c == '"') {
252 if (!is_valid_utf8(output)) {
253 return error(serialization_errc::invalid_utf8, "string is not valid UTF-8");
254 }
255 return output;
256 }
257 if (c < 0x20) {
258 return error(serialization_errc::invalid_syntax, "unescaped control character in string");
259 }
260 if (c != '\\') {
261 output.push_back(static_cast<char>(c));
262 } else {
263 if (at_end()) {
264 return error(serialization_errc::unexpected_end, "incomplete string escape");
265 }
266 switch (input_[position_++]) {
267 case '"':
268 output.push_back('"');
269 break;
270 case '\\':
271 output.push_back('\\');
272 break;
273 case '/':
274 output.push_back('/');
275 break;
276 case 'b':
277 output.push_back('\b');
278 break;
279 case 'f':
280 output.push_back('\f');
281 break;
282 case 'n':
283 output.push_back('\n');
284 break;
285 case 'r':
286 output.push_back('\r');
287 break;
288 case 't':
289 output.push_back('\t');
290 break;
291 case 'u': {
292 auto first = parse_hex4();
293 if (!first) {
294 return first.error();
295 }
296 std::uint32_t code_point = *first;
297 if (code_point >= 0xd800 && code_point <= 0xdbff) {
298 if (!consume('\\') || !consume('u')) {
300 "high surrogate must be followed by a low surrogate");
301 }
302 auto second = parse_hex4();
303 if (!second) {
304 return second.error();
305 }
306 if (*second < 0xdc00 || *second > 0xdfff) {
307 return error(serialization_errc::invalid_escape, "invalid low surrogate");
308 }
309 code_point = 0x10000 + ((code_point - 0xd800) << 10) + (*second - 0xdc00);
310 } else if (code_point >= 0xdc00 && code_point <= 0xdfff) {
311 return error(serialization_errc::invalid_escape, "unexpected low surrogate");
312 }
314 break;
315 }
316 default:
317 return error(serialization_errc::invalid_escape, "invalid string escape");
318 }
319 }
320 if (output.size() > options_.max_string_size) {
322 "JSON string exceeds the configured size limit");
323 }
324 }
325 return error(serialization_errc::unexpected_end, "unterminated string");
326 }
327
329 const std::size_t start = position_;
330 consume('-');
331 if (consume('0')) {
332 if (peek() >= '0' && peek() <= '9') {
333 return error(serialization_errc::invalid_number, "leading zero in JSON number");
334 }
335 } else {
336 if (peek() < '1' || peek() > '9') {
337 return error(serialization_errc::invalid_number, "invalid JSON number");
338 }
339 while (peek() >= '0' && peek() <= '9') {
340 ++position_;
341 }
342 }
343 bool floating = false;
344 if (consume('.')) {
345 floating = true;
346 if (peek() < '0' || peek() > '9') {
347 return error(serialization_errc::invalid_number, "fraction requires at least one digit");
348 }
349 while (peek() >= '0' && peek() <= '9') {
350 ++position_;
351 }
352 }
353 if (peek() == 'e' || peek() == 'E') {
354 floating = true;
355 ++position_;
356 if (peek() == '+' || peek() == '-') {
357 ++position_;
358 }
359 if (peek() < '0' || peek() > '9') {
360 return error(serialization_errc::invalid_number, "exponent requires at least one digit");
361 }
362 while (peek() >= '0' && peek() <= '9') {
363 ++position_;
364 }
365 }
366
367 const auto token = input_.substr(start, position_ - start);
368 if (!floating) {
369 if (!token.empty() && token.front() == '-') {
370 std::int64_t integer = 0;
371 const auto parsed = std::from_chars(token.data(), token.data() + token.size(), integer);
372 if (parsed.ec == std::errc{} && parsed.ptr == token.data() + token.size()) {
373 return serialization_value(integer);
374 }
375 } else {
376 std::uint64_t integer = 0;
377 const auto parsed = std::from_chars(token.data(), token.data() + token.size(), integer);
378 if (parsed.ec == std::errc{} && parsed.ptr == token.data() + token.size()) {
379 return serialization_value(integer);
380 }
381 }
382 }
383
384 double number = 0;
385 const auto parsed = std::from_chars(token.data(), token.data() + token.size(), number,
386 std::chars_format::general);
387 if (parsed.ec != std::errc{} || parsed.ptr != token.data() + token.size() ||
388 !std::isfinite(number)) {
389 return error(serialization_errc::invalid_number, "JSON number is out of range");
390 }
391 return serialization_value(number);
392 }
393
394 std::string_view input_;
395 json_read_options options_;
396 std::size_t position_ = 0;
397};
398
399inline serialization_result<void> append_json_string(std::string &output, std::string_view string,
400 std::size_t max_size) {
401 if (!is_valid_utf8(string)) {
402 return make_serialization_error(serialization_errc::invalid_utf8, "string is not valid UTF-8");
403 }
404 std::size_t encoded_size = 2;
405 for (const unsigned char c : string) {
406 const std::size_t width =
407 c == '"' || c == '\\' || c == '\b' || c == '\f' || c == '\n' || c == '\r' || c == '\t' ? 2
408 : c < 0x20 ? 6
409 : 1;
412 "JSON output exceeds configured limit");
413 }
415 }
416 if (output.size() > max_size || encoded_size > max_size - output.size()) {
418 "JSON output exceeds configured limit");
419 }
420 output.reserve(output.size() + encoded_size);
421 constexpr char hex[] = "0123456789abcdef";
422 output.push_back('"');
423 for (const unsigned char c : string) {
424 switch (c) {
425 case '"':
426 output += "\\\"";
427 break;
428 case '\\':
429 output += "\\\\";
430 break;
431 case '\b':
432 output += "\\b";
433 break;
434 case '\f':
435 output += "\\f";
436 break;
437 case '\n':
438 output += "\\n";
439 break;
440 case '\r':
441 output += "\\r";
442 break;
443 case '\t':
444 output += "\\t";
445 break;
446 default:
447 if (c < 0x20) {
448 output += "\\u00";
449 output.push_back(hex[c >> 4]);
450 output.push_back(hex[c & 15]);
451 } else {
452 output.push_back(static_cast<char>(c));
453 }
454 }
455 }
456 output.push_back('"');
457 return {};
458}
459
460class json_writer {
461public:
462 explicit json_writer(json_write_options options = {}) : options_(options) {}
463
464 serialization_result<void> write_null() {
465 return scalar("null");
466 }
467
468 serialization_result<void> write_bool(bool v) {
469 return scalar(v ? "true" : "false");
470 }
471
472 serialization_result<void> write_signed(std::int64_t v) {
473 return number(v);
474 }
475
476 serialization_result<void> write_unsigned(std::uint64_t v) {
477 return number(v);
478 }
479
480 serialization_result<void> write_floating(double v) {
481 if (!std::isfinite(v)) {
483 "non-finite number cannot be represented");
484 }
485 char b[64];
486 auto c = std::to_chars(b, b + sizeof(b), v, std::chars_format::general,
487 std::numeric_limits<double>::max_digits10);
488 if (c.ec != std::errc{}) {
490 "failed to format floating point value");
491 }
492 auto s = before_value();
493 if (!s) {
494 return s;
495 }
496 return append(std::string_view(b, static_cast<std::size_t>(c.ptr - b)));
497 }
498
499 serialization_result<void> write_string(std::string_view v) {
500 auto s = before_value();
501 if (!s) {
502 return s;
503 }
504 return append_json_string(output_, v, options_.max_output_size);
505 }
506
507 serialization_result<void> begin_array(std::size_t) {
508 return begin(false);
509 }
510
511 serialization_result<void> end_array() {
512 return end(false);
513 }
514
515 serialization_result<void> begin_object(std::size_t) {
516 return begin(true);
517 }
518
519 serialization_result<void> write_key(std::string_view key) {
520 if (stack_.empty() || !stack_.back().object || stack_.back().expecting_value) {
522 "object key in invalid archive state");
523 }
524 auto &f = stack_.back();
525 serialization_result<void> s;
526 if (f.count && !(s = append_char(','))) {
527 return s;
528 }
529 if (options_.pretty) {
530 if (!(s = append_char('\n')) || !(s = append_indent(stack_.size()))) {
531 return s;
532 }
533 }
534 if (!(s = append_json_string(output_, key, options_.max_output_size))) {
535 return s;
536 }
537 if (!(s = append(options_.pretty ? ": " : ":"))) {
538 return s;
539 }
540 ++f.count;
541 f.expecting_value = true;
542 return {};
543 }
544
545 serialization_result<void> end_object() {
546 return end(true);
547 }
548
550 if (finished_) {
552 "JSON archive is already finished");
553 }
554 if (!stack_.empty() || !has_root_) {
555 return make_serialization_error(serialization_errc::custom_error, "incomplete JSON archive");
556 }
557 finished_ = true;
558 return std::move(output_);
559 }
560
561private:
562 struct frame {
563 bool object;
564 std::size_t count = 0;
565 bool expecting_value = false;
566 };
567
568 serialization_result<void> before_value() {
569 if (finished_) {
571 "JSON archive is already finished");
572 }
573 if (stack_.empty()) {
574 if (has_root_) {
575 return make_serialization_error(serialization_errc::custom_error, "multiple root values");
576 }
577 has_root_ = true;
578 return {};
579 }
580 auto &f = stack_.back();
581 if (f.object) {
582 if (!f.expecting_value) {
584 "object value without key");
585 }
586 f.expecting_value = false;
587 } else {
588 serialization_result<void> s;
589 if (f.count && !(s = append_char(','))) {
590 return s;
591 }
592 if (options_.pretty) {
593 if (!(s = append_char('\n')) || !(s = append_indent(stack_.size()))) {
594 return s;
595 }
596 }
597 ++f.count;
598 }
599 return {};
600 }
601
602 serialization_result<void> begin(bool object) {
603 if (stack_.size() >= options_.max_depth) {
605 "maximum JSON output depth exceeded");
606 }
607 auto s = before_value();
608 if (!s) {
609 return s;
610 }
611 if (!(s = append_char(object ? '{' : '['))) {
612 return s;
613 }
614 stack_.push_back({object});
615 return {};
616 }
617
618 serialization_result<void> end(bool object) {
619 if (stack_.empty() || stack_.back().object != object || stack_.back().expecting_value) {
621 "mismatched JSON archive end");
622 }
623 auto f = stack_.back();
624 stack_.pop_back();
625 serialization_result<void> s;
626 if (options_.pretty && f.count) {
627 if (!(s = append_char('\n')) || !(s = append_indent(stack_.size()))) {
628 return s;
629 }
630 }
631 return append_char(object ? '}' : ']');
632 }
633
634 serialization_result<void> scalar(std::string_view text) {
635 auto s = before_value();
636 if (!s) {
637 return s;
638 }
639 return append(text);
640 }
641
642 template <typename I> serialization_result<void> number(I v) {
643 char b[32];
644 auto c = std::to_chars(b, b + sizeof(b), v);
645 if (c.ec != std::errc{}) {
647 "failed to format integer");
648 }
649 auto s = before_value();
650 if (!s) {
651 return s;
652 }
653 return append(std::string_view(b, static_cast<std::size_t>(c.ptr - b)));
654 }
655
656 serialization_result<void> append(std::string_view text) {
657 if (output_.size() > options_.max_output_size ||
658 text.size() > options_.max_output_size - output_.size()) {
660 "JSON output exceeds configured limit");
661 }
662 output_.append(text);
663 return {};
664 }
665
666 serialization_result<void> append_char(char value) {
667 return append(std::string_view(&value, 1));
668 }
669
670 serialization_result<void> append_indent(std::size_t depth) {
671 if (output_.size() > options_.max_output_size) {
673 "JSON output exceeds configured limit");
674 }
675 const auto remaining =
676 options_.max_output_size >= output_.size() ? options_.max_output_size - output_.size() : 0;
677 if (depth != 0 && options_.indent_width > remaining / depth) {
679 "JSON output exceeds configured limit");
680 }
681 output_.append(depth * options_.indent_width, ' ');
682 return {};
683 }
684
685 json_write_options options_;
686 std::string output_;
687 std::vector<frame> stack_;
688 bool has_root_ = false;
689 bool finished_ = false;
690};
691
692} // namespace detail
693
695 json_read_options options = {}) {
696 return detail::json_parser(input, options).parse();
697}
698
700 json_write_options options = {}) {
701 detail::json_writer writer(options);
702 auto s = detail::write_serialization_value(writer, value);
703 if (!s) {
704 return s.error();
705 }
706 return writer.finish();
707}
708
709template <typename T>
711 json_write_options json = {}) {
712 detail::json_writer writer(json);
713 basic_serializer archive(writer, serialization);
714 auto s = archive.encode(value);
715 if (!s) {
716 return s.error();
717 }
718 return writer.finish();
719}
720
721template <typename T>
724 json_read_options json = {}) {
725 auto value = parse_json(input, json);
726 if (!value) {
727 return value.error();
728 }
730}
731
732template <typename T>
733std::string to_json_or_throw(const T &value, serialization_options serialization = {},
734 json_write_options json = {}) {
735 auto r = to_json(value, serialization, json);
736 if (!r) {
737 throw serialization_exception(r.error());
738 }
739 return std::move(*r);
740}
741
742template <typename T>
744 json_read_options json = {}) {
746 if (!r) {
747 throw serialization_exception(r.error());
748 }
749 return std::move(*r);
750}
751
752} // namespace gmp
753
754#endif // GMP_SERIALIZATION_JSON_HPP_
std::vector< std::pair< std::string, serialization_value > > object
Definition value.hpp:32
std::vector< serialization_value > array
Definition value.hpp:31
consteval auto enum_values()
Get all enumerator values of an enumeration type at compile-time.
Definition meta.hpp:193
Definition lock.hpp:21
serialization_result< std::string > write_json(const serialization_value &value, json_write_options options={})
Definition json.hpp:699
serialization_error make_serialization_error(serialization_errc code, std::string message, std::string path={}, std::size_t offset=serialization_error::unknown_offset)
Definition error.hpp:160
std::string to_json_or_throw(const T &value, serialization_options serialization={}, json_write_options json={})
Definition json.hpp:733
serialization_errc
Definition error.hpp:26
serialization_result< serialization_value > parse_json(std::string_view input, json_read_options options={})
Definition json.hpp:694
serialization_result< T > from_json(std::string_view input, deserialization_options deserialization={}, json_read_options json={})
Definition json.hpp:722
serialization_result< std::string > to_json(const T &value, serialization_options serialization={}, json_write_options json={})
Definition json.hpp:710
T from_json_or_throw(std::string_view input, deserialization_options deserialization={}, json_read_options json={})
Definition json.hpp:743