WPILibC++ 2024.3.2
serializer.h
Go to the documentation of this file.
1// __ _____ _____ _____
2// __| | __| | | | JSON for Modern C++
3// | | |__ | | | | | | version 3.11.2
4// |_____|_____|_____|_|___| https://github.com/nlohmann/json
5//
6// SPDX-FileCopyrightText: 2008-2009 Björn Hoehrmann <bjoern@hoehrmann.de>
7// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>
8// SPDX-License-Identifier: MIT
9
10#pragma once
11
12#include <algorithm> // reverse, remove, fill, find, none_of
13#include <array> // array
14#include <clocale> // localeconv, lconv
15#include <cmath> // labs, isfinite, isnan, signbit
16#include <cstddef> // size_t, ptrdiff_t
17#include <cstdint> // uint8_t
18#include <cstdio> // snprintf
19#include <limits> // numeric_limits
20#include <string> // string, char_traits
21#include <iomanip> // setfill, setw
22#include <type_traits> // is_same
23#include <utility> // move
24
32#include <wpi/detail/value_t.h>
33
35namespace detail
36{
37
38///////////////////
39// serialization //
40///////////////////
41
42/// how to treat decoding errors
44{
45 strict, ///< throw a type_error exception in case of invalid UTF-8
46 replace, ///< replace invalid UTF-8 sequences with U+FFFD
47 ignore ///< ignore invalid UTF-8 sequences
48};
49
50template<typename BasicJsonType>
52{
53 using string_t = typename BasicJsonType::string_t;
54 using number_float_t = typename BasicJsonType::number_float_t;
55 using number_integer_t = typename BasicJsonType::number_integer_t;
56 using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
57 using binary_char_t = typename BasicJsonType::binary_t::value_type;
58 static constexpr std::uint8_t UTF8_ACCEPT = 0;
59 static constexpr std::uint8_t UTF8_REJECT = 1;
60
61 public:
62 /*!
63 @param[in] s output stream to serialize to
64 @param[in] ichar indentation character to use
65 @param[in] error_handler_ how to react on decoding errors
66 */
69 size_t indent_init_len = 512)
70 : o(std::move(s))
71 , loc(std::localeconv())
72 , thousands_sep(loc->thousands_sep == nullptr ? '\0' : std::char_traits<char>::to_char_type(* (loc->thousands_sep)))
73 , decimal_point(loc->decimal_point == nullptr ? '\0' : std::char_traits<char>::to_char_type(* (loc->decimal_point)))
74 , indent_char(ichar)
75 , indent_string(indent_init_len, indent_char)
76 , error_handler(error_handler_)
77 {}
78
79 serializer(raw_ostream& os, const char ichar,
80 size_t indent_init_len = 512,
82 : serializer(output_adapter<char>(os), ichar, error_handler_, indent_init_len)
83 {}
84
85 // delete because of pointer members
86 serializer(const serializer&) = delete;
87 serializer& operator=(const serializer&) = delete;
90 ~serializer() = default;
91
92 /*!
93 @brief internal implementation of the serialization function
94
95 This function is called by the public member function dump and organizes
96 the serialization internally. The indentation level is propagated as
97 additional parameter. In case of arrays and objects, the function is
98 called recursively.
99
100 - strings and object keys are escaped using `escape_string()`
101 - integer numbers are converted implicitly via `operator<<`
102 - floating-point numbers are converted to a string using `"%g"` format
103 - binary values are serialized as objects containing the subtype and the
104 byte array
105
106 @param[in] val value to serialize
107 @param[in] pretty_print whether the output shall be pretty-printed
108 @param[in] ensure_ascii If @a ensure_ascii is true, all non-ASCII characters
109 in the output are escaped with `\uXXXX` sequences, and the result consists
110 of ASCII characters only.
111 @param[in] indent_step the indent level
112 @param[in] current_indent the current indent level (only used internally)
113 */
114 void dump(const BasicJsonType& val,
115 const bool pretty_print,
116 const bool ensure_ascii,
117 const unsigned int indent_step,
118 const unsigned int current_indent = 0)
119 {
120 switch (val.m_type)
121 {
122 case value_t::object:
123 {
124 if (val.m_value.object->empty())
125 {
126 o->write_characters("{}", 2);
127 return;
128 }
129
130 if (pretty_print)
131 {
132 o->write_characters("{\n", 2);
133
134 // variable to hold indentation for recursive calls
135 const auto new_indent = current_indent + indent_step;
136 if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))
137 {
138 indent_string.resize(indent_string.size() * 2, ' ');
139 }
140
141 // first n-1 elements
142 auto i = val.m_value.object->cbegin();
143 for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i)
144 {
145 o->write_characters(indent_string.c_str(), new_indent);
146 o->write_character('\"');
147 dump_escaped(i->first, ensure_ascii);
148 o->write_characters("\": ", 3);
149 dump(i->second, true, ensure_ascii, indent_step, new_indent);
150 o->write_characters(",\n", 2);
151 }
152
153 // last element
154 JSON_ASSERT(i != val.m_value.object->cend());
155 JSON_ASSERT(std::next(i) == val.m_value.object->cend());
156 o->write_characters(indent_string.c_str(), new_indent);
157 o->write_character('\"');
158 dump_escaped(i->first, ensure_ascii);
159 o->write_characters("\": ", 3);
160 dump(i->second, true, ensure_ascii, indent_step, new_indent);
161
162 o->write_character('\n');
163 o->write_characters(indent_string.c_str(), current_indent);
164 o->write_character('}');
165 }
166 else
167 {
168 o->write_character('{');
169
170 // first n-1 elements
171 auto i = val.m_value.object->cbegin();
172 for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i)
173 {
174 o->write_character('\"');
175 dump_escaped(i->first, ensure_ascii);
176 o->write_characters("\":", 2);
177 dump(i->second, false, ensure_ascii, indent_step, current_indent);
178 o->write_character(',');
179 }
180
181 // last element
182 JSON_ASSERT(i != val.m_value.object->cend());
183 JSON_ASSERT(std::next(i) == val.m_value.object->cend());
184 o->write_character('\"');
185 dump_escaped(i->first, ensure_ascii);
186 o->write_characters("\":", 2);
187 dump(i->second, false, ensure_ascii, indent_step, current_indent);
188
189 o->write_character('}');
190 }
191
192 return;
193 }
194
195 case value_t::array:
196 {
197 if (val.m_value.array->empty())
198 {
199 o->write_characters("[]", 2);
200 return;
201 }
202
203 if (pretty_print)
204 {
205 o->write_characters("[\n", 2);
206
207 // variable to hold indentation for recursive calls
208 const auto new_indent = current_indent + indent_step;
209 if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))
210 {
211 indent_string.resize(indent_string.size() * 2, ' ');
212 }
213
214 // first n-1 elements
215 for (auto i = val.m_value.array->cbegin();
216 i != val.m_value.array->cend() - 1; ++i)
217 {
218 o->write_characters(indent_string.c_str(), new_indent);
219 dump(*i, true, ensure_ascii, indent_step, new_indent);
220 o->write_characters(",\n", 2);
221 }
222
223 // last element
224 JSON_ASSERT(!val.m_value.array->empty());
225 o->write_characters(indent_string.c_str(), new_indent);
226 dump(val.m_value.array->back(), true, ensure_ascii, indent_step, new_indent);
227
228 o->write_character('\n');
229 o->write_characters(indent_string.c_str(), current_indent);
230 o->write_character(']');
231 }
232 else
233 {
234 o->write_character('[');
235
236 // first n-1 elements
237 for (auto i = val.m_value.array->cbegin();
238 i != val.m_value.array->cend() - 1; ++i)
239 {
240 dump(*i, false, ensure_ascii, indent_step, current_indent);
241 o->write_character(',');
242 }
243
244 // last element
245 JSON_ASSERT(!val.m_value.array->empty());
246 dump(val.m_value.array->back(), false, ensure_ascii, indent_step, current_indent);
247
248 o->write_character(']');
249 }
250
251 return;
252 }
253
254 case value_t::string:
255 {
256 o->write_character('\"');
257 dump_escaped(*val.m_value.string, ensure_ascii);
258 o->write_character('\"');
259 return;
260 }
261
262 case value_t::binary:
263 {
264 if (pretty_print)
265 {
266 o->write_characters("{\n", 2);
267
268 // variable to hold indentation for recursive calls
269 const auto new_indent = current_indent + indent_step;
270 if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))
271 {
272 indent_string.resize(indent_string.size() * 2, ' ');
273 }
274
275 o->write_characters(indent_string.c_str(), new_indent);
276
277 o->write_characters("\"bytes\": [", 10);
278
279 if (!val.m_value.binary->empty())
280 {
281 for (auto i = val.m_value.binary->cbegin();
282 i != val.m_value.binary->cend() - 1; ++i)
283 {
284 dump_integer(*i);
285 o->write_characters(", ", 2);
286 }
287 dump_integer(val.m_value.binary->back());
288 }
289
290 o->write_characters("],\n", 3);
291 o->write_characters(indent_string.c_str(), new_indent);
292
293 o->write_characters("\"subtype\": ", 11);
294 if (val.m_value.binary->has_subtype())
295 {
296 dump_integer(val.m_value.binary->subtype());
297 }
298 else
299 {
300 o->write_characters("null", 4);
301 }
302 o->write_character('\n');
303 o->write_characters(indent_string.c_str(), current_indent);
304 o->write_character('}');
305 }
306 else
307 {
308 o->write_characters("{\"bytes\":[", 10);
309
310 if (!val.m_value.binary->empty())
311 {
312 for (auto i = val.m_value.binary->cbegin();
313 i != val.m_value.binary->cend() - 1; ++i)
314 {
315 dump_integer(*i);
316 o->write_character(',');
317 }
318 dump_integer(val.m_value.binary->back());
319 }
320
321 o->write_characters("],\"subtype\":", 12);
322 if (val.m_value.binary->has_subtype())
323 {
324 dump_integer(val.m_value.binary->subtype());
325 o->write_character('}');
326 }
327 else
328 {
329 o->write_characters("null}", 5);
330 }
331 }
332 return;
333 }
334
335 case value_t::boolean:
336 {
337 if (val.m_value.boolean)
338 {
339 o->write_characters("true", 4);
340 }
341 else
342 {
343 o->write_characters("false", 5);
344 }
345 return;
346 }
347
349 {
350 dump_integer(val.m_value.number_integer);
351 return;
352 }
353
355 {
356 dump_integer(val.m_value.number_unsigned);
357 return;
358 }
359
361 {
362 dump_float(val.m_value.number_float);
363 return;
364 }
365
367 {
368 o->write_characters("<discarded>", 11);
369 return;
370 }
371
372 case value_t::null:
373 {
374 o->write_characters("null", 4);
375 return;
376 }
377
378 default: // LCOV_EXCL_LINE
379 JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
380 }
381 }
382
383 public:
384 /*!
385 @brief dump escaped string
386
387 Escape a string by replacing certain special characters by a sequence of an
388 escape character (backslash) and another character and other control
389 characters by a sequence of "\u" followed by a four-digit hex
390 representation. The escaped string is written to output stream @a o.
391
392 @param[in] s the string to escape
393 @param[in] ensure_ascii whether to escape non-ASCII characters with
394 \uXXXX sequences
395
396 @complexity Linear in the length of string @a s.
397 */
398 void dump_escaped(std::string_view s, const bool ensure_ascii)
399 {
400 std::uint32_t codepoint{};
401 std::uint8_t state = UTF8_ACCEPT;
402 std::size_t bytes = 0; // number of bytes written to string_buffer
403
404 // number of bytes written at the point of the last valid byte
405 std::size_t bytes_after_last_accept = 0;
406 std::size_t undumped_chars = 0;
407
408 for (std::size_t i = 0; i < s.size(); ++i)
409 {
410 const auto byte = static_cast<std::uint8_t>(s[i]);
411
412 switch (decode(state, codepoint, byte))
413 {
414 case UTF8_ACCEPT: // decode found a new code point
415 {
416 switch (codepoint)
417 {
418 case 0x08: // backspace
419 {
420 string_buffer[bytes++] = '\\';
421 string_buffer[bytes++] = 'b';
422 break;
423 }
424
425 case 0x09: // horizontal tab
426 {
427 string_buffer[bytes++] = '\\';
428 string_buffer[bytes++] = 't';
429 break;
430 }
431
432 case 0x0A: // newline
433 {
434 string_buffer[bytes++] = '\\';
435 string_buffer[bytes++] = 'n';
436 break;
437 }
438
439 case 0x0C: // formfeed
440 {
441 string_buffer[bytes++] = '\\';
442 string_buffer[bytes++] = 'f';
443 break;
444 }
445
446 case 0x0D: // carriage return
447 {
448 string_buffer[bytes++] = '\\';
449 string_buffer[bytes++] = 'r';
450 break;
451 }
452
453 case 0x22: // quotation mark
454 {
455 string_buffer[bytes++] = '\\';
456 string_buffer[bytes++] = '\"';
457 break;
458 }
459
460 case 0x5C: // reverse solidus
461 {
462 string_buffer[bytes++] = '\\';
463 string_buffer[bytes++] = '\\';
464 break;
465 }
466
467 default:
468 {
469 // escape control characters (0x00..0x1F) or, if
470 // ensure_ascii parameter is used, non-ASCII characters
471 if ((codepoint <= 0x1F) || (ensure_ascii && (codepoint >= 0x7F)))
472 {
473 if (codepoint <= 0xFFFF)
474 {
475 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
476 static_cast<void>((std::snprintf)(string_buffer.data() + bytes, 7, "\\u%04x",
477 static_cast<std::uint16_t>(codepoint)));
478 bytes += 6;
479 }
480 else
481 {
482 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
483 static_cast<void>((std::snprintf)(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x",
484 static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)),
485 static_cast<std::uint16_t>(0xDC00u + (codepoint & 0x3FFu))));
486 bytes += 12;
487 }
488 }
489 else
490 {
491 // copy byte to buffer (all previous bytes
492 // been copied have in default case above)
493 string_buffer[bytes++] = s[i];
494 }
495 break;
496 }
497 }
498
499 // write buffer and reset index; there must be 13 bytes
500 // left, as this is the maximal number of bytes to be
501 // written ("\uxxxx\uxxxx\0") for one code point
502 if (string_buffer.size() - bytes < 13)
503 {
504 o->write_characters(string_buffer.data(), bytes);
505 bytes = 0;
506 }
507
508 // remember the byte position of this accept
509 bytes_after_last_accept = bytes;
510 undumped_chars = 0;
511 break;
512 }
513
514 case UTF8_REJECT: // decode found invalid UTF-8 byte
515 {
516 switch (error_handler)
517 {
519 {
520 JSON_THROW(type_error::create(316, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", hex_bytes(byte | 0)), nullptr));
521 }
522
525 {
526 // in case we saw this character the first time, we
527 // would like to read it again, because the byte
528 // may be OK for itself, but just not OK for the
529 // previous sequence
530 if (undumped_chars > 0)
531 {
532 --i;
533 }
534
535 // reset length buffer to the last accepted index;
536 // thus removing/ignoring the invalid characters
537 bytes = bytes_after_last_accept;
538
540 {
541 // add a replacement character
542 if (ensure_ascii)
543 {
544 string_buffer[bytes++] = '\\';
545 string_buffer[bytes++] = 'u';
546 string_buffer[bytes++] = 'f';
547 string_buffer[bytes++] = 'f';
548 string_buffer[bytes++] = 'f';
549 string_buffer[bytes++] = 'd';
550 }
551 else
552 {
556 }
557
558 // write buffer and reset index; there must be 13 bytes
559 // left, as this is the maximal number of bytes to be
560 // written ("\uxxxx\uxxxx\0") for one code point
561 if (string_buffer.size() - bytes < 13)
562 {
563 o->write_characters(string_buffer.data(), bytes);
564 bytes = 0;
565 }
566
567 bytes_after_last_accept = bytes;
568 }
569
570 undumped_chars = 0;
571
572 // continue processing the string
573 state = UTF8_ACCEPT;
574 break;
575 }
576
577 default: // LCOV_EXCL_LINE
578 JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
579 }
580 break;
581 }
582
583 default: // decode found yet incomplete multi-byte code point
584 {
585 if (!ensure_ascii)
586 {
587 // code point will not be escaped - copy byte to buffer
588 string_buffer[bytes++] = s[i];
589 }
590 ++undumped_chars;
591 break;
592 }
593 }
594 }
595
596 // we finished processing the string
597 if (JSON_HEDLEY_LIKELY(state == UTF8_ACCEPT))
598 {
599 // write buffer
600 if (bytes > 0)
601 {
602 o->write_characters(string_buffer.data(), bytes);
603 }
604 }
605 else
606 {
607 // we finish reading, but do not accept: string was incomplete
608 switch (error_handler)
609 {
611 {
612 JSON_THROW(type_error::create(316, concat("incomplete UTF-8 string; last byte: 0x", hex_bytes(static_cast<std::uint8_t>(s.back() | 0))), nullptr));
613 }
614
616 {
617 // write all accepted bytes
618 o->write_characters(string_buffer.data(), bytes_after_last_accept);
619 break;
620 }
621
623 {
624 // write all accepted bytes
625 o->write_characters(string_buffer.data(), bytes_after_last_accept);
626 // add a replacement character
627 if (ensure_ascii)
628 {
629 o->write_characters("\\ufffd", 6);
630 }
631 else
632 {
633 o->write_characters("\xEF\xBF\xBD", 3);
634 }
635 break;
636 }
637
638 default: // LCOV_EXCL_LINE
639 JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
640 }
641 }
642 }
643
644 private:
645 /*!
646 @brief count digits
647
648 Count the number of decimal (base 10) digits for an input unsigned integer.
649
650 @param[in] x unsigned integer number to count its digits
651 @return number of decimal digits
652 */
653 inline unsigned int count_digits(number_unsigned_t x) noexcept
654 {
655 unsigned int n_digits = 1;
656 for (;;)
657 {
658 if (x < 10)
659 {
660 return n_digits;
661 }
662 if (x < 100)
663 {
664 return n_digits + 1;
665 }
666 if (x < 1000)
667 {
668 return n_digits + 2;
669 }
670 if (x < 10000)
671 {
672 return n_digits + 3;
673 }
674 x = x / 10000u;
675 n_digits += 4;
676 }
677 }
678
679 /*!
680 * @brief convert a byte to a uppercase hex representation
681 * @param[in] byte byte to represent
682 * @return representation ("00".."FF")
683 */
684 static std::string hex_bytes(std::uint8_t byte)
685 {
686 std::string result = "FF";
687 constexpr const char* nibble_to_hex = "0123456789ABCDEF";
688 result[0] = nibble_to_hex[byte / 16];
689 result[1] = nibble_to_hex[byte % 16];
690 return result;
691 }
692
693 // templates to avoid warnings about useless casts
694 template <typename NumberType, enable_if_t<std::is_signed<NumberType>::value, int> = 0>
695 bool is_negative_number(NumberType x)
696 {
697 return x < 0;
698 }
699
700 template < typename NumberType, enable_if_t <std::is_unsigned<NumberType>::value, int > = 0 >
701 bool is_negative_number(NumberType /*unused*/)
702 {
703 return false;
704 }
705
706 public:
707 /*!
708 @brief dump an integer
709
710 Dump a given integer to output stream @a o. Works internally with
711 @a number_buffer.
712
713 @param[in] x integer number (signed or unsigned) to dump
714 @tparam NumberType either @a number_integer_t or @a number_unsigned_t
715 */
716 template < typename NumberType, detail::enable_if_t <
717 std::is_integral<NumberType>::value ||
718 std::is_same<NumberType, number_unsigned_t>::value ||
719 std::is_same<NumberType, number_integer_t>::value ||
720 std::is_same<NumberType, binary_char_t>::value,
721 int > = 0 >
722 void dump_integer(NumberType x)
723 {
724 static constexpr std::array<std::array<char, 2>, 100> digits_to_99
725 {
726 {
727 {{'0', '0'}}, {{'0', '1'}}, {{'0', '2'}}, {{'0', '3'}}, {{'0', '4'}}, {{'0', '5'}}, {{'0', '6'}}, {{'0', '7'}}, {{'0', '8'}}, {{'0', '9'}},
728 {{'1', '0'}}, {{'1', '1'}}, {{'1', '2'}}, {{'1', '3'}}, {{'1', '4'}}, {{'1', '5'}}, {{'1', '6'}}, {{'1', '7'}}, {{'1', '8'}}, {{'1', '9'}},
729 {{'2', '0'}}, {{'2', '1'}}, {{'2', '2'}}, {{'2', '3'}}, {{'2', '4'}}, {{'2', '5'}}, {{'2', '6'}}, {{'2', '7'}}, {{'2', '8'}}, {{'2', '9'}},
730 {{'3', '0'}}, {{'3', '1'}}, {{'3', '2'}}, {{'3', '3'}}, {{'3', '4'}}, {{'3', '5'}}, {{'3', '6'}}, {{'3', '7'}}, {{'3', '8'}}, {{'3', '9'}},
731 {{'4', '0'}}, {{'4', '1'}}, {{'4', '2'}}, {{'4', '3'}}, {{'4', '4'}}, {{'4', '5'}}, {{'4', '6'}}, {{'4', '7'}}, {{'4', '8'}}, {{'4', '9'}},
732 {{'5', '0'}}, {{'5', '1'}}, {{'5', '2'}}, {{'5', '3'}}, {{'5', '4'}}, {{'5', '5'}}, {{'5', '6'}}, {{'5', '7'}}, {{'5', '8'}}, {{'5', '9'}},
733 {{'6', '0'}}, {{'6', '1'}}, {{'6', '2'}}, {{'6', '3'}}, {{'6', '4'}}, {{'6', '5'}}, {{'6', '6'}}, {{'6', '7'}}, {{'6', '8'}}, {{'6', '9'}},
734 {{'7', '0'}}, {{'7', '1'}}, {{'7', '2'}}, {{'7', '3'}}, {{'7', '4'}}, {{'7', '5'}}, {{'7', '6'}}, {{'7', '7'}}, {{'7', '8'}}, {{'7', '9'}},
735 {{'8', '0'}}, {{'8', '1'}}, {{'8', '2'}}, {{'8', '3'}}, {{'8', '4'}}, {{'8', '5'}}, {{'8', '6'}}, {{'8', '7'}}, {{'8', '8'}}, {{'8', '9'}},
736 {{'9', '0'}}, {{'9', '1'}}, {{'9', '2'}}, {{'9', '3'}}, {{'9', '4'}}, {{'9', '5'}}, {{'9', '6'}}, {{'9', '7'}}, {{'9', '8'}}, {{'9', '9'}},
737 }
738 };
739
740 // special case for "0"
741 if (x == 0)
742 {
743 o->write_character('0');
744 return;
745 }
746
747 // use a pointer to fill the buffer
748 auto buffer_ptr = number_buffer.begin(); // NOLINT(llvm-qualified-auto,readability-qualified-auto,cppcoreguidelines-pro-type-vararg,hicpp-vararg)
749
750 number_unsigned_t abs_value;
751
752 unsigned int n_chars{};
753
754 if (is_negative_number(x))
755 {
756 *buffer_ptr = '-';
757 abs_value = remove_sign(static_cast<number_integer_t>(x));
758
759 // account one more byte for the minus sign
760 n_chars = 1 + count_digits(abs_value);
761 }
762 else
763 {
764 abs_value = static_cast<number_unsigned_t>(x);
765 n_chars = count_digits(abs_value);
766 }
767
768 // spare 1 byte for '\0'
769 JSON_ASSERT(n_chars < number_buffer.size() - 1);
770
771 // jump to the end to generate the string from backward,
772 // so we later avoid reversing the result
773 buffer_ptr += n_chars;
774
775 // Fast int2ascii implementation inspired by "Fastware" talk by Andrei Alexandrescu
776 // See: https://www.youtube.com/watch?v=o4-CwDo2zpg
777 while (abs_value >= 100)
778 {
779 const auto digits_index = static_cast<unsigned>((abs_value % 100));
780 abs_value /= 100;
781 *(--buffer_ptr) = digits_to_99[digits_index][1];
782 *(--buffer_ptr) = digits_to_99[digits_index][0];
783 }
784
785 if (abs_value >= 10)
786 {
787 const auto digits_index = static_cast<unsigned>(abs_value);
788 *(--buffer_ptr) = digits_to_99[digits_index][1];
789 *(--buffer_ptr) = digits_to_99[digits_index][0];
790 }
791 else
792 {
793 *(--buffer_ptr) = static_cast<char>('0' + abs_value);
794 }
795
796 o->write_characters(number_buffer.data(), n_chars);
797 }
798
799 /*!
800 @brief dump a floating-point number
801
802 Dump a given floating-point number to output stream @a o. Works internally
803 with @a number_buffer.
804
805 @param[in] x floating-point number to dump
806 */
807 void dump_float(number_float_t x)
808 {
809 // NaN / inf
810 if (!std::isfinite(x))
811 {
812 o->write_characters("null", 4);
813 return;
814 }
815
816 // If number_float_t is an IEEE-754 single or double precision number,
817 // use the Grisu2 algorithm to produce short numbers which are
818 // guaranteed to round-trip, using strtof and strtod, resp.
819 //
820 // NB: The test below works if <long double> == <double>.
821 static constexpr bool is_ieee_single_or_double
822 = (std::numeric_limits<number_float_t>::is_iec559 && std::numeric_limits<number_float_t>::digits == 24 && std::numeric_limits<number_float_t>::max_exponent == 128) ||
823 (std::numeric_limits<number_float_t>::is_iec559 && std::numeric_limits<number_float_t>::digits == 53 && std::numeric_limits<number_float_t>::max_exponent == 1024);
824
825 dump_float(x, std::integral_constant<bool, is_ieee_single_or_double>());
826 }
827
828 void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/)
829 {
830 auto* begin = number_buffer.data();
831 auto* end = ::wpi::detail::to_chars(begin, begin + number_buffer.size(), x);
832
833 o->write_characters(begin, static_cast<size_t>(end - begin));
834 }
835
836 void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/)
837 {
838 // get number of digits for a float -> text -> float round-trip
839 static constexpr auto d = std::numeric_limits<number_float_t>::max_digits10;
840
841 // the actual conversion
842 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
843 std::ptrdiff_t len = (std::snprintf)(number_buffer.data(), number_buffer.size(), "%.*g", d, x);
844
845 // negative value indicates an error
846 JSON_ASSERT(len > 0);
847 // check if buffer was large enough
848 JSON_ASSERT(static_cast<std::size_t>(len) < number_buffer.size());
849
850 // erase thousands separator
851 if (thousands_sep != '\0')
852 {
853 // NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::remove returns an iterator, see https://github.com/nlohmann/json/issues/3081
854 const auto end = std::remove(number_buffer.begin(), number_buffer.begin() + len, thousands_sep);
855 std::fill(end, number_buffer.end(), '\0');
856 JSON_ASSERT((end - number_buffer.begin()) <= len);
857 len = (end - number_buffer.begin());
858 }
859
860 // convert decimal point to '.'
861 if (decimal_point != '\0' && decimal_point != '.')
862 {
863 // NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::find returns an iterator, see https://github.com/nlohmann/json/issues/3081
864 const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point);
865 if (dec_pos != number_buffer.end())
866 {
867 *dec_pos = '.';
868 }
869 }
870
871 o->write_characters(number_buffer.data(), static_cast<std::size_t>(len));
872
873 // determine if we need to append ".0"
874 const bool value_is_int_like =
875 std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1,
876 [](char c)
877 {
878 return c == '.' || c == 'e';
879 });
880
881 if (value_is_int_like)
882 {
883 o->write_characters(".0", 2);
884 }
885 }
886
887 private:
888 /*!
889 @brief check whether a string is UTF-8 encoded
890
891 The function checks each byte of a string whether it is UTF-8 encoded. The
892 result of the check is stored in the @a state parameter. The function must
893 be called initially with state 0 (accept). State 1 means the string must
894 be rejected, because the current byte is not allowed. If the string is
895 completely processed, but the state is non-zero, the string ended
896 prematurely; that is, the last byte indicated more bytes should have
897 followed.
898
899 @param[in,out] state the state of the decoding
900 @param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT)
901 @param[in] byte next byte to decode
902 @return new state
903
904 @note The function has been edited: a std::array is used.
905
906 @copyright Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
907 @sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
908 */
909 static std::uint8_t decode(std::uint8_t& state, std::uint32_t& codep, const std::uint8_t byte) noexcept
910 {
911 static const std::array<std::uint8_t, 400> utf8d =
912 {
913 {
914 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F
915 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F
916 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F
917 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F
918 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F
919 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF
920 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF
921 0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF
922 0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF
923 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0
924 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2
925 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4
926 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6
927 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8
928 }
929 };
930
931 JSON_ASSERT(byte < utf8d.size());
932 const std::uint8_t type = utf8d[byte];
933
934 codep = (state != UTF8_ACCEPT)
935 ? (byte & 0x3fu) | (codep << 6u)
936 : (0xFFu >> type) & (byte);
937
938 std::size_t index = 256u + static_cast<size_t>(state) * 16u + static_cast<size_t>(type);
939 JSON_ASSERT(index < 400);
940 state = utf8d[index];
941 return state;
942 }
943
944 /*
945 * Overload to make the compiler happy while it is instantiating
946 * dump_integer for number_unsigned_t.
947 * Must never be called.
948 */
949 number_unsigned_t remove_sign(number_unsigned_t x)
950 {
951 JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
952 return x; // LCOV_EXCL_LINE
953 }
954
955 /*
956 * Helper function for dump_integer
957 *
958 * This function takes a negative signed integer and returns its absolute
959 * value as unsigned integer. The plus/minus shuffling is necessary as we can
960 * not directly remove the sign of an arbitrary signed integer as the
961 * absolute values of INT_MIN and INT_MAX are usually not the same. See
962 * #1708 for details.
963 */
964 inline number_unsigned_t remove_sign(number_integer_t x) noexcept
965 {
966 JSON_ASSERT(x < 0 && x < (std::numeric_limits<number_integer_t>::max)()); // NOLINT(misc-redundant-expression)
967 return static_cast<number_unsigned_t>(-(x + 1)) + 1;
968 }
969
970 private:
971 /// the output of the serializer
972 output_adapter_t<char> o = nullptr;
973
974 /// a (hopefully) large enough character buffer
975 std::array<char, 64> number_buffer{{}};
976
977 /// the locale
978 const std::lconv* loc = nullptr;
979 /// the locale's thousand separator character
980 const char thousands_sep = '\0';
981 /// the locale's decimal point character
982 const char decimal_point = '\0';
983
984 /// string buffer
985 std::array<char, 512> string_buffer{{}};
986
987 /// the indentation character
988 const char indent_char;
989 /// the indentation string
990 string_t indent_string;
991
992 /// error_handler how to react on decoding errors
993 const error_handler_t error_handler;
994};
995
996} // namespace detail
#define WPI_JSON_NAMESPACE_END
Definition: abi_macros.h:59
#define WPI_JSON_NAMESPACE_BEGIN
Definition: abi_macros.h:53
Definition: format.h:4134
static constexpr CharType to_char_type(std::uint8_t x) noexcept
Definition: binary_writer.h:1795
Definition: output_adapters.h:146
Definition: serializer.h:52
serializer & operator=(const serializer &)=delete
void dump_escaped(std::string_view s, const bool ensure_ascii)
dump escaped string
Definition: serializer.h:398
~serializer()=default
void dump_float(number_float_t x)
dump a floating-point number
Definition: serializer.h:807
serializer(raw_ostream &os, const char ichar, size_t indent_init_len=512, error_handler_t error_handler_=error_handler_t::strict)
Definition: serializer.h:79
serializer & operator=(serializer &&)=delete
serializer(output_adapter_t< char > s, const char ichar, error_handler_t error_handler_=error_handler_t::strict, size_t indent_init_len=512)
Definition: serializer.h:67
void dump_float(number_float_t x, std::false_type)
Definition: serializer.h:836
void dump_integer(NumberType x)
dump an integer
Definition: serializer.h:722
void dump_float(number_float_t x, std::true_type)
Definition: serializer.h:828
void dump(const BasicJsonType &val, const bool pretty_print, const bool ensure_ascii, const unsigned int indent_step, const unsigned int current_indent=0)
internal implementation of the serialization function
Definition: serializer.h:114
serializer(const serializer &)=delete
serializer(serializer &&)=delete
static type_error create(int id_, const std::string &what_arg, BasicJsonContext context)
Definition: exceptions.h:209
basic_string_view< char > string_view
Definition: core.h:501
#define JSON_HEDLEY_LIKELY(expr)
Definition: hedley.h:1395
#define JSON_HEDLEY_UNLIKELY(expr)
Definition: hedley.h:1396
#define JSON_ASSERT(x)
Definition: macro_scope.h:192
#define JSON_THROW(exception)
Definition: macro_scope.h:163
detail namespace with internal helper functions
Definition: xchar.h:20
std::shared_ptr< output_adapter_protocol< CharType > > output_adapter_t
a type to simplify interfaces
Definition: output_adapters.h:47
OutStringType concat(Args &&... args)
Definition: string_concat.h:137
JSON_HEDLEY_RETURNS_NON_NULL char * to_chars(char *first, const char *last, FloatType value)
generates a decimal representation of the floating-point number value in [first, last).
Definition: to_chars.h:1065
FMT_NOINLINE FMT_CONSTEXPR auto fill(OutputIt it, size_t n, const fill_t< Char > &fill) -> OutputIt
Definition: format.h:1779
typename std::enable_if< B, T >::type enable_if_t
Definition: cpp_future.h:38
FMT_CONSTEXPR auto find(Ptr first, Ptr last, T value, Ptr &out) -> bool
Definition: core.h:2120
@ null
null value
@ number_integer
number value (signed integer)
@ boolean
boolean value
@ discarded
discarded by the parser callback function
@ binary
binary array (ordered collection of bytes)
@ object
object (unordered set of name/value pairs)
@ string
string value
@ number_float
number value (floating-point)
@ number_unsigned
number value (unsigned integer)
@ array
array (ordered collection of values)
bool isfinite(T)
Definition: chrono.h:1600
error_handler_t
how to treat decoding errors
Definition: serializer.h:44
@ strict
throw a type_error exception in case of invalid UTF-8
@ ignore
ignore invalid UTF-8 sequences
@ replace
replace invalid UTF-8 sequences with U+FFFD
state
Definition: core.h:2271
type
Definition: core.h:556
Definition: array.h:89
static constexpr const velocity::meters_per_second_t c(299792458.0)
Speed of light in vacuum.
std::string to_string(const T &t)
Definition: base.h:92
UnitTypeLhs() max(const UnitTypeLhs &lhs, const UnitTypeRhs &rhs)
Definition: base.h:3417
Definition: core.h:632