Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
Variant/JSON/Reader.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#include <optional>
7#include <stack>
8
9#if __has_include("boost/json.hpp")
10#include <boost/json.hpp>
11#include <boost/json/basic_parser_impl.hpp>
12#endif
13
14#include "Stroika/Foundation/Characters/FloatConversion.h"
16#include "Stroika/Foundation/Characters/String2Int.h"
18#include "Stroika/Foundation/Containers/Concrete/Mapping_HashTable.h"
19#include "Stroika/Foundation/Containers/Support/ReserveTweaks.h"
20#include "Stroika/Foundation/DataExchange/BadFormatException.h"
27
28#include "Reader.h"
29
30using namespace Stroika::Foundation;
33using namespace Stroika::Foundation::Execution;
34using namespace Stroika::Foundation::Memory;
35using namespace Stroika::Foundation::Streams;
36using namespace Stroika::Foundation::Traversal;
37
39using Memory::MakeSharedPtr;
40
41// Comment this in to turn on aggressive noisy DbgTrace in this module
42//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
43
44namespace {
45
46 /*
47 * Parse strategy:
48 * o Pre-pass to map all input to UNICODE - and then just handle a sequence of unicode strings
49 * o Classic recursive decent parser.
50 * o Inline lexical analysis (cuz very simple)
51 */
52
53 enum ReadState_ {
54 eNeutral_,
55 eInObject_,
56 eInArray_,
57 eInNumber_,
58 eInString_,
59 };
60
61 // use StreamReader to get buffering of input data (performance tweak), and a couple helper methods
62 struct MyBufferedStreamReader_ final : StreamReader<Character> {
63 MyBufferedStreamReader_ (const InputStream::Ptr<ElementType>& underlyingReadFromStreamAdopted)
64 : StreamReader<Character>{underlyingReadFromStreamAdopted}
65 {
66 }
67 [[nodiscard]] inline char32_t NextChar ()
68 {
69 Require (not IsAtEOF ());
70 return ReadBlocking ()->As<char32_t> ();
71 }
72 inline void AdvanceOne ()
73 {
74 Require (not IsAtEOF ());
75 Seek (eFromCurrent, 1);
76 }
77 inline void BackupOne ()
78 {
79 Seek (eFromCurrent, -1);
80 }
81 };
82
83 VariantValue Reader_value_ (MyBufferedStreamReader_& in);
84
85 /*
86 */
87 inline bool IsJSONSpace_ (char32_t c)
88 {
89 // iswspace was pretty slow (on windoze) - showing up as significant in performance profiling.
90 // According to:
91 // https://www.json.org/json-en.html
92 // only spaces allowed in json source are:
93 // ws = *(
94 // %x20 / ; Space
95 // %x09 / ; Horizontal tab
96 // %x0A / ; Line feed or New line
97 // %x0D ; Carriage return
98 // )
99 switch (c) {
100 case ' ':
101 case '\t':
102 case '\r':
103 case '\n':
104 return true;
105 default:
106 return false;
107 }
108 }
109
110 inline bool IsJSONDigit_ (char32_t c)
111 {
112 // iswdigit tweak (showed up as significant - 6% of full runtime)
113 // According to:
114 // https://www.json.org/json-en.html
115 // appears just digits 0..9
116 // slightly longer way but doesn't assume ascii, and probably compiles to the same thing is '0' <= n <= '9'
117 switch (c) {
118 case '0':
119 case '1':
120 case '2':
121 case '3':
122 case '4':
123 case '5':
124 case '6':
125 case '7':
126 case '8':
127 case '9':
128 return true;
129 default:
130 return false;
131 }
132 }
133
134 // throw if bad hex digit
135 uint8_t HexChar2Num_ (char c)
136 {
137 if ('0' <= c and c <= '9') [[likely]] {
138 return static_cast<uint8_t> (c - '0');
139 }
140 if ('a' <= c and c <= 'f') [[likely]] {
141 return static_cast<uint8_t> ((c - 'a') + 10);
142 }
143 if ('A' <= c and c <= 'F') [[likely]] {
144 return static_cast<uint8_t> ((c - 'A') + 10);
145 }
146 static const auto kException_{BadFormatException{"JSON: bad hex digit after \\u"sv}};
147 Throw (kException_);
148 }
149
150 // 'in' is positioned to the start of string, and we read, leaving in positioned just after the end of the string
151 String Reader_String_ (MyBufferedStreamReader_& in)
152 {
153 Require (not in.IsAtEOF ());
154 char32_t c = in.NextChar ();
155 if (c != '\"') [[unlikely]] {
156 static const auto kException_{BadFormatException{"JSON: Expected quoted string"sv}};
157 Throw (kException_);
158 }
159 /*
160 * accumulate chars, and check for close-quote
161 */
162 StringBuilder<StringBuilder_Options<char8_t, 64>> result; // StringBuilder_Options: text often ascii, empirically a bit faster with on windows on jsonparser regtest
163 while (true) {
164 if (in.IsAtEOF ()) [[unlikely]] {
165 static const auto kException_{BadFormatException{"JSON: Unexpected EOF reading string (looking for close quote)"sv}};
166 Throw (kException_);
167 }
168 c = in.NextChar ();
169 if (c == '\"') [[unlikely]] {
170 return result.str ();
171 }
172 else if (c == '\\') [[unlikely]] {
173 // quoted character read...
174 if (in.IsAtEOF ()) [[unlikely]] {
175 static const auto kException_{BadFormatException{"JSON: Unexpected EOF reading string (looking for close quote)"sv}};
176 Throw (kException_);
177 }
178 c = in.NextChar ();
179 switch (c) {
180 case 'b':
181 c = '\b';
182 break;
183 case 'f':
184 c = '\f';
185 break;
186 case 'n':
187 c = '\n';
188 break;
189 case 'r':
190 c = '\r';
191 break;
192 case 't':
193 c = '\t';
194 break;
195 case 'u': {
196 // Not sure this is right -- But I hope so ... -- LGP 2012-11-29
197 char32_t newC = '\0';
198 for (int n = 0; n < 4; ++n) {
199 if (in.IsAtEOF ()) [[unlikely]] {
200 static const auto kException_{BadFormatException{"JSON: Unexpected EOF reading string (looking for close quote)"sv}};
201 Throw (kException_);
202 }
203 newC += HexChar2Num_ (static_cast<char> (in.NextChar ()));
204 if (n != 3) {
205 newC <<= 4;
206 }
207 }
208 c = newC;
209 } break;
210 default: {
211 // if we have \N for any unrecognized N, just treat it as N
212 }
213 }
214 }
215 result += c; // must handle other character quoting (besides \u which was preflighted)
216 }
217 }
218
219 // 'in' is positioned to the second character of number (first passed as arg), and we read, leaving in positioned just after end of number
220 static constexpr Character kDash_{'-'};
221 VariantValue Reader_Number_ (char32_t initialChar, MyBufferedStreamReader_& in)
222 {
223 Require (initialChar == '-' or IsJSONDigit_ (initialChar));
224
225 bool containsDot = false;
226 // ACCUMULATE STRING, and then call builtin number parsing functions...
227 // This accumulation is NOT as restrictive as it could be - but should accept all valid numbers
228 StringBuilder tmp;
229 for (char32_t c = initialChar; c != '\0'; c = in.ReadBlocking ().value_or ('\0').As<char32_t> ()) {
230 if (IsJSONDigit_ (c) or c == '.' or c == 'e' or c == 'E' or c == '+' or c == '-') [[likely]] {
231 tmp += c;
232 if (c == '.') [[unlikely]] {
233 containsDot = true;
234 }
235 }
236 else {
237 // any other character signals end of number (not a syntax error)
238 // but backup - don't consume next character - not part of number
239 Assert (not tmp.empty ()); // at least consumed 'initialChar'
240 in.BackupOne ();
241 break;
242 }
243 }
244 Assert (not tmp.empty ());
245 StackBuffer<char32_t> ignoreBuf;
246 span<const char32_t> tmpData = tmp.GetData (&ignoreBuf);
247 if (containsDot) {
248 return VariantValue{FloatConversion::ToFloat<long double> (tmpData)};
249 }
250 else {
251 // if no - use unsigned since has wider range (if no -)
252 return (initialChar == kDash_) ? VariantValue{String2Int<long long int> (tmpData)}
253 : VariantValue{String2Int<unsigned long long int> (tmpData)};
254 }
255 }
256
257 // NOTE: THIS STARTS SEEKED JUST PAST OPENING '{'
258 VariantValue Reader_Object_ (MyBufferedStreamReader_& in)
259 {
260 Mapping_HashTable<String, VariantValue>::DEFAULT_HASHTABLE<> result; // slight tweak using stl map, and move-construct Stroika map at the end
261
262 // accumulate elements, and check for close-array
263 enum LookingFor {
264 eName, // this means looking for start of next field/member of the object
265 eValue,
266 eColon,
267 eComma // when looking for comma could either find it, or } marking end of object
268 };
269 LookingFor lf = eName;
270 optional<String> curName;
271 while (true) {
272 optional<Character> oNextChar = in.Peek ();
273 if (not oNextChar.has_value ()) [[unlikely]] {
274 static const auto kException_{BadFormatException{"JSON: Unexpected EOF reading object (looking for '}')"sv}};
275 Throw (kException_);
276 }
277 char32_t nextChar = oNextChar->As<char32_t> ();
278 if (IsJSONSpace_ (nextChar)) [[likely]] {
279 in.AdvanceOne (); // skip char
280 continue;
281 }
282 switch (lf) {
283 case eName: {
284 Assert (curName == nullopt);
285 if (nextChar == '}') {
286 in.AdvanceOne (); // finished object
288 }
289 else if (nextChar == '\"') [[likely]] {
290 curName = Reader_String_ (in); // starting a new data member (with a string)
291 lf = eColon;
292 }
293 else {
294 static const auto kException_{
295 BadFormatException{"JSON: Reading object, looking for a name, didn't find a close brace or open quote"sv}};
296 Throw (kException_);
297 }
298 } break;
299 case eComma: {
300 Assert (curName == nullopt);
301 if (nextChar == '}') {
302 in.AdvanceOne (); // finished object
304 }
305 else if (nextChar == ',') [[likely]] {
306 in.AdvanceOne (); // consume it, and look for name next (start of next object member)
307 lf = eName;
308 }
309 else {
310 static const auto kException_{BadFormatException{"JSON: Reading object, looking for a comma, but found something else"sv}};
311 Throw (kException_);
312 }
313 } break;
314 case eColon: {
315 Assert (curName);
316 if (nextChar == ':') [[likely]] {
317 in.AdvanceOne (); // consume the separator, and look the the value
318 lf = eValue;
319 }
320 else {
321 static const auto kException_{BadFormatException{"JSON: Reading object, looking for a colon, but found something else"sv}};
322 Throw (kException_);
323 }
324 } break;
325 case eValue: {
326 Assert (curName);
327 // dont care what the character is, read a new value
328 result.insert ({ValueOf (curName), Reader_value_ (in)});
329 curName = nullopt;
330 lf = eComma; // and look for another field/data member
331 } break;
332 }
333 }
334 }
335
336 // NOTE - called with OPENING '[' already read
337 VariantValue Reader_Array_ (MyBufferedStreamReader_& in)
338 {
339 vector<VariantValue> result;
340
341 // accumulate elements, and check for close-array
342 bool lookingForElt = true;
343 while (true) {
344 if (in.IsAtEOF ()) [[unlikely]] {
345 static const auto kException_{BadFormatException{"JSON: Unexpected EOF reading array (looking for ']')"sv}};
346 Throw (kException_);
347 }
348 char32_t peekedChar = in.Peek ()->As<char32_t> ();
349 if (peekedChar == ']') {
350 if (lookingForElt) {
351 // allow ending ',' - harmless - could be more aggressive - but if so - careful of zero-sized array special case
352 }
353 in.AdvanceOne ();
355 }
356 else if (peekedChar == ',') {
357 if (lookingForElt) [[unlikely]] {
358 static const auto kException_{BadFormatException{"JSON: Unexpected second ',' in reading array"sv}};
359 Throw (kException_);
360 }
361 else {
362 lookingForElt = true;
363 }
364 in.AdvanceOne ();
365 }
366 else if (IsJSONSpace_ (peekedChar)) {
367 in.AdvanceOne ();
368 }
369 else {
370 // not looking at whitespace, in midst of array, and array not terminated, so better be looking at a value
371 if (lookingForElt) [[likely]] {
372 Containers::Support::ReserveTweaks::Reserve4Add1 (result);
373 result.push_back (Reader_value_ (in));
374 lookingForElt = false;
375 }
376 else {
377 static const auto kException_{BadFormatException{"JSON: Unexpected character (missing ',' ?) in reading array"sv}};
378 Throw (kException_);
379 }
380 }
381 }
382 }
383
384 VariantValue Reader_SpecialToken_ (char32_t initialChar, MyBufferedStreamReader_& in)
385 {
386 switch (initialChar) {
387 case 'f': {
388 Character buf[4];
389 if (in.ReadAll (begin (buf), end (buf)) == 4 and buf[0] == 'a' and buf[1] == 'l' and buf[2] == 's' and buf[3] == 'e') {
390 return VariantValue{false};
391 }
392 } break;
393 case 't': {
394 Character buf[3];
395 if (in.ReadAll (begin (buf), end (buf)) == 3 and buf[0] == 'r' and buf[1] == 'u' and buf[2] == 'e') {
396 return VariantValue{true};
397 }
398 } break;
399 case 'n': {
400 Character buf[3];
401 if (in.ReadAll (begin (buf), end (buf)) == 3 and buf[0] == 'u' and buf[1] == 'l' and buf[2] == 'l') {
402 return VariantValue{};
403 }
404 } break;
405 }
406 static const auto kException_{BadFormatException{"JSON: Unrecognized token"sv}};
407 Throw (kException_);
408 }
409
410 VariantValue Reader_value_ (MyBufferedStreamReader_& in)
411 {
412 // Skip initial whitespace, and look for any value:
413 // string
414 // number
415 // object
416 // array
417 // true
418 // false
419 // null
420 for (optional<Character> oc = in.ReadBlocking (); oc; oc = in.ReadBlocking ()) {
421 switch (oc->As<char32_t> ()) {
422 case '\"':
423 in.BackupOne ();
424 return Reader_String_ (in);
425
426 case '0':
427 case '1':
428 case '2':
429 case '3':
430 case '4':
431 case '5':
432 case '6':
433 case '7':
434 case '8':
435 case '9':
436 case '-':
437 return Reader_Number_ (oc->As<char32_t> (), in);
438
439 case '{':
440 return Reader_Object_ (in);
441 case '[':
442 return Reader_Array_ (in);
443
444 case 't':
445 case 'f':
446 case 'n':
447 return Reader_SpecialToken_ (oc->As<char32_t> (), in);
448
449 default: {
450 if (IsJSONSpace_ (oc->As<char32_t> ())) [[likely]] {
451 // ignore
452 }
453 else {
454 static const auto kException_{BadFormatException{"JSON: Unexpected character looking for start of value"sv}};
455 Throw (kException_);
456 }
457 }
458 }
459 }
460 // if we get here - nothing found
461 static const auto kException_{BadFormatException{"JSON: Unexpected EOF looking for value"sv}};
462 Throw (kException_);
463 }
464}
465
466/*
467 ********************************************************************************
468 ************************* Variant::JSON::Reader ********************************
469 ********************************************************************************
470 */
471class Variant::JSON::Reader::NativeRep_ : public Variant::Reader::_IRep {
472public:
473 NativeRep_ () = default;
474 NativeRep_ (const NativeRep_&) = default;
475 virtual optional<filesystem::path> GetDefaultFileSuffix () const override
476 {
477 return ".json"sv;
478 }
479 virtual VariantValue Read (const InputStream::Ptr<byte>& in) const override
480 {
481 return Read (BinaryToText::Reader::New (in, SeekableFlag::eSeekable));
482 }
483 virtual VariantValue Read (const InputStream::Ptr<Character>& in) const override
484 {
485#if USE_NOISY_TRACE_IN_THIS_MODULE_
486 Debug::TraceContextBumper ctx{"DataExchange::JSON::Reader::NativeRep_::Read"};
487#endif
488 MyBufferedStreamReader_ reader{in.IsSeekable () ? in : BufferedInputStream::New (in, SeekableFlag::eSeekable)};
489 return Reader_value_ (reader);
490 }
491};
492#if __has_include("boost/json.hpp")
493
494namespace {
495 // Based on example: https://www.boost.org/doc/libs/1_83_0/libs/json/doc/html/json/examples.html#json.examples.validate
496 struct BoostSAXHandler_ {
497 /// The maximum number of elements allowed in an array
498 static constexpr std::size_t max_array_size = static_cast<size_t> (-1);
499
500 /// The maximum number of elements allowed in an object
501 static constexpr std::size_t max_object_size = static_cast<size_t> (-1);
502
503 /// The maximum number of characters allowed in a string
504 static constexpr std::size_t max_string_size = static_cast<size_t> (-1);
505
506 /// The maximum number of characters allowed in a key
507 static constexpr std::size_t max_key_size = static_cast<size_t> (-1);
508
509 BoostSAXHandler_ () = default;
510 BoostSAXHandler_ (const BoostSAXHandler_&) = delete;
511
512 bool on_document_begin ([[maybe_unused]] error_code& ec)
513 {
514 Assert (fStack_.empty ());
515 fStack_.emplace (Context_::eSimple);
516 return true;
517 }
518 bool on_document_end ([[maybe_unused]] error_code& ec)
519 {
520 Assert (fStack_.size () == 1);
521 this->PopContext_ ();
522 Assert (fStack_.empty ());
523 return true;
524 }
525
526 bool on_array_begin ([[maybe_unused]] error_code& ec)
527 {
528 fStack_.emplace (Context_::eArray);
529 return true;
530 }
531 bool on_array_end ([[maybe_unused]] std::size_t n, [[maybe_unused]] error_code& ec)
532 {
533 Assert (fStack_.top ().GetContextType () == Context_::eArray);
534 Assert (fStack_.top ().PeekAccumVector_ ().size () == n);
535 PopContext_ ();
536 return true;
537 }
538
539 bool on_object_begin ([[maybe_unused]] error_code& ec)
540 {
541 fStack_.emplace (Context_::eMap);
542 return true;
543 }
544 bool on_object_end ([[maybe_unused]] std::size_t n, [[maybe_unused]] error_code& ec)
545 {
546 Assert (fStack_.top ().GetContextType () == Context_::eMap);
547 Assert (fStack_.top ().PeekAccumObj_ ().size () == n);
548 PopContext_ ();
549 return true;
550 }
551
552 bool on_string_part (string_view s, [[maybe_unused]] std::size_t n, [[maybe_unused]] error_code& ec)
553 {
554 fPartSaver_.push_back (span<const char>{s});
555 return true;
556 }
557 bool on_string (string_view s, [[maybe_unused]] std::size_t n, [[maybe_unused]] error_code& ec)
558 {
559 // Tricky - not really documented (I can find) - but seems if n != s.size() - we must use fPartSaver from on_key_part
560 if (s.size () == n) {
561 AddCompleteValue_ (VariantValue{toStroikaString_ (s)});
562 }
563 else {
564 fPartSaver_.push_back (span<const char>{s});
565 String res = toStroikaString_ (fPartSaver_);
566 fPartSaver_.clear ();
567 AddCompleteValue_ (VariantValue{res});
568 }
569 return true;
570 }
571
572 bool on_key_part (string_view s, [[maybe_unused]] std::size_t n, [[maybe_unused]] error_code& ec)
573 {
574 // tricky - save text in buffer, for use on subsequent onKey
575 Assert (fStack_.top ().GetContextType () == Context_::eMap);
576 fPartSaver_.push_back (span<const char>{s});
577 return true;
578 }
579 bool on_key (string_view s, [[maybe_unused]] std::size_t n, [[maybe_unused]] error_code& ec)
580 {
581 Assert (fStack_.top ().GetContextType () == Context_::eMap);
582 Assert (s.size () <= n);
583 // Tricky - not really documented (I can find) - but seems if n != s.size() - we must use fPartSaver from on_key_part
584 if (s.size () == n) {
585 fStack_.top ().fKey = toStroikaString_ (s);
586 }
587 else {
588 fPartSaver_.push_back (span<const char>{s});
589 fStack_.top ().fKey = toStroikaString_ (fPartSaver_);
590 fPartSaver_.clear ();
591 }
592 return true;
593 }
594
595 bool on_number_part ([[maybe_unused]] string_view s, [[maybe_unused]] error_code& ec)
596 {
597 // No need to track anything for numbers, as boost appears to incrementally parse and keep its state internally
598 return true;
599 }
600 bool on_int64 (int64_t i, [[maybe_unused]] string_view s, [[maybe_unused]] error_code& ec)
601 {
602 AddCompleteValue_ (VariantValue{i});
603 return true;
604 }
605 bool on_uint64 (uint64_t u, [[maybe_unused]] string_view s, [[maybe_unused]] error_code& ec)
606 {
607 AddCompleteValue_ (VariantValue{u});
608 return true;
609 }
610 bool on_double (double d, [[maybe_unused]] string_view s, [[maybe_unused]] error_code& ec)
611 {
612 AddCompleteValue_ (VariantValue{d});
613 return true;
614 }
615
616 bool on_bool (bool b, [[maybe_unused]] error_code& ec)
617 {
618 AddCompleteValue_ (VariantValue{b});
619 return true;
620 }
621
622 bool on_null ([[maybe_unused]] error_code& ec)
623 {
624 AddCompleteValue_ (VariantValue{});
625 return true;
626 }
627
628 bool on_comment_part ([[maybe_unused]] string_view s, [[maybe_unused]] error_code& ec)
629 {
630 // ignore comments
631 return true;
632 }
633 bool on_comment ([[maybe_unused]] string_view s, [[maybe_unused]] error_code& ec)
634 {
635 // ignore comments
636 return true;
637 }
638
639 // Careful not to use string_view directly cuz Stroika assumes we can keep that pointer (String_Constant) - so map to span, and also character to use char8_t
640 // to signal character set, and not ASCII
641 template <typename CONTAINER_OF_CHAR_BUT_REALLY_UTF8>
642 static String toStroikaString_ (CONTAINER_OF_CHAR_BUT_REALLY_UTF8 sv)
643 requires requires (CONTAINER_OF_CHAR_BUT_REALLY_UTF8 t) {
644 { span<const char>{t} };
645 }
646 {
647 return String{SpanBytesCast<span<const char8_t>> (span<const char>{sv})};
648 }
649
650 VariantValue GetConstructedValue () const
651 {
652 return fCompletedFinalValue_;
653 }
654 void AddCompleteValue_ (VariantValue v)
655 {
656 Assert (not fStack_.empty ());
657 Context_& t = fStack_.top ();
658 switch (t.GetContextType ()) {
659 case Context_::eArray:
660 t.PeekAccumVector_ ().push_back (v);
661 break;
662 case Context_::eMap:
663 t.PeekAccumObj_ ().insert ({t.fKey, v});
664 break;
665 case Context_::eSimple:
666 t.PeekSimpleValue_ () = v;
667 break;
668 default:
670 }
671 }
672 void PopContext_ ()
673 {
674 Assert (not fStack_.empty ());
675 // complete what is at the top of the stack and do AddCompleteValue_ to the new top of the stack
676 Context_& t = fStack_.top ();
677 VariantValue vv = [&t] () {
678 switch (t.GetContextType ()) {
679 case Context_::eArray:
680 return VariantValue{move (t.PeekAccumVector_ ())};
681 case Context_::eMap:
682 return VariantValue{Mapping_HashTable<String, VariantValue>{move (t.PeekAccumObj_ ())}};
683 case Context_::eSimple:
684 return t.PeekSimpleValue_ ();
685 default:
687 return VariantValue{};
688 }
689 }();
690 fStack_.pop ();
691 if (fStack_.empty ()) {
692 fCompletedFinalValue_ = vv;
693 }
694 else {
695 AddCompleteValue_ (vv);
696 }
697 }
698
699 /*
700 * We have a stack of context objects for in progress parses. This cheaply maintains the data at each point of the stack.
701 */
702 struct Context_ {
703
704 // NOTE - critical these enums correspond to index values of std::variant<> below
705 enum ContextType_ {
706 eSimple,
707 eArray,
708 eMap
709 };
710
711 Context_ () = delete;
712 Context_ (ContextType_ ct)
713 {
714 switch (ct) {
715 case eSimple:
716 fVV_ = VariantValue{};
717 break;
718 case eArray:
719 fVV_ = vector<VariantValue>{};
720 break;
721 case eMap:
723 break;
724 }
725 Ensure (ct == GetContextType ()); // ensure ContextType_ enum in same order as variant<> arguments
726 }
727 ~Context_ () = default;
728 Context_ (const Context_&) = delete;
729 Context_ (Context_&&) = default;
730
731 ContextType_ GetContextType () const
732 {
733 return static_cast<ContextType_> (fVV_.index ());
734 }
735
736 vector<VariantValue>& PeekAccumVector_ ()
737 {
738 Require (GetContextType () == eArray);
739 return get<vector<VariantValue>> (fVV_);
740 }
742 {
743 Require (GetContextType () == eMap);
744 return get<Mapping_HashTable<String, VariantValue>::DEFAULT_HASHTABLE<>> (fVV_);
745 }
746 VariantValue& PeekSimpleValue_ ()
747 {
748 Require (GetContextType () == eSimple);
749 return get<VariantValue> (fVV_);
750 }
751
752 // use variant to save construct/destruct of unneeded parts
753 variant<VariantValue, vector<VariantValue>, Mapping_HashTable<String, VariantValue>::DEFAULT_HASHTABLE<>> fVV_;
754
755 String fKey; // only allowed of context type = eMap (so COULD embed in above variant, but KISS) - also could use optional<String> which would help some things and make others worse...
756 };
757
758 std::stack<Context_> fStack_;
759 VariantValue fCompletedFinalValue_; // only filled in when stack is empty
760
761 // doesn't need to be in stack context cuz cannot fill partial string/key/etc with intervening pop/push
762 // Not using StringBuilder here cuz could contain partial strings
763 InlineBuffer<char, 512> fPartSaver_;
764 };
765}
766class Variant::JSON::Reader::BoostRep_ : public Variant::Reader::_IRep {
767public:
768 BoostRep_ () = default;
769 BoostRep_ (const BoostRep_&) = default;
770 virtual optional<filesystem::path> GetDefaultFileSuffix () const override
771 {
772 return ".json"sv;
773 }
774 virtual VariantValue Read (const InputStream::Ptr<byte>& in) const override
775 {
776#if USE_NOISY_TRACE_IN_THIS_MODULE_
777 Debug::TraceContextBumper ctx{"DataExchange::JSON::Reader::BoostRep_::Read"};
778#endif
779 using namespace boost;
780 InputStream::Ptr<byte> useInStream = in.IsSeekable () ? in : BufferedInputStream::New (in, SeekableFlag::eSeekable);
781 Assert (useInStream.IsSeekable ());
782 byte buf[8 * 1024]; // experimentally - larger buffers didn't help speed
783 constexpr bool kUseSAX_ = true; // experimentally, on windows, sax about 10% faster than stream_parser/convert way
784 try {
785 if constexpr (kUseSAX_) {
786 json::basic_parser<BoostSAXHandler_> p{json::parse_options{}};
787 const size_t targetChunkSize = std::size (buf);
788 size_t actualChunkSize;
789 boost::system::error_code ec;
790 while ((actualChunkSize = useInStream.ReadBlocking (span{buf, targetChunkSize}).size ()) != 0) {
791 ec.clear ();
792 size_t nParsed = p.write_some (true, reinterpret_cast<const char*> (begin (buf)), actualChunkSize, ec);
793 Assert (nParsed <= actualChunkSize);
794 if (nParsed < actualChunkSize) {
795 useInStream.Seek (eFromCurrent, static_cast<SignedSeekOffsetType> (nParsed) - static_cast<SignedSeekOffsetType> (actualChunkSize));
796 break;
797 }
798 if (p.done ()) {
799 break; // good parse
800 }
801 else if (ec) {
802 Throw (BadFormatException{String::FromNarrowSDKString (ec.what ())});
803 }
804 }
805 if (not p.done ()) {
806 p.write_some (false, nullptr, 0, ec);
807 if (ec and ec != boost::json::error::extra_data /*and ec != boost::json::error::incomplete*/) {
808 Throw (BadFormatException{String::FromNarrowSDKString (ec.what ())});
809 }
810 }
811 return p.handler ().GetConstructedValue ();
812 }
813 else {
814 json::stream_parser p;
815 const size_t targetChunkSize = std::size (buf);
816 size_t actualChunkSize;
817 while ((actualChunkSize = useInStream.ReadBlocking (span{buf, targetChunkSize}).size ()) != 0) {
818 boost::system::error_code ec;
819 size_t nParsed = p.write_some (reinterpret_cast<const char*> (begin (buf)), actualChunkSize, ec);
820 Assert (nParsed <= actualChunkSize);
821 if (nParsed < actualChunkSize) {
822 useInStream.Seek (eFromCurrent, static_cast<SignedSeekOffsetType> (nParsed) - static_cast<SignedSeekOffsetType> (actualChunkSize));
823 break;
824 }
825 if (p.done ()) {
826 break; // good parse
827 }
828 else if (ec) {
829 Throw (BadFormatException{String::FromNarrowSDKString (ec.what ())});
830 }
831 }
832 if (not p.done ()) {
833 p.finish (); // in case wrote text like '3' to buffer, ambiguous if done
834 }
835 Assert (p.done ());
836 return DataExchange::VariantValue{p.release ()}; // Transform boost objects to Stroika objects
837 }
838 }
839 catch (...) {
840 Throw (BadFormatException{Characters::ToString (current_exception ())});
841 }
842 }
843 virtual VariantValue Read (const InputStream::Ptr<Character>& in) const override
844 {
845#if USE_NOISY_TRACE_IN_THIS_MODULE_
846 Debug::TraceContextBumper ctx{"DataExchange::JSON::Reader::BoostRep_::Read"};
847#endif
848 Require (in.IsSeekable ());
849 return Read (_ToByteReader (in));
850 }
851};
852#endif
853
854inline auto Variant::JSON::Reader::mk_ (const ReaderOptions& options) -> shared_ptr<_IRep>
855{
856 switch (options.fPreferredAlgorithm.value_or (ReaderOptions::Algorithm::eDEFAULT)) {
857 case ReaderOptions::Algorithm::eStroikaNative:
858 return MakeSharedPtr<NativeRep_> ();
859#if __has_include("boost/json.hpp")
860 case ReaderOptions::Algorithm::eBoost:
861 return MakeSharedPtr<BoostRep_> ();
862#endif
863 default:
865 return nullptr;
866 }
867}
868
869Variant::JSON::Reader::Reader (const ReaderOptions& options)
870 : inherited{mk_ (options)}
871{
872}
#define AssertNotReached()
Definition Assertions.h:358
constexpr const T & ValueOf(const optional< T > &t)
Same as *t, but Requires that 't' is engaged.
Definition Optional.inl:156
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
nonvirtual span< const CHAR_T > GetData(Memory::StackBuffer< CHAR_T > *probablyIgnoredBuf) const
access a span of data located inside the StringBuilder. Return internal pointer, or pointer internal ...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
Mapping_HashTable<KEY_TYPE, MAPPED_VALUE_TYPE, TRAITS> is a HashTable based concrete implementation o...
Sequence_stdvector<T> is an std::vector-based concrete implementation of the Sequence<T> container pa...
nonvirtual void insert(ArgByValueType< value_type > kvp)
Definition Mapping.inl:461
Simple variant-value (case variant union) object, with (variant) basic types analogous to a value in ...
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
InputStream<>::Ptr is Smart pointer (with abstract Rep) class defining the interface to reading from ...
nonvirtual optional< ElementType > ReadBlocking() const
ReadBlocking () reads either a single element, or fills in argument intoBuffer - but never blocks (no...
nonvirtual SeekOffsetType Seek(SeekOffsetType offset) const
nonvirtual bool IsSeekable() const
Returns true iff this object was constructed with a seekable input stream rep.
Definition Stream.inl:44
void Throw(T &&e2Throw)
like the builtin C++ 'throw', but stamps in the current Activity context and does helpful,...
Definition Throw.inl:54
Ptr New(const InputStream::Ptr< byte > &src, optional< SeekableFlag > seekable={}, ReadAhead readAhead=eReadAheadAllowed)
Create an InputStream::Ptr<Character> from the arguments (usually binary source) - which can be used ...
StreamReader is an non-essential Stream utility, adding simplicity of use for a common use case,...
nonvirtual SeekOffsetType Seek(SeekOffsetType offset)
Logically the same as InputStream::Ptr<ELEMENT_TYPE>::Seek () - but without being 'synchronized' it m...
nonvirtual optional< ElementType > ReadBlocking()
ReadBlocking () reads either a single element, or fills in argument intoBuffer - but never blocks (no...