├── README.md ├── compile_flags.txt ├── example.cpp ├── json.hpp └── tests.cpp /README.md: -------------------------------------------------------------------------------- 1 | # cpp-json 2 | reflection in c++ for compile-time json de/serialization implemented as a single header library. 3 | requires **c++20**. Works with `g++` on macos and linux. Other operating systems & compilers are untested and may not work. 4 | 5 | ```cpp 6 | std::string serializedJson = json::serialize(myStruct); 7 | MyStruct deserialized = json::deserialize(serializedJson); 8 | ``` 9 | 10 | ### public domain 11 | do whatever you want with this header file. use it, modify it, sell it. you do not need to credit me in any way. 12 | 13 | ### supported datatypes: 14 | - strings: `std::string`, `const char *` and `char *` 15 | - arrays: `std::vector`, and `T[]` 16 | - maps: `std::map` and `std::unordered_map` 17 | - pointers: `T*`, `std::unique_ptr` 18 | - all arithmetic types (`int`, `unsigned long long`, `double`, `char`, etc) 19 | - various STL types: `std::tuple`, `std::pair`, `std::optional`, `std::queue`, `std::deque`, `std::list`, `std::set`, `std::unordered_set` 20 | - enums 21 | - classes and structs via `REFLECT` 22 | - can be extended via template specializations. 23 | 24 | #### freeing pointers 25 | if you are smart and use `std::vector`, `std::unique_ptr`, and `std::string`, then you don't need to worry about memory management. you can skip this section. 26 | 27 | pointers are not treated as arrays. they are expected to reference a single value. 28 | when populating a pointer with `json::deserialize()`, the data is created using `new` and so should be freed with `delete`. 29 | `const char *` and `char *` are treated differently. they are expected to point to strings. they are created using `new []` and so should be deleted with `delete []`. 30 | 31 | #### exceptions 32 | if any problems occur during serialization or deserialization, a `json::exception` is thrown. 33 | the `json::exception` is a struct containing a short description in `std::string desc` and the json index where it was located in `int idx`. 34 | 35 | # example usage with serializing/deserializing structs 36 | ```c++ 37 | #include 38 | #include "json.hpp" 39 | 40 | struct Person { 41 | std::string name; 42 | int age; 43 | std::vector friends; 44 | }; 45 | 46 | // use the REFLECT() macro to make the json serializer aware of the Person type 47 | REFLECT(Person, name, age, friends); 48 | 49 | int main() { 50 | Person person = {"joe", 20, {"ben", "sam"}}; 51 | 52 | // create a json string representing the person 53 | std::string personJson = json::serialize(person); 54 | 55 | // prettify and print the json string to stdout 56 | json::Prettifier prettifier; 57 | std::cout << prettifier.prettify(personJson) << std::endl; 58 | 59 | // construct a new person from the person json 60 | Person person2 = json::deserialize(personJson); 61 | 62 | std::cout << person2.name << std::endl; 63 | 64 | return 0; 65 | } 66 | 67 | ``` 68 | 69 | # design decisions 70 | - the `json::Prettifier` class is used for prettifying the json, instead of having the prettifying capability built into the `json::serialize()` function. this is to keep the `json::serialize()` signature simple. You can create your own `json::serialize()` function specializations from outside of the header file. the signature is simply `std::string json::serialize(const T& item);` (very beautiful). 71 | - enums are treated as integers. this seems to be common practice and i do not want to force the user to uglify their enum declarations just so reflection works. 72 | - only public fields can be serialized. this is also common practice yet you can normally force them to be serialized. i do not see the point of allowing private fields to be serialized since it breaks the idea of encapsulation. 73 | -------------------------------------------------------------------------------- /compile_flags.txt: -------------------------------------------------------------------------------- 1 | -std=c++20 2 | -------------------------------------------------------------------------------- /example.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // compile me with `g++ -std=c++20 example.cpp -I .` 3 | // 4 | 5 | #include 6 | #include "json.hpp" 7 | 8 | struct Person { 9 | std::string name; 10 | int age; 11 | std::vector friends; 12 | }; 13 | 14 | // use the REFLECT() macro to make the json serializer aware of the Person type 15 | REFLECT(Person, name, age, friends); 16 | 17 | int main() { 18 | Person person = {"joe", 20, {"ben", "sam"}}; 19 | 20 | // create a json string representing the person 21 | std::string personJson = json::serialize(person); 22 | 23 | // prettify and print the json string to stdout 24 | json::Prettifier prettifier; 25 | std::cout << prettifier.prettify(personJson) << std::endl; 26 | 27 | // construct a new person from the person json 28 | Person person2 = json::deserialize(personJson); 29 | 30 | std::cout << person2.name << std::endl; 31 | 32 | return 0; 33 | } 34 | -------------------------------------------------------------------------------- /json.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace json { 20 | // CONSTEXPR UTILS {{{ 21 | template class Ref> 22 | struct is_specialization : std::false_type {}; 23 | 24 | template class Ref, typename... Args> 25 | struct is_specialization, Ref>: std::true_type {}; 26 | 27 | template 28 | constexpr size_t array_size(const T (&)[n]) { 29 | return n; 30 | } 31 | // }}} 32 | // FOR EACH MACRO {{{ 33 | #define PARENS () 34 | 35 | #define EXPAND(...) EXPAND4(EXPAND4(EXPAND4(EXPAND4(__VA_ARGS__)))) 36 | #define EXPAND4(...) EXPAND3(EXPAND3(EXPAND3(EXPAND3(__VA_ARGS__)))) 37 | #define EXPAND3(...) EXPAND2(EXPAND2(EXPAND2(EXPAND2(__VA_ARGS__)))) 38 | #define EXPAND2(...) EXPAND1(EXPAND1(EXPAND1(EXPAND1(__VA_ARGS__)))) 39 | #define EXPAND1(...) __VA_ARGS__ 40 | 41 | #define FOR_EACH(macro, ...) \ 42 | __VA_OPT__(EXPAND(FOR_EACH_HELPER(macro, __VA_ARGS__))) 43 | 44 | #define FOR_EACH_HELPER(macro, a1, ...) \ 45 | macro(a1) __VA_OPT__(FOR_EACH_AGAIN PARENS(macro, __VA_ARGS__)) 46 | 47 | #define FOR_EACH_AGAIN() FOR_EACH_HELPER 48 | // }}} 49 | // REFLECTION {{{ 50 | template 51 | constexpr void for_sequence(std::integer_sequence, F &&f) { 52 | (static_cast(f(std::integral_constant{})), ...); 53 | } 54 | 55 | template 56 | constexpr auto properties() {} 57 | 58 | template 59 | struct Property 60 | { 61 | using Type = T; 62 | const char *key; 63 | T Class::*value; 64 | 65 | constexpr Property(const char *key, T Class::*value) { 66 | this->key = key; 67 | this->value = value; 68 | } 69 | }; 70 | 71 | #define REFLECT_PROPERTY(KEY) json::Property(#KEY, &_class::KEY), 72 | #define REFLECT(CLASS, ...) \ 73 | template <> \ 74 | constexpr auto json::properties() { \ 75 | using _class = CLASS; \ 76 | return std::tuple{FOR_EACH(REFLECT_PROPERTY, __VA_ARGS__)}; \ 77 | } 78 | // }}} 79 | // JSON EXCEPTION {{{ 80 | struct exception { 81 | std::string description; 82 | int idx; 83 | exception(std::string description, int idx) { 84 | this->description = description; 85 | this->idx = idx; 86 | } 87 | }; 88 | // }}} 89 | // CURSOR {{{ 90 | struct Cursor 91 | { 92 | const std::string &string; 93 | int idx = 0; 94 | 95 | Cursor(const std::string &string) : string(string) {} 96 | 97 | void expect(char c) { 98 | if (next() != c) { 99 | std::string desc = std::string("expected '") + c + "'"; 100 | desc += std::string(" but got '") + peek(-1) + "'"; 101 | throw exception(desc, idx); 102 | } 103 | } 104 | 105 | const char *c_str() { 106 | return string.c_str() + idx; 107 | } 108 | 109 | const char &peek() { 110 | return string[idx]; 111 | } 112 | 113 | const char &peek(int i) { 114 | return string[idx + i]; 115 | } 116 | 117 | const char &next() { 118 | return string[idx++]; 119 | } 120 | 121 | void next(int i) { 122 | idx += i; 123 | } 124 | 125 | void skipWhitespaceAndComments() { 126 | while (true) { 127 | if (isspace(peek())) { 128 | next(); 129 | continue; 130 | } 131 | if (peek() != '/') { 132 | break; 133 | } 134 | next(); 135 | if (peek() == '/') { 136 | next(); 137 | while (peek() && peek() != '\n') { 138 | next(); 139 | } 140 | } 141 | else if (peek() == '*') { 142 | next(); 143 | while (peek()) { 144 | if (peek() == '*' && peek(1) == '/') { 145 | next(2); 146 | break; 147 | } 148 | next(); 149 | } 150 | } 151 | else { 152 | break; 153 | } 154 | } 155 | } 156 | 157 | std::string peekKeyword() { 158 | std::string keyword; 159 | if (isalpha(peek())) { 160 | int size = 0; 161 | do { 162 | keyword += peek(size++); 163 | } while (isalpha(peek(size))); 164 | } 165 | return keyword; 166 | } 167 | 168 | std::string getKeyword() { 169 | std::string keyword; 170 | if (isalpha(peek())) { 171 | do { 172 | keyword += next(); 173 | } while (isalpha(peek())); 174 | } 175 | return keyword; 176 | } 177 | 178 | std::string substr(int length) { 179 | idx += length; 180 | return string.substr(idx - length, length); 181 | } 182 | }; 183 | // }}} 184 | // UTF-8 {{{ 185 | namespace utf8 { 186 | inline int glyphLen(unsigned char ch) { 187 | if (ch < 128) { 188 | return 1; 189 | } 190 | else if (ch >> 5 == 0b110) { 191 | return 2; 192 | } 193 | else if (ch >> 4 == 0b1110) { 194 | return 3; 195 | } 196 | else if (ch >> 3 == 0b11110) { 197 | return 4; 198 | } 199 | throw std::exception(); 200 | } 201 | 202 | inline int desurrogatePair(int cp1, int cp2) { 203 | return 0x10000 + (((cp1 - 0xd800) << 10) | (cp2 - 0xdc00)); 204 | } 205 | 206 | inline void surrogatePair(int cp, int *s1, int *s2) { 207 | cp -= 0x10000; 208 | *s1 = 0xd800 | ((cp >> 10) & 0x3ff); 209 | *s2 = 0xdc00 | (cp & 0x3ff); 210 | } 211 | 212 | inline int parseCodepoint(const char *c) { 213 | int cp; 214 | char buf[5] = {0}; 215 | for (int i = 0; i < 4; i++) { 216 | switch (*c) { 217 | case '0' ... '9': 218 | case 'a' ... 'f': 219 | case 'A' ... 'F': 220 | buf[i] = *c; 221 | break; 222 | default: 223 | throw std::exception(); 224 | } 225 | c++; 226 | } 227 | sscanf(buf, "%x", &cp); 228 | return cp; 229 | } 230 | 231 | inline int codepointToBytes(int cp, char *buf) { 232 | switch (cp) { 233 | case 0x0000 ... 0x007F: 234 | buf[0] = cp; 235 | return 1; 236 | case 0x0080 ... 0x07FF: 237 | buf[0] = 0b11000000 | (0b00011111 & (cp >> 6)); 238 | buf[1] = 0b10000000 | (0b00111111 & (cp >> 0)); 239 | return 2; 240 | case 0x0800 ... 0xFFFF: 241 | buf[0] = 0b11100000 | (0b00001111 & (cp >> 12)); 242 | buf[1] = 0b10000000 | (0b00111111 & (cp >> 6)); 243 | buf[2] = 0b10000000 | (0b00111111 & (cp >> 0)); 244 | return 3; 245 | case 0x10000 ... 0x10FFFF: 246 | buf[0] = 0b11110000 | (0b00000111 & (cp >> 18)); 247 | buf[1] = 0b10000000 | (0b00111111 & (cp >> 12)); 248 | buf[2] = 0b10000000 | (0b00111111 & (cp >> 6)); 249 | buf[3] = 0b10000000 | (0b00111111 & (cp >> 0)); 250 | return 4; 251 | default: 252 | throw std::exception(); 253 | } 254 | } 255 | 256 | inline int bytesToCodepoint(const char *buf, int *cp) { 257 | int length = glyphLen(*buf); 258 | switch (length) { 259 | case 1: 260 | *cp = (((*(buf + 0)) & 0b00111111) << 0); 261 | break; 262 | case 2: 263 | *cp = (((*(buf + 0)) & 0b00011111) << 6) + 264 | (((*(buf + 1)) & 0b00111111) << 0); 265 | break; 266 | case 3: 267 | *cp = (((*(buf + 0)) & 0b00001111) << 12) + 268 | (((*(buf + 1)) & 0b00111111) << 6) + 269 | (((*(buf + 2)) & 0b00111111) << 0); 270 | break; 271 | case 4: 272 | *cp = (((*(buf + 0)) & 0b00000111) << 18) + 273 | (((*(buf + 1)) & 0b00111111) << 12) + 274 | (((*(buf + 2)) & 0b00111111) << 6) + 275 | (((*(buf + 3)) & 0b00111111) << 0); 276 | break; 277 | default: 278 | throw std::exception(); 279 | } 280 | return length; 281 | } 282 | 283 | inline std::string escapeCodepoint(const char *str, int *offset) { 284 | int cp; 285 | *offset += utf8::bytesToCodepoint(str + *offset, &cp) - 1; 286 | char buf[16]; 287 | if (cp < 0x10000) { 288 | snprintf(buf, sizeof(buf), "\\u%.4x", cp); 289 | return std::string(buf, 6); 290 | } 291 | else { 292 | int s1, s2; 293 | utf8::surrogatePair(cp, &s1, &s2); 294 | snprintf(buf, sizeof(buf), "\\u%.4x\\u%.4x", s1, s2); 295 | return std::string(buf, 12); 296 | } 297 | } 298 | 299 | inline std::string unescapeCodepoint(Cursor &cursor) { 300 | cursor.next(); 301 | int cp = utf8::parseCodepoint(cursor.c_str()); 302 | cursor.next(3); 303 | if (0xd800 <= cp && cp <= 0xdbff) { 304 | if (cursor.peek(1) != '\\' || cursor.peek(2) != 'u') { 305 | throw exception("expected utf-8 surrogate pair", cursor.idx); 306 | } 307 | int cp2 = utf8::parseCodepoint(cursor.c_str() + 3); 308 | if (!(0xdc00 <= cp2 && cp2 <= 0xdfff)) { 309 | throw exception("invalid utf-8 surrogate pair", cursor.idx); 310 | } 311 | cp = utf8::desurrogatePair(cp, cp2); 312 | cursor.next(6); 313 | } 314 | if (cp > 0x10FFFF) { 315 | throw exception("invalid utf-8 codepoint", cursor.idx); 316 | } 317 | char buf[4]; 318 | int size = utf8::codepointToBytes(cp, buf); 319 | return std::string(buf, size); 320 | } 321 | }; 322 | // }}} 323 | // JSON BUILDERS {{{ 324 | class JsonArrayBuilder 325 | { 326 | std::string m_buffer; 327 | 328 | public: 329 | void add(std::string item) { 330 | m_buffer += ','; 331 | m_buffer += item; 332 | } 333 | 334 | const std::string &build() { 335 | if (!m_buffer.size()) { 336 | m_buffer = "[]"; 337 | return m_buffer; 338 | } 339 | m_buffer[0] = '['; 340 | m_buffer += ']'; 341 | return m_buffer; 342 | } 343 | }; 344 | 345 | class JsonObjectBuilder 346 | { 347 | std::string m_buffer; 348 | 349 | public: 350 | void quotedKey(const std::string &key) { 351 | m_buffer += ','; 352 | m_buffer += '"'; 353 | m_buffer += key; 354 | m_buffer += '"'; 355 | } 356 | 357 | void key(const std::string &key) { 358 | m_buffer += ','; 359 | m_buffer += key; 360 | } 361 | 362 | void value(const std::string &value) { 363 | m_buffer += ':'; 364 | m_buffer += value; 365 | } 366 | 367 | const std::string &build() { 368 | if (!m_buffer.size()) { 369 | m_buffer = "{}"; 370 | return m_buffer; 371 | } 372 | m_buffer[0] = '{'; 373 | m_buffer += '}'; 374 | return m_buffer; 375 | } 376 | }; 377 | // }}} 378 | // JSON PARSERS {{{ 379 | class JsonArrayParser 380 | { 381 | Cursor &m_cursor; 382 | bool m_first = true; 383 | 384 | public: 385 | JsonArrayParser(Cursor &cursor) : m_cursor(cursor) {} 386 | 387 | void start() { 388 | m_cursor.skipWhitespaceAndComments(); 389 | m_cursor.expect('['); 390 | } 391 | 392 | void finish() { 393 | m_cursor.skipWhitespaceAndComments(); 394 | m_cursor.expect(']'); 395 | } 396 | 397 | bool optionalNext() { 398 | m_cursor.skipWhitespaceAndComments(); 399 | if (m_cursor.peek() == ']') { 400 | return false; 401 | } 402 | m_cursor.skipWhitespaceAndComments(); 403 | if (!m_first) { 404 | m_cursor.expect(','); 405 | } 406 | m_cursor.skipWhitespaceAndComments(); 407 | m_first = false; 408 | return true; 409 | } 410 | 411 | void next() { 412 | if (!optionalNext()) { 413 | throw exception("not enough elements in array", m_cursor.idx); 414 | } 415 | } 416 | }; 417 | 418 | class JsonObjectParser 419 | { 420 | Cursor &m_cursor; 421 | bool m_first = true; 422 | 423 | public: 424 | JsonObjectParser(Cursor &cursor) : m_cursor(cursor) {} 425 | 426 | void start() { 427 | m_cursor.skipWhitespaceAndComments(); 428 | m_cursor.expect('{'); 429 | } 430 | 431 | void finish() { 432 | m_cursor.skipWhitespaceAndComments(); 433 | m_cursor.expect('}'); 434 | } 435 | 436 | bool optionalNext() { 437 | m_cursor.skipWhitespaceAndComments(); 438 | if (m_cursor.peek() == '}') { 439 | return false; 440 | } 441 | m_cursor.skipWhitespaceAndComments(); 442 | if (!m_first) { 443 | m_cursor.expect(','); 444 | } 445 | m_cursor.skipWhitespaceAndComments(); 446 | m_first = false; 447 | return true; 448 | } 449 | 450 | void next() { 451 | if (!optionalNext()) { 452 | throw exception("not enough elements in object", m_cursor.idx); 453 | } 454 | } 455 | 456 | void value() { 457 | m_cursor.skipWhitespaceAndComments(); 458 | m_cursor.expect(':'); 459 | m_cursor.skipWhitespaceAndComments(); 460 | } 461 | }; 462 | // }}} 463 | // SERIALIZE {{{ 464 | template 465 | std::string serialize(const T &item); 466 | 467 | template 468 | std::string serializeUniquePointer(const std::unique_ptr &item) { 469 | if (item) { 470 | return serialize(*item); 471 | } 472 | else { 473 | return "null"; 474 | } 475 | } 476 | 477 | template 478 | std::string serializeOptional(const std::optional &item) { 479 | if (item) { 480 | return serialize(*item); 481 | } 482 | else { 483 | return "null"; 484 | } 485 | } 486 | 487 | template 488 | std::string serializePair(const std::pair &item) { 489 | JsonArrayBuilder array; 490 | array.add(serialize(item.first)); 491 | array.add(serialize(item.second)); 492 | return array.build(); 493 | } 494 | 495 | template 496 | std::string serializeTuple(const std::tuple &item) { 497 | JsonArrayBuilder array; 498 | constexpr auto size = std::tuple_size>::value; 499 | for_sequence(std::make_index_sequence{}, [&](auto i) { 500 | array.add(serialize(std::get(item))); 501 | }); 502 | return array.build(); 503 | } 504 | 505 | inline std::string serializeBoolVector(const std::vector &item) { 506 | JsonArrayBuilder array; 507 | for (const auto &elem : item) { 508 | array.add(elem ? "true" : "false"); 509 | } 510 | return array.build(); 511 | } 512 | 513 | template 514 | std::string serializeVector(const T &item) { 515 | JsonArrayBuilder array; 516 | for (const auto &elem : item) { 517 | array.add(serialize(elem)); 518 | } 519 | return array.build(); 520 | } 521 | 522 | template 523 | std::string serializeQueue(const std::queue &item) { 524 | JsonArrayBuilder array; 525 | std::queue copy = item; 526 | while (copy.size()) { 527 | array.add(serialize(copy.front())); 528 | copy.pop(); 529 | } 530 | return array.build(); 531 | } 532 | 533 | template 534 | std::string serializeSet(const T &item) { 535 | std::deque x; 536 | JsonArrayBuilder array; 537 | for (const auto &elem : item) { 538 | array.add(serialize(elem)); 539 | } 540 | return array.build(); 541 | } 542 | 543 | template 544 | std::string serializeMap(const T &item) { 545 | using KeyType = typename std::decayfirst)>::type; 546 | JsonObjectBuilder jsonObject; 547 | for (const auto &it : item) { 548 | constexpr bool isString = std::is_same().value || 549 | std::is_same().value || 550 | std::is_same().value; 551 | if constexpr (isString) { 552 | jsonObject.key(serialize(it.first)); 553 | } 554 | else { 555 | jsonObject.key(serialize(serialize(it.first))); 556 | } 557 | jsonObject.value(serialize(it.second)); 558 | } 559 | return jsonObject.build(); 560 | } 561 | 562 | template 563 | std::string serializeString(const T &item) { 564 | const char *str; 565 | size_t len; 566 | if constexpr (std::is_same().value) { 567 | str = item.c_str(); 568 | len = item.size(); 569 | } 570 | else { 571 | str = item; 572 | len = strlen(item); 573 | } 574 | std::string string; 575 | string += '"'; 576 | for (int i = 0; i < len; i++) { 577 | switch ((unsigned char)str[i]) { 578 | case 128 ... 255: { 579 | #ifndef JSON_ENCODE_ASCII 580 | string += str[i]; 581 | #else 582 | try { 583 | string += utf8::escapeCodepoint(str, &i); 584 | } 585 | catch (const std::exception&) { 586 | throw exception("invalid utf-8 codepoint", 0); 587 | } 588 | #endif 589 | break; 590 | } 591 | case 0 ... 7: 592 | case 11 ... 12: 593 | case 14 ... 31: 594 | case 127: 595 | char buf[7]; 596 | snprintf(buf, sizeof(buf), "\\u%.4x", (unsigned char)str[i]); 597 | string += std::string(buf, 6); 598 | break; 599 | case '\n': 600 | string += "\\n"; 601 | break; 602 | case '\b': 603 | string += "\\b"; 604 | break; 605 | case '\r': 606 | string += "\\r"; 607 | break; 608 | case '\t': 609 | string += "\\t"; 610 | break; 611 | case '"': 612 | string += "\\\""; 613 | break; 614 | case '\\': 615 | string += "\\\\"; 616 | break; 617 | default: 618 | string += *(str + i); 619 | break; 620 | } 621 | } 622 | return string + '"'; 623 | } 624 | 625 | inline std::string serializeBool(const bool &item) { 626 | return item ? "true" : "false"; 627 | } 628 | 629 | template 630 | std::string serializeArray(const T(&item)[N]) { 631 | JsonArrayBuilder array; 632 | for (int i = 0; i < N; i++) { 633 | array.add(serialize(item[i])); 634 | } 635 | return array.build(); 636 | } 637 | 638 | template 639 | std::string serializePointer(const T &item) { 640 | if (item == nullptr) { 641 | return "null"; 642 | } 643 | else { 644 | return serialize(*item); 645 | } 646 | } 647 | 648 | template 649 | std::string serializeEnum(const T &item) { 650 | return std::to_string(item); 651 | } 652 | 653 | template 654 | std::string serializeNumber(const T &item) { 655 | return std::to_string(item); 656 | } 657 | 658 | inline std::string serializeChar(const char &item) { 659 | unsigned char value = item; 660 | return std::to_string(value); 661 | } 662 | 663 | template 664 | std::string serializeClass(const T& item) { 665 | JsonObjectBuilder jsonObject; 666 | constexpr auto props = properties(); 667 | constexpr auto size = std::tuple_size::value; 668 | for_sequence(std::make_index_sequence{}, [&](auto i) { 669 | constexpr auto property = std::get(properties()); 670 | jsonObject.quotedKey(property.key); 671 | jsonObject.value(serialize(item.*(property.value))); 672 | }); 673 | return jsonObject.build(); 674 | } 675 | 676 | template 677 | std::string serialize(const T &item) { 678 | if constexpr (is_specialization().value) { 679 | return serializeUniquePointer(item); 680 | } 681 | else if constexpr (is_specialization().value) { 682 | return serializeOptional(item); 683 | } 684 | else if constexpr (is_specialization().value) { 685 | return serializePair(item); 686 | } 687 | else if constexpr (is_specialization().value) { 688 | return serializeTuple(item); 689 | } 690 | else if constexpr (std::is_same>().value) { 691 | return serializeBoolVector(item); 692 | } 693 | else if constexpr (is_specialization().value) { 694 | return serializeVector(item); 695 | } 696 | else if constexpr (is_specialization().value) { 697 | return serializeVector(item); 698 | } 699 | else if constexpr (is_specialization().value) { 700 | return serializeVector(item); 701 | } 702 | else if constexpr (is_specialization().value) { 703 | return serializeQueue(item); 704 | } 705 | else if constexpr (is_specialization().value) { 706 | return serializeSet(item); 707 | } 708 | else if constexpr (is_specialization().value) { 709 | return serializeSet(item); 710 | } 711 | else if constexpr (is_specialization().value) { 712 | return serializeMap(item); 713 | } 714 | else if constexpr (is_specialization().value) { 715 | return serializeMap(item); 716 | } 717 | else if constexpr (std::is_same().value) { 718 | return serializeString(item); 719 | } 720 | else if constexpr (std::is_same().value) { 721 | return serializeString(item); 722 | } 723 | else if constexpr (std::is_same().value) { 724 | return serializeString(item); 725 | } 726 | else if constexpr (std::is_array().value) { 727 | return serializeArray(item); 728 | } 729 | else if constexpr (std::is_same().value) { 730 | return serializeBool(item); 731 | } 732 | else if constexpr (std::is_pointer().value) { 733 | return serializePointer(item); 734 | } 735 | else if constexpr (std::is_enum().value) { 736 | return serializeEnum(item); 737 | } 738 | else if constexpr (std::is_same().value) { 739 | return serializeChar(item); 740 | } 741 | else if constexpr (std::is_arithmetic().value) { 742 | return serializeNumber(item); 743 | } 744 | else if constexpr (std::is_class().value) { 745 | return serializeClass(item); 746 | } 747 | } 748 | // }}} 749 | // DESERIALIZE {{{ 750 | template 751 | void deserialize(T &item, Cursor &cursor); 752 | 753 | template 754 | void deserialize(T &item, const std::string &json); 755 | 756 | template 757 | T deserialize(const std::string &json); 758 | 759 | template 760 | void deserializeUniquePointer(std::unique_ptr &item, Cursor &cursor) { 761 | item = std::make_unique(); 762 | deserialize(*item, cursor); 763 | } 764 | 765 | template 766 | void deserializeOptional(std::optional &item, Cursor &cursor) { 767 | std::string keyword = cursor.peekKeyword(); 768 | if (keyword == "null") { 769 | cursor.next(keyword.size()); 770 | item.reset(); 771 | } 772 | else { 773 | T tmp; 774 | deserialize(tmp, cursor); 775 | item = tmp; 776 | } 777 | } 778 | 779 | template 780 | void deserializePair(std::pair &item, Cursor &cursor) { 781 | item = std::pair(); 782 | JsonArrayParser arrayParser(cursor); 783 | arrayParser.start(); 784 | arrayParser.next(); 785 | deserialize(item.first, cursor); 786 | arrayParser.next(); 787 | deserialize(item.second, cursor); 788 | arrayParser.finish(); 789 | } 790 | 791 | template 792 | void deserializeTuple(std::tuple &item, Cursor &cursor) { 793 | item = std::tuple(); 794 | constexpr auto size = std::tuple_size>::value; 795 | JsonArrayParser arrayParser(cursor); 796 | arrayParser.start(); 797 | for_sequence(std::make_index_sequence{}, [&](auto i) { 798 | arrayParser.next(); 799 | deserialize(std::get(item), cursor); 800 | }); 801 | arrayParser.finish(); 802 | } 803 | 804 | inline void deserializeBoolVector(std::vector &item, Cursor &cursor) { 805 | item = std::vector(); 806 | JsonArrayParser arrayParser(cursor); 807 | arrayParser.start(); 808 | while (arrayParser.optionalNext()) { 809 | bool result; 810 | deserialize(result, cursor); 811 | item.push_back(result); 812 | } 813 | arrayParser.finish(); 814 | } 815 | 816 | template 817 | void deserializeVector(T &item, Cursor &cursor) { 818 | using Type = typename std::decay::type; 819 | item = T(); 820 | JsonArrayParser arrayParser(cursor); 821 | arrayParser.start(); 822 | while (arrayParser.optionalNext()) { 823 | item.push_back(Type()); 824 | deserialize(item.back(), cursor); 825 | } 826 | arrayParser.finish(); 827 | } 828 | 829 | template 830 | void deserializeQueue(std::queue &item, Cursor &cursor) { 831 | item = std::queue(); 832 | JsonArrayParser arrayParser(cursor); 833 | arrayParser.start(); 834 | while (arrayParser.optionalNext()) { 835 | T elem; 836 | deserialize(elem, cursor); 837 | item.push(elem); 838 | } 839 | arrayParser.finish(); 840 | } 841 | 842 | template 843 | void deserializeSet(T &item, Cursor &cursor) { 844 | using Type = typename std::decay::type; 845 | item = T(); 846 | JsonArrayParser arrayParser(cursor); 847 | arrayParser.start(); 848 | while (arrayParser.optionalNext()) { 849 | Type elem; 850 | deserialize(elem, cursor); 851 | item.insert(elem); 852 | } 853 | arrayParser.finish(); 854 | } 855 | 856 | template 857 | void deserializeMap(T &item, Cursor &cursor) { 858 | using KeyType = typename std::decayfirst)>::type; 859 | JsonObjectParser objectParser(cursor); 860 | objectParser.start(); 861 | while (objectParser.optionalNext()) { 862 | KeyType key; 863 | constexpr bool isString = std::is_same().value || 864 | std::is_same().value || 865 | std::is_same().value; 866 | if constexpr (isString) { 867 | deserialize(key, cursor); 868 | } 869 | else { 870 | std::string stringKey; 871 | deserialize(stringKey, cursor); 872 | deserialize(key, stringKey); 873 | } 874 | objectParser.value(); 875 | deserialize(item[key], cursor); 876 | } 877 | objectParser.finish(); 878 | } 879 | 880 | template 881 | void deserializeString(T &item, Cursor &cursor) { 882 | if constexpr (std::is_pointer().value) { 883 | std::string keyword = cursor.getKeyword(); 884 | if (keyword == "null") { 885 | item = nullptr; 886 | return; 887 | } 888 | else if (keyword.size()) { 889 | throw exception("invalid keyword '" + keyword + "'", cursor.idx); 890 | } 891 | } 892 | cursor.expect('"'); 893 | int length = 0; 894 | std::string string; 895 | while (cursor.peek() != '"') { 896 | if (cursor.peek() == '\\') { 897 | cursor.next(); 898 | switch (cursor.peek()) { 899 | case '\\': 900 | string += '\\'; 901 | break; 902 | case '"': 903 | string += '"'; 904 | break; 905 | case 't': 906 | string += '\t'; 907 | break; 908 | case 'n': 909 | string += '\n'; 910 | break; 911 | case 'r': 912 | string += '\r'; 913 | break; 914 | case 'b': 915 | string += '\b'; 916 | break; 917 | case 'u': { 918 | try { 919 | string += utf8::unescapeCodepoint(cursor); 920 | } 921 | catch (const std::exception&) { 922 | throw exception("invalid utf-8 codepoint", cursor.idx); 923 | } 924 | break; 925 | } 926 | default: 927 | throw exception("invalid escape character", cursor.idx); 928 | } 929 | } 930 | else { 931 | string += cursor.peek(); 932 | } 933 | cursor.next(); 934 | } 935 | cursor.expect('"'); 936 | if constexpr (std::is_pointer().value) { 937 | string += '\0'; 938 | item = new char[string.size()]; 939 | memcpy((void *)item, string.c_str(), string.size()); 940 | } 941 | else { 942 | item = string; 943 | } 944 | } 945 | 946 | inline void deserializeBool(bool &item, Cursor &cursor) { 947 | std::string keyword = cursor.getKeyword(); 948 | if (keyword == "true") { 949 | item = true; 950 | } 951 | else if (keyword == "false") { 952 | item = false; 953 | } 954 | else { 955 | throw exception("invalid keyword '" + keyword + "'", cursor.idx); 956 | } 957 | } 958 | 959 | template 960 | void deserializeArray(T(&item)[N], Cursor &cursor) { 961 | JsonArrayParser arrayParser(cursor); 962 | arrayParser.start(); 963 | for (int i = 0; i < N && arrayParser.optionalNext(); i++) { 964 | deserialize(item[i], cursor); 965 | } 966 | arrayParser.finish(); 967 | } 968 | 969 | template 970 | void deserializePointer(T &item, Cursor &cursor) { 971 | using Type = typename std::remove_pointer::type; 972 | if (cursor.peekKeyword() == "null") { 973 | cursor.next(4); 974 | item = nullptr; 975 | return; 976 | } 977 | item = new Type(); 978 | deserialize(*item, cursor); 979 | } 980 | 981 | template 982 | void deserializeEnum(T &item, Cursor &cursor) { 983 | int value; 984 | deserialize(value, cursor); 985 | item = (T)value; 986 | } 987 | 988 | template 989 | void deserializeNumber(T &item, Cursor &cursor) { 990 | int length = 0; 991 | if (cursor.peek(length) == '-') { 992 | length++; 993 | } 994 | while (isdigit(cursor.peek(length))) { 995 | length++; 996 | } 997 | if constexpr (std::is_floating_point().value) { 998 | if (cursor.peek(length) == '.') { 999 | length++; 1000 | while (isdigit(cursor.peek(length))) { 1001 | length++; 1002 | } 1003 | if (tolower(cursor.peek(length)) == 'e') { 1004 | length++; 1005 | if (cursor.peek(length) == '-') { 1006 | length++; 1007 | } 1008 | while (isdigit(cursor.peek(length))) { 1009 | length++; 1010 | } 1011 | } 1012 | } 1013 | } 1014 | std::string number = cursor.substr(length); 1015 | try { 1016 | if constexpr (std::is_floating_point().value) { 1017 | item = (T)std::stold(number); 1018 | } 1019 | else if constexpr (std::is_signed().value) { 1020 | item = (T)std::stoll(number); 1021 | } 1022 | else { 1023 | item = (T)std::stoull(number); 1024 | } 1025 | } 1026 | catch (const std::invalid_argument &) { 1027 | throw exception("invalid number", cursor.idx); 1028 | } 1029 | catch (const std::out_of_range &) { 1030 | throw exception("invalid number", cursor.idx); 1031 | } 1032 | } 1033 | 1034 | inline void deserializeChar(char &item, Cursor &cursor) { 1035 | unsigned char value; 1036 | deserializeNumber(value, cursor); 1037 | item = value; 1038 | } 1039 | 1040 | template 1041 | void deserializeClass(T &item, Cursor &cursor) { 1042 | constexpr auto props = properties(); 1043 | constexpr auto size = std::tuple_size::value; 1044 | JsonObjectParser objectParser(cursor); 1045 | objectParser.start(); 1046 | while (objectParser.optionalNext()) { 1047 | std::string key; 1048 | deserialize(key, cursor); 1049 | objectParser.value(); 1050 | for_sequence(std::make_index_sequence{}, [&](auto i) { 1051 | constexpr auto property = std::get(properties()); 1052 | if (strcmp(property.key, key.c_str()) == 0) { 1053 | deserialize(item.*(property.value), cursor); 1054 | } 1055 | }); 1056 | } 1057 | objectParser.finish(); 1058 | } 1059 | 1060 | template 1061 | void deserialize(T &item, Cursor &cursor) { 1062 | if constexpr (is_specialization().value) { 1063 | deserializeUniquePointer(item, cursor); 1064 | } 1065 | else if constexpr (is_specialization().value) { 1066 | deserializeOptional(item, cursor); 1067 | } 1068 | else if constexpr (is_specialization().value) { 1069 | deserializePair(item, cursor); 1070 | } 1071 | else if constexpr (is_specialization().value) { 1072 | deserializeTuple(item, cursor); 1073 | } 1074 | else if constexpr (std::is_same>().value) { 1075 | deserializeBoolVector(item, cursor); 1076 | } 1077 | else if constexpr (is_specialization().value) { 1078 | deserializeVector(item, cursor); 1079 | } 1080 | else if constexpr (is_specialization().value) { 1081 | deserializeVector(item, cursor); 1082 | } 1083 | else if constexpr (is_specialization().value) { 1084 | deserializeVector(item, cursor); 1085 | } 1086 | else if constexpr (is_specialization().value) { 1087 | deserializeQueue(item, cursor); 1088 | } 1089 | else if constexpr (is_specialization().value) { 1090 | deserializeSet(item, cursor); 1091 | } 1092 | else if constexpr (is_specialization().value) { 1093 | deserializeSet(item, cursor); 1094 | } 1095 | else if constexpr (is_specialization().value) { 1096 | deserializeSet(item, cursor); 1097 | } 1098 | else if constexpr (is_specialization().value) { 1099 | deserializeMap(item, cursor); 1100 | } 1101 | else if constexpr (is_specialization().value) { 1102 | deserializeMap(item, cursor); 1103 | } 1104 | else if constexpr (std::is_same().value) { 1105 | deserializeString(item, cursor); 1106 | } 1107 | else if constexpr (std::is_same().value) { 1108 | deserializeString(item, cursor); 1109 | } 1110 | else if constexpr (std::is_same().value) { 1111 | deserializeString(item, cursor); 1112 | } 1113 | else if constexpr (std::is_same().value) { 1114 | deserializeBool(item, cursor); 1115 | } 1116 | else if constexpr (std::is_array().value) { 1117 | deserializeArray(item, cursor); 1118 | } 1119 | else if constexpr (std::is_pointer().value) { 1120 | deserializePointer(item, cursor); 1121 | } 1122 | else if constexpr (std::is_enum().value) { 1123 | deserializeEnum(item, cursor); 1124 | } 1125 | else if constexpr (std::is_same().value) { 1126 | deserializeChar(item, cursor); 1127 | } 1128 | else if constexpr (std::is_arithmetic().value) { 1129 | deserializeNumber(item, cursor); 1130 | } 1131 | else if constexpr (std::is_class().value) { 1132 | deserializeClass(item, cursor); 1133 | } 1134 | } 1135 | // }}} 1136 | // DESERIALIZATION HELPERS IMPLEMENTATION {{{ 1137 | template 1138 | void deserialize(T &item, const std::string &json) { 1139 | Cursor cursor(json); 1140 | deserialize(item, cursor); 1141 | cursor.skipWhitespaceAndComments(); 1142 | if (cursor.peek()) { 1143 | throw exception("expected EOF", cursor.idx); 1144 | } 1145 | } 1146 | 1147 | template 1148 | T deserialize(const std::string &json) { 1149 | T item; 1150 | deserialize(item, json); 1151 | return item; 1152 | } 1153 | // }}} 1154 | // JSON PRETTIFIER {{{ 1155 | class Prettifier 1156 | { 1157 | int m_indent; 1158 | 1159 | public: 1160 | Prettifier() : m_indent(4) {} 1161 | Prettifier(int indent) : m_indent(indent) {} 1162 | 1163 | void setIndent(int indent) { 1164 | m_indent = indent; 1165 | } 1166 | 1167 | std::string prettify(std::string json) { 1168 | std::string prettyJson; 1169 | int depth = 0; 1170 | for (int i = 0; i < json.size(); i++) { 1171 | switch (json[i]) { 1172 | case ' ': 1173 | case '\t': 1174 | case '\n': 1175 | break; 1176 | case '"': 1177 | prettyJson += json[i]; 1178 | while (json[++i] != '"') { 1179 | prettyJson += json[i]; 1180 | if (json[i] == '\\') { 1181 | prettyJson += json[++i]; 1182 | } 1183 | } 1184 | prettyJson += json[i]; 1185 | break; 1186 | case '[': 1187 | case '{': 1188 | prettyJson += json[i]; 1189 | while (isspace(json[i + 1])) { 1190 | i++; 1191 | } 1192 | if (json[i + 1] == '}' || json[i + 1] == ']') { 1193 | prettyJson += json[++i]; 1194 | } 1195 | else { 1196 | prettyJson += '\n'; 1197 | depth++; 1198 | for (int j = 0; j < depth * m_indent; j++) { 1199 | prettyJson += ' '; 1200 | } 1201 | } 1202 | break; 1203 | case ']': 1204 | case '}': 1205 | depth--; 1206 | prettyJson += '\n'; 1207 | for (int j = 0; j < depth * m_indent; j++) { 1208 | prettyJson += ' '; 1209 | } 1210 | prettyJson += json[i]; 1211 | break; 1212 | case ':': 1213 | prettyJson += json[i]; 1214 | prettyJson += ' '; 1215 | break; 1216 | case ',': 1217 | prettyJson += json[i]; 1218 | prettyJson += '\n'; 1219 | for (int j = 0; j < depth * m_indent; j++) { 1220 | prettyJson += ' '; 1221 | } 1222 | break; 1223 | default: 1224 | prettyJson += json[i]; 1225 | break; 1226 | } 1227 | } 1228 | return prettyJson; 1229 | } 1230 | }; 1231 | // }}} 1232 | }; 1233 | -------------------------------------------------------------------------------- /tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define JSON_ENCODE_ASCII 7 | 8 | #include "json.hpp" 9 | 10 | json::Prettifier prettifier(4); 11 | 12 | struct EmptyStruct { 13 | bool operator==(const EmptyStruct &rhs) { 14 | return true; 15 | } 16 | }; 17 | REFLECT(EmptyStruct); 18 | 19 | struct RealisticStruct { 20 | std::string string; 21 | int integer; 22 | float float1; 23 | float float2; 24 | std::vector integers; 25 | }; 26 | REFLECT(RealisticStruct, string, integer, float1, float2, integers); 27 | 28 | struct Keyword { 29 | std::string value; 30 | }; 31 | 32 | template<> 33 | std::string json::serialize(const Keyword& s) { 34 | return s.value; 35 | } 36 | 37 | template<> 38 | void json::deserialize(Keyword& s, Cursor &cursor) { 39 | s.value = cursor.getKeyword(); 40 | if (s.value.size() == 0) { 41 | throw std::exception(); 42 | } 43 | } 44 | 45 | struct MassiveStruct { 46 | std::string string1; 47 | std::string string2; 48 | std::string string3; 49 | int int1; 50 | int int2; 51 | int int3; 52 | float float1; 53 | float float2; 54 | float float3; 55 | double double1; 56 | double double2; 57 | double double3; 58 | EmptyStruct emptyStruct; 59 | std::vector structs1; 60 | RealisticStruct structs2[3]; 61 | std::map structs3; 62 | std::optional optional1; 63 | std::optional optional2; 64 | std::optional optional3; 65 | std::optional optional4; 66 | std::optional optional5; 67 | Keyword keyword1; 68 | Keyword keyword2; 69 | std::tuple tuple1; 70 | std::tuple<> tuple2; 71 | std::tuple tuple3; 72 | std::tuple tuple4; 73 | std::pair pair1; 74 | std::pair> pair2; 75 | std::pair> pair3; 76 | }; 77 | REFLECT( 78 | MassiveStruct, string1, string2, string3, int1, int2, int3, float1, float2, 79 | float3, double1, double2, double3, emptyStruct, structs1, structs2, 80 | structs3, optional1, optional2, optional3, optional4, optional5, keyword1, 81 | keyword2, tuple1, tuple2, tuple3, tuple4, pair1, pair2, pair3 82 | ) 83 | 84 | template 85 | struct Node { 86 | T value; 87 | Node *next; 88 | }; 89 | REFLECT(Node, value, next); 90 | 91 | template 92 | struct TreeNode { 93 | T value; 94 | std::vector*> children; 95 | 96 | TreeNode() {} 97 | TreeNode(T value) { 98 | this->value = value; 99 | } 100 | 101 | void addChild(T value) { 102 | children.push_back(new TreeNode(value)); 103 | } 104 | }; 105 | REFLECT(TreeNode, value, children); 106 | 107 | template 108 | bool equals(const T& lhs, const T& rhs) { 109 | return lhs == rhs; 110 | } 111 | 112 | template <> 113 | bool equals(const EmptyStruct &lhs, const EmptyStruct &rhs) { 114 | return true; 115 | } 116 | 117 | template <> 118 | bool equals(const Keyword &lhs, const Keyword &rhs) { 119 | return lhs.value == rhs.value; 120 | } 121 | 122 | template <> 123 | bool equals(const RealisticStruct &lhs, const RealisticStruct &rhs) { 124 | return equals(lhs.string, rhs.string) 125 | && equals(lhs.integer, rhs.integer) 126 | && equals(lhs.float1, rhs.float1) 127 | && equals(lhs.float2, rhs.float2) 128 | && equals(lhs.integers, rhs.integers); 129 | } 130 | 131 | template <> 132 | bool equals( 133 | const MassiveStruct &lhs, 134 | const MassiveStruct &rhs 135 | ) { 136 | return true; 137 | } 138 | 139 | template <> 140 | bool equals( 141 | const std::vector &lhs, 142 | const std::vector &rhs 143 | ) { 144 | if (lhs.size() != rhs.size()) { 145 | return false; 146 | } 147 | for (int i = 0; i < lhs.size(); i++) { 148 | if (strcmp(lhs[i], rhs[i]) != 0) { 149 | return false; 150 | } 151 | } 152 | return true; 153 | } 154 | 155 | template 156 | bool equals(const Node &lhs, const Node &rhs) { 157 | if (!equals(lhs.value, rhs.value)) { 158 | return false; 159 | } 160 | if (lhs.next == nullptr) { 161 | if (rhs.next != lhs.next) { 162 | return false; 163 | } 164 | return true; 165 | } 166 | return equals(*lhs.next, *rhs.next); 167 | } 168 | 169 | template 170 | bool equals(const TreeNode &lhs, const TreeNode &rhs) { 171 | if (!equals(lhs.value, rhs.value)) { 172 | printf("here\n"); 173 | return false; 174 | } 175 | if (lhs.children.size() != rhs.children.size()) { 176 | printf("here2\n"); 177 | return false; 178 | } 179 | for (int i = 0; i < lhs.children.size(); i++) { 180 | if (!equals(*lhs.children[i], *rhs.children[i])) { 181 | return false; 182 | } 183 | } 184 | return true; 185 | } 186 | 187 | template 188 | void test(std::string desc, T item, std::string expected) { 189 | printf("%-20s", desc.c_str()); 190 | std::string serialized; 191 | std::string reserialized; 192 | try { 193 | serialized = json::serialize(item); 194 | } 195 | catch (const json::exception& ex) { 196 | printf("SERIALIZE EXCEPTION\n"); 197 | printf(" %-15s %s\n", "desc", ex.description.c_str()); 198 | return; 199 | } 200 | if (serialized != expected) { 201 | printf("SERIALIZE FAIL\n"); 202 | printf(" %-15s %s\n", "expected", expected.c_str()); 203 | printf(" %-15s %s\n", "serialized", serialized.c_str()); 204 | return; 205 | } 206 | T deserialized; 207 | try { 208 | deserialized = json::deserialize(serialized); 209 | } 210 | catch (const json::exception& ex) { 211 | printf("DESERIALIZE EXCEPTION\n"); 212 | printf(" %-15s %s\n", "expected", expected.c_str()); 213 | printf(" %-15s %s\n", "serialized", serialized.c_str()); 214 | printf(" %-15s %s\n", "desc", ex.description.c_str()); 215 | return; 216 | } 217 | if constexpr (!std::is_pointer()) { 218 | if (!equals(deserialized, item)) { 219 | printf("DESERIALIZE FAIL\n"); 220 | printf(" %-15s %s\n", "json", expected.c_str()); 221 | return; 222 | } 223 | } 224 | 225 | try { 226 | reserialized = json::serialize(deserialized); 227 | } 228 | catch (const json::exception& ex) { 229 | printf("RESERIALIZE EXCEPTION\n"); 230 | printf(" %-15s %s\n", "expected", expected.c_str()); 231 | printf(" %-15s %s\n", "serialized", serialized.c_str()); 232 | printf(" %-15s %s\n", "desc", ex.description.c_str()); 233 | return; 234 | } 235 | if (reserialized != expected) { 236 | printf("RESERIALIZE FAIL\n"); 237 | printf(" %-15s %s\n", "expected", expected.c_str()); 238 | printf(" %-15s %s\n", "reserialized", reserialized.c_str()); 239 | return; 240 | } 241 | 242 | std::string prettyJson = prettifier.prettify(serialized); 243 | try { 244 | deserialized = json::deserialize(prettyJson); 245 | } 246 | catch (const json::exception &ex) { 247 | printf("PRETTY DESERIALIZE EXCEPTION\n"); 248 | printf(" %-15s %s\n", "json", expected.c_str()); 249 | printf(" %-15s %s\n", "pretty", prettyJson.c_str()); 250 | printf(" %-15s %s\n", "desc", ex.description.c_str()); 251 | return; 252 | } 253 | if constexpr (!std::is_pointer()) { 254 | if (!equals(deserialized, item)) { 255 | printf("PRETTY DESERIALIZE FAIL\n"); 256 | printf(" %-15s %s\n", "json", expected.c_str()); 257 | printf(" %-15s %s\n", "pretty", prettyJson.c_str()); 258 | return; 259 | } 260 | } 261 | 262 | printf("PASS\n"); 263 | } 264 | 265 | template 266 | void test(std::string desc, T (&item)[Y], std::string expected) { 267 | printf("%-20s", desc.c_str()); 268 | std::string serialized; 269 | std::string reserialized; 270 | try { 271 | serialized = json::serialize(item); 272 | } 273 | catch (const json::exception&) { 274 | printf("SERIALIZE EXCEPTION\n"); 275 | return; 276 | } 277 | if (serialized != expected) { 278 | printf("SERIALIZE FAIL\n"); 279 | printf(" %-15s %s\n", "expected", expected.c_str()); 280 | printf(" %-15s %s\n", "serialized", serialized.c_str()); 281 | return; 282 | } 283 | T deserialized[Y]; 284 | try { 285 | json::deserialize(deserialized, serialized); 286 | } 287 | catch (const json::exception&) { 288 | printf("DESERIALIZE EXCEPTION\n"); 289 | printf(" %-15s %s\n", "expected", expected.c_str()); 290 | printf(" %-15s %s\n", "serialized", serialized.c_str()); 291 | return; 292 | } 293 | if constexpr (!std::is_pointer()) { 294 | for (int i = 0; i < Y; i++) { 295 | if (deserialized[i] != item[i]) { 296 | printf("DESERIALIZE FAIL\n"); 297 | printf(" %-15s %s\n", "expected", expected.c_str()); 298 | printf(" %-15s %s\n", "reserialized", reserialized.c_str()); 299 | return; 300 | } 301 | } 302 | } 303 | 304 | try { 305 | reserialized = json::serialize(deserialized); 306 | } 307 | catch (const json::exception&) { 308 | printf("RESERIALIZE EXCEPTION\n"); 309 | printf(" %-15s %s\n", "expected", expected.c_str()); 310 | printf(" %-15s %s\n", "serialized", serialized.c_str()); 311 | return; 312 | } 313 | 314 | if (reserialized != expected) { 315 | printf("RESERIALIZE FAIL\n"); 316 | printf(" %-15s %s\n", "expected", expected.c_str()); 317 | printf(" %-15s %s\n", "reserialized", reserialized.c_str()); 318 | return; 319 | } 320 | printf("PASS\n"); 321 | } 322 | 323 | bool operator==( 324 | const std::vector &lhs, 325 | const std::vector &rhs 326 | ) { 327 | if (lhs.size() != rhs.size()) { 328 | printf("Wow\n"); 329 | return false; 330 | } 331 | for (int i = 0; i < lhs.size(); i++) { 332 | if (strcmp(lhs[i], rhs[i]) != 0) { 333 | printf("Wow\n"); 334 | return false; 335 | } 336 | } 337 | printf("Wow\n"); 338 | return true; 339 | } 340 | 341 | void stringTest() { 342 | char buffer[4] = "foo"; 343 | 344 | test("std::string", "foo", "\"foo\""); 345 | test("empty string", "", "\"\""); 346 | std::string stringWithNull("foo\0bar", 7); 347 | test("string with null", stringWithNull, "\"foo\\u0000bar\""); 348 | test("char*", buffer, "\"foo\""); 349 | test("char[]", buffer, "[102,111,111,0]"); 350 | test("string escapes", "\"\r\n\b\t\"", "\"\\\"\\r\\n\\b\\t\\\"\""); 351 | test( 352 | "unicode", "t🖕÷🔫🚬└💣→æ💎ï🗿🤖", 353 | "\"t\\ud83d\\udd95\\u00f7\\ud83d\\udd2b\\ud83d\\udeac\\u2514\\ud83d\\ud" 354 | "c" 355 | "a3\\u2192\\u00e6\\ud83d\\udc8e\\u00ef\\ud83d\\uddff\\ud83e\\udd16\"" 356 | ); 357 | std::string longString; 358 | for (int i = 0; i < 10000; i++) { 359 | longString += std::to_string(i) + ','; 360 | } 361 | test("long string", longString, '"' + longString + '"'); 362 | } 363 | 364 | void charTest() { 365 | test("basic char", 'c', "99"); 366 | test("tab char", '\t', "9"); 367 | test("backspace char", '\b', "8"); 368 | test("null char", '\0', "0"); 369 | test("negative char", -1, "255"); 370 | } 371 | 372 | void intTest() { 373 | test("basic int", 1024, "1024"); 374 | test("negative int", -1024, "-1024"); 375 | test("unsigned int", UINT_MAX, std::to_string(UINT_MAX)); 376 | } 377 | 378 | void floatTest() { 379 | test("basic float", 2.5, "2.500000"); 380 | } 381 | 382 | void arrayTest() { 383 | int intArray[9] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; 384 | test("int array", intArray, "[1,2,3,4,5,6,7,8,9]"); 385 | char charArray[9] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; 386 | test("char array", charArray, "[1,2,3,4,5,6,7,8,9]"); 387 | std::string stringArray[3] = {"foo", "bar", "baz"}; 388 | test("string array", stringArray, "[\"foo\",\"bar\",\"baz\"]"); 389 | } 390 | 391 | void vectorTest() { 392 | test("basic vector", std::vector { 393 | {1, 2, 3, 4, 5, 6, 7, 8, 9} 394 | }, "[1,2,3,4,5,6,7,8,9]"); 395 | test("empty vector", std::vector {}, "[]"); 396 | test("2d vector", std::vector> { 397 | {1, 2, 3}, {4, 5, 6}, {7, 8, 9} 398 | }, "[[1,2,3],[4,5,6],[7,8,9]]"); 399 | test("string vector", std::vector { 400 | "foo", "bar", "baz"}, "[\"foo\",\"bar\",\"baz\"]"); 401 | test("char* vector", std::vector { 402 | "foo", "bar", "baz"}, "[\"foo\",\"bar\",\"baz\"]"); 403 | test("bool vector", std::vector { 404 | true, false, true}, "[true,false,true]"); 405 | test("vector*", new std::vector { 406 | true, false, true}, "[true,false,true]"); 407 | test("vector*", new std::vector { 408 | 1, 2, 3}, "[1,2,3]"); 409 | } 410 | 411 | void listTest() { 412 | test("basic list", std::list { 413 | {1, 2, 3, 4, 5, 6, 7, 8, 9} 414 | }, "[1,2,3,4,5,6,7,8,9]"); 415 | test("empty list", std::list {}, "[]"); 416 | test("2d list", std::list> { 417 | {1, 2, 3}, {4, 5, 6}, {7, 8, 9} 418 | }, "[[1,2,3],[4,5,6],[7,8,9]]"); 419 | test("string list", std::list { 420 | "foo", "bar", "baz"}, "[\"foo\",\"bar\",\"baz\"]"); 421 | test("bool list", std::list { 422 | true, false, true}, "[true,false,true]"); 423 | test("list*", new std::list { 424 | true, false, true}, "[true,false,true]"); 425 | test("list*", new std::list { 426 | 1, 2, 3}, "[1,2,3]"); 427 | } 428 | 429 | void setTest() { 430 | test("basic set", std::set { 431 | {1, 2, 3, 4, 5, 6, 7, 8, 9} 432 | }, "[1,2,3,4,5,6,7,8,9]"); 433 | test("empty set", std::set {}, "[]"); 434 | test("2d set", std::set> { 435 | {1, 2, 3}, {4, 5, 6}, {7, 8, 9} 436 | }, "[[1,2,3],[4,5,6],[7,8,9]]"); 437 | test("string set", std::set { 438 | "bar", "baz", "foo"}, "[\"bar\",\"baz\",\"foo\"]"); 439 | test("bool set", std::set { 440 | true, false}, "[false,true]"); 441 | test("set*", new std::set { 442 | true, false}, "[false,true]"); 443 | test("set*", new std::set { 444 | 1, 2, 3}, "[1,2,3]"); 445 | } 446 | 447 | void dequeTest() { 448 | test("basic deque", std::deque { 449 | {1, 2, 3, 4, 5, 6, 7, 8, 9} 450 | }, "[1,2,3,4,5,6,7,8,9]"); 451 | test("empty deque", std::deque {}, "[]"); 452 | test("2d deque", std::deque> { 453 | {1, 2, 3}, {4, 5, 6}, {7, 8, 9} 454 | }, "[[1,2,3],[4,5,6],[7,8,9]]"); 455 | test("string deque", std::deque { 456 | "bar", "baz", "foo"}, "[\"bar\",\"baz\",\"foo\"]"); 457 | test("bool deque", std::deque { 458 | true, false}, "[true,false]"); 459 | test("deque*", new std::deque { 460 | true, false}, "[true,false]"); 461 | test("deque*", new std::deque { 462 | 1, 2, 3}, "[1,2,3]"); 463 | } 464 | 465 | void queueTest() { 466 | std::queue intQueue; 467 | for (int i = 1; i < 10; i++) { 468 | intQueue.push(i); 469 | } 470 | test("basic queue", intQueue, "[1,2,3,4,5,6,7,8,9]"); 471 | test("empty queue", std::queue(), "[]"); 472 | std::queue> queue2d; 473 | std::queue subQueue; 474 | for (int i = 1; i < 10; i++) { 475 | subQueue.push(i); 476 | if (i % 3 == 0) { 477 | queue2d.push(subQueue); 478 | subQueue = std::queue(); 479 | } 480 | } 481 | test("2d queue", queue2d, "[[1,2,3],[4,5,6],[7,8,9]]"); 482 | std::queue stringQueue; 483 | stringQueue.push("foo"); 484 | stringQueue.push("bar"); 485 | stringQueue.push("baz"); 486 | test("string queue", stringQueue, "[\"foo\",\"bar\",\"baz\"]"); 487 | std::queue boolQueue; 488 | boolQueue.push(true); 489 | boolQueue.push(false); 490 | boolQueue.push(true); 491 | test("bool queue", boolQueue, "[true,false,true]"); 492 | std::queue *boolQueuePtr = new std::queue(); 493 | boolQueuePtr->push(true); 494 | boolQueuePtr->push(false); 495 | boolQueuePtr->push(true); 496 | test("queue*", boolQueuePtr, "[true,false,true]"); 497 | std::queue *intQueuePtr = new std::queue(); 498 | for (int i = 1; i < 10; i++) { 499 | intQueuePtr->push(i); 500 | } 501 | test("queue*", intQueuePtr, "[1,2,3,4,5,6,7,8,9]"); 502 | } 503 | 504 | void mapTest() { 505 | test( 506 | "basic map", 507 | std::map{ 508 | {"foo", "bar"}, 509 | {"bar", "baz"}, 510 | {"baz", "foo"}}, 511 | "{\"bar\":\"baz\",\"baz\":\"foo\",\"foo\":\"bar\"}" 512 | ); 513 | 514 | test( 515 | "int keys map", 516 | std::map{ 517 | {1, "bar"}, 518 | {2, "baz"}, 519 | {3, "foo"}}, 520 | "{\"1\":\"bar\",\"2\":\"baz\",\"3\":\"foo\"}" 521 | ); 522 | 523 | test( 524 | "bool map", 525 | std::map{ 526 | {"bar", true}, 527 | {"baz", false}, 528 | {"foo", true}}, 529 | "{\"bar\":true,\"baz\":false,\"foo\":true}" 530 | ); 531 | 532 | test( 533 | "float map", 534 | std::map{{1.10, 11.0}, {2.20, 22.0}, {3.30, 33.0}}, 535 | "{\"1.100000\":11.000000,\"2.200000\":22.000000,\"3.300000\":33.000000}" 536 | ); 537 | 538 | test( 539 | "extra bool map", 540 | std::map{ 541 | {false, false}, 542 | {true, true}}, 543 | "{\"false\":false,\"true\":true}" 544 | ); 545 | 546 | test( 547 | "unordered map", 548 | std::unordered_map{{"foo", "bar"}}, 549 | "{\"foo\":\"bar\"}" 550 | ); 551 | 552 | test( 553 | "2d map", 554 | std::map>{ 555 | { 556 | "foo", 557 | {{1, "bar"}, {2, "baz"}, {3, "foo"}}, 558 | }, 559 | { 560 | "bar", 561 | {{1, "bar"}, {2, "baz"}, {3, "foo"}}, 562 | }, 563 | { 564 | "baz", 565 | {{1, "bar"}, {2, "baz"}, {3, "foo"}}, 566 | }}, 567 | "{\"bar\":{\"1\":\"bar\",\"2\":\"baz\",\"3\":\"foo\"},\"baz\":{\"1\":" 568 | "\"bar\",\"2\":\"baz\",\"3\":\"foo\"},\"foo\":{\"1\":\"bar\",\"2\":" 569 | "\"baz\",\"3\":\"foo\"}}" 570 | ); 571 | } 572 | 573 | void structTest() { 574 | EmptyStruct emptyStruct; 575 | test("empty struct", emptyStruct, "{}"); 576 | 577 | RealisticStruct realisticStruct { 578 | "foo", 579 | 100000, 580 | 1.2345, 581 | 1234.5, 582 | { 1, 2, 3, 4, 5, 6, 7, 8, 9 } 583 | }; 584 | test( 585 | "realistic struct", realisticStruct, 586 | "{\"string\":\"foo\",\"integer\":100000,\"float1\":1.234500," 587 | "\"float2\":1234.500000,\"integers\":[1,2,3,4,5,6,7,8,9]}" 588 | ); 589 | 590 | Keyword keyword { "test" }; 591 | test("custom funcs", keyword, "test"); 592 | 593 | MassiveStruct massiveStruct { 594 | "foo", "bar", "baz", 1, 2, 3, 0.5, 1.5, 2.5, 10000, 20000, 30000, {}, 595 | { 596 | {"foo", 100, 1.345, -134.5, {1, 6, 7, 8, 9}}, 597 | {"foo", -200, 1.245, 124.5, {1, 2, 3, 8, 9}}, 598 | {"foo", 300, -1.235, 123.5, {1, 2, 3, 8, 9}}, 599 | {"foo", 400, 1.234, -234.5, {5, 6, 7, 8, 9}}, 600 | }, 601 | { 602 | {"foo", 1, 3.5345, 2.5, {1, 2, 3}}, 603 | {"bar", 2, 2.5345, 4.5, {4, 5, 6}}, 604 | {"baz", 3, 1.5345, 8.5, {4, 5, 6}}, 605 | }, 606 | { 607 | {"foo", {"foo", 1, 3.5345, 2.5, {1, 2, 3}}}, 608 | {"bar", {"bar", 2, 2.5345, 4.5, {4, 5, 6}}}, 609 | {"baz", {"baz", 3, 1.5345, 8.5, {4, 5, 6}}}, 610 | }, 611 | std::optional(), 612 | 100, 613 | std::optional(), 614 | std::optional(), 615 | realisticStruct, 616 | { "test" }, 617 | { "anotherTest" }, 618 | {1, -2, 3.5, "foo"}, 619 | {}, 620 | {realisticStruct, 1.6}, 621 | {"foo", "bar"}, 622 | {-100, 1.5}, 623 | {"foo bar baz", "baz bar foo"}, 624 | {"foo bar baz", {{"baz bar foo", "foo bar baz"}}}, 625 | }; 626 | test( 627 | "massive struct", massiveStruct, 628 | "{\"string1\":\"foo\",\"string2\":\"bar\",\"string3\":\"baz\",\"int1\":" 629 | "1,\"int2\":2,\"int3\":3,\"float1\":0.500000,\"float2\":1.500000," 630 | "\"float3\":2.500000,\"double1\":10000.000000,\"double2\":20000.000000," 631 | "\"double3\":30000.000000,\"emptyStruct\":{},\"structs1\":[{\"string\":" 632 | "\"foo\",\"integer\":100,\"float1\":1.345000,\"float2\":-134.500000," 633 | "\"integers\":[1,6,7,8,9]},{\"string\":\"foo\",\"integer\":-200," 634 | "\"float1\":1.245000,\"float2\":124.500000,\"integers\":[1,2,3,8,9]},{" 635 | "\"string\":\"foo\",\"integer\":300,\"float1\":-1.235000,\"float2\":" 636 | "123.500000,\"integers\":[1,2,3,8,9]},{\"string\":\"foo\",\"integer\":" 637 | "400,\"float1\":1.234000,\"float2\":-234.500000,\"integers\":[5,6,7,8," 638 | "9]}],\"structs2\":[{\"string\":\"foo\",\"integer\":1,\"float1\":3." 639 | "534500,\"float2\":2.500000,\"integers\":[1,2,3]},{\"string\":\"bar\"," 640 | "\"integer\":2,\"float1\":2.534500,\"float2\":4.500000,\"integers\":[4," 641 | "5,6]},{\"string\":\"baz\",\"integer\":3,\"float1\":1.534500," 642 | "\"float2\":8.500000,\"integers\":[4,5,6]}],\"structs3\":{\"bar\":{" 643 | "\"string\":\"bar\",\"integer\":2,\"float1\":2.534500,\"float2\":4." 644 | "500000,\"integers\":[4,5,6]},\"baz\":{\"string\":\"baz\",\"integer\":" 645 | "3,\"float1\":1.534500,\"float2\":8.500000,\"integers\":[4,5,6]}," 646 | "\"foo\":{\"string\":\"foo\",\"integer\":1,\"float1\":3.534500," 647 | "\"float2\":2.500000,\"integers\":[1,2,3]}},\"optional1\":null," 648 | "\"optional2\":100,\"optional3\":null,\"optional4\":null,\"optional5\":" 649 | "{\"string\":\"foo\",\"integer\":100000,\"float1\":1.234500,\"float2\":" 650 | "1234.500000,\"integers\":[1,2,3,4,5,6,7,8,9]},\"keyword1\":test," 651 | "\"keyword2\":anotherTest,\"tuple1\":[1,-2,3.500000,\"foo\"]," 652 | "\"tuple2\":[],\"tuple3\":[{\"string\":\"foo\",\"integer\":100000," 653 | "\"float1\":1.234500,\"float2\":1234.500000,\"integers\":[1,2,3,4,5,6," 654 | "7,8,9]},1.600000],\"tuple4\":[\"foo\",\"bar\"],\"pair1\":[-100,1." 655 | "500000],\"pair2\":[\"foo bar baz\",\"baz bar foo\"],\"pair3\":[\"foo " 656 | "bar baz\",{\"baz bar foo\":\"foo bar baz\"}]}" 657 | ); 658 | }; 659 | 660 | void linkedListTest() { 661 | Node root; 662 | root.value = 10; 663 | root.next = new Node(); 664 | root.next->value = 20; 665 | root.next->next = new Node(); 666 | root.next->next->value = 30; 667 | root.next->next->next = new Node(); 668 | root.next->next->next->value = 40; 669 | root.next->next->next->next = new Node(); 670 | root.next->next->next->next->value = 40; 671 | test( 672 | "linked list", root, 673 | "{\"value\":10,\"next\":{\"value\":20,\"next\":{\"value\":30,\"next\":{" 674 | "\"value\":40,\"next\":{\"value\":40,\"next\":null}}}}}" 675 | ); 676 | } 677 | 678 | void treeTest() { 679 | TreeNode root("root"); 680 | root.addChild("foo"); 681 | root.addChild("bar"); 682 | root.addChild("baz"); 683 | 684 | root.children[0]->addChild("foo foo"); 685 | root.children[0]->addChild("foo bar"); 686 | root.children[0]->addChild("foo baz"); 687 | 688 | root.children[1]->addChild("bar foo"); 689 | root.children[1]->addChild("bar bar"); 690 | root.children[1]->addChild("bar baz"); 691 | 692 | root.children[2]->addChild("baz foo"); 693 | root.children[2]->addChild("baz bar"); 694 | root.children[2]->addChild("baz baz"); 695 | 696 | root.children[0]->children[0]->addChild("foo foo foo"); 697 | root.children[0]->children[0]->addChild("foo foo bar"); 698 | root.children[0]->children[0]->addChild("foo foo baz"); 699 | 700 | test( 701 | "tree", root, 702 | "{\"value\":\"root\",\"children\":[{\"value\":\"foo\",\"children\":[{" 703 | "\"value\":\"foo foo\",\"children\":[{\"value\":\"foo foo " 704 | "foo\",\"children\":[]},{\"value\":\"foo foo " 705 | "bar\",\"children\":[]},{\"value\":\"foo foo " 706 | "baz\",\"children\":[]}]},{\"value\":\"foo " 707 | "bar\",\"children\":[]},{\"value\":\"foo " 708 | "baz\",\"children\":[]}]},{\"value\":\"bar\",\"children\":[{\"value\":" 709 | "\"bar foo\",\"children\":[]},{\"value\":\"bar " 710 | "bar\",\"children\":[]},{\"value\":\"bar " 711 | "baz\",\"children\":[]}]},{\"value\":\"baz\",\"children\":[{\"value\":" 712 | "\"baz foo\",\"children\":[]},{\"value\":\"baz " 713 | "bar\",\"children\":[]},{\"value\":\"baz baz\",\"children\":[]}]}]}" 714 | ); 715 | } 716 | 717 | void commentTest() { 718 | std::string string; 719 | int integer; 720 | float float1; 721 | float float2; 722 | std::vector integers; 723 | printf("%-20s", "comment/whitespace"); 724 | 725 | RealisticStruct realisticStruct; 726 | std::string 727 | serialized = "{\"string\" /* comment\n/*/://\n\"foo bar\"// " 728 | "comment\n,/*comment */ \"integer\" /*comment " 729 | "*/: //comment\n " 730 | "/*comment*/42/**/,\"float1\"//\n:42.0/***comment " 731 | "\n\n****/,// \n \"float2\"/**//**//**/:\n \n " 732 | "4.2\n\n//comment}\n//comment[\n,\n\"integers\"\n//" 733 | "\n:\n\n//comment\n/*comment*/[\n/*comment*/1\n,2 \n\n , " 734 | "/**/ 3 /**/,//comment\n4\n/*comment]*/]} \n//comment"; 735 | try { 736 | realisticStruct = json::deserialize(serialized); 737 | } 738 | catch (const json::exception &ex) { 739 | printf("FAIL\n"); 740 | return; 741 | } 742 | if (!equals(realisticStruct, {"foo bar", 42, 42.0, 4.2, {1, 2, 3, 4}})) { 743 | printf("FAIL\n"); 744 | return; 745 | } 746 | printf("PASS\n"); 747 | } 748 | 749 | int main() { 750 | if constexpr (false) { 751 | RealisticStruct realisticStruct; 752 | realisticStruct.string = "foo bar baz"; 753 | realisticStruct.integer = 1; 754 | realisticStruct.float1 = 1.5; 755 | realisticStruct.float2 = 3.0; 756 | realisticStruct.integers = {10, 20, 30, 40, 50}; 757 | 758 | std::string json = json::serialize(realisticStruct); 759 | printf("%s\n", json.c_str()); 760 | printf("%s\n", prettifier.prettify(json).c_str()); 761 | return 0; 762 | 763 | int total = 0; 764 | for (int i = 0; i < 5'000'000; i++) { 765 | std::string json = json::serialize(realisticStruct); 766 | RealisticStruct deserialized = json::deserialize(json); 767 | total += deserialized.integer; 768 | } 769 | printf("%d\n", total); 770 | } 771 | else { 772 | stringTest(); 773 | charTest(); 774 | intTest(); 775 | floatTest(); 776 | vectorTest(); 777 | listTest(); 778 | setTest(); 779 | dequeTest(); 780 | queueTest(); 781 | arrayTest(); 782 | mapTest(); 783 | structTest(); 784 | linkedListTest(); 785 | treeTest(); 786 | commentTest(); 787 | } 788 | } 789 | --------------------------------------------------------------------------------