Gaia-ECS v0.9.3
A simple and powerful entity component system
Loading...
Searching...
No Matches
ser_json.h
1#pragma once
2#include "gaia/config/config.h"
3
4#include <cctype>
5#include <cmath>
6#include <cstdint>
7#include <cstdio>
8#include <cstdlib>
9#include <cstring>
10#include <limits>
11#include <type_traits>
12
13#include "gaia/cnt/darray.h"
14#include "gaia/core/utility.h"
15#include "gaia/ser/ser_buffer_binary.h"
16#include "gaia/util/str.h"
17
18namespace gaia {
19 namespace ser {
20 using json_str_view = util::str_view;
21 using json_str = util::str;
22
23 enum JsonSaveFlags : uint32_t {
24 None = 0,
25 BinarySnapshot = 1u << 0, // Include binary snapshot
26 RawFallback = 1u << 1, // Allow raw data fallback
27 Default = BinarySnapshot | RawFallback
28 };
29
30 enum class JsonDiagSeverity : uint8_t { Info, Warning, Error };
31 enum class JsonDiagReason : uint8_t {
32 None,
33 UnknownField,
34 FieldOutOfBounds,
35 FieldValueAdjusted,
36 TagValueIgnored,
37 NullComponentPayload,
38 MissingSchemaOrRawPayload,
39 SoaRawUnsupported,
40 UnknownComponent,
41 TagComponentUnsupported,
42 DuplicateEntityName,
43 MissingComponentStorage,
44 MissingArchetypesSection,
45 MissingFormatField,
46 UnsupportedFormatVersion,
47 InvalidJson
48 };
49
51 JsonDiagSeverity severity = JsonDiagSeverity::Warning;
52 JsonDiagReason reason = JsonDiagReason::None;
53 json_str path;
54 json_str message;
55 };
56
58 static constexpr uint32_t MaxDiagPathLength = 1024;
59 static constexpr uint32_t MaxDiagMessageLength = 2048;
60
62 bool hasWarnings = false;
63 bool hasErrors = false;
64
65 void add(JsonDiagSeverity severity, JsonDiagReason reason, json_str_view path, json_str_view message) {
66 JsonDiagnostic diag;
67 diag.severity = severity;
68 diag.reason = reason;
69 diag.path.assign(path);
70 diag.message.assign(message);
71 items.push_back(diag);
72
73 if (severity == JsonDiagSeverity::Warning)
74 hasWarnings = true;
75 else if (severity == JsonDiagSeverity::Error)
76 hasErrors = true;
77 }
78 void add(JsonDiagSeverity severity, JsonDiagReason reason, json_str_view path, const char* message) {
79 add(severity, reason, path, json_str_view(message, (uint32_t)GAIA_STRLEN(message, MaxDiagMessageLength)));
80 }
81 void add(JsonDiagSeverity severity, JsonDiagReason reason, const char* path, const char* message) {
82 add(severity, reason, json_str_view(path, (uint32_t)GAIA_STRLEN(path, MaxDiagPathLength)),
83 json_str_view(message, (uint32_t)GAIA_STRLEN(message, MaxDiagMessageLength)));
84 }
85
86 GAIA_NODISCARD bool has_issues() const {
87 return hasWarnings || hasErrors;
88 }
89
90 void clear() {
91 items.clear();
92 hasWarnings = false;
93 hasErrors = false;
94 }
95 };
96
101 class ser_json {
102 static constexpr uint32_t MaxImplicitKeyLength = 384;
103 static constexpr uint32_t MaxImplicitStringLength = 16u * 1024u * 1024u;
104 static constexpr uint32_t MaxLiteralLength = 256u;
105
106 enum class CtxType : uint8_t { Object, Array };
107
108 struct Ctx {
109 CtxType type = CtxType::Object;
110 bool first = true;
111 bool needsValue = false;
112 };
113
114 json_str m_out;
115 json_str m_parseScratch;
116 cnt::darray<Ctx> m_ctx;
117 const char* m_it = nullptr;
118 const char* m_end = nullptr;
119
120 static void add_escaped(json_str& out, const char* str, uint32_t len) {
121 GAIA_FOR(len) {
122 const char ch = str[i];
123 switch (ch) {
124 case '"':
125 out.append("\\\"");
126 break;
127 case '\\':
128 out.append("\\\\");
129 break;
130 case '\n':
131 out.append("\\n");
132 break;
133 case '\r':
134 out.append("\\r");
135 break;
136 case '\t':
137 out.append("\\t");
138 break;
139 default:
140 out.append(ch);
141 break;
142 }
143 }
144 }
145
146 void before_value() {
147 if (m_ctx.empty())
148 return;
149
150 auto& ctx = m_ctx.back();
151 if (ctx.type == CtxType::Array) {
152 if (!ctx.first)
153 m_out.append(",");
154 ctx.first = false;
155 } else {
156 GAIA_ASSERT(ctx.needsValue);
157 ctx.needsValue = false;
158 }
159 }
160
161 public:
162 ser_json() = default;
163 ser_json(const char* json, uint32_t len) {
164 reset_input(json, len);
165 }
166 template <size_t N>
167 explicit ser_json(const char (&json)[N]) {
168 static_assert(N > 0);
169 reset_input(json, (uint32_t)(N - 1));
170 }
171
173 void reset_input(const char* json, uint32_t len) {
174 if (json == nullptr) {
175 m_it = nullptr;
176 m_end = nullptr;
177 return;
178 }
179
180 m_it = json;
181 m_end = json + len;
182 }
183 template <size_t N>
184 void reset_input(const char (&json)[N]) {
185 static_assert(N > 0);
186 reset_input(json, (uint32_t)(N - 1));
187 }
188
190 void clear() {
191 m_out.clear();
192 m_parseScratch.clear();
193 m_ctx.clear();
194 }
195
197 GAIA_NODISCARD const json_str& str() const {
198 return m_out;
199 }
200
202 GAIA_NODISCARD bool eof() const {
203 return m_it == nullptr || m_end == nullptr || m_it >= m_end;
204 }
205
207 GAIA_NODISCARD char peek() const {
208 GAIA_ASSERT(m_it != nullptr && m_it < m_end);
209 return *m_it;
210 }
211
213 void ws() {
214 if (m_it == nullptr || m_end == nullptr)
215 return;
216 while (m_it < m_end && std::isspace((unsigned char)*m_it))
217 ++m_it;
218 }
219
220 GAIA_NODISCARD const char* pos() const {
221 return m_it;
222 }
223
224 GAIA_NODISCARD const char* end() const {
225 return m_end;
226 }
227
228 void begin_object() {
229 before_value();
230 m_out.append("{");
231 m_ctx.push_back({CtxType::Object, true, false});
232 }
233
234 void end_object() {
235 GAIA_ASSERT(!m_ctx.empty() && m_ctx.back().type == CtxType::Object);
236 m_ctx.pop_back();
237 m_out.append("}");
238 }
239
240 void begin_array() {
241 before_value();
242 m_out.append("[");
243 m_ctx.push_back({CtxType::Array, true, false});
244 }
245
246 void end_array() {
247 GAIA_ASSERT(!m_ctx.empty() && m_ctx.back().type == CtxType::Array);
248 m_ctx.pop_back();
249 m_out.append("]");
250 }
251
252 void key(const char* name, uint32_t len = 0) {
253 GAIA_ASSERT(name != nullptr);
254 GAIA_ASSERT(!m_ctx.empty() && m_ctx.back().type == CtxType::Object);
255 auto& ctx = m_ctx.back();
256 GAIA_ASSERT(!ctx.needsValue);
257
258 if (!ctx.first)
259 m_out.append(",");
260 ctx.first = false;
261 ctx.needsValue = true;
262
263 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(name, MaxImplicitKeyLength) : len;
264 m_out.append("\"");
265 add_escaped(m_out, name, l);
266 m_out.append("\":");
267 }
268
269 void value_null() {
270 before_value();
271 m_out.append("null");
272 }
273
274 void value_bool(bool v) {
275 before_value();
276 if (v)
277 m_out.append("true");
278 else
279 m_out.append("false");
280 }
281
282 template <typename TInt, typename = std::enable_if_t<std::is_integral_v<TInt> && !std::is_same_v<TInt, bool>>>
283 void value_int(TInt v) {
284 before_value();
285
286 char buff[64];
287 if constexpr (std::is_signed_v<TInt>) {
288 (void)GAIA_STRFMT(buff, sizeof(buff), "%lld", (long long)v);
289 } else {
290 (void)GAIA_STRFMT(buff, sizeof(buff), "%llu", (unsigned long long)v);
291 }
292 m_out.append(buff, (uint32_t)GAIA_STRLEN(buff, (size_t)sizeof(buff)));
293 }
294
295 void value_float(float v) {
296 before_value();
297 char buff[64];
298 (void)GAIA_STRFMT(buff, sizeof(buff), "%.9g", (double)v);
299 m_out.append(buff, (uint32_t)GAIA_STRLEN(buff, (size_t)sizeof(buff)));
300 }
301
302 void value_float(double v) {
303 before_value();
304 char buff[64];
305 (void)GAIA_STRFMT(buff, sizeof(buff), "%.17g", v);
306 m_out.append(buff, (uint32_t)GAIA_STRLEN(buff, (size_t)sizeof(buff)));
307 }
308
309 void value_string(const char* str, uint32_t len = 0) {
310 GAIA_ASSERT(str != nullptr);
311 before_value();
312 const auto l = len == 0 ? (uint32_t)GAIA_STRLEN(str, MaxImplicitStringLength) : len;
313 m_out.append("\"");
314 add_escaped(m_out, str, l);
315 m_out.append("\"");
316 }
317
318 bool consume(char ch) {
319 ws();
320 if (m_it == nullptr || m_end == nullptr || m_it >= m_end || *m_it != ch)
321 return false;
322 ++m_it;
323 return true;
324 }
325
326 bool expect(char ch) {
327 return consume(ch);
328 }
329
330 bool parse_literal(const char* lit) {
331 ws();
332 if (m_it == nullptr || m_end == nullptr || lit == nullptr)
333 return false;
334
335 const auto litLen = (uint32_t)GAIA_STRLEN(lit, MaxLiteralLength);
336 if (litLen >= MaxLiteralLength)
337 return false;
338 if ((uint32_t)(m_end - m_it) < litLen)
339 return false;
340 if (memcmp(m_it, lit, litLen) != 0)
341 return false;
342 m_it += litLen;
343 return true;
344 }
345
346 bool parse_string_view(json_str_view& out, bool* fromScratch = nullptr) {
347 ws();
348 if (m_it == nullptr || m_end == nullptr || m_it >= m_end || *m_it != '"')
349 return false;
350
351 ++m_it;
352 const char* begin = m_it;
353 bool escaped = false;
354 m_parseScratch.clear();
355 while (m_it < m_end) {
356 const char ch = *m_it++;
357 if (ch == '"')
358 break;
359
360 if (ch == '\\') {
361 if (!escaped) {
362 escaped = true;
363 const auto prefixLen = (size_t)((m_it - 1) - begin);
364 if (prefixLen > 0)
365 m_parseScratch.append(begin, (uint32_t)prefixLen);
366 }
367
368 if (m_it >= m_end)
369 return false;
370 const char esc = *m_it++;
371 switch (esc) {
372 case '"':
373 case '\\':
374 case '/':
375 m_parseScratch.append(esc);
376 break;
377 case 'b':
378 m_parseScratch.append('\b');
379 break;
380 case 'f':
381 m_parseScratch.append('\f');
382 break;
383 case 'n':
384 m_parseScratch.append('\n');
385 break;
386 case 'r':
387 m_parseScratch.append('\r');
388 break;
389 case 't':
390 m_parseScratch.append('\t');
391 break;
392 case 'u': {
393 if ((uint32_t)(m_end - m_it) < 4)
394 return false;
395 GAIA_FOR(4) {
396 const char h = m_it[i];
397 const bool isHex = (h >= '0' && h <= '9') || (h >= 'a' && h <= 'f') || (h >= 'A' && h <= 'F');
398 if (!isHex)
399 return false;
400 }
401 m_it += 4;
402 m_parseScratch.append('?');
403 break;
404 }
405 default:
406 return false;
407 }
408 } else if (escaped)
409 m_parseScratch.append(ch);
410 }
411
412 if (m_it <= begin || m_it > m_end || *(m_it - 1) != '"')
413 return false;
414
415 if (escaped) {
416 if (fromScratch != nullptr)
417 *fromScratch = true;
418 out = m_parseScratch.view();
419 } else {
420 if (fromScratch != nullptr)
421 *fromScratch = false;
422 out = json_str_view(begin, (uint32_t)((m_it - 1) - begin));
423 }
424 return true;
425 }
426
427 bool parse_string(json_str& out) {
428 json_str_view view;
429 if (!parse_string_view(view))
430 return false;
431 out.assign(view);
432 return true;
433 }
434
435 bool parse_string_eq(const char* literal) {
436 json_str_view view;
437 if (!parse_string_view(view))
438 return false;
439 const auto literalLen = (size_t)GAIA_STRLEN(literal, MaxLiteralLength);
440 if (literalLen >= MaxLiteralLength)
441 return false;
442 return view.size() == literalLen && memcmp(view.data(), literal, literalLen) == 0;
443 }
444
445 bool parse_number(double& value) {
446 ws();
447 if (m_it == nullptr || m_end == nullptr || m_it >= m_end)
448 return false;
449
450 char* pEnd = nullptr;
451 value = std::strtod(m_it, &pEnd);
452 if (pEnd == m_it)
453 return false;
454 m_it = pEnd;
455 return true;
456 }
457
458 bool parse_bool(bool& value) {
459 if (parse_literal("true")) {
460 value = true;
461 return true;
462 }
463 if (parse_literal("false")) {
464 value = false;
465 return true;
466 }
467 return false;
468 }
469
470 bool parse_null() {
471 return parse_literal("null");
472 }
473
474 bool skip_value() {
475 ws();
476 if (m_it == nullptr || m_end == nullptr || m_it >= m_end)
477 return false;
478
479 if (*m_it == '{') {
480 ++m_it;
481 ws();
482 if (consume('}'))
483 return true;
484
485 while (true) {
486 json_str_view key;
487 if (!parse_string_view(key))
488 return false;
489 if (!expect(':'))
490 return false;
491 if (!skip_value())
492 return false;
493
494 ws();
495 if (consume(','))
496 continue;
497 if (consume('}'))
498 return true;
499 return false;
500 }
501 }
502
503 if (*m_it == '[') {
504 ++m_it;
505 ws();
506 if (consume(']'))
507 return true;
508
509 while (true) {
510 if (!skip_value())
511 return false;
512 ws();
513 if (consume(','))
514 continue;
515 if (consume(']'))
516 return true;
517 return false;
518 }
519 }
520
521 if (*m_it == '"') {
522 json_str_view tmp;
523 return parse_string_view(tmp);
524 }
525
526 if (*m_it == 't' || *m_it == 'f') {
527 bool v = false;
528 return parse_bool(v);
529 }
530
531 if (*m_it == 'n')
532 return parse_null();
533
534 double v = 0.0;
535 return parse_number(v);
536 }
537 };
538
539 namespace detail {
540 template <typename T>
541 inline void copy_field_bytes(uint8_t* pFieldData, uint32_t size, const T& v) {
542 memcpy(pFieldData, &v, size < sizeof(v) ? size : (uint32_t)sizeof(v));
543 }
544
545 template <typename TInt>
546 inline bool read_schema_field_json_int(ser_json& reader, uint8_t* pFieldData, uint32_t size, bool& ok) {
547 double d = 0.0;
548 if (!reader.parse_number(d))
549 return false;
550
551 if (!std::isfinite(d)) {
552 ok = false;
553 const TInt v = 0;
554 copy_field_bytes(pFieldData, size, v);
555 return true;
556 }
557
558 double clamped = std::trunc(d);
559 if (clamped != d)
560 ok = false;
561
562 constexpr auto minVal = (double)(std::numeric_limits<TInt>::lowest)();
563 constexpr auto maxVal = (double)(std::numeric_limits<TInt>::max)();
564 if (clamped < minVal) {
565 clamped = minVal;
566 ok = false;
567 } else if (clamped > maxVal) {
568 clamped = maxVal;
569 ok = false;
570 }
571
572 const TInt v = (TInt)clamped;
573 copy_field_bytes(pFieldData, size, v);
574 return true;
575 }
576
577 template <typename TFloat>
578 inline bool read_schema_field_json_float(ser_json& reader, uint8_t* pFieldData, uint32_t size, bool& ok) {
579 double d = 0.0;
580 if (!reader.parse_number(d))
581 return false;
582
583 if (!std::isfinite(d)) {
584 ok = false;
585 const TFloat v = (TFloat)0;
586 copy_field_bytes(pFieldData, size, v);
587 return true;
588 }
589
590 const TFloat v = (TFloat)d;
591 copy_field_bytes(pFieldData, size, v);
592 return true;
593 }
594
595 inline bool
596 write_schema_field_json(ser_json& writer, const uint8_t* pFieldData, serialization_type_id type, uint32_t size) {
597 switch (type) {
598 case serialization_type_id::s8: {
599 int8_t v = 0;
600 memcpy(&v, pFieldData, sizeof(v));
601 writer.value_int(v);
602 return true;
603 }
604 case serialization_type_id::u8: {
605 uint8_t v = 0;
606 memcpy(&v, pFieldData, sizeof(v));
607 writer.value_int(v);
608 return true;
609 }
610 case serialization_type_id::s16: {
611 int16_t v = 0;
612 memcpy(&v, pFieldData, sizeof(v));
613 writer.value_int(v);
614 return true;
615 }
616 case serialization_type_id::u16: {
617 uint16_t v = 0;
618 memcpy(&v, pFieldData, sizeof(v));
619 writer.value_int(v);
620 return true;
621 }
622 case serialization_type_id::s32: {
623 int32_t v = 0;
624 memcpy(&v, pFieldData, sizeof(v));
625 writer.value_int(v);
626 return true;
627 }
628 case serialization_type_id::u32: {
629 uint32_t v = 0;
630 memcpy(&v, pFieldData, sizeof(v));
631 writer.value_int(v);
632 return true;
633 }
634 case serialization_type_id::s64: {
635 int64_t v = 0;
636 memcpy(&v, pFieldData, sizeof(v));
637 writer.value_int(v);
638 return true;
639 }
640 case serialization_type_id::u64: {
641 uint64_t v = 0;
642 memcpy(&v, pFieldData, sizeof(v));
643 writer.value_int(v);
644 return true;
645 }
646 case serialization_type_id::b: {
647 bool v = false;
648 memcpy(&v, pFieldData, sizeof(v));
649 writer.value_bool(v);
650 return true;
651 }
652 case serialization_type_id::f32: {
653 float v = 0.0f;
654 memcpy(&v, pFieldData, sizeof(v));
655 writer.value_float(v);
656 return true;
657 }
658 case serialization_type_id::f64: {
659 double v = 0.0;
660 memcpy(&v, pFieldData, sizeof(v));
661 writer.value_float(v);
662 return true;
663 }
664 case serialization_type_id::c8: {
665 const auto len = (uint32_t)GAIA_STRLEN((const char*)pFieldData, size);
666 writer.value_string((const char*)pFieldData, len);
667 return true;
668 }
669 default:
670 writer.value_null();
671 return false;
672 }
673 }
674
675 inline bool read_schema_field_json(
676 ser_json& reader, uint8_t* pFieldData, serialization_type_id type, uint32_t size, bool& ok) {
677 if (reader.parse_null()) {
678 ok = false;
679 return true;
680 }
681
682 switch (type) {
683 case serialization_type_id::s8: {
684 return read_schema_field_json_int<int8_t>(reader, pFieldData, size, ok);
685 }
686 case serialization_type_id::u8: {
687 return read_schema_field_json_int<uint8_t>(reader, pFieldData, size, ok);
688 }
689 case serialization_type_id::s16: {
690 return read_schema_field_json_int<int16_t>(reader, pFieldData, size, ok);
691 }
692 case serialization_type_id::u16: {
693 return read_schema_field_json_int<uint16_t>(reader, pFieldData, size, ok);
694 }
695 case serialization_type_id::s32: {
696 return read_schema_field_json_int<int32_t>(reader, pFieldData, size, ok);
697 }
698 case serialization_type_id::u32: {
699 return read_schema_field_json_int<uint32_t>(reader, pFieldData, size, ok);
700 }
701 case serialization_type_id::s64: {
702 return read_schema_field_json_int<int64_t>(reader, pFieldData, size, ok);
703 }
704 case serialization_type_id::u64: {
705 return read_schema_field_json_int<uint64_t>(reader, pFieldData, size, ok);
706 }
707 case serialization_type_id::f32: {
708 return read_schema_field_json_float<float>(reader, pFieldData, size, ok);
709 }
710 case serialization_type_id::f64: {
711 return read_schema_field_json_float<double>(reader, pFieldData, size, ok);
712 }
713 case serialization_type_id::b: {
714 bool v = false;
715 if (!reader.parse_bool(v))
716 return false;
717 copy_field_bytes(pFieldData, size, v);
718 return true;
719 }
720 case serialization_type_id::c8: {
721 json_str_view str;
722 if (!reader.parse_string_view(str))
723 return false;
724 if (size == 0) {
725 ok = false;
726 return true;
727 }
728
729 memset(pFieldData, 0, size);
730 const auto maxLen = size > 0 ? size - 1 : 0;
731 const auto strLen = (uint32_t)str.size();
732 const auto copyLen = strLen < maxLen ? strLen : maxLen;
733 if (strLen > maxLen)
734 ok = false;
735 if (copyLen > 0)
736 memcpy(pFieldData, str.data(), copyLen);
737 return true;
738 }
739 default:
740 ok = false;
741 return reader.skip_value();
742 }
743 }
744
745 template <typename TByteSink>
746 inline bool parse_json_byte_array(ser_json& reader, TByteSink& out) {
747 if (!reader.expect('['))
748 return false;
749
750 reader.ws();
751 if (reader.consume(']'))
752 return true;
753
754 while (true) {
755 double d = 0.0;
756 if (!reader.parse_number(d))
757 return false;
758 if (d < 0.0 || d > 255.0)
759 return false;
760
761 const auto v = (uint32_t)d;
762 if ((double)v != d)
763 return false;
764
765 const uint8_t byte = (uint8_t)v;
766 out.save_raw(&byte, 1, serialization_type_id::u8);
767
768 reader.ws();
769 if (reader.consume(','))
770 continue;
771 if (reader.consume(']'))
772 return true;
773 return false;
774 }
775 }
776 } // namespace detail
777 } // namespace ser
778} // namespace gaia
Array with variable size of elements of type.
Definition darray_impl.h:25
Lightweight JSON serializer/deserializer.
Definition ser_json.h:101
GAIA_NODISCARD const json_str & str() const
Returns currently emitted output text.
Definition ser_json.h:197
void ws()
Skips whitespace in parser input.
Definition ser_json.h:213
GAIA_NODISCARD bool eof() const
Returns true if parser has reached end of input.
Definition ser_json.h:202
void clear()
Clears output JSON text and writer context.
Definition ser_json.h:190
void reset_input(const char *json, uint32_t len)
Sets an input JSON buffer for parsing.
Definition ser_json.h:173
GAIA_NODISCARD char peek() const
Returns next non-consumed character.
Definition ser_json.h:207
Checks if endianess was detected correctly at compile-time.
Definition bitset.h:9
Definition ser_json.h:50
Definition ser_json.h:57
Lightweight non-owning string view over a character sequence.
Definition str.h:13
Lightweight owning string container with explicit length semantics (no implicit null terminator).
Definition str.h:331
void append(const char *data, uint32_t size)
Appends size characters from data.
Definition str.h:389
void clear()
Removes all characters from the string.
Definition str.h:352
GAIA_NODISCARD str_view view() const
Returns a non-owning view over the current contents.
Definition str.h:443
void assign(const char *data, uint32_t size)
Replaces contents with size characters from data.
Definition str.h:365