diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index db9eaf2bd2..76667617b0 100644 --- a/include/nlohmann/detail/conversions/to_json.hpp +++ b/include/nlohmann/detail/conversions/to_json.hpp @@ -67,6 +67,26 @@ struct external_constructor } }; +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, const typename BasicJsonType::binary_t& b) + { + j.m_type = value_t::binary; + j.m_value = b; + j.assert_invariant(); + } + + template + static void construct(BasicJsonType& j, typename BasicJsonType::binary_t&& b) + { + j.m_type = value_t::binary; + j.m_value = std::move(b); + j.assert_invariant(); + } +}; + template<> struct external_constructor { diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 62b0e487c8..be7d2c08c3 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -217,6 +217,7 @@ class binary_reader @pre len >= 0 @return `true` if the byte array was successfully parsed */ + template bool get_bson_binary(const NumberType len, binary_t& result) { if (JSON_UNLIKELY(len < 0)) @@ -918,17 +919,16 @@ class binary_reader case 0x5F: // Binary data (indefinite length) { - bool success = true; while (get() != 0xFF) { - if (JSON_UNLIKELY(not unexpect_eof(format, "binary"))) + binary_t chunk; + if (not get_cbor_binary(chunk)) { - success = false; - break; + return false; } - result.append(static_cast(current)); + result.insert(result.end(), chunk.begin(), chunk.end()); } - return success; + return true; } default: diff --git a/include/nlohmann/detail/input/json_sax.hpp b/include/nlohmann/detail/input/json_sax.hpp index a41fa0feba..1aecacb1b6 100644 --- a/include/nlohmann/detail/input/json_sax.hpp +++ b/include/nlohmann/detail/input/json_sax.hpp @@ -202,6 +202,7 @@ class json_sax_dom_parser bool number_float(number_float_t val, const string_t& /*unused*/) { + (void)_; handle_value(val); return true; } @@ -658,6 +659,7 @@ class json_sax_acceptor using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; bool null() { @@ -694,7 +696,7 @@ class json_sax_acceptor return true; } - bool start_object(std::size_t /*unused*/ = std::size_t(-1)) + bool start_object(std::size_t /*unused*/ = std::size_t(-1)) { return true; } @@ -709,7 +711,7 @@ class json_sax_acceptor return true; } - bool start_array(std::size_t /*unused*/ = std::size_t(-1)) + bool start_array(std::size_t /*unused*/ = std::size_t(-1)) { return true; } diff --git a/include/nlohmann/detail/macro_scope.hpp b/include/nlohmann/detail/macro_scope.hpp index 2be7581d17..a06ed7efff 100644 --- a/include/nlohmann/detail/macro_scope.hpp +++ b/include/nlohmann/detail/macro_scope.hpp @@ -112,10 +112,10 @@ template class ArrayType, \ class StringType, class BooleanType, class NumberIntegerType, \ class NumberUnsignedType, class NumberFloatType, \ - template class AllocatorType, \ + class BinaryType, template class AllocatorType, \ template class JSONSerializer> #define NLOHMANN_BASIC_JSON_TPL \ basic_json + BinaryType, AllocatorType, JSONSerializer> diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp index fd4c2d7b09..a491d18da1 100644 --- a/include/nlohmann/detail/output/binary_writer.hpp +++ b/include/nlohmann/detail/output/binary_writer.hpp @@ -26,6 +26,7 @@ template class binary_writer { using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; public: /*! @@ -291,7 +292,7 @@ class binary_writer // step 2: write each element oa->write_characters( - reinterpret_cast(j.m_value.binary->data()), + reinterpret_cast(j.m_value.binary->data()), N); break; @@ -570,7 +571,7 @@ class binary_writer // step 2: write the byte string oa->write_characters( - reinterpret_cast(j.m_value.binary->data()), + reinterpret_cast(j.m_value.binary->data()), N); break; @@ -740,7 +741,7 @@ class binary_writer } oa->write_characters( - reinterpret_cast(j.m_value.binary->data()), + reinterpret_cast(j.m_value.binary->data()), j.m_value.binary->size()); if (not use_count) @@ -1005,14 +1006,14 @@ class binary_writer @brief Writes a BSON element with key @a name and binary value @a value */ void write_bson_binary(const string_t& name, - const string_t& value) + const binary_t& value) { write_bson_entry_header(name, 0x05); write_number(static_cast(value.size())); write_number(0x00); // Generic Binary Subtype oa->write_characters( - reinterpret_cast(value.data()), + reinterpret_cast(value.data()), value.size()); } diff --git a/include/nlohmann/detail/output/serializer.hpp b/include/nlohmann/detail/output/serializer.hpp index 9b3ca7928f..cdcce6270e 100644 --- a/include/nlohmann/detail/output/serializer.hpp +++ b/include/nlohmann/detail/output/serializer.hpp @@ -238,7 +238,7 @@ class serializer case value_t::binary: { - JSON_THROW(type_error::create(317, "cannot serialize binary data to text JSON")) + JSON_THROW(type_error::create(317, "cannot serialize binary data to text JSON")); } case value_t::boolean: diff --git a/include/nlohmann/detail/value_t.hpp b/include/nlohmann/detail/value_t.hpp index 60456119da..0ac45170a6 100644 --- a/include/nlohmann/detail/value_t.hpp +++ b/include/nlohmann/detail/value_t.hpp @@ -67,7 +67,7 @@ Returns an ordering that is similar to Python: */ inline bool operator<(const value_t lhs, const value_t rhs) noexcept { - static constexpr std::array order = {{ + static constexpr std::array order = {{ 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */, 6 /* binary */ diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 8fa19397f7..a4c6daf798 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -978,7 +978,7 @@ class basic_json case value_t::binary: { - array = create(); + binary = create(); break; } @@ -1060,16 +1060,16 @@ class basic_json array = create(std::move(value)); } - /// constructor for arrays + /// constructor for binary arrays json_value(const binary_t& value) { - array = create(value); + binary = create(value); } - /// constructor for rvalue arrays + /// constructor for rvalue binary arrays json_value(binary_t&& value) { - array = create(std::move(value)); + binary = create(std::move(value)); } void destroy(value_t t) noexcept @@ -2683,7 +2683,7 @@ class basic_json @brief get a value (explicit) Explicit type conversion between the JSON value and a compatible value - which is [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible) + which is [Copynonstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible) and [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). The value is converted by calling the @ref json_serializer `from_json()` method. diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index c2128dcc2e..203b10fafb 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -1726,13 +1726,13 @@ JSON_HEDLEY_DIAGNOSTIC_POP template class ArrayType, \ class StringType, class BooleanType, class NumberIntegerType, \ class NumberUnsignedType, class NumberFloatType, \ - template class AllocatorType, \ + class BinaryType, template class AllocatorType, \ template class JSONSerializer> #define NLOHMANN_BASIC_JSON_TPL \ basic_json + BinaryType, AllocatorType, JSONSerializer> namespace nlohmann @@ -2325,6 +2325,7 @@ template class ObjectType = class NumberIntegerType = std::int64_t, class NumberUnsignedType = std::uint64_t, class NumberFloatType = double, + class BinaryType = std::vector, template class AllocatorType = std::allocator, template class JSONSerializer = adl_serializer> @@ -2758,6 +2759,7 @@ enum class value_t : std::uint8_t number_integer, ///< number value (signed integer) number_unsigned, ///< number value (unsigned integer) number_float, ///< number value (floating-point) + binary, ///< binary array (ordered collection of bytes) discarded ///< discarded by the the parser callback function }; @@ -2765,17 +2767,21 @@ enum class value_t : std::uint8_t @brief comparison operator for JSON types Returns an ordering that is similar to Python: -- order: null < boolean < number < object < array < string +- order: null < boolean < number < object < array < string < binary - furthermore, each type is not smaller than itself - discarded values are not comparable +- binary is represented as a b"" string in python and directly comparable to a + string; however, making a binary array directly comparable with a string would + be surprising behavior in a JSON file. @since version 1.0.0 */ inline bool operator<(const value_t lhs, const value_t rhs) noexcept { - static constexpr std::array order = {{ + static constexpr std::array order = {{ 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, - 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */ + 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */, + 6 /* binary */ } }; @@ -3402,6 +3408,26 @@ struct external_constructor } }; +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, const typename BasicJsonType::binary_t& b) + { + j.m_type = value_t::binary; + j.m_value = b; + j.assert_invariant(); + } + + template + static void construct(BasicJsonType& j, typename BasicJsonType::binary_t&& b) + { + j.m_type = value_t::binary; + j.m_value = std::move(b); + j.assert_invariant(); + } +}; + template<> struct external_constructor { @@ -4228,6 +4254,7 @@ struct json_sax using number_float_t = typename BasicJsonType::number_float_t; /// type for strings using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; /*! @brief a null value was read @@ -4272,6 +4299,14 @@ struct json_sax */ virtual bool string(string_t& val) = 0; + /*! + @brief a binary string was read + @param[in] val binary value + @return whether parsing should proceed + @note It is safe to move the passed binary. + */ + virtual bool binary(binary_t& val) = 0; + /*! @brief the beginning of an object was read @param[in] elements number of object elements or -1 if unknown @@ -4346,6 +4381,7 @@ class json_sax_dom_parser using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; /*! @param[in, out] r reference to a JSON value that is manipulated while @@ -4387,8 +4423,9 @@ class json_sax_dom_parser return true; } - bool number_float(number_float_t val, const string_t& /*unused*/) + bool number_float(number_float_t val, const string_t& _) { + (void)_; handle_value(val); return true; } @@ -4399,6 +4436,12 @@ class json_sax_dom_parser return true; } + bool binary(binary_t& val) + { + handle_value(val); + return true; + } + bool start_object(std::size_t len) { ref_stack.push_back(handle_value(BasicJsonType::value_t::object)); @@ -4444,9 +4487,10 @@ class json_sax_dom_parser return true; } - bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, + bool parse_error(std::size_t _a, const std::string& _b, const detail::exception& ex) { + (void)_a; (void)_b; // mark variables as unused for the linter errored = true; if (allow_exceptions) { @@ -4528,6 +4572,7 @@ class json_sax_dom_callback_parser using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; using parser_callback_t = typename BasicJsonType::parser_callback_t; using parse_event_t = typename BasicJsonType::parse_event_t; @@ -4570,8 +4615,9 @@ class json_sax_dom_callback_parser return true; } - bool number_float(number_float_t val, const string_t& /*unused*/) + bool number_float(number_float_t val, const string_t& _) { + (void)_; // mark variable as unused for the linter handle_value(val); return true; } @@ -4582,6 +4628,12 @@ class json_sax_dom_callback_parser return true; } + bool binary(binary_t& val) + { + handle_value(val); + return true; + } + bool start_object(std::size_t len) { // check callback for object start @@ -4691,9 +4743,10 @@ class json_sax_dom_callback_parser return true; } - bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, + bool parse_error(std::size_t _a, const std::string& _b, const detail::exception& ex) { + (void)_a; (void)_b; // mark variables as unused for the linter errored = true; if (allow_exceptions) { @@ -4832,6 +4885,7 @@ class json_sax_acceptor using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; bool null() { @@ -4843,6 +4897,8 @@ class json_sax_acceptor return true; } + // These below do not register as a false positve cast C-style cast from the linter + // But I've changed them anyway for consistency bool number_integer(number_integer_t /*unused*/) { return true; @@ -4863,7 +4919,12 @@ class json_sax_acceptor return true; } - bool start_object(std::size_t /*unused*/ = std::size_t(-1)) + bool binary(binary_t& /*unused*/) + { + return true; + } + + bool start_object(std::size_t /*unused*/ = std::size_t(-1)) { return true; } @@ -4878,7 +4939,7 @@ class json_sax_acceptor return true; } - bool start_array(std::size_t /*unused*/ = std::size_t(-1)) + bool start_array(std::size_t /*unused*/ = std::size_t(-1)) { return true; } @@ -5066,6 +5127,7 @@ class binary_reader using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; using json_sax_t = SAX; public: @@ -5235,6 +5297,27 @@ class binary_reader return get_string(input_format_t::bson, len - static_cast(1), result) and get() != std::char_traits::eof(); } + /*! + @brief Parses a byte array input of length @a len from the BSON input. + @param[in] len The length of the byte array to be read. + @param[in, out] result A reference to the binary variable where the read + array is to be stored. + @tparam NumberType The type of the length @a len + @pre len >= 0 + @return `true` if the byte array was successfully parsed + */ + template + bool get_bson_binary(const NumberType len, binary_t& result) + { + if (JSON_UNLIKELY(len < 0)) + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::bson, "byte array length cannot be negative, is " + std::to_string(len), "binary"))); + } + + return get_binary(input_format_t::bson, len, result) and get() != std::char_traits::eof(); + } + /*! @brief Read a BSON document element of the given @a element_type. @param[in] element_type The BSON element type, c.f. http://bsonspec.org/spec.html @@ -5273,6 +5356,13 @@ class binary_reader return parse_bson_array(); } + case 0x05: // binary + { + std::int32_t len; + binary_t value; + return get_number(input_format_t::bson, len) and get_bson_binary(len, value) and sax->binary(value); + } + case 0x08: // boolean { return sax->boolean(get() != 0); @@ -5493,6 +5583,41 @@ class binary_reader - static_cast(number)); } + // Binary data (0x00..0x17 bytes follow) + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + case 0x58: // Binary data (one-byte uint8_t for n follows) + case 0x59: // Binary data (two-byte uint16_t for n follow) + case 0x5A: // Binary data (four-byte uint32_t for n follow) + case 0x5B: // Binary data (eight-byte uint64_t for n follow) + case 0x5F: // Binary data (indefinite length) + { + binary_t b; + return get_cbor_binary(b) and sax->binary(b); + } + // UTF-8 string (0x00..0x17 bytes follow) case 0x60: case 0x61: @@ -5808,6 +5933,101 @@ class binary_reader } } + /*! + @brief reads a CBOR byte array + + This function first reads starting bytes to determine the expected + byte array length and then copies this number of bytes into the byte array. + Additionally, CBOR's byte arrays with indefinite lengths are supported. + + @param[out] result created byte array + + @return whether byte array creation completed + */ + bool get_cbor_binary(binary_t& result) + { + if (JSON_UNLIKELY(not unexpect_eof(input_format_t::cbor, "binary"))) + { + return false; + } + + switch (current) + { + // Binary data (0x00..0x17 bytes follow) + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + { + return get_binary(input_format_t::cbor, static_cast(current) & 0x1Fu, result); + } + + case 0x58: // Binary data (one-byte uint8_t for n follows) + { + std::uint8_t len; + return get_number(input_format_t::cbor, len) and get_binary(input_format_t::cbor, len, result); + } + + case 0x59: // Binary data (two-byte uint16_t for n follow) + { + std::uint16_t len; + return get_number(input_format_t::cbor, len) and get_binary(input_format_t::cbor, len, result); + } + + case 0x5A: // Binary data (four-byte uint32_t for n follow) + { + std::uint32_t len; + return get_number(input_format_t::cbor, len) and get_binary(input_format_t::cbor, len, result); + } + + case 0x5B: // Binary data (eight-byte uint64_t for n follow) + { + std::uint64_t len; + return get_number(input_format_t::cbor, len) and get_binary(input_format_t::cbor, len, result); + } + + case 0x5F: // Binary data (indefinite length) + { + while (get() != 0xFF) + { + binary_t chunk; + if (not get_cbor_binary(chunk)) + { + return false; + } + result.insert(result.end(), chunk.begin(), chunk.end()); + } + return true; + } + + default: + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::cbor, "expected length specification (0x40-0x5B) or indefinite binary array type (0x5F); last byte: 0x" + last_token, "binary"))); + } + } + } + /*! @param[in] len the length of the array or std::size_t(-1) for an array of indefinite size @@ -6125,6 +6345,14 @@ class binary_reader case 0xC3: // true return sax->boolean(true); + case 0xC4: // bin 8 + case 0xC5: // bin 16 + case 0xC6: // bin 32 + { + binary_t b; + return get_msgpack_binary(b) and sax->binary(b); + } + case 0xCA: // float 32 { float number; @@ -6342,6 +6570,51 @@ class binary_reader } } + /*! + @brief reads a MessagePack byte array + + This function first reads starting bytes to determine the expected + byte array length and then copies this number of bytes into a byte array. + + @param[out] result created byte array + + @return whether byte array creation completed + */ + bool get_msgpack_binary(binary_t& result) + { + if (JSON_UNLIKELY(not unexpect_eof(input_format_t::msgpack, "binary"))) + { + return false; + } + + switch (current) + { + case 0xC4: // str 8 + { + std::uint8_t len; + return get_number(input_format_t::msgpack, len) and get_binary(input_format_t::msgpack, len, result); + } + + case 0xC5: // str 16 + { + std::uint16_t len; + return get_number(input_format_t::msgpack, len) and get_binary(input_format_t::msgpack, len, result); + } + + case 0xC6: // str 32 + { + std::uint32_t len; + return get_number(input_format_t::msgpack, len) and get_binary(input_format_t::msgpack, len, result); + } + + default: + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::msgpack, "expected length specification (0xC4-0xC6); last byte: 0x" + last_token, "binary"))); + } + } + } + /*! @param[in] len the length of the array @return whether array creation completed @@ -6826,6 +7099,9 @@ class binary_reader return sax->end_object(); } + // Note, no reader for UBJSON binary types is implemented because they do + // not exist + /////////////////////// // Utility functions // /////////////////////// @@ -6933,6 +7209,38 @@ class binary_reader return success; } + /*! + @brief create a byte array by reading bytes from the input + + @tparam NumberType the type of the number + @param[in] format the current format (for diagnostics) + @param[in] len number of bytes to read + @param[out] result byte array created by reading @a len bytes + + @return whether byte array creation completed + + @note We can not reserve @a len bytes for the result, because @a len + may be too large. Usually, @ref unexpect_eof() detects the end of + the input before we run out of memory. + */ + template + bool get_binary(const input_format_t format, + const NumberType len, + binary_t& result) + { + bool success = true; + std::generate_n(std::back_inserter(result), len, [this, &success, &format]() + { + get(); + if (JSON_UNLIKELY(not unexpect_eof(format, "binary"))) + { + success = false; + } + return static_cast(current); + }); + return success; + } + /*! @param[in] format the current format (for diagnostics) @param[in] context further context information (for diagnostics) @@ -9183,6 +9491,8 @@ template struct internal_iterator typename BasicJsonType::object_t::iterator object_iterator {}; /// iterator for JSON arrays typename BasicJsonType::array_t::iterator array_iterator {}; + /// iterator for JSON binary arrays + typename BasicJsonType::binary_t::iterator binary_iterator {}; /// generic iterator for all other types primitive_iterator_t primitive_iterator {}; }; @@ -11211,6 +11521,7 @@ template class binary_writer { using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; public: /*! @@ -11443,6 +11754,45 @@ class binary_writer break; } + case value_t::binary: + { + // step 1: write control byte and the binary array size + const auto N = j.m_value.binary->size(); + if (N <= 0x17) + { + write_number(static_cast(0x40 + N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x58)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x59)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x5A)); + write_number(static_cast(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x5B)); + write_number(static_cast(N)); + } + // LCOV_EXCL_STOP + + // step 2: write each element + oa->write_characters( + reinterpret_cast(j.m_value.binary->data()), + N); + + break; + } + case value_t::object: { // step 1: write control byte and the object size @@ -11691,6 +12041,37 @@ class binary_writer break; } + case value_t::binary: + { + // step 1: write control byte and the byte string length + const auto N = j.m_value.binary->size(); + if (N <= (std::numeric_limits::max)()) + { + // bin 8 + oa->write_character(to_char_type(0xC4)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // bin 16 + oa->write_character(to_char_type(0xC5)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // bin 32 + oa->write_character(to_char_type(0xC6)); + write_number(static_cast(N)); + } + + // step 2: write the byte string + oa->write_characters( + reinterpret_cast(j.m_value.binary->data()), + N); + + break; + } + case value_t::object: { // step 1: write control byte and the object size @@ -11834,6 +12215,38 @@ class binary_writer break; } + case value_t::binary: + { + if (add_prefix) + { + oa->write_character(to_char_type('[')); + } + + if (use_type and not j.m_value.binary->empty()) + { + assert(use_count); + oa->write_character(to_char_type('$')); + oa->write_character('U'); + } + + if (use_count) + { + oa->write_character(to_char_type('#')); + write_number_with_ubjson_prefix(j.m_value.binary->size(), true); + } + + oa->write_characters( + reinterpret_cast(j.m_value.binary->data()), + j.m_value.binary->size()); + + if (not use_count) + { + oa->write_character(to_char_type(']')); + } + + break; + } + case value_t::object: { if (add_prefix) @@ -12057,6 +12470,14 @@ class binary_writer return sizeof(std::int32_t) + embedded_document_size + 1ul; } + /*! + @return The size of the BSON-encoded binary array @a value + */ + static std::size_t calc_bson_binary_size(const typename BasicJsonType::binary_t& value) + { + return sizeof(std::int32_t) + value.size() + 1ul; + } + /*! @brief Writes a BSON element with key @a name and array @a value */ @@ -12076,6 +12497,21 @@ class binary_writer oa->write_character(to_char_type(0x00)); } + /*! + @brief Writes a BSON element with key @a name and binary value @a value + */ + void write_bson_binary(const string_t& name, + const binary_t& value) + { + write_bson_entry_header(name, 0x05); + + write_number(static_cast(value.size())); + write_number(0x00); // Generic Binary Subtype + oa->write_characters( + reinterpret_cast(value.data()), + value.size()); + } + /*! @brief Calculates the size necessary to serialize the JSON value @a j with its @a name @return The calculated size for the BSON document entry for @a j with the given @a name. @@ -12092,6 +12528,9 @@ class binary_writer case value_t::array: return header_size + calc_bson_array_size(*j.m_value.array); + case value_t::binary: + return header_size + calc_bson_binary_size(*j.m_value.binary); + case value_t::boolean: return header_size + 1ul; @@ -12136,6 +12575,9 @@ class binary_writer case value_t::array: return write_bson_array(name, *j.m_value.array); + case value_t::binary: + return write_bson_binary(name, *j.m_value.binary); + case value_t::boolean: return write_bson_boolean(name, j.m_value.boolean); @@ -12416,7 +12858,8 @@ class binary_writer case value_t::string: return 'S'; - case value_t::array: + case value_t::array: // fallthrough + case value_t::binary: return '['; case value_t::object: @@ -13875,6 +14318,11 @@ class serializer return; } + case value_t::binary: + { + JSON_THROW(type_error::create(317, "cannot serialize binary data to text JSON")); + } + case value_t::boolean: { if (val.m_value.boolean) @@ -14508,6 +14956,9 @@ default; will be used in @ref number_integer_t) `uint64_t` by default; will be used in @ref number_unsigned_t) @tparam NumberFloatType type for JSON floating-point numbers (`double` by default; will be used in @ref number_float_t) +@tparam BinaryType type for packed binary data for compatibility with binary +serialization formats (`std::vector` by default; will be used in +@ref binary_t) @tparam AllocatorType type of the allocator to use (`std::allocator` by default) @tparam JSONSerializer the serializer to resolve internal calls to `to_json()` @@ -15229,6 +15680,58 @@ class basic_json */ using number_float_t = NumberFloatType; + /*! + @brief a type for a packed binary type + + This type is a type designed to carry binary data that appears in various + serialized formats, such as CBOR's Major Type 2, MessagePack's bin, and + BSON's generic binary subtype. This type is NOT a part of standard JSON and + exists solely for compatibility with these binary types. As such, it is + simply defined as an ordered sequence of zero or more byte values. + + [CBOR's RFC 7049](https://tools.ietf.org/html/rfc7049) describes this type + as: + > Major type 2: a byte string. The string's length in bytes is + > represented following the rules for positive integers (major type + > 0). + + [MessagePack's documentation on the bin type + family](https://github.com/msgpack/msgpack/blob/master/spec.md#bin-format-family) + describes this type as: + > Bin format family stores an byte array in 2, 3, or 5 bytes of extra bytes + > in addition to the size of the byte array. + + [BSON's specifications](http://bsonspec.org/spec.html) describe several + binary types; however, this type is intended to represent the generic binary + type which has the description: + > Generic binary subtype - This is the most commonly used binary subtype and + > should be the 'default' for drivers and tools. + + None of these impose any limitations on the internal representation other + than the basic unit of storage be some type of array whose parts are + decomposible into bytes. + + The default representation of this binary format is a + `std::vector`, which is a very common way to represent a byte + array in modern C++. + + #### Default type + + The default values for @a BinaryType is `std::vector` + + #### Storage + + Binary Arrays are stored as pointers in a @ref basic_json type. That is, + for any access to array values, a pointer of the type `binary_t*` must be + dereferenced. + + @sa @ref array_t -- type for an array value + + @since version 3.7.0 + */ + + using binary_t = BinaryType; + /// @} private: @@ -15271,6 +15774,7 @@ class basic_json number | number_integer | @ref number_integer_t number | number_unsigned | @ref number_unsigned_t number | number_float | @ref number_float_t + binary | binary | pointer to @ref binary_t null | null | *no value is stored* @note Variable-length types (objects, arrays, and strings) are stored as @@ -15287,6 +15791,8 @@ class basic_json array_t* array; /// string (stored with pointer to save storage) string_t* string; + /// binary (stored with pointer to save storage) + binary_t* binary; /// boolean boolean_t boolean; /// number (integer) @@ -15329,6 +15835,12 @@ class basic_json break; } + case value_t::binary: + { + binary = create(); + break; + } + case value_t::boolean: { boolean = boolean_t(false); @@ -15407,6 +15919,18 @@ class basic_json array = create(std::move(value)); } + /// constructor for binary arrays + json_value(const binary_t& value) + { + binary = create(value); + } + + /// constructor for rvalue binary arrays + json_value(binary_t&& value) + { + binary = create(std::move(value)); + } + void destroy(value_t t) noexcept { switch (t) @@ -15435,6 +15959,14 @@ class basic_json break; } + case value_t::binary: + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, binary); + std::allocator_traits::deallocate(alloc, binary, 1); + break; + } + default: { break; @@ -15555,6 +16087,7 @@ class basic_json number | `0` object | `{}` array | `[]` + binary | empty array @param[in] v the type of the value to create @@ -15626,6 +16159,12 @@ class basic_json @ref number_float_t, and all convertible number types such as `int`, `size_t`, `int64_t`, `float` or `double` can be used. - **boolean**: @ref boolean_t / `bool` can be used. + - **binary**: @ref binary_t / `std::vector` may be used, + unfortunately because string literals cannot be distinguished from binary + character arrays by the C++ type system, all types compatible with `const + char*` will be directed to the string constructor instead. This is both + for backwards compatibility, and due to the fact that a binary type is not + a standard JSON type. See the examples below. @@ -15707,6 +16246,7 @@ class basic_json using other_string_t = typename BasicJsonType::string_t; using other_object_t = typename BasicJsonType::object_t; using other_array_t = typename BasicJsonType::array_t; + using other_binary_t = typename BasicJsonType::binary_t; switch (val.type()) { @@ -15731,6 +16271,9 @@ class basic_json case value_t::array: JSONSerializer::to_json(*this, val.template get_ref()); break; + case value_t::binary: + JSONSerializer::to_json(*this, val.template get_ref()); + break; case value_t::null: *this = nullptr; break; @@ -16124,6 +16667,13 @@ class basic_json break; } + case value_t::binary: + { + m_value.binary = create(first.m_it.binary_iterator, + last.m_it.binary_iterator); + break; + } + default: JSON_THROW(invalid_iterator::create(206, "cannot construct with iterators from " + std::string(first.m_object->type_name()))); @@ -16217,6 +16767,11 @@ class basic_json break; } + case value_t::binary: + { + m_value = *other.m_value.binary; + } + default: break; } @@ -16415,6 +16970,7 @@ class basic_json number (floating-point) | value_t::number_float object | value_t::object array | value_t::array + binary | value_t::binary discarded | value_t::discarded @complexity Constant. @@ -16457,12 +17013,13 @@ class basic_json @sa @ref is_string() -- returns whether JSON value is a string @sa @ref is_boolean() -- returns whether JSON value is a boolean @sa @ref is_number() -- returns whether JSON value is a number + @sa @ref is_binary() -- returns whether JSON value is a binary array @since version 1.0.0 */ constexpr bool is_primitive() const noexcept { - return is_null() or is_string() or is_boolean() or is_number(); + return is_null() or is_string() or is_boolean() or is_number() or is_binary(); } /*! @@ -16717,6 +17274,28 @@ class basic_json return m_type == value_t::string; } + /*! + @brief return whether value is a binary array + + This function returns true if and only if the JSON value is a binary array. + + @return `true` if type is binary array, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_binary()` for all JSON + types.,is_binary} + + @since version 3.7.0 + */ + constexpr bool is_binary() const noexcept + { + return m_type == value_t::binary; + } + /*! @brief return whether value is discarded @@ -16872,6 +17451,18 @@ class basic_json return is_number_float() ? &m_value.number_float : nullptr; } + /// get a pointer to the value (binary) + binary_t* get_impl_ptr(binary_t* /*unused*/) noexcept + { + return is_binary() ? m_value.binary : nullptr; + } + + /// get a pointer to the value (binary) + constexpr const binary_t* get_impl_ptr(const binary_t* /*unused*/) const noexcept + { + return is_binary() ? m_value.binary : nullptr; + } + /*! @brief helper function to implement get_ref() @@ -16951,7 +17542,7 @@ class basic_json @brief get a value (explicit) Explicit type conversion between the JSON value and a compatible value - which is [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible) + which is [Copynonstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible) and [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). The value is converted by calling the @ref json_serializer `from_json()` method. @@ -17929,8 +18520,8 @@ class basic_json container `c`, the expression `c.front()` is equivalent to `*c.begin()`. @return In case of a structured type (array or object), a reference to the - first element is returned. In case of number, string, or boolean values, a - reference to the value is returned. + first element is returned. In case of number, string, boolean, or binary + values, a reference to the value is returned. @complexity Constant. @@ -17972,8 +18563,8 @@ class basic_json @endcode @return In case of a structured type (array or object), a reference to the - last element is returned. In case of number, string, or boolean values, a - reference to the value is returned. + last element is returned. In case of number, string, boolean, or binary + values, a reference to the value is returned. @complexity Constant. @@ -18039,7 +18630,7 @@ class basic_json @complexity The complexity depends on the type: - objects: amortized constant - arrays: linear in distance between @a pos and the end of the container - - strings: linear in the length of the string + - strings and binary: linear in the length of the member - other types: constant @liveexample{The example shows the result of `erase()` for different JSON @@ -18075,6 +18666,7 @@ class basic_json case value_t::number_integer: case value_t::number_unsigned: case value_t::string: + case value_t::binary: { if (JSON_HEDLEY_UNLIKELY(not pos.m_it.primitive_iterator.is_begin())) { @@ -18088,6 +18680,13 @@ class basic_json std::allocator_traits::deallocate(alloc, m_value.string, 1); m_value.string = nullptr; } + else if (is_binary()) + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, m_value.binary); + std::allocator_traits::deallocate(alloc, m_value.binary, 1); + m_value.binary = nullptr; + } m_type = value_t::null; assert_invariant(); @@ -18145,7 +18744,7 @@ class basic_json - objects: `log(size()) + std::distance(first, last)` - arrays: linear in the distance between @a first and @a last, plus linear in the distance between @a last and end of the container - - strings: linear in the length of the string + - strings and binary: linear in the length of the member - other types: constant @liveexample{The example shows the result of `erase()` for different JSON @@ -18180,6 +18779,7 @@ class basic_json case value_t::number_integer: case value_t::number_unsigned: case value_t::string: + case value_t::binary: { if (JSON_HEDLEY_LIKELY(not first.m_it.primitive_iterator.is_begin() or not last.m_it.primitive_iterator.is_end())) @@ -18194,6 +18794,13 @@ class basic_json std::allocator_traits::deallocate(alloc, m_value.string, 1); m_value.string = nullptr; } + else if (is_binary()) + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, m_value.binary); + std::allocator_traits::deallocate(alloc, m_value.binary, 1); + m_value.binary = nullptr; + } m_type = value_t::null; assert_invariant(); @@ -18913,6 +19520,7 @@ class basic_json boolean | `false` string | `false` number | `false` + binary | `false` object | result of function `object_t::empty()` array | result of function `array_t::empty()` @@ -18984,6 +19592,7 @@ class basic_json boolean | `1` string | `1` number | `1` + binary | `1` object | result of function object_t::size() array | result of function array_t::size() @@ -19058,6 +19667,7 @@ class basic_json boolean | `1` (same as `size()`) string | `1` (same as `size()`) number | `1` (same as `size()`) + binary | `1` (same as `size()`) object | result of function `object_t::max_size()` array | result of function `array_t::max_size()` @@ -19130,6 +19740,7 @@ class basic_json boolean | `false` string | `""` number | `0` + binary | An empty byte vector object | `{}` array | `[]` @@ -19187,6 +19798,12 @@ class basic_json break; } + case value_t::binary: + { + m_value.binary->clear(); + break; + } + case value_t::array: { m_value.array->clear(); @@ -20065,6 +20682,9 @@ class basic_json case value_t::number_float: return lhs.m_value.number_float == rhs.m_value.number_float; + case value_t::binary: + return lhs.m_value.binary == rhs.m_value.binary; + default: return false; } @@ -20225,6 +20845,9 @@ class basic_json case value_t::number_float: return (lhs.m_value.number_float) < (rhs.m_value.number_float); + case value_t::binary: + return lhs.m_value.binary < rhs.m_value.binary; + default: return false; } @@ -20789,6 +21412,7 @@ class basic_json number | `"number"` (for all number types) object | `"object"` array | `"array"` + binary | `"binary"` discarded | `"discarded"` @exceptionsafety No-throw guarantee: this function never throws exceptions. @@ -20820,6 +21444,8 @@ class basic_json return "string"; case value_t::boolean: return "boolean"; + case value_t::binary: + return "binary"; case value_t::discarded: return "discarded"; default: @@ -20895,6 +21521,11 @@ class basic_json object | *size*: 256..65535 | map (2 bytes follow) | 0xB9 object | *size*: 65536..4294967295 | map (4 bytes follow) | 0xBA object | *size*: 4294967296..18446744073709551615 | map (8 bytes follow) | 0xBB + binary | *size*: 0..23 | byte string | 0x40..0x57 + binary | *size*: 23..255 | byte string (1 byte follow) | 0x58 + binary | *size*: 256..65535 | byte string (2 bytes follow) | 0x59 + binary | *size*: 65536..4294967295 | byte string (4 bytes follow) | 0x5A + binary | *size*: 4294967296..18446744073709551615 | byte string (8 bytes follow) | 0x5B @note The mapping is **complete** in the sense that any JSON value type can be converted to a CBOR value. @@ -20904,10 +21535,10 @@ class basic_json function which serializes NaN or Infinity to `null`. @note The following CBOR types are not used in the conversion: - - byte strings (0x40..0x5F) - UTF-8 strings terminated by "break" (0x7F) - arrays terminated by "break" (0x9F) - maps terminated by "break" (0xBF) + - byte strings terminated by "break" (0x5F) - date/time (0xC0..0xC1) - bignum (0xC2..0xC3) - decimal fraction (0xC4) @@ -20994,17 +21625,20 @@ class basic_json object | *size*: 0..15 | fix map | 0x80..0x8F object | *size*: 16..65535 | map 16 | 0xDE object | *size*: 65536..4294967295 | map 32 | 0xDF + binary | *size*: 0..255 | bin 8 | 0xC4 + binary | *size*: 256..65535 | bin 16 | 0xC5 + binary | *size*: 65536..4294967295 | bin 32 | 0xC6 @note The mapping is **complete** in the sense that any JSON value type can be converted to a MessagePack value. @note The following values can **not** be converted to a MessagePack value: - strings with more than 4294967295 bytes + - byte strings with more than 4294967295 bytes - arrays with more than 4294967295 elements - objects with more than 4294967295 elements @note The following MessagePack types are not used in the conversion: - - bin 8 - bin 32 (0xC4..0xC6) - ext 8 - ext 32 (0xC7..0xC9) - float 32 (0xCA) - fixext 1 - fixext 16 (0xD4..0xD8) @@ -21110,6 +21744,12 @@ class basic_json the benefit of this parameter is that the receiving side is immediately informed on the number of elements of the container. + @note If the JSON data contains the binary type, the value stored is a list + of integers, as suggested by the UBJSON documentation. In particular, + this means that serialization and the deserialization of a JSON + containing binary values into UBJSON and back will result in a + different JSON object. + @param[in] j JSON value to serialize @param[in] use_size whether to add size annotations to container types @param[in] use_type whether to add type annotations to container types @@ -21174,6 +21814,7 @@ class basic_json string | *any value* | string | 0x02 array | *any value* | document | 0x04 object | *any value* | document | 0x03 + binary | *any value* | binary | 0x05 @warning The mapping is **incomplete**, since only JSON-objects (and things contained therein) can be serialized to BSON. @@ -21190,6 +21831,9 @@ class basic_json @note Any BSON output created via @ref to_bson can be successfully parsed by @ref from_bson. + @note While the deserializer will accept any binary subtype, it will only + encode to 0x00 (generic binary subtype). + @param[in] j JSON value to serialize @return BSON serialization as byte vector @@ -21255,7 +21899,11 @@ class basic_json Negative integer | number_integer | 0x39 Negative integer | number_integer | 0x3A Negative integer | number_integer | 0x3B - Negative integer | number_integer | 0x40..0x57 + Byte string | binary | 0x40..0x57 + Byte string | binary | 0x58 + Byte string | binary | 0x59 + Byte string | binary | 0x5A + Byte string | binary | 0x5B UTF-8 string | string | 0x60..0x77 UTF-8 string | string | 0x78 UTF-8 string | string | 0x79 @@ -21284,7 +21932,6 @@ class basic_json @warning The mapping is **incomplete** in the sense that not all CBOR types can be converted to a JSON value. The following CBOR types are not supported and will yield parse errors (parse_error.112): - - byte strings (0x40..0x5F) - date/time (0xC0..0xC1) - bignum (0xC2..0xC3) - decimal fraction (0xC4) diff --git a/test/src/unit-allocator.cpp b/test/src/unit-allocator.cpp index c46afc59e7..7a13ba939b 100644 --- a/test/src/unit-allocator.cpp +++ b/test/src/unit-allocator.cpp @@ -60,6 +60,7 @@ TEST_CASE("bad_alloc") std::int64_t, std::uint64_t, double, + std::vector, bad_allocator>; // creating an object should throw @@ -146,6 +147,7 @@ TEST_CASE("controlled bad_alloc") std::int64_t, std::uint64_t, double, + std::vector, my_allocator>; SECTION("class json_value") diff --git a/test/src/unit-alt-string.cpp b/test/src/unit-alt-string.cpp index 39206b657a..60092c5580 100644 --- a/test/src/unit-alt-string.cpp +++ b/test/src/unit-alt-string.cpp @@ -162,6 +162,7 @@ using alt_json = nlohmann::basic_json < std::int64_t, std::uint64_t, double, + std::vector, std::allocator, nlohmann::adl_serializer >; diff --git a/test/src/unit-bson.cpp b/test/src/unit-bson.cpp index cc4b5a6597..a1a6f7f052 100644 --- a/test/src/unit-bson.cpp +++ b/test/src/unit-bson.cpp @@ -647,6 +647,11 @@ class SaxCountdown return events_left-- > 0; } + bool binary(std::vector&) + { + return events_left-- > 0; + } + bool start_object(std::size_t) { return events_left-- > 0; diff --git a/test/src/unit-cbor.cpp b/test/src/unit-cbor.cpp index 8b4a17078f..af345f6d46 100644 --- a/test/src/unit-cbor.cpp +++ b/test/src/unit-cbor.cpp @@ -36,6 +36,7 @@ using nlohmann::json; #include #include #include +#include #include namespace @@ -75,6 +76,11 @@ class SaxCountdown { return events_left-- > 0; } + + bool binary(std::vector&) + { + return events_left-- > 0; + } bool start_object(std::size_t) { @@ -1285,10 +1291,156 @@ TEST_CASE("CBOR") CHECK(json::from_cbor(result, true, false) == j); } } + + SECTION("binary") + { + SECTION("N = 0..23") + { + for (size_t N = 0; N <= 0x17; ++N) + { + CAPTURE(N) + + // create JSON value with byte array containing of N * 'x' + const auto s = std::vector(N, 'x'); + json j = s; + + // create expected byte vector + std::vector expected; + expected.push_back(static_cast(0x40 + N)); + for (size_t i = 0; i < N; ++i) + { + expected.push_back(0x78); + } + + // compare result + size + const auto result = json::to_cbor(j); + CHECK(result == expected); + CHECK(result.size() == N + 1); + // check that no null byte is appended + if (N > 0) + { + CHECK(result.back() != '\x00'); + } + + // roundtrip + CHECK(json::from_cbor(result) == j); + CHECK(json::from_cbor(result, true, false) == j); + } + } + + SECTION("N = 24..255") + { + for (size_t N = 24; N <= 255; ++N) + { + CAPTURE(N) + + // create JSON value with string containing of N * 'x' + const auto s = std::vector(N, 'x'); + json j = s; + + // create expected byte vector + std::vector expected; + expected.push_back(0x58); + 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_cbor(j); + CHECK(result == expected); + CHECK(result.size() == N + 2); + // check that no null byte is appended + CHECK(result.back() != '\x00'); + + // roundtrip + CHECK(json::from_cbor(result) == j); + CHECK(json::from_cbor(result, true, false) == j); + } + } + + SECTION("N = 256..65535") + { + for (size_t N : + { + 256u, 999u, 1025u, 3333u, 2048u, 65535u + }) + { + CAPTURE(N) + + // create JSON value with string containing of N * 'x' + const auto s = std::vector(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(), 0x59); + + // compare result + size + const auto result = json::to_cbor(j); + CHECK(result == expected); + CHECK(result.size() == N + 3); + // check that no null byte is appended + CHECK(result.back() != '\x00'); + + // roundtrip + CHECK(json::from_cbor(result) == j); + CHECK(json::from_cbor(result, true, false) == j); + } + } + + SECTION("N = 65536..4294967295") + { + for (size_t N : + { + 65536u, 77777u, 1048576u + }) + { + CAPTURE(N) + + // create JSON value with string containing of N * 'x' + const auto s = std::vector(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(), 0x5a); + + // compare result + size + const auto result = json::to_cbor(j); + CHECK(result == expected); + CHECK(result.size() == N + 5); + // check that no null byte is appended + CHECK(result.back() != '\x00'); + + // roundtrip + CHECK(json::from_cbor(result) == j); + CHECK(json::from_cbor(result, true, false) == j); + } + } + } } SECTION("additonal deserialization") { + SECTION("0x5b (byte array)") + { + std::vector given = {0x4b, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x61 + }; + json j = json::from_cbor(given); + CHECK(j == std::vector{'a'}); + } + SECTION("0x7b (string)") { std::vector given = {0x7b, 0x00, 0x00, 0x00, 0x00, @@ -1455,14 +1607,8 @@ TEST_CASE("CBOR") 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, // ? @@ -1929,12 +2075,6 @@ TEST_CASE("all CBOR first bytes") { //// 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 @@ -2144,6 +2284,21 @@ TEST_CASE("examples from RFC 7049 Appendix A") CHECK(json::parse("\"streaming\"") == json::from_cbor(std::vector({0x7f, 0x65, 0x73, 0x74, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x67, 0xff}))); } + SECTION("byte arrays") + { + std::ifstream f_cbor("test/data/binary_data/cbor_binary.cbor", std::ios::binary); + std::vector packed((std::istreambuf_iterator(f_cbor)), + std::istreambuf_iterator()); + json j; + CHECK_NOTHROW(j = json::from_cbor(packed)); + + std::ifstream f_bin("test/data/binary_data/cbor_binary.out", std::ios::binary); + std::vector expected((std::istreambuf_iterator(f_cbor)), + std::istreambuf_iterator()); + CHECK(j == expected); + + } + SECTION("arrays") { CHECK(json::to_cbor(json::parse("[]")) == std::vector({0x80})); diff --git a/test/src/unit-class_parser.cpp b/test/src/unit-class_parser.cpp index fa09c5658e..73f188ebe2 100644 --- a/test/src/unit-class_parser.cpp +++ b/test/src/unit-class_parser.cpp @@ -77,6 +77,20 @@ class SaxEventLogger return true; } + bool binary(std::vector& val) + { + std::string binary_contents = "binary("; + std::string commaSpace = ""; + for (auto b : val) { + binary_contents.append(commaSpace); + binary_contents.append(std::to_string(static_cast(b))); + commaSpace = ", "; + } + binary_contents.append(")"); + events.push_back(binary_contents); + return true; + } + bool start_object(std::size_t elements) { if (elements == std::size_t(-1)) @@ -167,6 +181,11 @@ class SaxCountdown : public nlohmann::json::json_sax_t { return events_left-- > 0; } + + bool binary(std::vector&) override + { + return events_left-- > 0; + } bool start_object(std::size_t) override { diff --git a/test/src/unit-deserialization.cpp b/test/src/unit-deserialization.cpp index f49f1025dd..5e8fe6718a 100644 --- a/test/src/unit-deserialization.cpp +++ b/test/src/unit-deserialization.cpp @@ -76,6 +76,20 @@ struct SaxEventLogger : public nlohmann::json_sax return true; } + bool binary(std::vector& val) + { + std::string binary_contents = "binary("; + std::string commaSpace = ""; + for (auto b : val) { + binary_contents.append(commaSpace); + binary_contents.append(std::to_string(static_cast(b))); + commaSpace = ", "; + } + binary_contents.append(")"); + events.push_back(binary_contents); + return true; + } + bool start_object(std::size_t elements) override { if (elements == std::size_t(-1)) diff --git a/test/src/unit-msgpack.cpp b/test/src/unit-msgpack.cpp index 7a095544f0..ed475a5699 100644 --- a/test/src/unit-msgpack.cpp +++ b/test/src/unit-msgpack.cpp @@ -74,6 +74,11 @@ class SaxCountdown { return events_left-- > 0; } + + bool binary(std::vector&) + { + return events_left-- > 0; + } bool start_object(std::size_t) { @@ -1227,11 +1232,6 @@ TEST_CASE("MessagePack") CHECK_THROWS_WITH(_ = json::from_msgpack(std::vector({0xc1})), "[json.exception.parse_error.112] parse error at byte 1: syntax error while parsing MessagePack value: invalid byte: 0xC1"); CHECK(json::from_msgpack(std::vector({0xc6}), true, false).is_discarded()); - - CHECK_THROWS_AS(_ = json::from_msgpack(std::vector({0xc6})), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_msgpack(std::vector({0xc6})), - "[json.exception.parse_error.112] parse error at byte 1: syntax error while parsing MessagePack value: invalid byte: 0xC6"); - CHECK(json::from_msgpack(std::vector({0xc6}), true, false).is_discarded()); } SECTION("all unsupported bytes") diff --git a/test/src/unit-regression.cpp b/test/src/unit-regression.cpp index 0cadd608ae..137054d0a6 100644 --- a/test/src/unit-regression.cpp +++ b/test/src/unit-regression.cpp @@ -107,7 +107,7 @@ struct foo_serializer < T, typename std::enable_if < !std::is_same::valu } using foo_json = nlohmann::basic_json; + std::uint64_t, double, std::vector, std::allocator, ns::foo_serializer>; ///////////////////////////////////////////////////////////////////// // for #805 diff --git a/test/src/unit-ubjson.cpp b/test/src/unit-ubjson.cpp index 46539dd638..345bc052d9 100644 --- a/test/src/unit-ubjson.cpp +++ b/test/src/unit-ubjson.cpp @@ -73,6 +73,11 @@ class SaxCountdown return events_left-- > 0; } + bool binary(std::vector&) + { + return events_left-- > 0; + } + bool start_object(std::size_t) { return events_left-- > 0; diff --git a/test/src/unit-udt.cpp b/test/src/unit-udt.cpp index 6e70af83bc..2b421325ac 100644 --- a/test/src/unit-udt.cpp +++ b/test/src/unit-udt.cpp @@ -653,8 +653,8 @@ TEST_CASE("custom serializer for pods" * doctest::test_suite("udt")) { using custom_json = nlohmann::basic_json; + std::int64_t, std::uint64_t, double, std::vector, + std::allocator, pod_serializer>; auto p = udt::small_pod{42, '/', 42}; custom_json j = p; @@ -672,7 +672,7 @@ TEST_CASE("custom serializer for pods" * doctest::test_suite("udt")) template struct another_adl_serializer; -using custom_json = nlohmann::basic_json; +using custom_json = nlohmann::basic_json, std::allocator, another_adl_serializer>; template struct another_adl_serializer