diff --git a/docs/mkdocs/docs/features/binary_formats/bjdata.md b/docs/mkdocs/docs/features/binary_formats/bjdata.md index a89a228858..c24290097f 100644 --- a/docs/mkdocs/docs/features/binary_formats/bjdata.md +++ b/docs/mkdocs/docs/features/binary_formats/bjdata.md @@ -2,10 +2,10 @@ The [BJData format](https://neurojson.org) was derived from and improved upon [Universal Binary JSON(UBJSON)](https://ubjson.org) specification (Draft 12). Specifically, it introduces an optimized -array container for efficient storage of N-dimensional packed arrays (**ND-arrays**); it also adds 4 new type markers - -`[u] - uint16`, `[m] - uint32`, `[M] - uint64` and `[h] - float16` - to unambiguously map common binary numeric types; -furthermore, it uses little-endian (LE) to store all numerics instead of big-endian (BE) as in UBJSON to avoid -unnecessary conversions on commonly available platforms. +array container for efficient storage of N-dimensional packed arrays (**ND-arrays**); it also adds 5 new type markers - +`[u] - uint16`, `[m] - uint32`, `[M] - uint64`, `[h] - float16` and `[B] - byte` - to unambiguously map common binary +numeric types; furthermore, it uses little-endian (LE) to store all numerics instead of big-endian (BE) as in UBJSON to +avoid unnecessary conversions on commonly available platforms. Compared to other binary JSON-like formats such as MessagePack and CBOR, both BJData and UBJSON demonstrate a rare combination of being both binary and **quasi-human-readable**. This is because all semantic elements in BJData and @@ -49,6 +49,7 @@ The library uses the following mapping from JSON values types to BJData types ac | string | *with shortest length indicator* | string | `S` | | array | *see notes on optimized format/ND-array* | array | `[` | | object | *see notes on optimized format* | map | `{` | +| binary | *see notes on binary values* | array | `[$B` | !!! success "Complete mapping" @@ -128,15 +129,17 @@ The library uses the following mapping from JSON values types to BJData types ac Due to diminished space saving, hampered readability, and increased security risks, in BJData, the allowed data types following the `$` marker in an optimized array and object container are restricted to - **non-zero-fixed-length** data types. Therefore, the valid optimized type markers can only be one of `UiuImlMLhdDC`. - This also means other variable (`[{SH`) or zero-length types (`TFN`) can not be used in an optimized array or object - in BJData. + **non-zero-fixed-length** data types. Therefore, the valid optimized type markers can only be one of + `UiuImlMLhdDCB`. This also means other variable (`[{SH`) or zero-length types (`TFN`) can not be used in an + optimized array or object in BJData. !!! info "Binary values" - If the JSON data contains the binary type, the value stored is a list of integers, as suggested by the BJData - documentation. In particular, this means that the serialization and the deserialization of JSON containing binary - values into BJData and back will result in a different JSON object. + BJData provides a dedicated `B` marker (defined in the [BJData specification (Draft 3)][BJDataBinArr]) that is used + in optimized arrays to designate binary data. This means that, unlike UBJSON, binary data can be both serialized and + deserialized. + + [BJDataBinArr]: https://github.com/NeuroJSON/bjdata/blob/master/Binary_JData_Specification.md#optimized-binary-array) ??? example @@ -171,11 +174,13 @@ The library maps BJData types to JSON value types as follows: | int32 | number_integer | `l` | | uint64 | number_unsigned | `M` | | int64 | number_integer | `L` | +| byte | number_unsigned | `B` | | string | string | `S` | | char | string | `C` | | array | array (optimized values are supported) | `[` | | ND-array | object (in JData annotated array format)|`[$.#[.`| | object | object (optimized values are supported) | `{` | +| binary | binary (strongly-typed byte array) | `[$B` | !!! success "Complete mapping" diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index a6e100e761..d832b8dd5d 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -2310,6 +2310,16 @@ class binary_reader case 'Z': // null return sax->null(); + case 'B': // byte + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint8_t number{}; + return get_number(input_format, number) && sax->number_unsigned(number); + } + case 'U': { std::uint8_t number{}; @@ -2510,7 +2520,7 @@ class binary_reader return false; } - if (size_and_type.second == 'C') + if (size_and_type.second == 'C' || size_and_type.second == 'B') { size_and_type.second = 'U'; } @@ -2532,6 +2542,13 @@ class binary_reader return (sax->end_array() && sax->end_object()); } + // If BJData type marker is 'B' decode as binary + if (input_format == input_format_t::bjdata && size_and_type.first != npos && size_and_type.second == 'B') + { + binary_t result; + return get_binary(input_format, size_and_type.first, result) && sax->binary(result); + } + if (size_and_type.first != npos) { if (JSON_HEDLEY_UNLIKELY(!sax->start_array(size_and_type.first))) @@ -2973,6 +2990,7 @@ class binary_reader #define JSON_BINARY_READER_MAKE_BJD_TYPES_MAP_ \ make_array( \ + bjd_type{'B', "byte"}, \ bjd_type{'C', "char"}, \ bjd_type{'D', "double"}, \ bjd_type{'I', "int16"}, \ diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp index 11c46311b3..57f7cfed42 100644 --- a/include/nlohmann/detail/output/binary_writer.hpp +++ b/include/nlohmann/detail/output/binary_writer.hpp @@ -847,11 +847,11 @@ class binary_writer oa->write_character(to_char_type('[')); } - if (use_type && !j.m_data.m_value.binary->empty()) + if (use_type && (use_bjdata || !j.m_data.m_value.binary->empty())) { JSON_ASSERT(use_count); oa->write_character(to_char_type('$')); - oa->write_character('U'); + oa->write_character(use_bjdata ? 'B' : 'U'); } if (use_count) @@ -870,7 +870,7 @@ class binary_writer { for (size_t i = 0; i < j.m_data.m_value.binary->size(); ++i) { - oa->write_character(to_char_type('U')); + oa->write_character(to_char_type(use_bjdata ? 'B' : 'U')); oa->write_character(j.m_data.m_value.binary->data()[i]); } } @@ -1618,7 +1618,8 @@ class binary_writer bool write_bjdata_ndarray(const typename BasicJsonType::object_t& value, const bool use_count, const bool use_type) { std::map bjdtype = {{"uint8", 'U'}, {"int8", 'i'}, {"uint16", 'u'}, {"int16", 'I'}, - {"uint32", 'm'}, {"int32", 'l'}, {"uint64", 'M'}, {"int64", 'L'}, {"single", 'd'}, {"double", 'D'}, {"char", 'C'} + {"uint32", 'm'}, {"int32", 'l'}, {"uint64", 'M'}, {"int64", 'L'}, {"single", 'd'}, {"double", 'D'}, + {"char", 'C'}, {"byte", 'B'} }; string_t key = "_ArrayType_"; @@ -1651,7 +1652,7 @@ class binary_writer write_ubjson(value.at(key), use_count, use_type, true, true); key = "_ArrayData_"; - if (dtype == 'U' || dtype == 'C') + if (dtype == 'U' || dtype == 'C' || dtype == 'B') { for (const auto& el : value.at(key)) { diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index a6b4c3a713..90760659e8 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -11563,6 +11563,16 @@ class binary_reader case 'Z': // null return sax->null(); + case 'B': // byte + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint8_t number{}; + return get_number(input_format, number) && sax->number_unsigned(number); + } + case 'U': { std::uint8_t number{}; @@ -11763,7 +11773,7 @@ class binary_reader return false; } - if (size_and_type.second == 'C') + if (size_and_type.second == 'C' || size_and_type.second == 'B') { size_and_type.second = 'U'; } @@ -11785,6 +11795,13 @@ class binary_reader return (sax->end_array() && sax->end_object()); } + // If BJData type marker is 'B' decode as binary + if (input_format == input_format_t::bjdata && size_and_type.first != npos && size_and_type.second == 'B') + { + binary_t result; + return get_binary(input_format, size_and_type.first, result) && sax->binary(result); + } + if (size_and_type.first != npos) { if (JSON_HEDLEY_UNLIKELY(!sax->start_array(size_and_type.first))) @@ -12226,6 +12243,7 @@ class binary_reader #define JSON_BINARY_READER_MAKE_BJD_TYPES_MAP_ \ make_array( \ + bjd_type{'B', "byte"}, \ bjd_type{'C', "char"}, \ bjd_type{'D', "double"}, \ bjd_type{'I', "int16"}, \ @@ -15994,11 +16012,11 @@ class binary_writer oa->write_character(to_char_type('[')); } - if (use_type && !j.m_data.m_value.binary->empty()) + if (use_type && (use_bjdata || !j.m_data.m_value.binary->empty())) { JSON_ASSERT(use_count); oa->write_character(to_char_type('$')); - oa->write_character('U'); + oa->write_character(use_bjdata ? 'B' : 'U'); } if (use_count) @@ -16017,7 +16035,7 @@ class binary_writer { for (size_t i = 0; i < j.m_data.m_value.binary->size(); ++i) { - oa->write_character(to_char_type('U')); + oa->write_character(to_char_type(use_bjdata ? 'B' : 'U')); oa->write_character(j.m_data.m_value.binary->data()[i]); } } @@ -16765,7 +16783,8 @@ class binary_writer bool write_bjdata_ndarray(const typename BasicJsonType::object_t& value, const bool use_count, const bool use_type) { std::map bjdtype = {{"uint8", 'U'}, {"int8", 'i'}, {"uint16", 'u'}, {"int16", 'I'}, - {"uint32", 'm'}, {"int32", 'l'}, {"uint64", 'M'}, {"int64", 'L'}, {"single", 'd'}, {"double", 'D'}, {"char", 'C'} + {"uint32", 'm'}, {"int32", 'l'}, {"uint64", 'M'}, {"int64", 'L'}, {"single", 'd'}, {"double", 'D'}, + {"char", 'C'}, {"byte", 'B'} }; string_t key = "_ArrayType_"; @@ -16798,7 +16817,7 @@ class binary_writer write_ubjson(value.at(key), use_count, use_type, true, true); key = "_ArrayData_"; - if (dtype == 'U' || dtype == 'C') + if (dtype == 'U' || dtype == 'C' || dtype == 'B') { for (const auto& el : value.at(key)) { diff --git a/tests/src/unit-bjdata.cpp b/tests/src/unit-bjdata.cpp index a753bc3b1b..e47311599d 100644 --- a/tests/src/unit-bjdata.cpp +++ b/tests/src/unit-bjdata.cpp @@ -267,6 +267,34 @@ TEST_CASE("BJData") } } + SECTION("byte") + { + SECTION("0..255 (uint8)") + { + for (size_t i = 0; i <= 255; ++i) + { + CAPTURE(i) + + // create JSON value with integer number (no byte type in JSON) + json j = -1; + j.get_ref() = static_cast(i); + + // check type + CHECK(j.is_number_integer()); + + // create byte vector + std::vector const value + { + static_cast('B'), + static_cast(i), + }; + + // compare value + CHECK(json::from_bjdata(value) == j); + } + } + } + SECTION("number") { SECTION("signed") @@ -1514,11 +1542,8 @@ TEST_CASE("BJData") // create expected byte vector std::vector expected; expected.push_back(static_cast('[')); - if (N != 0) - { - expected.push_back(static_cast('$')); - expected.push_back(static_cast('U')); - } + expected.push_back(static_cast('$')); + expected.push_back(static_cast('B')); expected.push_back(static_cast('#')); expected.push_back(static_cast('i')); expected.push_back(static_cast(N)); @@ -1530,14 +1555,7 @@ TEST_CASE("BJData") // compare result + size const auto result = json::to_bjdata(j, true, true); CHECK(result == expected); - if (N == 0) - { - CHECK(result.size() == N + 4); - } - else - { - CHECK(result.size() == N + 6); - } + CHECK(result.size() == N + 6); // check that no null byte is appended if (N > 0) @@ -1545,10 +1563,9 @@ TEST_CASE("BJData") CHECK(result.back() != '\x00'); } - // roundtrip only works to an array of numbers - json j_out = s; - CHECK(json::from_bjdata(result) == j_out); - CHECK(json::from_bjdata(result, true, false) == j_out); + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); } } @@ -1566,7 +1583,7 @@ TEST_CASE("BJData") std::vector expected; expected.push_back(static_cast('[')); expected.push_back(static_cast('$')); - expected.push_back(static_cast('U')); + expected.push_back(static_cast('B')); expected.push_back(static_cast('#')); expected.push_back(static_cast('U')); expected.push_back(static_cast(N)); @@ -1582,10 +1599,9 @@ TEST_CASE("BJData") // check that no null byte is appended CHECK(result.back() != '\x00'); - // roundtrip only works to an array of numbers - json j_out = s; - CHECK(json::from_bjdata(result) == j_out); - CHECK(json::from_bjdata(result, true, false) == j_out); + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); } } @@ -1606,7 +1622,7 @@ TEST_CASE("BJData") std::vector expected(N + 7, 'x'); expected[0] = '['; expected[1] = '$'; - expected[2] = 'U'; + expected[2] = 'B'; expected[3] = '#'; expected[4] = 'I'; expected[5] = static_cast(N & 0xFF); @@ -1619,10 +1635,9 @@ TEST_CASE("BJData") // check that no null byte is appended CHECK(result.back() != '\x00'); - // roundtrip only works to an array of numbers - json j_out = s; - CHECK(json::from_bjdata(result) == j_out); - CHECK(json::from_bjdata(result, true, false) == j_out); + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); } } @@ -1643,7 +1658,7 @@ TEST_CASE("BJData") std::vector expected(N + 7, 'x'); expected[0] = '['; expected[1] = '$'; - expected[2] = 'U'; + expected[2] = 'B'; expected[3] = '#'; expected[4] = 'u'; expected[5] = static_cast(N & 0xFF); @@ -1656,10 +1671,9 @@ TEST_CASE("BJData") // check that no null byte is appended CHECK(result.back() != '\x00'); - // roundtrip only works to an array of numbers - json j_out = s; - CHECK(json::from_bjdata(result) == j_out); - CHECK(json::from_bjdata(result, true, false) == j_out); + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); } } @@ -1680,7 +1694,7 @@ TEST_CASE("BJData") std::vector expected(N + 9, 'x'); expected[0] = '['; expected[1] = '$'; - expected[2] = 'U'; + expected[2] = 'B'; expected[3] = '#'; expected[4] = 'l'; expected[5] = static_cast(N & 0xFF); @@ -1695,10 +1709,9 @@ TEST_CASE("BJData") // check that no null byte is appended CHECK(result.back() != '\x00'); - // roundtrip only works to an array of numbers - json j_out = s; - CHECK(json::from_bjdata(result) == j_out); - CHECK(json::from_bjdata(result, true, false) == j_out); + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); } } @@ -1714,7 +1727,7 @@ TEST_CASE("BJData") expected.push_back(static_cast('[')); for (std::size_t i = 0; i < N; ++i) { - expected.push_back(static_cast('U')); + expected.push_back(static_cast('B')); expected.push_back(static_cast(0x78)); } expected.push_back(static_cast(']')); @@ -1742,7 +1755,7 @@ TEST_CASE("BJData") for (size_t i = 0; i < N; ++i) { - expected.push_back(static_cast('U')); + expected.push_back(static_cast('B')); expected.push_back(static_cast(0x78)); } @@ -2334,6 +2347,7 @@ TEST_CASE("BJData") std::vector const v_D = {'[', '#', 'i', 2, 'D', 0x4a, 0xd8, 0x12, 0x4d, 0xfb, 0x21, 0x09, 0x40, 'D', 0x4a, 0xd8, 0x12, 0x4d, 0xfb, 0x21, 0x09, 0x40}; std::vector const v_S = {'[', '#', 'i', 2, 'S', 'i', 1, 'a', 'S', 'i', 1, 'a'}; std::vector const v_C = {'[', '#', 'i', 2, 'C', 'a', 'C', 'a'}; + std::vector const v_B = {'[', '#', 'i', 2, 'B', 0xFF, 'B', 0xFF}; // check if vector is parsed correctly CHECK(json::from_bjdata(v_TU) == json({true, true})); @@ -2351,6 +2365,7 @@ TEST_CASE("BJData") CHECK(json::from_bjdata(v_D) == json({3.1415926, 3.1415926})); CHECK(json::from_bjdata(v_S) == json({"a", "a"})); CHECK(json::from_bjdata(v_C) == json({"a", "a"})); + CHECK(json::from_bjdata(v_B) == json({255, 255})); // roundtrip: output should be optimized CHECK(json::to_bjdata(json::from_bjdata(v_T), true) == v_T); @@ -2367,6 +2382,7 @@ TEST_CASE("BJData") CHECK(json::to_bjdata(json::from_bjdata(v_D), true) == v_D); CHECK(json::to_bjdata(json::from_bjdata(v_S), true) == v_S); CHECK(json::to_bjdata(json::from_bjdata(v_C), true) == v_S); // char is serialized to string + CHECK(json::to_bjdata(json::from_bjdata(v_B), true) == v_U); // byte is serialized to uint8 } SECTION("optimized version (type and length)") @@ -2383,6 +2399,7 @@ TEST_CASE("BJData") std::vector const v_D = {'[', '$', 'D', '#', 'i', 2, 0x4a, 0xd8, 0x12, 0x4d, 0xfb, 0x21, 0x09, 0x40, 0x4a, 0xd8, 0x12, 0x4d, 0xfb, 0x21, 0x09, 0x40}; std::vector const v_S = {'[', '#', 'i', 2, 'S', 'i', 1, 'a', 'S', 'i', 1, 'a'}; std::vector const v_C = {'[', '$', 'C', '#', 'i', 2, 'a', 'a'}; + std::vector const v_B = {'[', '$', 'B', '#', 'i', 2, 0xFF, 0xFF}; // check if vector is parsed correctly CHECK(json::from_bjdata(v_i) == json({127, 127})); @@ -2396,6 +2413,7 @@ TEST_CASE("BJData") CHECK(json::from_bjdata(v_D) == json({3.1415926, 3.1415926})); CHECK(json::from_bjdata(v_S) == json({"a", "a"})); CHECK(json::from_bjdata(v_C) == json({"a", "a"})); + CHECK(json::from_bjdata(v_B) == json::binary(std::vector({static_cast(255), static_cast(255)}))); // roundtrip: output should be optimized std::vector const v_empty = {'[', '#', 'i', 0}; @@ -2410,6 +2428,7 @@ TEST_CASE("BJData") CHECK(json::to_bjdata(json::from_bjdata(v_D), true, true) == v_D); CHECK(json::to_bjdata(json::from_bjdata(v_S), true, true) == v_S); CHECK(json::to_bjdata(json::from_bjdata(v_C), true, true) == v_S); // char is serialized to string + CHECK(json::to_bjdata(json::from_bjdata(v_B), true, true) == v_B); } SECTION("optimized ndarray (type and vector-size as optimized 1D array)") @@ -2428,6 +2447,7 @@ TEST_CASE("BJData") std::vector const v_D = {'[', '$', 'D', '#', '[', '$', 'i', '#', 'i', 2, 1, 2, 0x4a, 0xd8, 0x12, 0x4d, 0xfb, 0x21, 0x09, 0x40, 0x4a, 0xd8, 0x12, 0x4d, 0xfb, 0x21, 0x09, 0x40}; std::vector const v_S = {'[', '#', '[', '$', 'i', '#', 'i', 2, 1, 2, 'S', 'i', 1, 'a', 'S', 'i', 1, 'a'}; std::vector const v_C = {'[', '$', 'C', '#', '[', '$', 'i', '#', 'i', 2, 1, 2, 'a', 'a'}; + std::vector const v_B = {'[', '$', 'B', '#', '[', '$', 'i', '#', 'i', 2, 1, 2, 0xFF, 0xFF}; // check if vector is parsed correctly CHECK(json::from_bjdata(v_0) == json::array()); @@ -2443,6 +2463,7 @@ TEST_CASE("BJData") CHECK(json::from_bjdata(v_D) == json({3.1415926, 3.1415926})); CHECK(json::from_bjdata(v_S) == json({"a", "a"})); CHECK(json::from_bjdata(v_C) == json({"a", "a"})); + CHECK(json::from_bjdata(v_B) == json::binary(std::vector({static_cast(255), static_cast(255)}))); } SECTION("optimized ndarray (type and vector-size ndarray with JData annotations)") @@ -2460,6 +2481,7 @@ TEST_CASE("BJData") std::vector const v_d = {'[', '$', 'd', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0xA0, 0x40, 0x00, 0x00, 0xC0, 0x40}; std::vector const v_D = {'[', '$', 'D', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x40}; std::vector const v_C = {'[', '$', 'C', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 'a', 'b', 'c', 'd', 'e', 'f'}; + std::vector const v_B = {'[', '$', 'B', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06}; // check if vector is parsed correctly CHECK(json::from_bjdata(v_e) == json({{"_ArrayData_", {254, 255}}, {"_ArraySize_", {2, 1}}, {"_ArrayType_", "uint8"}})); @@ -2475,6 +2497,7 @@ TEST_CASE("BJData") CHECK(json::from_bjdata(v_d) == json({{"_ArrayData_", {1.f, 2.f, 3.f, 4.f, 5.f, 6.f}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "single"}})); CHECK(json::from_bjdata(v_D) == json({{"_ArrayData_", {1., 2., 3., 4., 5., 6.}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "double"}})); CHECK(json::from_bjdata(v_C) == json({{"_ArrayData_", {'a', 'b', 'c', 'd', 'e', 'f'}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "char"}})); + CHECK(json::from_bjdata(v_B) == json({{"_ArrayData_", {1, 2, 3, 4, 5, 6}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "byte"}})); // roundtrip: output should be optimized CHECK(json::to_bjdata(json::from_bjdata(v_e), true, true) == v_e); @@ -2489,6 +2512,7 @@ TEST_CASE("BJData") CHECK(json::to_bjdata(json::from_bjdata(v_d), true, true) == v_d); CHECK(json::to_bjdata(json::from_bjdata(v_D), true, true) == v_D); CHECK(json::to_bjdata(json::from_bjdata(v_C), true, true) == v_C); + CHECK(json::to_bjdata(json::from_bjdata(v_B), true, true) == v_B); } SECTION("optimized ndarray (type and vector-size as 1D array)") @@ -2507,6 +2531,7 @@ TEST_CASE("BJData") std::vector const v_D = {'[', '$', 'D', '#', '[', 'i', 1, 'i', 2, ']', 0x4a, 0xd8, 0x12, 0x4d, 0xfb, 0x21, 0x09, 0x40, 0x4a, 0xd8, 0x12, 0x4d, 0xfb, 0x21, 0x09, 0x40}; std::vector const v_S = {'[', '#', '[', 'i', 1, 'i', 2, ']', 'S', 'i', 1, 'a', 'S', 'i', 1, 'a'}; std::vector const v_C = {'[', '$', 'C', '#', '[', 'i', 1, 'i', 2, ']', 'a', 'a'}; + std::vector const v_B = {'[', '$', 'B', '#', '[', 'i', 1, 'i', 2, ']', 0xFF, 0xFF}; std::vector const v_R = {'[', '#', '[', 'i', 2, ']', 'i', 6, 'U', 7}; // check if vector is parsed correctly @@ -2523,6 +2548,7 @@ TEST_CASE("BJData") CHECK(json::from_bjdata(v_D) == json({3.1415926, 3.1415926})); CHECK(json::from_bjdata(v_S) == json({"a", "a"})); CHECK(json::from_bjdata(v_C) == json({"a", "a"})); + CHECK(json::from_bjdata(v_B) == json::binary(std::vector({static_cast(255), static_cast(255)}))); CHECK(json::from_bjdata(v_R) == json({6, 7})); } @@ -2540,6 +2566,7 @@ TEST_CASE("BJData") std::vector const v_D = {'[', '$', 'D', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2, 0x4a, 0xd8, 0x12, 0x4d, 0xfb, 0x21, 0x09, 0x40, 0x4a, 0xd8, 0x12, 0x4d, 0xfb, 0x21, 0x09, 0x40}; std::vector const v_S = {'[', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2, 'S', 'i', 1, 'a', 'S', 'i', 1, 'a'}; std::vector const v_C = {'[', '$', 'C', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2, 'a', 'a'}; + std::vector const v_B = {'[', '$', 'B', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2, 0xFF, 0xFF}; // check if vector is parsed correctly CHECK(json::from_bjdata(v_i) == json({127, 127})); @@ -2553,6 +2580,7 @@ TEST_CASE("BJData") CHECK(json::from_bjdata(v_D) == json({3.1415926, 3.1415926})); CHECK(json::from_bjdata(v_S) == json({"a", "a"})); CHECK(json::from_bjdata(v_C) == json({"a", "a"})); + CHECK(json::from_bjdata(v_B) == json::binary(std::vector({static_cast(255), static_cast(255)}))); } SECTION("invalid ndarray annotations remains as object") @@ -2594,6 +2622,17 @@ TEST_CASE("BJData") } } + SECTION("byte") + { + SECTION("parse bjdata markers in ubjson") + { + std::vector const v = {'B', 1}; + + json _; + CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v), "[json.exception.parse_error.112] parse error at byte 1: syntax error while parsing UBJSON value: invalid byte: 0x42", json::parse_error&); + } + } + SECTION("strings") { SECTION("eof after S byte") @@ -2803,6 +2842,10 @@ TEST_CASE("BJData") std::vector const v0 = {'[', '#', 'T', ']'}; CHECK_THROWS_WITH(_ = json::from_bjdata(v0), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing BJData size: expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x54"); CHECK(json::from_bjdata(v0, true, false).is_discarded()); + + std::vector const vB = {'[', '#', 'B', ']'}; + CHECK_THROWS_WITH(_ = json::from_bjdata(vB), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing BJData size: expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x42"); + CHECK(json::from_bjdata(v0, true, false).is_discarded()); } SECTION("parse bjdata markers as array size in ubjson") @@ -2904,6 +2947,10 @@ TEST_CASE("BJData") CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vU), "[json.exception.parse_error.110] parse error at byte 18: syntax error while parsing BJData number: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(vU, true, false).is_discarded()); + std::vector const vB = {'[', '$', 'B', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 1, 2, 3, 4, 5}; + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vU), "[json.exception.parse_error.110] parse error at byte 18: syntax error while parsing BJData number: unexpected end of input", json::parse_error&); + CHECK(json::from_bjdata(vU, true, false).is_discarded()); + std::vector const vT1 = {'[', '$', 'T', '#', '[', '$', 'i', '#', 'i', 2, 2, 3}; CHECK(json::from_bjdata(vT1, true, false).is_discarded()); @@ -3197,6 +3244,20 @@ TEST_CASE("Universal Binary JSON Specification Examples 1") CHECK(json::from_bjdata(v) == j); } + SECTION("Byte Type") + { + const auto s = std::vector({ + static_cast(222), + static_cast(173), + static_cast(190), + static_cast(239) + }); + json const j = {{"binary", json::binary(s)}, {"val", 123}}; + std::vector const v = {'{', 'i', 6, 'b', 'i', 'n', 'a', 'r', 'y', '[', '$', 'B', '#', 'i', 4, 222, 173, 190, 239, 'i', 3, 'v', 'a', 'l', 'i', 123, '}'}; + //CHECK(json::to_bjdata(j) == v); + CHECK(json::from_bjdata(v) == j); + } + SECTION("String Type") { SECTION("English") @@ -3448,7 +3509,7 @@ TEST_CASE("all BJData first bytes") // these bytes will fail immediately with exception parse_error.112 std::set supported = { - 'T', 'F', 'Z', 'U', 'i', 'I', 'l', 'L', 'd', 'D', 'C', 'S', '[', '{', 'N', 'H', 'u', 'm', 'M', 'h' + 'T', 'F', 'Z', 'B', 'U', 'i', 'I', 'l', 'L', 'd', 'D', 'C', 'S', '[', '{', 'N', 'H', 'u', 'm', 'M', 'h' }; for (auto i = 0; i < 256; ++i)