diff --git a/docs/mkdocs/docs/api/basic_json/to_bjdata.md b/docs/mkdocs/docs/api/basic_json/to_bjdata.md index 48598a5e6..a82512d9e 100644 --- a/docs/mkdocs/docs/api/basic_json/to_bjdata.md +++ b/docs/mkdocs/docs/api/basic_json/to_bjdata.md @@ -4,13 +4,16 @@ // (1) static std::vector to_bjdata(const basic_json& j, const bool use_size = false, - const bool use_type = false); + const bool use_type = false, + const bjdata_version_t version = bjdata_version_t::draft2); // (2) static void to_bjdata(const basic_json& j, detail::output_adapter o, - const bool use_size = false, const bool use_type = false); + const bool use_size = false, const bool use_type = false, + const bjdata_version_t version = bjdata_version_t::draft2); static void to_bjdata(const basic_json& j, detail::output_adapter o, - const bool use_size = false, const bool use_type = false); + const bool use_size = false, const bool use_type = false, + const bjdata_version_t version = bjdata_version_t::draft2); ``` Serializes a given JSON value `j` to a byte vector using the BJData (Binary JData) serialization format. BJData aims to @@ -34,6 +37,9 @@ The exact mapping and its limitations is described on a [dedicated page](../../f `use_type` (in) : whether to add type annotations to container types (must be combined with `#!cpp use_size = true`); optional, + +`version` (in) +: which version of BJData to use (see [draft 3](../../features/binary_formats/bjdata.md#draft-3-binary-format)); optional, `#!cpp false` by default. ## Return value @@ -68,3 +74,4 @@ Linear in the size of the JSON value `j`. ## Version history - Added in version 3.11.0. +- BJData version parameter (for draft3 binary encoding) added in version 3.12.0. \ No newline at end of file diff --git a/docs/mkdocs/docs/features/binary_formats/bjdata.md b/docs/mkdocs/docs/features/binary_formats/bjdata.md index a89a22885..cddb8ca07 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,24 @@ 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. + + To preserve compatibility with BJData Draft 2, the Draft 3 optimized binary array must be explicitly enabled using + the `version` parameter of [`to_bjdata`](../../api/basic_json/to_bjdata.md). + + In Draft2 mode (default), if the JSON data contains the binary type, the value stored as 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. + + [BJDataBinArr]: https://github.com/NeuroJSON/bjdata/blob/master/Binary_JData_Specification.md#optimized-binary-array) ??? example @@ -171,11 +181,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 75babd55a..498ca6216 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -2313,6 +2313,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{}; @@ -2513,7 +2523,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'; } @@ -2535,6 +2545,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))) @@ -3008,6 +3025,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 70dd752e8..71f1ebd17 100644 --- a/include/nlohmann/detail/output/binary_writer.hpp +++ b/include/nlohmann/detail/output/binary_writer.hpp @@ -28,6 +28,13 @@ NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { +/// how to encode BJData +enum class bjdata_version_t +{ + draft2, + draft3, +}; + /////////////////// // binary writer // /////////////////// @@ -735,11 +742,14 @@ class binary_writer @param[in] use_type whether to use '$' prefixes (optimized format) @param[in] add_prefix whether prefixes need to be used for this value @param[in] use_bjdata whether write in BJData format, default is false + @param[in] bjdata_version which BJData version to use, default is draft2 */ void write_ubjson(const BasicJsonType& j, const bool use_count, const bool use_type, const bool add_prefix = true, - const bool use_bjdata = false) + const bool use_bjdata = false, const bjdata_version_t bjdata_version = bjdata_version_t::draft2) { + const bool bjdata_draft3 = bjdata_version == bjdata_version_t::draft3; + switch (j.type()) { case value_t::null: @@ -829,7 +839,7 @@ class binary_writer for (const auto& el : *j.m_data.m_value.array) { - write_ubjson(el, use_count, use_type, prefix_required, use_bjdata); + write_ubjson(el, use_count, use_type, prefix_required, use_bjdata, bjdata_version); } if (!use_count) @@ -847,11 +857,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 && bjdata_draft3) || !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 && bjdata_draft3 ? 'B' : 'U'); } if (use_count) @@ -870,7 +880,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 && bjdata_draft3) ? 'B' : 'U')); oa->write_character(j.m_data.m_value.binary->data()[i]); } } @@ -887,7 +897,7 @@ class binary_writer { if (use_bjdata && j.m_data.m_value.object->size() == 3 && j.m_data.m_value.object->find("_ArrayType_") != j.m_data.m_value.object->end() && j.m_data.m_value.object->find("_ArraySize_") != j.m_data.m_value.object->end() && j.m_data.m_value.object->find("_ArrayData_") != j.m_data.m_value.object->end()) { - if (!write_bjdata_ndarray(*j.m_data.m_value.object, use_count, use_type)) // decode bjdata ndarray in the JData format (https://github.com/NeuroJSON/jdata) + if (!write_bjdata_ndarray(*j.m_data.m_value.object, use_count, use_type, bjdata_version)) // decode bjdata ndarray in the JData format (https://github.com/NeuroJSON/jdata) { break; } @@ -931,7 +941,7 @@ class binary_writer oa->write_characters( reinterpret_cast(el.first.c_str()), el.first.size()); - write_ubjson(el.second, use_count, use_type, prefix_required, use_bjdata); + write_ubjson(el.second, use_count, use_type, prefix_required, use_bjdata, bjdata_version); } if (!use_count) @@ -1615,10 +1625,11 @@ class binary_writer /*! @return false if the object is successfully converted to a bjdata ndarray, true if the type or size is invalid */ - bool write_bjdata_ndarray(const typename BasicJsonType::object_t& value, const bool use_count, const bool use_type) + bool write_bjdata_ndarray(const typename BasicJsonType::object_t& value, const bool use_count, const bool use_type, const bjdata_version_t bjdata_version) { 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_"; @@ -1648,10 +1659,10 @@ class binary_writer oa->write_character('#'); key = "_ArraySize_"; - write_ubjson(value.at(key), use_count, use_type, true, true); + write_ubjson(value.at(key), use_count, use_type, true, true, bjdata_version); key = "_ArrayData_"; - if (dtype == 'U' || dtype == 'C') + if (dtype == 'U' || dtype == 'C' || dtype == 'B') { for (const auto& el : value.at(key)) { diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 9b3b5950f..c8b762606 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -171,6 +171,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec using error_handler_t = detail::error_handler_t; /// how to treat CBOR tags using cbor_tag_handler_t = detail::cbor_tag_handler_t; + /// how to encode BJData + using bjdata_version_t = detail::bjdata_version_t; /// helper type for initializer lists of basic_json values using initializer_list_t = std::initializer_list>; @@ -4352,27 +4354,30 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ static std::vector to_bjdata(const basic_json& j, const bool use_size = false, - const bool use_type = false) + const bool use_type = false, + const bjdata_version_t version = bjdata_version_t::draft2) { std::vector result; - to_bjdata(j, result, use_size, use_type); + to_bjdata(j, result, use_size, use_type, version); return result; } /// @brief create a BJData serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ static void to_bjdata(const basic_json& j, detail::output_adapter o, - const bool use_size = false, const bool use_type = false) + const bool use_size = false, const bool use_type = false, + const bjdata_version_t version = bjdata_version_t::draft2) { - binary_writer(o).write_ubjson(j, use_size, use_type, true, true); + binary_writer(o).write_ubjson(j, use_size, use_type, true, true, version); } /// @brief create a BJData serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ static void to_bjdata(const basic_json& j, detail::output_adapter o, - const bool use_size = false, const bool use_type = false) + const bool use_size = false, const bool use_type = false, + const bjdata_version_t version = bjdata_version_t::draft2) { - binary_writer(o).write_ubjson(j, use_size, use_type, true, true); + binary_writer(o).write_ubjson(j, use_size, use_type, true, true, version); } /// @brief create a BSON serialization of a given JSON value diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 9314724f6..118df4a3c 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -11991,6 +11991,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{}; @@ -12191,7 +12201,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'; } @@ -12213,6 +12223,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))) @@ -12686,6 +12703,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"}, \ @@ -15646,6 +15664,13 @@ NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { +/// how to encode BJData +enum class bjdata_version_t +{ + draft2, + draft3, +}; + /////////////////// // binary writer // /////////////////// @@ -16353,11 +16378,14 @@ class binary_writer @param[in] use_type whether to use '$' prefixes (optimized format) @param[in] add_prefix whether prefixes need to be used for this value @param[in] use_bjdata whether write in BJData format, default is false + @param[in] bjdata_version which BJData version to use, default is draft2 */ void write_ubjson(const BasicJsonType& j, const bool use_count, const bool use_type, const bool add_prefix = true, - const bool use_bjdata = false) + const bool use_bjdata = false, const bjdata_version_t bjdata_version = bjdata_version_t::draft2) { + const bool bjdata_draft3 = bjdata_version == bjdata_version_t::draft3; + switch (j.type()) { case value_t::null: @@ -16447,7 +16475,7 @@ class binary_writer for (const auto& el : *j.m_data.m_value.array) { - write_ubjson(el, use_count, use_type, prefix_required, use_bjdata); + write_ubjson(el, use_count, use_type, prefix_required, use_bjdata, bjdata_version); } if (!use_count) @@ -16465,11 +16493,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 && bjdata_draft3) || !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 && bjdata_draft3 ? 'B' : 'U'); } if (use_count) @@ -16488,7 +16516,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 && bjdata_draft3) ? 'B' : 'U')); oa->write_character(j.m_data.m_value.binary->data()[i]); } } @@ -16505,7 +16533,7 @@ class binary_writer { if (use_bjdata && j.m_data.m_value.object->size() == 3 && j.m_data.m_value.object->find("_ArrayType_") != j.m_data.m_value.object->end() && j.m_data.m_value.object->find("_ArraySize_") != j.m_data.m_value.object->end() && j.m_data.m_value.object->find("_ArrayData_") != j.m_data.m_value.object->end()) { - if (!write_bjdata_ndarray(*j.m_data.m_value.object, use_count, use_type)) // decode bjdata ndarray in the JData format (https://github.com/NeuroJSON/jdata) + if (!write_bjdata_ndarray(*j.m_data.m_value.object, use_count, use_type, bjdata_version)) // decode bjdata ndarray in the JData format (https://github.com/NeuroJSON/jdata) { break; } @@ -16549,7 +16577,7 @@ class binary_writer oa->write_characters( reinterpret_cast(el.first.c_str()), el.first.size()); - write_ubjson(el.second, use_count, use_type, prefix_required, use_bjdata); + write_ubjson(el.second, use_count, use_type, prefix_required, use_bjdata, bjdata_version); } if (!use_count) @@ -17233,10 +17261,11 @@ class binary_writer /*! @return false if the object is successfully converted to a bjdata ndarray, true if the type or size is invalid */ - bool write_bjdata_ndarray(const typename BasicJsonType::object_t& value, const bool use_count, const bool use_type) + bool write_bjdata_ndarray(const typename BasicJsonType::object_t& value, const bool use_count, const bool use_type, const bjdata_version_t bjdata_version) { 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_"; @@ -17266,10 +17295,10 @@ class binary_writer oa->write_character('#'); key = "_ArraySize_"; - write_ubjson(value.at(key), use_count, use_type, true, true); + write_ubjson(value.at(key), use_count, use_type, true, true, bjdata_version); key = "_ArrayData_"; - if (dtype == 'U' || dtype == 'C') + if (dtype == 'U' || dtype == 'C' || dtype == 'B') { for (const auto& el : value.at(key)) { @@ -20051,6 +20080,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec using error_handler_t = detail::error_handler_t; /// how to treat CBOR tags using cbor_tag_handler_t = detail::cbor_tag_handler_t; + /// how to encode BJData + using bjdata_version_t = detail::bjdata_version_t; /// helper type for initializer lists of basic_json values using initializer_list_t = std::initializer_list>; @@ -24232,27 +24263,30 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ static std::vector to_bjdata(const basic_json& j, const bool use_size = false, - const bool use_type = false) + const bool use_type = false, + const bjdata_version_t version = bjdata_version_t::draft2) { std::vector result; - to_bjdata(j, result, use_size, use_type); + to_bjdata(j, result, use_size, use_type, version); return result; } /// @brief create a BJData serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ static void to_bjdata(const basic_json& j, detail::output_adapter o, - const bool use_size = false, const bool use_type = false) + const bool use_size = false, const bool use_type = false, + const bjdata_version_t version = bjdata_version_t::draft2) { - binary_writer(o).write_ubjson(j, use_size, use_type, true, true); + binary_writer(o).write_ubjson(j, use_size, use_type, true, true, version); } /// @brief create a BJData serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ static void to_bjdata(const basic_json& j, detail::output_adapter o, - const bool use_size = false, const bool use_type = false) + const bool use_size = false, const bool use_type = false, + const bjdata_version_t version = bjdata_version_t::draft2) { - binary_writer(o).write_ubjson(j, use_size, use_type, true, true); + binary_writer(o).write_ubjson(j, use_size, use_type, true, true, version); } /// @brief create a BSON serialization of a given JSON value diff --git a/tests/src/unit-bjdata.cpp b/tests/src/unit-bjdata.cpp index cf770e5f5..e926a767c 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") @@ -1501,262 +1529,316 @@ TEST_CASE("BJData") SECTION("binary") { - SECTION("N = 0..127") - { - for (std::size_t N = 0; N <= 127; ++N) - { - CAPTURE(N) - - // create JSON value with byte array containing of N * 'x' - const auto s = std::vector(N, 'x'); - json const j = json::binary(s); - - // create expected byte vector - std::vector expected; - expected.push_back(static_cast('[')); - if (N != 0) + for (json::bjdata_version_t bjdata_version : { + json::bjdata_version_t::draft2, json::bjdata_version_t::draft3 + }) + { + CAPTURE(bjdata_version) + const bool draft3 = (bjdata_version == json::bjdata_version_t::draft3); + + SECTION("N = 0..127") + { + for (std::size_t N = 0; N <= 127; ++N) + { + CAPTURE(N) + + // create JSON value with byte array containing of N * 'x' + const auto s = std::vector(N, 'x'); + json const j = json::binary(s); + + // create expected byte vector + std::vector expected; + expected.push_back(static_cast('[')); + if (draft3 || N != 0) + { + expected.push_back(static_cast('$')); + expected.push_back(static_cast(draft3 ? 'B' : 'U')); + } + expected.push_back(static_cast('#')); + expected.push_back(static_cast('i')); + expected.push_back(static_cast(N)); + for (size_t i = 0; i < N; ++i) + { + expected.push_back(0x78); + } + + // compare result + size + const auto result = json::to_bjdata(j, true, true, bjdata_version); + CHECK(result == expected); + if (!draft3 && N == 0) + { + CHECK(result.size() == N + 4); + } + else + { + CHECK(result.size() == N + 6); + } + + // check that no null byte is appended + if (N > 0) + { + CHECK(result.back() != '\x00'); + } + + if (draft3) + { + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + else + { + // 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); + } + } + } + + SECTION("N = 128..255") + { + for (std::size_t N = 128; N <= 255; ++N) + { + CAPTURE(N) + + // create JSON value with byte array containing of N * 'x' + const auto s = std::vector(N, 'x'); + json const j = json::binary(s); + + // create expected byte vector + std::vector expected; + expected.push_back(static_cast('[')); expected.push_back(static_cast('$')); + expected.push_back(static_cast(draft3 ? 'B' : 'U')); + expected.push_back(static_cast('#')); expected.push_back(static_cast('U')); - } - expected.push_back(static_cast('#')); - expected.push_back(static_cast('i')); - expected.push_back(static_cast(N)); - for (size_t i = 0; i < N; ++i) - { - expected.push_back(0x78); - } + expected.push_back(static_cast(N)); + for (size_t i = 0; i < N; ++i) + { + expected.push_back(0x78); + } - // compare result + size - const auto result = json::to_bjdata(j, true, true); - CHECK(result == expected); - if (N == 0) - { - CHECK(result.size() == N + 4); - } - else - { + // compare result + size + const auto result = json::to_bjdata(j, true, true, bjdata_version); + CHECK(result == expected); CHECK(result.size() == N + 6); - } - - // check that no null byte is appended - if (N > 0) - { + // 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); - } - } - - SECTION("N = 128..255") - { - for (std::size_t N = 128; N <= 255; ++N) - { - CAPTURE(N) - - // create JSON value with byte array containing of N * 'x' - const auto s = std::vector(N, 'x'); - json const j = json::binary(s); - - // create expected byte vector - std::vector expected; - expected.push_back(static_cast('[')); - expected.push_back(static_cast('$')); - expected.push_back(static_cast('U')); - expected.push_back(static_cast('#')); - expected.push_back(static_cast('U')); - expected.push_back(static_cast(N)); - for (size_t i = 0; i < N; ++i) - { - expected.push_back(0x78); - } - - // compare result + size - const auto result = json::to_bjdata(j, true, true); - CHECK(result == expected); - CHECK(result.size() == N + 6); - // 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); - } - } - - SECTION("N = 256..32767") - { - for (const std::size_t N : + if (draft3) { - 256u, 999u, 1025u, 3333u, 2048u, 32767u - }) - { - CAPTURE(N) - - // create JSON value with byte array containing of N * 'x' - const auto s = std::vector(N, 'x'); - json const j = json::binary(s); - - // create expected byte vector - std::vector expected(N + 7, 'x'); - expected[0] = '['; - expected[1] = '$'; - expected[2] = 'U'; - expected[3] = '#'; - expected[4] = 'I'; - expected[5] = static_cast(N & 0xFF); - expected[6] = static_cast((N >> 8) & 0xFF); - - // compare result + size - const auto result = json::to_bjdata(j, true, true); - CHECK(result == expected); - CHECK(result.size() == N + 7); - // 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); - } - } - - SECTION("N = 32768..65535") - { - for (const std::size_t N : + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + else { - 32768u, 55555u, 65535u - }) - { - CAPTURE(N) - - // create JSON value with byte array containing of N * 'x' - const auto s = std::vector(N, 'x'); - json const j = json::binary(s); - - // create expected byte vector - std::vector expected(N + 7, 'x'); - expected[0] = '['; - expected[1] = '$'; - expected[2] = 'U'; - expected[3] = '#'; - expected[4] = 'u'; - expected[5] = static_cast(N & 0xFF); - expected[6] = static_cast((N >> 8) & 0xFF); - - // compare result + size - const auto result = json::to_bjdata(j, true, true); - CHECK(result == expected); - CHECK(result.size() == N + 7); - // 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); - } - } - - SECTION("N = 65536..2147483647") - { - for (const std::size_t N : - { - 65536u, 77777u, 1048576u - }) - { - CAPTURE(N) - - // create JSON value with byte array containing of N * 'x' - const auto s = std::vector(N, 'x'); - json const j = json::binary(s); - - // create expected byte vector - std::vector expected(N + 9, 'x'); - expected[0] = '['; - expected[1] = '$'; - expected[2] = 'U'; - expected[3] = '#'; - expected[4] = 'l'; - expected[5] = static_cast(N & 0xFF); - expected[6] = static_cast((N >> 8) & 0xFF); - expected[7] = static_cast((N >> 16) & 0xFF); - expected[8] = static_cast((N >> 24) & 0xFF); - - // compare result + size - const auto result = json::to_bjdata(j, true, true); - CHECK(result == expected); - CHECK(result.size() == N + 9); - // 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); - } - } - - SECTION("Other Serializations") - { - const std::size_t N = 10; - const auto s = std::vector(N, 'x'); - json const j = json::binary(s); - - SECTION("No Count No Type") - { - std::vector expected; - 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(0x78)); + // 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); + } } - expected.push_back(static_cast(']')); - - // compare result + size - const auto result = json::to_bjdata(j, false, false); - CHECK(result == expected); - CHECK(result.size() == N + 12); - // 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); } - SECTION("Yes Count No Type") + SECTION("N = 256..32767") { - std::vector expected; - expected.push_back(static_cast('[')); - expected.push_back(static_cast('#')); - expected.push_back(static_cast('i')); - expected.push_back(static_cast(N)); - - for (size_t i = 0; i < N; ++i) + for (const std::size_t N : + { + 256u, 999u, 1025u, 3333u, 2048u, 32767u + }) { - expected.push_back(static_cast('U')); - expected.push_back(static_cast(0x78)); + CAPTURE(N) + + // create JSON value with byte array containing of N * 'x' + const auto s = std::vector(N, 'x'); + json const j = json::binary(s); + + // create expected byte vector + std::vector expected(N + 7, 'x'); + expected[0] = '['; + expected[1] = '$'; + expected[2] = draft3 ? 'B' : 'U'; + expected[3] = '#'; + expected[4] = 'I'; + expected[5] = static_cast(N & 0xFF); + expected[6] = static_cast((N >> 8) & 0xFF); + + // compare result + size + const auto result = json::to_bjdata(j, true, true, bjdata_version); + CHECK(result == expected); + CHECK(result.size() == N + 7); + // check that no null byte is appended + CHECK(result.back() != '\x00'); + + if (draft3) + { + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + else + { + // 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); + } + } + } + + SECTION("N = 32768..65535") + { + for (const std::size_t N : + { + 32768u, 55555u, 65535u + }) + { + CAPTURE(N) + + // create JSON value with byte array containing of N * 'x' + const auto s = std::vector(N, 'x'); + json const j = json::binary(s); + + // create expected byte vector + std::vector expected(N + 7, 'x'); + expected[0] = '['; + expected[1] = '$'; + expected[2] = draft3 ? 'B' : 'U'; + expected[3] = '#'; + expected[4] = 'u'; + expected[5] = static_cast(N & 0xFF); + expected[6] = static_cast((N >> 8) & 0xFF); + + // compare result + size + const auto result = json::to_bjdata(j, true, true, bjdata_version); + CHECK(result == expected); + CHECK(result.size() == N + 7); + // check that no null byte is appended + CHECK(result.back() != '\x00'); + + if (draft3) + { + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + else + { + // 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); + } + } + } + + SECTION("N = 65536..2147483647") + { + for (const std::size_t N : + { + 65536u, 77777u, 1048576u + }) + { + CAPTURE(N) + + // create JSON value with byte array containing of N * 'x' + const auto s = std::vector(N, 'x'); + json const j = json::binary(s); + + // create expected byte vector + std::vector expected(N + 9, 'x'); + expected[0] = '['; + expected[1] = '$'; + expected[2] = draft3 ? 'B' : 'U'; + expected[3] = '#'; + expected[4] = 'l'; + expected[5] = static_cast(N & 0xFF); + expected[6] = static_cast((N >> 8) & 0xFF); + expected[7] = static_cast((N >> 16) & 0xFF); + expected[8] = static_cast((N >> 24) & 0xFF); + + // compare result + size + const auto result = json::to_bjdata(j, true, true, bjdata_version); + CHECK(result == expected); + CHECK(result.size() == N + 9); + // check that no null byte is appended + CHECK(result.back() != '\x00'); + + if (draft3) + { + // roundtrip + CHECK(json::from_bjdata(result) == j); + CHECK(json::from_bjdata(result, true, false) == j); + } + else + { + // 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); + } + } + } + + SECTION("Other Serializations") + { + const std::size_t N = 10; + const auto s = std::vector(N, 'x'); + json const j = json::binary(s); + + SECTION("No Count No Type") + { + std::vector expected; + expected.push_back(static_cast('[')); + for (std::size_t i = 0; i < N; ++i) + { + expected.push_back(static_cast(draft3 ? 'B' : 'U')); + expected.push_back(static_cast(0x78)); + } + expected.push_back(static_cast(']')); + + // compare result + size + const auto result = json::to_bjdata(j, false, false, bjdata_version); + CHECK(result == expected); + CHECK(result.size() == N + 12); + // 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); } - // compare result + size - const auto result = json::to_bjdata(j, true, false); - CHECK(result == expected); - CHECK(result.size() == N + 14); - // check that no null byte is appended - CHECK(result.back() != '\x00'); + SECTION("Yes Count No Type") + { + std::vector expected; + expected.push_back(static_cast('[')); + expected.push_back(static_cast('#')); + expected.push_back(static_cast('i')); + expected.push_back(static_cast(N)); - // 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); + for (size_t i = 0; i < N; ++i) + { + expected.push_back(static_cast(draft3 ? 'B' : 'U')); + expected.push_back(static_cast(0x78)); + } + + // compare result + size + const auto result = json::to_bjdata(j, true, false, bjdata_version); + CHECK(result == expected); + CHECK(result.size() == N + 14); + // 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); + } } } } @@ -2334,6 +2416,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 +2434,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 +2451,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 +2468,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 +2482,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 +2497,8 @@ 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, json::bjdata_version_t::draft2) == v_U); + CHECK(json::to_bjdata(json::from_bjdata(v_B), true, true, json::bjdata_version_t::draft3) == v_B); } SECTION("optimized ndarray (type and vector-size as optimized 1D array)") @@ -2428,6 +2517,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 +2533,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 +2551,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 +2567,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 +2582,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 +2601,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 +2618,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 +2636,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 +2650,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 +2692,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 +2912,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 +3017,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 +3314,21 @@ 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); // 123 value gets encoded as uint8 + CHECK(json::from_bjdata(v) == j); + } + SECTION("String Type") { SECTION("English") @@ -3448,7 +3580,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)