From c9938ea8389cd723357f6d7a7e5216775e6a5b92 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 7 Jan 2018 22:09:12 +0100 Subject: [PATCH] :construction: started UBJSON implementation --- src/json.hpp | 405 ++++- test/Makefile | 1 + .../CouchDB4k.formatted.json | 275 +++ .../universal-binary-json-java/CouchDB4k.ubj | Bin 0 -> 2724 bytes test/data/universal-binary-json-java/LICENSE | 202 +++ .../MediaContent.formatted.json | 34 + .../MediaContent.ubj | Bin 0 -> 442 bytes test/data/universal-binary-json-java/README | 357 ++++ .../TwitterTimeline.formatted.json | 80 + .../TwitterTimeline.ubj | Bin 0 -> 1792 bytes test/data/universal-binary-json-java/url.txt | 1 + test/src/unit-ubjson.cpp | 1593 +++++++++++++++++ 12 files changed, 2929 insertions(+), 19 deletions(-) create mode 100644 test/data/universal-binary-json-java/CouchDB4k.formatted.json create mode 100644 test/data/universal-binary-json-java/CouchDB4k.ubj create mode 100644 test/data/universal-binary-json-java/LICENSE create mode 100644 test/data/universal-binary-json-java/MediaContent.formatted.json create mode 100644 test/data/universal-binary-json-java/MediaContent.ubj create mode 100644 test/data/universal-binary-json-java/README create mode 100644 test/data/universal-binary-json-java/TwitterTimeline.formatted.json create mode 100644 test/data/universal-binary-json-java/TwitterTimeline.ubj create mode 100644 test/data/universal-binary-json-java/url.txt create mode 100644 test/src/unit-ubjson.cpp diff --git a/src/json.hpp b/src/json.hpp index a12fcbe3c..a356bdef4 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -4534,6 +4534,27 @@ class binary_reader return res; } + /*! + @brief create a JSON value from UBJSON input + + @param[in] strict whether to expect the input to be consumed completed + @return JSON value created from UBJSON input + + @throw parse_error.110 if input ended unexpectedly or the end of file was + not reached when @a strict was set to true + @throw parse_error.112 if unsupported byte was read + */ + BasicJsonType parse_ubjson(const bool strict) + { + const auto res = parse_ubjson_internal(); + if (strict) + { + get(); + check_eof(true); + } + return res; + } + /*! @brief determine system byte order @@ -5195,6 +5216,68 @@ class binary_reader } } + /*! + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead + */ + BasicJsonType parse_ubjson_internal(const bool get_char = true) + { + switch (get_char ? get() : current) + { + case std::char_traits::eof(): // EOF + JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); + + case 'T': // true + return true; + case 'F': // false + return false; + + case 'Z': // null + return nullptr; + + case 'N': // no-op + return parse_ubjson_internal(); // read next byte + + case 'U': + return get_number(); + case 'i': + return get_number(); + case 'I': + return get_number(); + case 'l': + return get_number(); + case 'L': + return get_number(); + case 'd': + return get_number(); + case 'D': + return get_number(); + + case 'C': // char + { + get(); + check_eof(); + return std::string(1, static_cast(current)); + } + + case 'S': // string + return get_ubjson_string(); + + case '[': // array + return get_ubjson_array(); + + case '{': // object + return get_ubjson_object(); + + default: // anything else + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(112, chars_read, + "error reading UBJSON; last byte: 0x" + ss.str())); + } + } + /*! @brief get next character from the input @@ -5495,6 +5578,80 @@ class binary_reader return result; } + /*! + @brief reads a UBJSON string + + This function first reads starting bytes to determine the expected + string length and then copies this number of bytes into a string. + + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead + + @return string + + @throw parse_error.110 if input ended + @throw parse_error.113 if an unexpected byte is read + */ + std::string get_ubjson_string(const bool get_char = true) + { + if (get_char) + { + get(); + } + + check_eof(); + + switch (current) + { + case 'U': + return get_string(get_number()); + case 'i': + return get_string(get_number()); + case 'I': + return get_string(get_number()); + case 'l': + return get_string(get_number()); + case 'L': + return get_string(get_number()); + default: + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(113, chars_read, + "expected a UBJSON string; last byte: 0x" + ss.str())); + } + } + + BasicJsonType get_ubjson_array() + { + BasicJsonType result = value_t::array; + + while (get() != ']') + { + // skip no-op + if (current == 'N') + { + continue; + } + result.push_back(parse_ubjson_internal(false)); + } + + return result; + } + + BasicJsonType get_ubjson_object() + { + BasicJsonType result = value_t::object; + + while (get() != '}') + { + auto key = get_ubjson_string(false); + result[std::move(key)] = parse_ubjson_internal(); + } + + return result; + } + /*! @brief check if input ended @throw parse_error.110 if input ended @@ -5678,23 +5835,23 @@ class binary_writer { write_number(static_cast(0x60 + N)); } - else if (N <= 0xFF) + else if (N <= (std::numeric_limits::max)()) { oa->write_character(static_cast(0x78)); write_number(static_cast(N)); } - else if (N <= 0xFFFF) + else if (N <= (std::numeric_limits::max)()) { oa->write_character(static_cast(0x79)); write_number(static_cast(N)); } - else if (N <= 0xFFFFFFFF) + else if (N <= (std::numeric_limits::max)()) { oa->write_character(static_cast(0x7A)); write_number(static_cast(N)); } // LCOV_EXCL_START - else if (N <= 0xFFFFFFFFFFFFFFFF) + else if (N <= (std::numeric_limits::max)()) { oa->write_character(static_cast(0x7B)); write_number(static_cast(N)); @@ -5716,23 +5873,23 @@ class binary_writer { write_number(static_cast(0x80 + N)); } - else if (N <= 0xFF) + else if (N <= (std::numeric_limits::max)()) { oa->write_character(static_cast(0x98)); write_number(static_cast(N)); } - else if (N <= 0xFFFF) + else if (N <= (std::numeric_limits::max)()) { oa->write_character(static_cast(0x99)); write_number(static_cast(N)); } - else if (N <= 0xFFFFFFFF) + else if (N <= (std::numeric_limits::max)()) { oa->write_character(static_cast(0x9A)); write_number(static_cast(N)); } // LCOV_EXCL_START - else if (N <= 0xFFFFFFFFFFFFFFFF) + else if (N <= (std::numeric_limits::max)()) { oa->write_character(static_cast(0x9B)); write_number(static_cast(N)); @@ -5755,23 +5912,23 @@ class binary_writer { write_number(static_cast(0xA0 + N)); } - else if (N <= 0xFF) + else if (N <= (std::numeric_limits::max)()) { oa->write_character(static_cast(0xB8)); write_number(static_cast(N)); } - else if (N <= 0xFFFF) + else if (N <= (std::numeric_limits::max)()) { oa->write_character(static_cast(0xB9)); write_number(static_cast(N)); } - else if (N <= 0xFFFFFFFF) + else if (N <= (std::numeric_limits::max)()) { oa->write_character(static_cast(0xBA)); write_number(static_cast(N)); } // LCOV_EXCL_START - else if (N <= 0xFFFFFFFFFFFFFFFF) + else if (N <= (std::numeric_limits::max)()) { oa->write_character(static_cast(0xBB)); write_number(static_cast(N)); @@ -5939,19 +6096,19 @@ class binary_writer // fixstr write_number(static_cast(0xA0 | N)); } - else if (N <= 255) + else if (N <= (std::numeric_limits::max)()) { // str 8 oa->write_character(static_cast(0xD9)); write_number(static_cast(N)); } - else if (N <= 65535) + else if (N <= (std::numeric_limits::max)()) { // str 16 oa->write_character(static_cast(0xDA)); write_number(static_cast(N)); } - else if (N <= 4294967295) + else if (N <= (std::numeric_limits::max)()) { // str 32 oa->write_character(static_cast(0xDB)); @@ -5974,13 +6131,13 @@ class binary_writer // fixarray write_number(static_cast(0x90 | N)); } - else if (N <= 0xFFFF) + else if (N <= (std::numeric_limits::max)()) { // array 16 oa->write_character(static_cast(0xDC)); write_number(static_cast(N)); } - else if (N <= 0xFFFFFFFF) + else if (N <= (std::numeric_limits::max)()) { // array 32 oa->write_character(static_cast(0xDD)); @@ -6004,13 +6161,13 @@ class binary_writer // fixmap write_number(static_cast(0x80 | (N & 0xF))); } - else if (N <= 65535) + else if (N <= (std::numeric_limits::max)()) { // map 16 oa->write_character(static_cast(0xDE)); write_number(static_cast(N)); } - else if (N <= 4294967295) + else if (N <= (std::numeric_limits::max)()) { // map 32 oa->write_character(static_cast(0xDF)); @@ -6031,6 +6188,110 @@ class binary_writer } } + /*! + @brief[in] j JSON value to serialize + */ + void write_ubjson(const BasicJsonType& j, const bool use_count = false) + { + switch (j.type()) + { + case value_t::null: + { + oa->write_character(static_cast('Z')); + break; + } + + case value_t::boolean: + { + oa->write_character(j.m_value.boolean + ? static_cast('T') + : static_cast('F')); + break; + } + + case value_t::number_integer: + { + write_number_with_ubjson_prefix(j.m_value.number_integer); + break; + } + + case value_t::number_unsigned: + { + write_number_with_ubjson_prefix(j.m_value.number_unsigned); + break; + } + + case value_t::number_float: + { + write_number_with_ubjson_prefix(j.m_value.number_float); + break; + } + + case value_t::string: + { + oa->write_character(static_cast('S')); + write_number_with_ubjson_prefix(j.m_value.string->size()); + oa->write_characters( + reinterpret_cast(j.m_value.string->c_str()), + j.m_value.string->size()); + break; + } + + case value_t::array: + { + oa->write_character(static_cast('[')); + + if (use_count) + { + oa->write_character(static_cast('#')); + write_number_with_ubjson_prefix(j.m_value.array->size()); + } + + for (const auto& el : *j.m_value.array) + { + write_ubjson(el, use_count); + } + + if (not use_count) + { + oa->write_character(static_cast(']')); + } + + break; + } + + case value_t::object: + { + oa->write_character(static_cast('{')); + + if (use_count) + { + oa->write_character(static_cast('#')); + write_number_with_ubjson_prefix(j.m_value.object->size()); + } + + for (const auto& el : *j.m_value.object) + { + write_number_with_ubjson_prefix(el.first.size()); + oa->write_characters( + reinterpret_cast(el.first.c_str()), + el.first.size()); + write_ubjson(el.second, use_count); + } + + if (not use_count) + { + oa->write_character(static_cast('}')); + } + + break; + } + + default: + break; + } + } + private: /* @brief write a number to output input @@ -6058,6 +6319,82 @@ class binary_writer oa->write_characters(vec.data(), sizeof(NumberType)); } + template + void write_number_with_ubjson_prefix(const NumberType n) + { + if (std::is_floating_point::value) + { + oa->write_character(static_cast('D')); // float64 + write_number(n); + } + else if (std::is_unsigned::value) + { + if (n <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast('i')); // uint8 + write_number(static_cast(n)); + } + else if (n <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast('U')); // uint8 + write_number(static_cast(n)); + } + else if (n <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast('I')); // int16 + write_number(static_cast(n)); + } + else if (n <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast('l')); // int32 + write_number(static_cast(n)); + } + else if (n <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast('L')); // int64 + write_number(static_cast(n)); + } + else + { + // TODO: replace by exception + assert(false); + } + } + else + { + if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast('i')); // int8 + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast('U')); // uint8 + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast('I')); // int16 + write_number(static_cast(n)); + } + else if (-(std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast('l')); // int32 + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast('L')); // int64 + write_number(static_cast(n)); + } + else + { + // TODO: replace by exception + assert(false); + } + } + } + private: /// whether we can assume little endianess const bool is_little_endian = binary_reader::little_endianess(); @@ -13511,6 +13848,23 @@ class basic_json binary_writer(o).write_msgpack(j); } + static std::vector to_ubjson(const basic_json& j) + { + std::vector result; + to_ubjson(j, result); + return result; + } + + static void to_ubjson(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_ubjson(j); + } + + static void to_ubjson(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_ubjson(j); + } + /*! @brief create a JSON value from an input in CBOR format @@ -13705,6 +14059,19 @@ class basic_json return binary_reader(detail::input_adapter(std::forward(a1), std::forward(a2))).parse_msgpack(strict); } + static basic_json from_ubjson(detail::input_adapter i, + const bool strict = true) + { + return binary_reader(i).parse_ubjson(strict); + } + + template::value, int> = 0> + static basic_json from_ubjson(A1 && a1, A2 && a2, const bool strict = true) + { + return binary_reader(detail::input_adapter(std::forward(a1), std::forward(a2))).parse_ubjson(strict); + } + /// @} ////////////////////////// diff --git a/test/Makefile b/test/Makefile index a170acb96..d13fa1e87 100644 --- a/test/Makefile +++ b/test/Makefile @@ -39,6 +39,7 @@ SOURCES = src/unit.cpp \ src/unit-regression.cpp \ src/unit-serialization.cpp \ src/unit-testsuites.cpp \ + src/unit-ubjson.cpp \ src/unit-unicode.cpp OBJECTS = $(SOURCES:.cpp=.o) diff --git a/test/data/universal-binary-json-java/CouchDB4k.formatted.json b/test/data/universal-binary-json-java/CouchDB4k.formatted.json new file mode 100644 index 000000000..d12387daf --- /dev/null +++ b/test/data/universal-binary-json-java/CouchDB4k.formatted.json @@ -0,0 +1,275 @@ +{ + "data3":"ColreUHAtuYoUOx1N4ZloouQt2o6ugnUT6eYtS10gu7niM8i0vEiNufpk1RlMQXaHXlIwQBDsMFDFUQcFeg2vW5eD259Xm", + "data4":"zCxriJhL726WNNTdJJzurgSA8vKT6rHA0cFCb9koZcLUMXg4rmoXVPqIHWYaCV0ovl2t6xm7I1Hm36jXpLlXEb8fRfbwBeTW2V0OAsVqYH8eAT", + "data0":"9EVqHm5ARqcEB5jq2D14U2bCJPyBY0JWDr1Tjh8gTB0sWUNjqYiWDxFzlx6S", + "data7":"Bi1ujcgEvfADfBeyZudE7nwxc3Ik8qpYjsJIfKmwOMEbV2L3Bi0x2tcRpGuf4fiyvIbypDvJN1PPdQtfQW1Gv6zccXHwwZwKzUq6", + "data5":{ + "integers":[ + 756509, + 116117, + 776378, + 275045, + 703447, + 50156, + 685803, + 147958, + 941747, + 905651, + 57367, + 530248, + 312888, + 740951, + 988947, + 450154 + ], + "float1":76.572, + "float2":83.5299, + "nested1":{ + "integers":[ + 756509, + 116117, + 776378, + 275045, + 703447, + 50156, + 685803, + 147958, + 941747, + 905651, + 57367, + 530248, + 312888, + 740951, + 988947, + 450154 + ], + "floats":[ + 43121609.5543, + 99454976.3019, + 32945584.756, + 18122905.9212, + 45893183.44, + 63052200.6225, + 69032152.6897, + 3748217.6946, + 75449850.474, + 37111527.415, + 84852536.859, + 32906366.487, + 27027600.417, + 63874310.5614, + 39440408.51, + 97176857.1716, + 97438252.1171, + 49728043.5056, + 40818570.245, + 41415831.8949, + 24796297.4251, + 2819085.3449, + 84263963.4848, + 74503228.6878, + 67925677.403, + 4758851.9417, + 75227407.9214, + 76946667.8403, + 72518275.9469, + 94167085.9588, + 75883067.8321, + 27389831.6101, + 57987075.5053, + 1298995.2674, + 14590614.6939, + 45292214.2242, + 3332166.364, + 53784167.729, + 25193846.1867, + 81456965.477, + 68532032.39, + 73820009.7952, + 57736110.5717, + 37304166.7363, + 20054244.864, + 29746392.7397, + 86467624.6, + 45192685.8793, + 44008816.5186, + 1861872.8736, + 14595859.467, + 87795257.6703, + 57768720.8303, + 18290154.3126, + 45893183.44, + 63052200.6225, + 69032152.6897, + 3748217.6946, + 75449850.474, + 37111527.415, + 84852536.859, + 32906366.487, + 27027600.417, + 63874310.5614, + 39440408.51, + 97176857.1716, + 97438252.1171, + 49728043.5056, + 40818570.245, + 41415831.8949, + 24796297.4251, + 2819085.3449, + 84263963.4848, + 74503228.6878, + 67925677.403, + 4758851.9417, + 75227407.9214, + 76946667.8403, + 72518275.9469, + 94167085.9588, + 75883067.8321, + 27389831.6101, + 57987075.5053, + 1298995.2674, + 80858801.2712, + 98262252.4656, + 51612877.944, + 33397812.7835, + 36089655.3049, + 50164685.8153, + 16852105.5192, + 61171929.752, + 86376339.7175, + 73009014.5521, + 7397302.331, + 34345128.9589, + 98343269.4418, + 95039116.9058, + 44833102.5752, + 51052997.8873, + 22719195.6783, + 64883244.8699 + ] + }, + "nested2":{ + "integers":[ + 756509, + 116117, + 776378, + 275045, + 703447, + 50156, + 685803, + 147958, + 941747, + 905651, + 57367, + 530248, + 312888, + 740951, + 988947, + 450154 + ], + "float1":76.572, + "float2":83.5299 + } + }, + "strings":[ + "edx5XzRkPVeEW2MBQzQMcUSuMI4FntjhlJ9VGhQaBHKPEazAaT", + "2fQUbzRUax4A", + "jURcBZ0vrJcmf2roZUMzZJQoTsKZDIdj7KhO7itskKvM80jBU9", + "8jKLmo3N2zYdKyTyfTczfr2x6bPaarorlnTNJ7r8lIkiZyBvrP", + "jbUeAVOdBSPzYmYhH0sabUHUH39O5e", + "I8yAQKZsyZhMfpzWjArQU9pQ6PfU6b14q2eWvQjtCUdgAUxFjg", + "97N8ZmGcxRZO4ZabzRRcY4KVHqxJwQ8qY", + "0DtY1aWXmUfJENt9rYW9", + "DtpBUEppPwMnWexi8eIIxlXRO3GUpPgeNFG9ONpWJYvk8xBkVj", + "YsX8V2xOrTw6LhNIMMhO4F4VXFyXUXFr66L3sTkLWgFA9NZuBV", + "fKYYthv8iFvaYoFoYZyB", + "zGuLsPXoJqMbO4PcePteZfDMYFXdWtvNF8WvaplXypsd6" + ], + "data1":"9EVqHm5ARqcEB5jq21v2g0jVcG9CXB0Abk7uAF4NHYyTzeF3TnHhpZBECD14U2bCJPyBY0JWDr1Tjh8gTB0sWUNjqYiWDxFzlx6S", + "integers":[ + 756509, + 116117, + 776378, + 275045, + 703447, + 50156, + 685803, + 147958, + 941747, + 905651, + 57367, + 530248, + 312888, + 740951, + 988947, + 450154 + ], + "more_nested":{ + "integers":[ + 756509, + 116117, + 776378, + 275045, + 703447, + 50156, + 685803, + 147958, + 941747, + 905651, + 57367, + 530248, + 312888, + 740951, + 988947, + 450154 + ], + "float1":76.572, + "float2":83.5299, + "nested1":{ + "integers":[ + 756509, + 116117, + 776378, + 275045, + 703447, + 50156, + 685803, + 147958, + 941747, + 905651, + 57367, + 530248, + 312888, + 740951, + 988947, + 450154 + ] + }, + "nested2":{ + "strings":[ + "2fQUbzRUax4A", + "jURcBZ0vrJcmf2roZUMzZJQoTsKZDIdj7KhO7itskKvM80jBU9", + "8jKLmo3N2zYdKyTyfTczfr2x6bPaarorlnTNJ7r8lIkiZyBvrP", + "jbUeAVOdBSPzYmYhH0sabUHUH39O5e", + "I8yAQKZsyZhMfpzWjArQU9pQ6PfU6b14q2eWvQjtCUdgAUxFjg", + "97N8ZmGcxRZO4ZabzRRcY4KVHqxJwQ8qY", + "0DtY1aWXmUfJENt9rYW9", + "DtpBUEppPwMnWexi8eIIxlXRO3GUpPgeNFG9ONpWJYvk8xBkVj", + "YsX8V2xOrTw6LhNIMMhO4F4VXFyXUXFr66L3sTkLWgFA9NZuBV", + "fKYYthv8iFvaYoFoYZyB", + "zGuLsPXoJqMbO4PcePteZfDMYFXdWtvNF8WvaplXypsd6" + ], + "integers":[ + 756509, + 116117, + 776378, + 57367, + 530248, + 312888, + 740951, + 988947, + 450154 + ] + } + } +} \ No newline at end of file diff --git a/test/data/universal-binary-json-java/CouchDB4k.ubj b/test/data/universal-binary-json-java/CouchDB4k.ubj new file mode 100644 index 0000000000000000000000000000000000000000..75a6465bc74dcbec4c856640935b331b9e6fa0c3 GIT binary patch literal 2724 zcmd1jEM`qfEJ-vjj&sh>DM}6Xa4ac}%n$XiF!VEt%E`|!4JV{T*??&lYh;^kFUT9h8_Xi?@JVpingXprpYoMf4uAC>GA z>Kl=6Qk0t?5f)JB=@A~8=p1H{UzTH3VpfrB?rG?eYiyPkQQ(sk;hJQT7L=A$?vxr5 zZWLzV?^qmG80le=>KFpC)}YwN(lxBmBiGb1s4&^p$uz6b$i>hk)F{c>E1=RT(!eX+ zrN}TOE5jl^#L1vIJk&3%FfudTrNXT$r@|~4Vu*Qhic_XxX;yN&YgwA3OPW(^WmIX3 zt9f2|MY6GHwnbqAKhsl_F! zDTet>bZ|g%Vp@uiO5LXvpS_zHQoMgh-AM87a`8>^(HESM;#1I;mEzO9S~|rm<>>Pi zpZ*v7Q+#+Q?n?1lc>Q^b_isM?6z_8;9w|P>E1gnfvwe0D}IPVw1!(=Elvc2jqX zkM!2ZDL%4))l$6o>ba(P=?Ivn_$=7boZ{0b)sf=U*mo_(Yob+0icf!yQi{){!*f%7 zx&i}Id^XQvOYxbo$34aS!uKO7KCvc@DW3Bt&Pwr)TQohzN9WkO6ff^Y$rK-7?O7?_ z2h4V-_{@%dnc~y5?L~@D&zZL=J~4K0QhfM)Z>4z8WGG7Ue)K6P#b?Qz))XI|)an!; z^@lMjo{!zzQ@rE03#a%jFFBs#6BBnN#k;%f>mc>H&n(MpDL%Wi=cV|#JT^}8{&!9) z#fNkCxfCD!n9nKR4U=3`d@`fVQhb)||CHj>?eIRu>*4;~6dz_5trVZ#lXX*kwz^(Q z@zG`rN%3(yP?+MqHZ(8Ar@W~fmfVc;ndy++u%`FolA_GK^x{OGVx!cQ3e$+HpzMIK zRM&7LU#GyTK;Puh;8I^t6SusQtc)Bl%P{whz(gkx?*P}tD#yf-VjiQkz|f?spwPq$ z6USnstk9rjrznH6BCq7!G^3*Ys8HXkD6hc$kYevB7tfR|bMFj)^URXsZ0|B(3xh1D zP|IQ?i!5)S+wq)?Ag4`WMz)6`-kPm4;&K<}vH%BT$Aw1TSe zEXSh2P|Jcqvw*Zvvm`^4LZj61vcRko=g^dN$IuG5tn^|a` zs-U3cNE7cckHQMC@<5Bi$YK!#my$@s#PEpR&@?YszY@!$$Z*SIBbSl_r%=~|f`D@0 zyztbDOp8=c&x)LgAb(@`(1L*UR6jR&OMkzDaIeU+Y>Nt~?69n2qsZb2i!h@K|Dup` zGoK7UPhZ~*e-k&8un4!xh|mbPA~Q1|kf$dZgQi%hq& z#K?TN{K%+Er()eI_fntYfQWprLf<5RlYr#ZfRfaxG#B4Uw}_PRk}^Lxi}131tS9qBmKmiRWQ8TWTRKNL88{|oo0mGenfQ4`R)$oix*3P$d1MqsIk`HM zQg3j=N^m0@6yaRC`9-OI;KF*~D+@-N=3Z!>WR%ZT%nr-0+@o1P=NGEy_sGfF%e7#JTEv!&%13QWME)m zC>GDmi!Vwo$f=Ak$&W8iE=oryc zgqjCp3l-!S7iT8rRK^#l<`rj_WR|766-y!;QCyN(Qd%4jabHw1M`~V4W=UphaXxb~ zOKDL~aXw2iGl&++C@Cqhiis)FOU{oeFUidJ&UOnf=1$2hF33r&j0Y*jfOWtOMb*fWkyxBjl9*nc$WY7+ z_F^u`Rrz@!0p|47{HS8qf}F(U)Tm#-7+Dz_SQ#29Xd4(97$_JS7#gC6FxV1U5U_xF`D*Yu<1jZjFtRi-G%Xe? zD9TUE%t?*U$;``+PtMQDFDhm;FtD_=v@GT-D9SHMO)g1INpXXOm?SjB%FE03N>Ye=j9jWCg$j66{HulfD%+Odq`?>hD%~) zv1m$aT4HHVNj!=zfFcnFO$=T^e`K5U&u)r`hHZ(OfD`v?_%u6q3O3f=4 z$D+0*Gbh!pn5(oTIX*uvtvD5w=2sjj=1xg1PA%`A>DDN4*M&PgoE zFLEmuEzZaQ**S@MDa8V5 ziDmhvMVTc?$&7)aSO(oaN%=)7sYP%XrGfw=KEWXZN{rC3bj2T*VC6(4N=}GBGV{{i zin-HM^AQoso0ngbnUev2=|s2r^cg4RY);UPG)gQDkxz>3)Shx9A&9RnQ57z z)B(@QC8-slf)|{VjlrOpBPSnfiC92>UU7b2Ql*XpEcA2~{Gy7PGE+QRrpHZ3xB=aM zP{0)Xf`Y^. +Copyright (c) 2013-2017 Niels Lohmann . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "catch.hpp" + +#include "json.hpp" +using nlohmann::json; + +#include + +TEST_CASE("UBJSON") +{ + SECTION("individual values") + { + SECTION("discarded") + { + // discarded values are not serialized + json j = json::value_t::discarded; + const auto result = json::to_ubjson(j); + CHECK(result.empty()); + } + + SECTION("null") + { + json j = nullptr; + std::vector expected = {'Z'}; + const auto result = json::to_ubjson(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_ubjson(result) == j); + } + + SECTION("boolean") + { + SECTION("true") + { + json j = true; + std::vector expected = {'T'}; + const auto result = json::to_ubjson(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_ubjson(result) == j); + } + + SECTION("false") + { + json j = false; + std::vector expected = {'F'}; + const auto result = json::to_ubjson(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_ubjson(result) == j); + } + } + + SECTION("number") + { + SECTION("signed") + { + SECTION("-9223372036854775808..-2147483649 (int64)") + { + std::vector numbers; + numbers.push_back(INT64_MIN); + numbers.push_back(-1000000000000000000); + numbers.push_back(-100000000000000000); + numbers.push_back(-10000000000000000); + numbers.push_back(-1000000000000000); + numbers.push_back(-100000000000000); + numbers.push_back(-10000000000000); + numbers.push_back(-1000000000000); + numbers.push_back(-100000000000); + numbers.push_back(-10000000000); + numbers.push_back(-2147483649); + for (auto i : numbers) + { + CAPTURE(i); + + // create JSON value with integer number + json j = i; + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(static_cast('L')); + expected.push_back(static_cast((i >> 56) & 0xff)); + expected.push_back(static_cast((i >> 48) & 0xff)); + expected.push_back(static_cast((i >> 40) & 0xff)); + expected.push_back(static_cast((i >> 32) & 0xff)); + expected.push_back(static_cast((i >> 24) & 0xff)); + expected.push_back(static_cast((i >> 16) & 0xff)); + expected.push_back(static_cast((i >> 8) & 0xff)); + expected.push_back(static_cast(i & 0xff)); + + // compare result + size + const auto result = json::to_ubjson(j); + CHECK(result == expected); + CHECK(result.size() == 9); + + // check individual bytes + CHECK(result[0] == 'L'); + uint64_t restored = (static_cast(result[1]) << 070) + + (static_cast(result[2]) << 060) + + (static_cast(result[3]) << 050) + + (static_cast(result[4]) << 040) + + (static_cast(result[5]) << 030) + + (static_cast(result[6]) << 020) + + (static_cast(result[7]) << 010) + + static_cast(result[8]); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_ubjson(result) == j); + } + } + + SECTION("-2147483648..-32769 (int32)") + { + std::vector numbers; + numbers.push_back(-32769); + numbers.push_back(-100000); + numbers.push_back(-1000000); + numbers.push_back(-10000000); + numbers.push_back(-100000000); + numbers.push_back(-1000000000); + numbers.push_back(-2147483648); + for (auto i : numbers) + { + CAPTURE(i); + + // create JSON value with integer number + json j = i; + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(static_cast('l')); + expected.push_back(static_cast((i >> 24) & 0xff)); + expected.push_back(static_cast((i >> 16) & 0xff)); + expected.push_back(static_cast((i >> 8) & 0xff)); + expected.push_back(static_cast(i & 0xff)); + + // compare result + size + const auto result = json::to_ubjson(j); + CHECK(result == expected); + CHECK(result.size() == 5); + + // check individual bytes + CHECK(result[0] == 'l'); + int32_t restored = (static_cast(result[1]) << 030) + + (static_cast(result[2]) << 020) + + (static_cast(result[3]) << 010) + + static_cast(result[4]); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_ubjson(result) == j); + } + } + + SECTION("-32768..-129 (int16)") + { + for (int32_t i = -32768; i <= -129; ++i) + { + CAPTURE(i); + + // create JSON value with integer number + json j = i; + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(static_cast('I')); + expected.push_back(static_cast((i >> 8) & 0xff)); + expected.push_back(static_cast(i & 0xff)); + + // compare result + size + const auto result = json::to_ubjson(j); + CHECK(result == expected); + CHECK(result.size() == 3); + + // check individual bytes + CHECK(result[0] == 'I'); + int16_t restored = static_cast(((result[1] << 8) + result[2])); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_ubjson(result) == j); + } + } + + SECTION("-9263 (int16)") + { + json j = -9263; + std::vector expected = {'I', 0xdb, 0xd1}; + + // compare result + size + const auto result = json::to_ubjson(j); + CHECK(result == expected); + CHECK(result.size() == 3); + + // check individual bytes + CHECK(result[0] == 'I'); + int16_t restored = static_cast(((result[1] << 8) + result[2])); + CHECK(restored == -9263); + + // roundtrip + CHECK(json::from_ubjson(result) == j); + } + + SECTION("-128..-1 (int8)") + { + for (auto i = -128; i <= -1; ++i) + { + CAPTURE(i); + + // create JSON value with integer number + json j = i; + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back('i'); + expected.push_back(static_cast(i)); + + // compare result + size + const auto result = json::to_ubjson(j); + CHECK(result == expected); + CHECK(result.size() == 2); + + // check individual bytes + CHECK(result[0] == 'i'); + CHECK(static_cast(result[1]) == i); + + // roundtrip + CHECK(json::from_ubjson(result) == j); + } + } + + SECTION("0..127 (int8)") + { + for (size_t i = 0; i <= 127; ++i) + { + CAPTURE(i); + + // create JSON value with integer number + json j = -1; + j.get_ref() = static_cast(i); + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(static_cast('i')); + expected.push_back(static_cast(i)); + + // compare result + size + const auto result = json::to_ubjson(j); + CHECK(result == expected); + CHECK(result.size() == 2); + + // check individual bytes + CHECK(result[0] == 'i'); + CHECK(result[1] == i); + + // roundtrip + CHECK(json::from_ubjson(result) == j); + } + } + + SECTION("128..255 (uint8)") + { + for (size_t i = 128; i <= 255; ++i) + { + CAPTURE(i); + + // create JSON value with integer number + json j = -1; + j.get_ref() = static_cast(i); + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(static_cast('U')); + expected.push_back(static_cast(i)); + + // compare result + size + const auto result = json::to_ubjson(j); + CHECK(result == expected); + CHECK(result.size() == 2); + + // check individual bytes + CHECK(result[0] == 'U'); + CHECK(result[1] == i); + + // roundtrip + CHECK(json::from_ubjson(result) == j); + } + } + + SECTION("256..32767 (int16)") + { + for (size_t i = 256; i <= 32767; ++i) + { + CAPTURE(i); + + // create JSON value with integer number + json j = -1; + j.get_ref() = static_cast(i); + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(static_cast('I')); + expected.push_back(static_cast((i >> 8) & 0xff)); + expected.push_back(static_cast(i & 0xff)); + + // compare result + size + const auto result = json::to_ubjson(j); + CHECK(result == expected); + CHECK(result.size() == 3); + + // check individual bytes + CHECK(result[0] == 'I'); + uint16_t restored = static_cast(static_cast(result[1]) * 256 + static_cast(result[2])); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_ubjson(result) == j); + } + } + + SECTION("65536..2147483647 (int32)") + { + for (uint32_t i : + { + 65536u, 77777u, 1048576u + }) + { + CAPTURE(i); + + // create JSON value with integer number + json j = -1; + j.get_ref() = static_cast(i); + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back('l'); + expected.push_back(static_cast((i >> 24) & 0xff)); + expected.push_back(static_cast((i >> 16) & 0xff)); + expected.push_back(static_cast((i >> 8) & 0xff)); + expected.push_back(static_cast(i & 0xff)); + + // compare result + size + const auto result = json::to_ubjson(j); + CHECK(result == expected); + CHECK(result.size() == 5); + + // check individual bytes + CHECK(result[0] == 'l'); + uint32_t restored = (static_cast(result[1]) << 030) + + (static_cast(result[2]) << 020) + + (static_cast(result[3]) << 010) + + static_cast(result[4]); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_ubjson(result) == j); + } + } + + SECTION("2147483648..9223372036854775807 (int64)") + { + for (uint64_t i : + { + 2147483648ul, 9223372036854775807ul + }) + { + CAPTURE(i); + + // create JSON value with integer number + json j = -1; + j.get_ref() = static_cast(i); + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back('L'); + expected.push_back(static_cast((i >> 070) & 0xff)); + expected.push_back(static_cast((i >> 060) & 0xff)); + expected.push_back(static_cast((i >> 050) & 0xff)); + expected.push_back(static_cast((i >> 040) & 0xff)); + expected.push_back(static_cast((i >> 030) & 0xff)); + expected.push_back(static_cast((i >> 020) & 0xff)); + expected.push_back(static_cast((i >> 010) & 0xff)); + expected.push_back(static_cast(i & 0xff)); + + // compare result + size + const auto result = json::to_ubjson(j); + CHECK(result == expected); + CHECK(result.size() == 9); + + // check individual bytes + CHECK(result[0] == 'L'); + uint64_t restored = (static_cast(result[1]) << 070) + + (static_cast(result[2]) << 060) + + (static_cast(result[3]) << 050) + + (static_cast(result[4]) << 040) + + (static_cast(result[5]) << 030) + + (static_cast(result[6]) << 020) + + (static_cast(result[7]) << 010) + + static_cast(result[8]); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_ubjson(result) == j); + } + } + } + + SECTION("unsigned") + { + SECTION("0..127 (int8)") + { + for (size_t i = 0; i <= 127; ++i) + { + CAPTURE(i); + + // create JSON value with unsigned integer number + json j = i; + + // check type + CHECK(j.is_number_unsigned()); + + // create expected byte vector + std::vector expected; + expected.push_back('i'); + expected.push_back(static_cast(i)); + + // compare result + size + const auto result = json::to_ubjson(j); + CHECK(result == expected); + CHECK(result.size() == 2); + + // check individual bytes + CHECK(result[0] == 'i'); + uint8_t restored = static_cast(result[1]); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_ubjson(result) == j); + } + } + + SECTION("128..255 (uint8)") + { + for (size_t i = 128; i <= 255; ++i) + { + CAPTURE(i); + + // create JSON value with unsigned integer number + json j = i; + + // check type + CHECK(j.is_number_unsigned()); + + // create expected byte vector + std::vector expected; + expected.push_back('U'); + expected.push_back(static_cast(i)); + + // compare result + size + const auto result = json::to_ubjson(j); + CHECK(result == expected); + CHECK(result.size() == 2); + + // check individual bytes + CHECK(result[0] == 'U'); + uint8_t restored = static_cast(result[1]); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_ubjson(result) == j); + } + } + + SECTION("256..32767 (int16)") + { + for (size_t i = 256; i <= 32767; ++i) + { + CAPTURE(i); + + // create JSON value with unsigned integer number + json j = i; + + // check type + CHECK(j.is_number_unsigned()); + + // create expected byte vector + std::vector expected; + expected.push_back('I'); + expected.push_back(static_cast((i >> 8) & 0xff)); + expected.push_back(static_cast(i & 0xff)); + + // compare result + size + const auto result = json::to_ubjson(j); + CHECK(result == expected); + CHECK(result.size() == 3); + + // check individual bytes + CHECK(result[0] == 'I'); + uint16_t restored = static_cast(static_cast(result[1]) * 256 + static_cast(result[2])); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_ubjson(result) == j); + } + } + + SECTION("65536..2147483647 (int32)") + { + for (uint32_t i : + { + 65536u, 77777u, 1048576u + }) + { + CAPTURE(i); + + // create JSON value with unsigned integer number + json j = i; + + // check type + CHECK(j.is_number_unsigned()); + + // create expected byte vector + std::vector expected; + expected.push_back('l'); + expected.push_back(static_cast((i >> 24) & 0xff)); + expected.push_back(static_cast((i >> 16) & 0xff)); + expected.push_back(static_cast((i >> 8) & 0xff)); + expected.push_back(static_cast(i & 0xff)); + + // compare result + size + const auto result = json::to_ubjson(j); + CHECK(result == expected); + CHECK(result.size() == 5); + + // check individual bytes + CHECK(result[0] == 'l'); + uint32_t restored = (static_cast(result[1]) << 030) + + (static_cast(result[2]) << 020) + + (static_cast(result[3]) << 010) + + static_cast(result[4]); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_ubjson(result) == j); + } + } + + SECTION("2147483648..9223372036854775807 (int64)") + { + for (uint64_t i : + { + 2147483648ul, 9223372036854775807ul + }) + { + CAPTURE(i); + + // create JSON value with integer number + json j = i; + + // check type + CHECK(j.is_number_unsigned()); + + // create expected byte vector + std::vector expected; + expected.push_back('L'); + expected.push_back(static_cast((i >> 070) & 0xff)); + expected.push_back(static_cast((i >> 060) & 0xff)); + expected.push_back(static_cast((i >> 050) & 0xff)); + expected.push_back(static_cast((i >> 040) & 0xff)); + expected.push_back(static_cast((i >> 030) & 0xff)); + expected.push_back(static_cast((i >> 020) & 0xff)); + expected.push_back(static_cast((i >> 010) & 0xff)); + expected.push_back(static_cast(i & 0xff)); + + // compare result + size + const auto result = json::to_ubjson(j); + CHECK(result == expected); + CHECK(result.size() == 9); + + // check individual bytes + CHECK(result[0] == 'L'); + uint64_t restored = (static_cast(result[1]) << 070) + + (static_cast(result[2]) << 060) + + (static_cast(result[3]) << 050) + + (static_cast(result[4]) << 040) + + (static_cast(result[5]) << 030) + + (static_cast(result[6]) << 020) + + (static_cast(result[7]) << 010) + + static_cast(result[8]); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_ubjson(result) == j); + } + } + } + + SECTION("float64") + { + SECTION("3.1415925") + { + double v = 3.1415925; + json j = v; + std::vector expected = + { + 'D', 0x40, 0x09, 0x21, 0xfb, 0x3f, 0xa6, 0xde, 0xfc + }; + const auto result = json::to_ubjson(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_ubjson(result) == j); + CHECK(json::from_ubjson(result) == v); + } + } + } + + SECTION("string") + { + SECTION("N = 0..127") + { + for (size_t N = 0; N <= 127; ++N) + { + CAPTURE(N); + + // create JSON value with string containing of N * 'x' + const auto s = std::string(N, 'x'); + json j = s; + + // create expected byte vector + std::vector expected; + expected.push_back('S'); + expected.push_back('i'); + expected.push_back(static_cast(N)); + for (size_t i = 0; i < N; ++i) + { + expected.push_back('x'); + } + + // compare result + size + const auto result = json::to_ubjson(j); + CHECK(result == expected); + CHECK(result.size() == N + 3); + // check that no null byte is appended + if (N > 0) + { + CHECK(result.back() != '\x00'); + } + + // roundtrip + CHECK(json::from_ubjson(result) == j); + } + } + + SECTION("N = 128..255") + { + for (size_t N = 128; N <= 255; ++N) + { + CAPTURE(N); + + // create JSON value with string containing of N * 'x' + const auto s = std::string(N, 'x'); + json j = s; + + // create expected byte vector + std::vector expected; + expected.push_back('S'); + expected.push_back('U'); + expected.push_back(static_cast(N)); + for (size_t i = 0; i < N; ++i) + { + expected.push_back('x'); + } + + // compare result + size + const auto result = json::to_ubjson(j); + CHECK(result == expected); + CHECK(result.size() == N + 3); + // check that no null byte is appended + CHECK(result.back() != '\x00'); + + // roundtrip + CHECK(json::from_ubjson(result) == j); + } + } + + SECTION("N = 256..32767") + { + for (size_t N : + { + 256u, 999u, 1025u, 3333u, 2048u, 32767u + }) + { + CAPTURE(N); + + // create JSON value with string containing of N * 'x' + const auto s = std::string(N, 'x'); + json j = s; + + // create expected byte vector (hack: create string first) + std::vector expected(N, 'x'); + // reverse order of commands, because we insert at begin() + expected.insert(expected.begin(), static_cast(N & 0xff)); + expected.insert(expected.begin(), static_cast((N >> 8) & 0xff)); + expected.insert(expected.begin(), 'I'); + expected.insert(expected.begin(), 'S'); + + // compare result + size + const auto result = json::to_ubjson(j); + CHECK(result == expected); + CHECK(result.size() == N + 4); + // check that no null byte is appended + CHECK(result.back() != '\x00'); + + // roundtrip + CHECK(json::from_ubjson(result) == j); + } + } + + SECTION("N = 65536..2147483647") + { + for (size_t N : + { + 65536u, 77777u, 1048576u + }) + { + CAPTURE(N); + + // create JSON value with string containing of N * 'x' + const auto s = std::string(N, 'x'); + json j = s; + + // create expected byte vector (hack: create string first) + std::vector expected(N, 'x'); + // reverse order of commands, because we insert at begin() + expected.insert(expected.begin(), static_cast(N & 0xff)); + expected.insert(expected.begin(), static_cast((N >> 8) & 0xff)); + expected.insert(expected.begin(), static_cast((N >> 16) & 0xff)); + expected.insert(expected.begin(), static_cast((N >> 24) & 0xff)); + expected.insert(expected.begin(), 'l'); + expected.insert(expected.begin(), 'S'); + + // compare result + size + const auto result = json::to_ubjson(j); + CHECK(result == expected); + CHECK(result.size() == N + 6); + // check that no null byte is appended + CHECK(result.back() != '\x00'); + + // roundtrip + CHECK(json::from_ubjson(result) == j); + } + } + } + + SECTION("array") + { + SECTION("empty") + { + json j = json::array(); + std::vector expected = {'[', ']'}; + const auto result = json::to_ubjson(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_ubjson(result) == j); + } + + SECTION("[null]") + { + json j = {nullptr}; + std::vector expected = {'[', 'Z', ']'}; + const auto result = json::to_ubjson(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_ubjson(result) == j); + } + + SECTION("[1,2,3,4,5]") + { + json j = json::parse("[1,2,3,4,5]"); + std::vector expected = {'[', 'i', 1, 'i', 2, 'i', 3, 'i', 4, 'i', 5, ']'}; + const auto result = json::to_ubjson(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_ubjson(result) == j); + } + + SECTION("[[[[]]]]") + { + json j = json::parse("[[[[]]]]"); + std::vector expected = {'[', '[', '[', '[', ']', ']', ']', ']'}; + const auto result = json::to_ubjson(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_ubjson(result) == j); + } + + SECTION("array with uint16_t elements") + { + json j(257, nullptr); + std::vector expected(j.size() + 2, 'Z'); // all null + expected[0] = '['; // opening array + expected[258] = ']'; // closing array + const auto result = json::to_ubjson(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_ubjson(result) == j); + } + + SECTION("array with uint32_t elements") + { + json j(65793, nullptr); + std::vector expected(j.size() + 2, 'Z'); // all null + expected[0] = '['; // opening array + expected[65794] = ']'; // closing array + const auto result = json::to_ubjson(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_ubjson(result) == j); + } + } + + SECTION("object") + { + SECTION("empty") + { + json j = json::object(); + std::vector expected = {'{', '}'}; + const auto result = json::to_ubjson(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_ubjson(result) == j); + } + + SECTION("{\"\":null}") + { + json j = {{"", nullptr}}; + std::vector expected = {'{', 'i', 0, 'Z', '}'}; + const auto result = json::to_ubjson(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_ubjson(result) == j); + } + + SECTION("{\"a\": {\"b\": {\"c\": {}}}}") + { + json j = json::parse("{\"a\": {\"b\": {\"c\": {}}}}"); + std::vector expected = + { + '{', 'i', 1, 'a', '{', 'i', 1, 'b', '{', 'i', 1, 'c', '{', '}', '}', '}', '}' + }; + const auto result = json::to_ubjson(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_ubjson(result) == j); + } + } + } + + SECTION("errors") + { + SECTION("empty byte vector") + { + CHECK_THROWS_AS(json::from_ubjson(std::vector()), json::parse_error&); + CHECK_THROWS_WITH(json::from_ubjson(std::vector()), + "[json.exception.parse_error.110] parse error at 1: unexpected end of input"); + } + + SECTION("too short byte vector") + { + } + + SECTION("unsupported bytes") + { + SECTION("concrete examples") + { + CHECK_THROWS_AS(json::from_cbor(std::vector({0x1c})), json::parse_error&); + CHECK_THROWS_WITH(json::from_cbor(std::vector({0x1c})), + "[json.exception.parse_error.112] parse error at 1: error reading CBOR; last byte: 0x1C"); + CHECK_THROWS_AS(json::from_cbor(std::vector({0xf8})), json::parse_error&); + CHECK_THROWS_WITH(json::from_cbor(std::vector({0xf8})), + "[json.exception.parse_error.112] parse error at 1: error reading CBOR; last byte: 0xF8"); + } + + SECTION("all unsupported bytes") + { + for (auto byte : + { + // ? + 0x1c, 0x1d, 0x1e, 0x1f, + // ? + 0x3c, 0x3d, 0x3e, 0x3f, + // byte strings + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + // byte strings + 0x58, 0x59, 0x5a, 0x5b, + // ? + 0x5c, 0x5d, 0x5e, + // byte string + 0x5f, + // ? + 0x7c, 0x7d, 0x7e, + // ? + 0x9c, 0x9d, 0x9e, + // ? + 0xbc, 0xbd, 0xbe, + // date/time + 0xc0, 0xc1, + // bignum + 0xc2, 0xc3, + // fraction + 0xc4, + // bigfloat + 0xc5, + // tagged item + 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, + // expected conversion + 0xd5, 0xd6, 0xd7, + // more tagged items + 0xd8, 0xd9, 0xda, 0xdb, + // ? + 0xdc, 0xdd, 0xde, 0xdf, + // (simple value) + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, + // undefined + 0xf7, + // simple value + 0xf8 + }) + { + CHECK_THROWS_AS(json::from_cbor(std::vector({static_cast(byte)})), json::parse_error&); + } + } + } + + SECTION("invalid string in map") + { + CHECK_THROWS_AS(json::from_cbor(std::vector({0xa1, 0xff, 0x01})), json::parse_error&); + CHECK_THROWS_WITH(json::from_cbor(std::vector({0xa1, 0xff, 0x01})), + "[json.exception.parse_error.113] parse error at 2: expected a CBOR string; last byte: 0xFF"); + } + + SECTION("strict mode") + { + std::vector vec = {0xf6, 0xf6}; + SECTION("non-strict mode") + { + const auto result = json::from_cbor(vec, false); + CHECK(result == json()); + } + + SECTION("strict mode") + { + CHECK_THROWS_AS(json::from_cbor(vec), json::parse_error&); + CHECK_THROWS_WITH(json::from_cbor(vec), + "[json.exception.parse_error.110] parse error at 2: expected end of input"); + } + } + } +} + +// use this testcase outside [hide] to run it with Valgrind +TEST_CASE("single CBOR roundtrip") +{ + SECTION("sample.json") + { + std::string filename = "test/data/json_testsuite/sample.json"; + + // parse JSON file + std::ifstream f_json(filename); + json j1 = json::parse(f_json); + + // parse MessagePack file + std::ifstream f_cbor(filename + ".cbor", std::ios::binary); + std::vector packed((std::istreambuf_iterator(f_cbor)), + std::istreambuf_iterator()); + json j2; + CHECK_NOTHROW(j2 = json::from_cbor(packed)); + + // compare parsed JSON values + CHECK(j1 == j2); + + SECTION("roundtrips") + { + SECTION("std::ostringstream") + { + std::ostringstream ss; + json::to_cbor(j1, ss); + json j3 = json::from_cbor(ss.str()); + CHECK(j1 == j3); + } + + SECTION("std::string") + { + std::string s; + json::to_cbor(j1, s); + json j3 = json::from_cbor(s); + CHECK(j1 == j3); + } + } + + // check with different start index + packed.insert(packed.begin(), 5, 0xff); + CHECK(j1 == json::from_cbor(packed.begin() + 5, packed.end())); + } +} + +TEST_CASE("CBOR roundtrips", "[hide]") +{ + SECTION("input from flynn") + { + for (std::string filename : + { + "test/data/json_nlohmann_tests/all_unicode.json", + "test/data/json.org/1.json", + "test/data/json.org/2.json", + "test/data/json.org/3.json", + "test/data/json.org/4.json", + "test/data/json.org/5.json", + "test/data/json_roundtrip/roundtrip01.json", + "test/data/json_roundtrip/roundtrip02.json", + "test/data/json_roundtrip/roundtrip03.json", + "test/data/json_roundtrip/roundtrip04.json", + "test/data/json_roundtrip/roundtrip05.json", + "test/data/json_roundtrip/roundtrip06.json", + "test/data/json_roundtrip/roundtrip07.json", + "test/data/json_roundtrip/roundtrip08.json", + "test/data/json_roundtrip/roundtrip09.json", + "test/data/json_roundtrip/roundtrip10.json", + "test/data/json_roundtrip/roundtrip11.json", + "test/data/json_roundtrip/roundtrip12.json", + "test/data/json_roundtrip/roundtrip13.json", + "test/data/json_roundtrip/roundtrip14.json", + "test/data/json_roundtrip/roundtrip15.json", + "test/data/json_roundtrip/roundtrip16.json", + "test/data/json_roundtrip/roundtrip17.json", + "test/data/json_roundtrip/roundtrip18.json", + "test/data/json_roundtrip/roundtrip19.json", + "test/data/json_roundtrip/roundtrip20.json", + "test/data/json_roundtrip/roundtrip21.json", + "test/data/json_roundtrip/roundtrip22.json", + "test/data/json_roundtrip/roundtrip23.json", + "test/data/json_roundtrip/roundtrip24.json", + "test/data/json_roundtrip/roundtrip25.json", + "test/data/json_roundtrip/roundtrip26.json", + "test/data/json_roundtrip/roundtrip27.json", + "test/data/json_roundtrip/roundtrip28.json", + "test/data/json_roundtrip/roundtrip29.json", + "test/data/json_roundtrip/roundtrip30.json", + "test/data/json_roundtrip/roundtrip31.json", + "test/data/json_roundtrip/roundtrip32.json", + "test/data/json_testsuite/sample.json", // kills AppVeyor + "test/data/json_tests/pass1.json", + "test/data/json_tests/pass2.json", + "test/data/json_tests/pass3.json", + "test/data/regression/floats.json", + "test/data/regression/signed_ints.json", + "test/data/regression/unsigned_ints.json", + "test/data/regression/working_file.json", + "test/data/nst_json_testsuite/test_parsing/y_array_arraysWithSpaces.json", + "test/data/nst_json_testsuite/test_parsing/y_array_empty-string.json", + "test/data/nst_json_testsuite/test_parsing/y_array_empty.json", + "test/data/nst_json_testsuite/test_parsing/y_array_ending_with_newline.json", + "test/data/nst_json_testsuite/test_parsing/y_array_false.json", + "test/data/nst_json_testsuite/test_parsing/y_array_heterogeneous.json", + "test/data/nst_json_testsuite/test_parsing/y_array_null.json", + "test/data/nst_json_testsuite/test_parsing/y_array_with_1_and_newline.json", + "test/data/nst_json_testsuite/test_parsing/y_array_with_leading_space.json", + "test/data/nst_json_testsuite/test_parsing/y_array_with_several_null.json", + "test/data/nst_json_testsuite/test_parsing/y_array_with_trailing_space.json", + "test/data/nst_json_testsuite/test_parsing/y_number.json", + "test/data/nst_json_testsuite/test_parsing/y_number_0e+1.json", + "test/data/nst_json_testsuite/test_parsing/y_number_0e1.json", + "test/data/nst_json_testsuite/test_parsing/y_number_after_space.json", + "test/data/nst_json_testsuite/test_parsing/y_number_double_close_to_zero.json", + "test/data/nst_json_testsuite/test_parsing/y_number_double_huge_neg_exp.json", + //"test/data/nst_json_testsuite/test_parsing/y_number_huge_exp.json", + "test/data/nst_json_testsuite/test_parsing/y_number_int_with_exp.json", + "test/data/nst_json_testsuite/test_parsing/y_number_minus_zero.json", + "test/data/nst_json_testsuite/test_parsing/y_number_negative_int.json", + "test/data/nst_json_testsuite/test_parsing/y_number_negative_one.json", + "test/data/nst_json_testsuite/test_parsing/y_number_negative_zero.json", + "test/data/nst_json_testsuite/test_parsing/y_number_real_capital_e.json", + "test/data/nst_json_testsuite/test_parsing/y_number_real_capital_e_neg_exp.json", + "test/data/nst_json_testsuite/test_parsing/y_number_real_capital_e_pos_exp.json", + "test/data/nst_json_testsuite/test_parsing/y_number_real_exponent.json", + "test/data/nst_json_testsuite/test_parsing/y_number_real_fraction_exponent.json", + "test/data/nst_json_testsuite/test_parsing/y_number_real_neg_exp.json", + //"test/data/nst_json_testsuite/test_parsing/y_number_real_neg_overflow.json", + "test/data/nst_json_testsuite/test_parsing/y_number_real_pos_exponent.json", + //"test/data/nst_json_testsuite/test_parsing/y_number_real_pos_overflow.json", + "test/data/nst_json_testsuite/test_parsing/y_number_real_underflow.json", + "test/data/nst_json_testsuite/test_parsing/y_number_simple_int.json", + "test/data/nst_json_testsuite/test_parsing/y_number_simple_real.json", + //"test/data/nst_json_testsuite/test_parsing/y_number_too_big_neg_int.json", + //"test/data/nst_json_testsuite/test_parsing/y_number_too_big_pos_int.json", + //"test/data/nst_json_testsuite/test_parsing/y_number_very_big_negative_int.json", + "test/data/nst_json_testsuite/test_parsing/y_object.json", + "test/data/nst_json_testsuite/test_parsing/y_object_basic.json", + "test/data/nst_json_testsuite/test_parsing/y_object_duplicated_key.json", + "test/data/nst_json_testsuite/test_parsing/y_object_duplicated_key_and_value.json", + "test/data/nst_json_testsuite/test_parsing/y_object_empty.json", + "test/data/nst_json_testsuite/test_parsing/y_object_empty_key.json", + "test/data/nst_json_testsuite/test_parsing/y_object_escaped_null_in_key.json", + "test/data/nst_json_testsuite/test_parsing/y_object_extreme_numbers.json", + "test/data/nst_json_testsuite/test_parsing/y_object_long_strings.json", + "test/data/nst_json_testsuite/test_parsing/y_object_simple.json", + "test/data/nst_json_testsuite/test_parsing/y_object_string_unicode.json", + "test/data/nst_json_testsuite/test_parsing/y_object_with_newlines.json", + "test/data/nst_json_testsuite/test_parsing/y_string_1_2_3_bytes_UTF-8_sequences.json", + "test/data/nst_json_testsuite/test_parsing/y_string_UTF-16_Surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json", + "test/data/nst_json_testsuite/test_parsing/y_string_accepted_surrogate_pair.json", + "test/data/nst_json_testsuite/test_parsing/y_string_accepted_surrogate_pairs.json", + "test/data/nst_json_testsuite/test_parsing/y_string_allowed_escapes.json", + "test/data/nst_json_testsuite/test_parsing/y_string_backslash_and_u_escaped_zero.json", + "test/data/nst_json_testsuite/test_parsing/y_string_backslash_doublequotes.json", + "test/data/nst_json_testsuite/test_parsing/y_string_comments.json", + "test/data/nst_json_testsuite/test_parsing/y_string_double_escape_a.json", + "test/data/nst_json_testsuite/test_parsing/y_string_double_escape_n.json", + "test/data/nst_json_testsuite/test_parsing/y_string_escaped_control_character.json", + "test/data/nst_json_testsuite/test_parsing/y_string_escaped_noncharacter.json", + "test/data/nst_json_testsuite/test_parsing/y_string_in_array.json", + "test/data/nst_json_testsuite/test_parsing/y_string_in_array_with_leading_space.json", + "test/data/nst_json_testsuite/test_parsing/y_string_last_surrogates_1_and_2.json", + "test/data/nst_json_testsuite/test_parsing/y_string_newline_uescaped.json", + "test/data/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+10FFFF.json", + "test/data/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+1FFFF.json", + "test/data/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+FFFF.json", + "test/data/nst_json_testsuite/test_parsing/y_string_null_escape.json", + "test/data/nst_json_testsuite/test_parsing/y_string_one-byte-utf-8.json", + "test/data/nst_json_testsuite/test_parsing/y_string_pi.json", + "test/data/nst_json_testsuite/test_parsing/y_string_simple_ascii.json", + "test/data/nst_json_testsuite/test_parsing/y_string_space.json", + "test/data/nst_json_testsuite/test_parsing/y_string_three-byte-utf-8.json", + "test/data/nst_json_testsuite/test_parsing/y_string_two-byte-utf-8.json", + "test/data/nst_json_testsuite/test_parsing/y_string_u+2028_line_sep.json", + "test/data/nst_json_testsuite/test_parsing/y_string_u+2029_par_sep.json", + "test/data/nst_json_testsuite/test_parsing/y_string_uEscape.json", + "test/data/nst_json_testsuite/test_parsing/y_string_unescaped_char_delete.json", + "test/data/nst_json_testsuite/test_parsing/y_string_unicode.json", + "test/data/nst_json_testsuite/test_parsing/y_string_unicodeEscapedBackslash.json", + "test/data/nst_json_testsuite/test_parsing/y_string_unicode_2.json", + "test/data/nst_json_testsuite/test_parsing/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json", + "test/data/nst_json_testsuite/test_parsing/y_string_unicode_U+2064_invisible_plus.json", + "test/data/nst_json_testsuite/test_parsing/y_string_unicode_escaped_double_quote.json", + // "test/data/nst_json_testsuite/test_parsing/y_string_utf16.json", + "test/data/nst_json_testsuite/test_parsing/y_string_utf8.json", + "test/data/nst_json_testsuite/test_parsing/y_string_with_del_character.json", + "test/data/nst_json_testsuite/test_parsing/y_structure_lonely_false.json", + "test/data/nst_json_testsuite/test_parsing/y_structure_lonely_int.json", + "test/data/nst_json_testsuite/test_parsing/y_structure_lonely_negative_real.json", + "test/data/nst_json_testsuite/test_parsing/y_structure_lonely_null.json", + "test/data/nst_json_testsuite/test_parsing/y_structure_lonely_string.json", + "test/data/nst_json_testsuite/test_parsing/y_structure_lonely_true.json", + "test/data/nst_json_testsuite/test_parsing/y_structure_string_empty.json", + "test/data/nst_json_testsuite/test_parsing/y_structure_trailing_newline.json", + "test/data/nst_json_testsuite/test_parsing/y_structure_true_in_array.json", + "test/data/nst_json_testsuite/test_parsing/y_structure_whitespace_array.json" + }) + { + CAPTURE(filename); + + // parse JSON file + std::ifstream f_json(filename); + json j1 = json::parse(f_json); + + SECTION("std::vector") + { + // parse CBOR file + std::ifstream f_cbor(filename + ".cbor", std::ios::binary); + std::vector packed( + (std::istreambuf_iterator(f_cbor)), + std::istreambuf_iterator()); + json j2; + CHECK_NOTHROW(j2 = json::from_cbor(packed)); + + // compare parsed JSON values + CHECK(j1 == j2); + } + + SECTION("std::ifstream") + { + // parse CBOR file + std::ifstream f_cbor(filename + ".cbor", std::ios::binary); + json j2; + CHECK_NOTHROW(j2 = json::from_cbor(f_cbor)); + + // compare parsed JSON values + CHECK(j1 == j2); + } + + SECTION("uint8_t* and size") + { + // parse CBOR file + std::ifstream f_cbor(filename + ".cbor", std::ios::binary); + std::vector packed( + (std::istreambuf_iterator(f_cbor)), + std::istreambuf_iterator()); + json j2; + CHECK_NOTHROW(j2 = json::from_cbor({packed.data(), packed.size()})); + + // compare parsed JSON values + CHECK(j1 == j2); + } + + SECTION("output to output adapters") + { + // parse CBOR file + std::ifstream f_cbor(filename + ".cbor", std::ios::binary); + std::vector packed( + (std::istreambuf_iterator(f_cbor)), + std::istreambuf_iterator()); + + SECTION("std::vector") + { + std::vector vec; + json::to_cbor(j1, vec); + CHECK(vec == packed); + } + } + } + } +} + +/* +TEST_CASE("Universal Binary JSON Specification Examples") +{ + for (std::string prefix : + { + "test/data/universal-binary-json-java/CouchDB4k", + "test/data/universal-binary-json-java/MediaContent", + "test/data/universal-binary-json-java/TwitterTimeline" + }) + { + CAPTURE(prefix); + + // parse JSON file + std::ifstream f_json(prefix + ".formatted.json"); + json j1 = json::parse(f_json); + + SECTION("std::vector") + { + // parse UBJSON file + std::ifstream f_ubjson(prefix + ".ubj", std::ios::binary); + std::vector packed( + (std::istreambuf_iterator(f_ubjson)), + std::istreambuf_iterator()); + json j2; + CHECK_NOTHROW(j2 = json::from_ubjson(packed)); + + // compare parsed JSON values + CHECK(j1 == j2); + } + } +} +*/ + + +/* +TEST_CASE("all first bytes", "[!throws]") +{ + // these bytes will fail immediately with exception parse_error.112 + std::set unsupported = + { + //// types not supported by this library + + // byte strings + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + // byte strings + 0x58, 0x59, 0x5a, 0x5b, 0x5f, + // date/time + 0xc0, 0xc1, + // bignum + 0xc2, 0xc3, + // decimal fracion + 0xc4, + // bigfloat + 0xc5, + // tagged item + 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, + 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd8, + 0xd9, 0xda, 0xdb, + // expected conversion + 0xd5, 0xd6, 0xd7, + // simple value + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xef, 0xf0, + 0xf1, 0xf2, 0xf3, + 0xf8, + // undefined + 0xf7, + + //// bytes not specified by CBOR + + 0x1c, 0x1d, 0x1e, 0x1f, + 0x3c, 0x3d, 0x3e, 0x3f, + 0x5c, 0x5d, 0x5e, + 0x7c, 0x7d, 0x7e, + 0x9c, 0x9d, 0x9e, + 0xbc, 0xbd, 0xbe, + 0xdc, 0xdd, 0xde, 0xdf, + 0xee, + 0xfc, 0xfe, 0xfd, + + /// break cannot be the first byte + + 0xff + }; + + for (auto i = 0; i < 256; ++i) + { + const auto byte = static_cast(i); + + try + { + json::from_cbor(std::vector(1, byte)); + } + catch (const json::parse_error& e) + { + // check that parse_error.112 is only thrown if the + // first byte is in the unsupported set + CAPTURE(e.what()); + if (std::find(unsupported.begin(), unsupported.end(), byte) != unsupported.end()) + { + CHECK(e.id == 112); + } + else + { + CHECK(e.id != 112); + } + } + } +} +*/ + +/* +TEST_CASE("examples from RFC 7049 Appendix A") +{ + SECTION("numbers") + { + CHECK(json::to_cbor(json::parse("0")) == std::vector({0x00})); + CHECK(json::parse("0") == json::from_cbor(std::vector({0x00}))); + + CHECK(json::to_cbor(json::parse("1")) == std::vector({0x01})); + CHECK(json::parse("1") == json::from_cbor(std::vector({0x01}))); + + CHECK(json::to_cbor(json::parse("10")) == std::vector({0x0a})); + CHECK(json::parse("10") == json::from_cbor(std::vector({0x0a}))); + + CHECK(json::to_cbor(json::parse("23")) == std::vector({0x17})); + CHECK(json::parse("23") == json::from_cbor(std::vector({0x17}))); + + CHECK(json::to_cbor(json::parse("24")) == std::vector({0x18, 0x18})); + CHECK(json::parse("24") == json::from_cbor(std::vector({0x18, 0x18}))); + + CHECK(json::to_cbor(json::parse("25")) == std::vector({0x18, 0x19})); + CHECK(json::parse("25") == json::from_cbor(std::vector({0x18, 0x19}))); + + CHECK(json::to_cbor(json::parse("100")) == std::vector({0x18, 0x64})); + CHECK(json::parse("100") == json::from_cbor(std::vector({0x18, 0x64}))); + + CHECK(json::to_cbor(json::parse("1000")) == std::vector({0x19, 0x03, 0xe8})); + CHECK(json::parse("1000") == json::from_cbor(std::vector({0x19, 0x03, 0xe8}))); + + CHECK(json::to_cbor(json::parse("1000000")) == std::vector({0x1a, 0x00, 0x0f, 0x42, 0x40})); + CHECK(json::parse("1000000") == json::from_cbor(std::vector({0x1a, 0x00, 0x0f, 0x42, 0x40}))); + + CHECK(json::to_cbor(json::parse("1000000000000")) == std::vector({0x1b, 0x00, 0x00, 0x00, 0xe8, 0xd4, 0xa5, 0x10, 0x00})); + CHECK(json::parse("1000000000000") == json::from_cbor(std::vector({0x1b, 0x00, 0x00, 0x00, 0xe8, 0xd4, 0xa5, 0x10, 0x00}))); + + CHECK(json::to_cbor(json::parse("18446744073709551615")) == std::vector({0x1b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff})); + CHECK(json::parse("18446744073709551615") == json::from_cbor(std::vector({0x1b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}))); + + // positive bignum is not supported + //CHECK(json::to_cbor(json::parse("18446744073709551616")) == std::vector({0xc2, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + //CHECK(json::parse("18446744073709551616") == json::from_cbor(std::vector({0xc2, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}))); + + //CHECK(json::to_cbor(json::parse("-18446744073709551616")) == std::vector({0x3b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff})); + //CHECK(json::parse("-18446744073709551616") == json::from_cbor(std::vector({0x3b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}))); + + // negative bignum is not supported + //CHECK(json::to_cbor(json::parse("-18446744073709551617")) == std::vector({0xc3, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + //CHECK(json::parse("-18446744073709551617") == json::from_cbor(std::vector({0xc3, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}))); + + CHECK(json::to_cbor(json::parse("-1")) == std::vector({0x20})); + CHECK(json::parse("-1") == json::from_cbor(std::vector({0x20}))); + + CHECK(json::to_cbor(json::parse("-10")) == std::vector({0x29})); + CHECK(json::parse("-10") == json::from_cbor(std::vector({0x29}))); + + CHECK(json::to_cbor(json::parse("-100")) == std::vector({0x38, 0x63})); + CHECK(json::parse("-100") == json::from_cbor(std::vector({0x38, 0x63}))); + + CHECK(json::to_cbor(json::parse("-1000")) == std::vector({0x39, 0x03, 0xe7})); + CHECK(json::parse("-1000") == json::from_cbor(std::vector({0x39, 0x03, 0xe7}))); + + // half-precision float + //CHECK(json::to_cbor(json::parse("0.0")) == std::vector({0xf9, 0x00, 0x00})); + CHECK(json::parse("0.0") == json::from_cbor(std::vector({0xf9, 0x00, 0x00}))); + + // half-precision float + //CHECK(json::to_cbor(json::parse("-0.0")) == std::vector({0xf9, 0x80, 0x00})); + CHECK(json::parse("-0.0") == json::from_cbor(std::vector({0xf9, 0x80, 0x00}))); + + // half-precision float + //CHECK(json::to_cbor(json::parse("1.0")) == std::vector({0xf9, 0x3c, 0x00})); + CHECK(json::parse("1.0") == json::from_cbor(std::vector({0xf9, 0x3c, 0x00}))); + + CHECK(json::to_cbor(json::parse("1.1")) == std::vector({0xfb, 0x3f, 0xf1, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9a})); + CHECK(json::parse("1.1") == json::from_cbor(std::vector({0xfb, 0x3f, 0xf1, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9a}))); + + // half-precision float + //CHECK(json::to_cbor(json::parse("1.5")) == std::vector({0xf9, 0x3e, 0x00})); + CHECK(json::parse("1.5") == json::from_cbor(std::vector({0xf9, 0x3e, 0x00}))); + + // half-precision float + //CHECK(json::to_cbor(json::parse("65504.0")) == std::vector({0xf9, 0x7b, 0xff})); + CHECK(json::parse("65504.0") == json::from_cbor(std::vector({0xf9, 0x7b, 0xff}))); + + //CHECK(json::to_cbor(json::parse("100000.0")) == std::vector({0xfa, 0x47, 0xc3, 0x50, 0x00})); + CHECK(json::parse("100000.0") == json::from_cbor(std::vector({0xfa, 0x47, 0xc3, 0x50, 0x00}))); + + //CHECK(json::to_cbor(json::parse("3.4028234663852886e+38")) == std::vector({0xfa, 0x7f, 0x7f, 0xff, 0xff})); + CHECK(json::parse("3.4028234663852886e+38") == json::from_cbor(std::vector({0xfa, 0x7f, 0x7f, 0xff, 0xff}))); + + CHECK(json::to_cbor(json::parse("1.0e+300")) == std::vector({0xfb, 0x7e, 0x37, 0xe4, 0x3c, 0x88, 0x00, 0x75, 0x9c})); + CHECK(json::parse("1.0e+300") == json::from_cbor(std::vector({0xfb, 0x7e, 0x37, 0xe4, 0x3c, 0x88, 0x00, 0x75, 0x9c}))); + + // half-precision float + //CHECK(json::to_cbor(json::parse("5.960464477539063e-8")) == std::vector({0xf9, 0x00, 0x01})); + CHECK(json::parse("-4.0") == json::from_cbor(std::vector({0xf9, 0xc4, 0x00}))); + + // half-precision float + //CHECK(json::to_cbor(json::parse("0.00006103515625")) == std::vector({0xf9, 0x04, 0x00})); + CHECK(json::parse("-4.0") == json::from_cbor(std::vector({0xf9, 0xc4, 0x00}))); + + // half-precision float + //CHECK(json::to_cbor(json::parse("-4.0")) == std::vector({0xf9, 0xc4, 0x00})); + CHECK(json::parse("-4.0") == json::from_cbor(std::vector({0xf9, 0xc4, 0x00}))); + + CHECK(json::to_cbor(json::parse("-4.1")) == std::vector({0xfb, 0xc0, 0x10, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66})); + CHECK(json::parse("-4.1") == json::from_cbor(std::vector({0xfb, 0xc0, 0x10, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66}))); + } + + SECTION("simple values") + { + CHECK(json::to_cbor(json::parse("false")) == std::vector({0xf4})); + CHECK(json::parse("false") == json::from_cbor(std::vector({0xf4}))); + + CHECK(json::to_cbor(json::parse("true")) == std::vector({0xf5})); + CHECK(json::parse("true") == json::from_cbor(std::vector({0xf5}))); + + CHECK(json::to_cbor(json::parse("true")) == std::vector({0xf5})); + CHECK(json::parse("true") == json::from_cbor(std::vector({0xf5}))); + } + + SECTION("strings") + { + CHECK(json::to_cbor(json::parse("\"\"")) == std::vector({0x60})); + CHECK(json::parse("\"\"") == json::from_cbor(std::vector({0x60}))); + + CHECK(json::to_cbor(json::parse("\"a\"")) == std::vector({0x61, 0x61})); + CHECK(json::parse("\"a\"") == json::from_cbor(std::vector({0x61, 0x61}))); + + CHECK(json::to_cbor(json::parse("\"IETF\"")) == std::vector({0x64, 0x49, 0x45, 0x54, 0x46})); + CHECK(json::parse("\"IETF\"") == json::from_cbor(std::vector({0x64, 0x49, 0x45, 0x54, 0x46}))); + + CHECK(json::to_cbor(json::parse("\"\\u00fc\"")) == std::vector({0x62, 0xc3, 0xbc})); + CHECK(json::parse("\"\\u00fc\"") == json::from_cbor(std::vector({0x62, 0xc3, 0xbc}))); + + CHECK(json::to_cbor(json::parse("\"\\u6c34\"")) == std::vector({0x63, 0xe6, 0xb0, 0xb4})); + CHECK(json::parse("\"\\u6c34\"") == json::from_cbor(std::vector({0x63, 0xe6, 0xb0, 0xb4}))); + + CHECK(json::to_cbor(json::parse("\"\\ud800\\udd51\"")) == std::vector({0x64, 0xf0, 0x90, 0x85, 0x91})); + CHECK(json::parse("\"\\ud800\\udd51\"") == json::from_cbor(std::vector({0x64, 0xf0, 0x90, 0x85, 0x91}))); + + // indefinite length strings + CHECK(json::parse("\"streaming\"") == json::from_cbor(std::vector({0x7f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0xff}))); + } + + SECTION("arrays") + { + CHECK(json::to_cbor(json::parse("[]")) == std::vector({0x80})); + CHECK(json::parse("[]") == json::from_cbor(std::vector({0x80}))); + + CHECK(json::to_cbor(json::parse("[1, 2, 3]")) == std::vector({0x83, 0x01, 0x02, 0x03})); + CHECK(json::parse("[1, 2, 3]") == json::from_cbor(std::vector({0x83, 0x01, 0x02, 0x03}))); + + CHECK(json::to_cbor(json::parse("[1, [2, 3], [4, 5]]")) == std::vector({0x83, 0x01, 0x82, 0x02, 0x03, 0x82, 0x04, 0x05})); + CHECK(json::parse("[1, [2, 3], [4, 5]]") == json::from_cbor(std::vector({0x83, 0x01, 0x82, 0x02, 0x03, 0x82, 0x04, 0x05}))); + + CHECK(json::to_cbor(json::parse("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]")) == std::vector({0x98, 0x19, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x18, 0x18, 0x19})); + CHECK(json::parse("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]") == json::from_cbor(std::vector({0x98, 0x19, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x18, 0x18, 0x19}))); + + // indefinite length arrays + CHECK(json::parse("[]") == json::from_cbor(std::vector({0x9f, 0xff}))); + CHECK(json::parse("[1, [2, 3], [4, 5]] ") == json::from_cbor(std::vector({0x9f, 0x01, 0x82, 0x02, 0x03, 0x9f, 0x04, 0x05, 0xff, 0xff}))); + CHECK(json::parse("[1, [2, 3], [4, 5]]") == json::from_cbor(std::vector({0x9f, 0x01, 0x82, 0x02, 0x03, 0x82, 0x04, 0x05, 0xff}))); + CHECK(json::parse("[1, [2, 3], [4, 5]]") == json::from_cbor(std::vector({0x83, 0x01, 0x82, 0x02, 0x03, 0x9f, 0x04, 0x05, 0xff}))); + CHECK(json::parse("[1, [2, 3], [4, 5]]") == json::from_cbor(std::vector({0x83, 0x01, 0x9f, 0x02, 0x03, 0xff, 0x82, 0x04, 0x05}))); + CHECK(json::parse("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]") == json::from_cbor(std::vector({0x9f, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x18, 0x18, 0x19, 0xff}))); + } + + SECTION("objects") + { + CHECK(json::to_cbor(json::parse("{}")) == std::vector({0xa0})); + CHECK(json::parse("{}") == json::from_cbor(std::vector({0xa0}))); + + CHECK(json::to_cbor(json::parse("{\"a\": 1, \"b\": [2, 3]}")) == std::vector({0xa2, 0x61, 0x61, 0x01, 0x61, 0x62, 0x82, 0x02, 0x03})); + CHECK(json::parse("{\"a\": 1, \"b\": [2, 3]}") == json::from_cbor(std::vector({0xa2, 0x61, 0x61, 0x01, 0x61, 0x62, 0x82, 0x02, 0x03}))); + + CHECK(json::to_cbor(json::parse("[\"a\", {\"b\": \"c\"}]")) == std::vector({0x82, 0x61, 0x61, 0xa1, 0x61, 0x62, 0x61, 0x63})); + CHECK(json::parse("[\"a\", {\"b\": \"c\"}]") == json::from_cbor(std::vector({0x82, 0x61, 0x61, 0xa1, 0x61, 0x62, 0x61, 0x63}))); + + CHECK(json::to_cbor(json::parse("{\"a\": \"A\", \"b\": \"B\", \"c\": \"C\", \"d\": \"D\", \"e\": \"E\"}")) == std::vector({0xa5, 0x61, 0x61, 0x61, 0x41, 0x61, 0x62, 0x61, 0x42, 0x61, 0x63, 0x61, 0x43, 0x61, 0x64, 0x61, 0x44, 0x61, 0x65, 0x61, 0x45})); + CHECK(json::parse("{\"a\": \"A\", \"b\": \"B\", \"c\": \"C\", \"d\": \"D\", \"e\": \"E\"}") == json::from_cbor(std::vector({0xa5, 0x61, 0x61, 0x61, 0x41, 0x61, 0x62, 0x61, 0x42, 0x61, 0x63, 0x61, 0x43, 0x61, 0x64, 0x61, 0x44, 0x61, 0x65, 0x61, 0x45}))); + + // indefinite length objects + CHECK(json::parse("{\"a\": 1, \"b\": [2, 3]}") == json::from_cbor(std::vector({0xbf, 0x61, 0x61, 0x01, 0x61, 0x62, 0x9f, 0x02, 0x03, 0xff, 0xff}))); + CHECK(json::parse("[\"a\", {\"b\": \"c\"}]") == json::from_cbor(std::vector({0x82, 0x61, 0x61, 0xbf, 0x61, 0x62, 0x61, 0x63, 0xff}))); + CHECK(json::parse("{\"Fun\": true, \"Amt\": -2}") == json::from_cbor(std::vector({0xbf, 0x63, 0x46, 0x75, 0x6e, 0xf5, 0x63, 0x41, 0x6d, 0x74, 0x21, 0xff}))); + } +} +*/