Merge pull request #1662 from OmnipotentEntity/develop

Add binary type support to all binary file formats, as well as an internally represented binary type
This commit is contained in:
Niels Lohmann 2020-04-16 11:14:32 +02:00 committed by GitHub
commit f2b43a36b2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 3008 additions and 106 deletions

View file

@ -67,6 +67,28 @@ struct external_constructor<value_t::string>
} }
}; };
template<>
struct external_constructor<value_t::binary>
{
template<typename BasicJsonType>
static void construct(BasicJsonType& j, const typename BasicJsonType::binary_t& b)
{
j.m_type = value_t::binary;
typename BasicJsonType::internal_binary_t value{b};
j.m_value = value;
j.assert_invariant();
}
template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::binary_t&& b)
{
j.m_type = value_t::binary;
typename BasicJsonType::internal_binary_t value{std::move(b)};
j.m_value = value;
j.assert_invariant();
}
};
template<> template<>
struct external_constructor<value_t::number_float> struct external_constructor<value_t::number_float>
{ {

View file

@ -38,6 +38,7 @@ class binary_reader
using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
using number_float_t = typename BasicJsonType::number_float_t; using number_float_t = typename BasicJsonType::number_float_t;
using string_t = typename BasicJsonType::string_t; using string_t = typename BasicJsonType::string_t;
using internal_binary_t = typename BasicJsonType::internal_binary_t;
using json_sax_t = SAX; using json_sax_t = SAX;
public: public:
@ -207,6 +208,30 @@ class binary_reader
return get_string(input_format_t::bson, len - static_cast<NumberType>(1), result) and get() != std::char_traits<char>::eof(); return get_string(input_format_t::bson, len - static_cast<NumberType>(1), result) and get() != std::char_traits<char>::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<typename NumberType>
bool get_bson_binary(const NumberType len, internal_binary_t& result)
{
if (JSON_HEDLEY_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")));
}
result.has_subtype = true; // All BSON binary values have a subtype
get_number<std::uint8_t>(input_format_t::bson, result.subtype);
return get_binary(input_format_t::bson, len, result);
}
/*! /*!
@brief Read a BSON document element of the given @a element_type. @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 @param[in] element_type The BSON element type, c.f. http://bsonspec.org/spec.html
@ -245,6 +270,13 @@ class binary_reader
return parse_bson_array(); return parse_bson_array();
} }
case 0x05: // binary
{
std::int32_t len;
internal_binary_t value;
return get_number<std::int32_t, true>(input_format_t::bson, len) and get_bson_binary(len, value) and sax->binary(value);
}
case 0x08: // boolean case 0x08: // boolean
{ {
return sax->boolean(get() != 0); return sax->boolean(get() != 0);
@ -291,6 +323,7 @@ class binary_reader
bool parse_bson_element_list(const bool is_array) bool parse_bson_element_list(const bool is_array)
{ {
string_t key; string_t key;
while (int element_type = get()) while (int element_type = get())
{ {
if (JSON_HEDLEY_UNLIKELY(not unexpect_eof(input_format_t::bson, "element list"))) if (JSON_HEDLEY_UNLIKELY(not unexpect_eof(input_format_t::bson, "element list")))
@ -465,6 +498,41 @@ class binary_reader
- static_cast<number_integer_t>(number)); - static_cast<number_integer_t>(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)
{
internal_binary_t b;
return get_cbor_binary(b) and sax->binary(b);
}
// UTF-8 string (0x00..0x17 bytes follow) // UTF-8 string (0x00..0x17 bytes follow)
case 0x60: case 0x60:
case 0x61: case 0x61:
@ -780,6 +848,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(internal_binary_t& result)
{
if (JSON_HEDLEY_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<unsigned int>(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)
{
internal_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 @param[in] len the length of the array or std::size_t(-1) for an
array of indefinite size array of indefinite size
@ -1100,6 +1263,22 @@ class binary_reader
case 0xC3: // true case 0xC3: // true
return sax->boolean(true); return sax->boolean(true);
case 0xC4: // bin 8
case 0xC5: // bin 16
case 0xC6: // bin 32
case 0xC7: // ext 8
case 0xC8: // ext 16
case 0xC9: // ext 32
case 0xD4: // fixext 1
case 0xD5: // fixext 2
case 0xD6: // fixext 4
case 0xD7: // fixext 8
case 0xD8: // fixext 16
{
internal_binary_t b;
return get_msgpack_binary(b) and sax->binary(b);
}
case 0xCA: // float 32 case 0xCA: // float 32
{ {
float number; float number;
@ -1309,6 +1488,108 @@ 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(internal_binary_t& result)
{
if (JSON_HEDLEY_UNLIKELY(not unexpect_eof(input_format_t::msgpack, "binary")))
{
return false;
}
switch (current)
{
case 0xC4: // bin 8
{
std::uint8_t len;
return get_number(input_format_t::msgpack, len) and get_binary(input_format_t::msgpack, len, result);
}
case 0xC5: // bin 16
{
std::uint16_t len;
return get_number(input_format_t::msgpack, len) and get_binary(input_format_t::msgpack, len, result);
}
case 0xC6: // bin 32
{
std::uint32_t len;
return get_number(input_format_t::msgpack, len) and get_binary(input_format_t::msgpack, len, result);
}
case 0xC7: // ext 8
{
std::uint8_t len;
result.has_subtype = true;
return get_number(input_format_t::msgpack, len) and
get_number(input_format_t::msgpack, result.subtype) and
get_binary(input_format_t::msgpack, len, result);
}
case 0xC8: // ext 16
{
std::uint16_t len;
result.has_subtype = true;
return get_number(input_format_t::msgpack, len) and
get_number(input_format_t::msgpack, result.subtype) and
get_binary(input_format_t::msgpack, len, result);
}
case 0xC9: // ext 32
{
std::uint32_t len;
result.has_subtype = true;
return get_number(input_format_t::msgpack, len) and
get_number(input_format_t::msgpack, result.subtype) and
get_binary(input_format_t::msgpack, len, result);
}
case 0xD4: // fixext 1
{
result.has_subtype = true;
return get_number(input_format_t::msgpack, result.subtype) and get_binary(input_format_t::msgpack, 1, result);
}
case 0xD5: // fixext 2
{
result.has_subtype = true;
return get_number(input_format_t::msgpack, result.subtype) and get_binary(input_format_t::msgpack, 2, result);
}
case 0xD6: // fixext 4
{
result.has_subtype = true;
return get_number(input_format_t::msgpack, result.subtype) and get_binary(input_format_t::msgpack, 4, result);
}
case 0xD7: // fixext 8
{
result.has_subtype = true;
return get_number(input_format_t::msgpack, result.subtype) and get_binary(input_format_t::msgpack, 8, result);
}
case 0xD8: // fixext 16
{
result.has_subtype = true;
return get_number(input_format_t::msgpack, result.subtype) and get_binary(input_format_t::msgpack, 16, 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 binary type specification (0xC4-0xC9, 0xD4-0xD8); last byte: 0x" + last_token, "binary")));
}
}
}
/*! /*!
@param[in] len the length of the array @param[in] len the length of the array
@return whether array creation completed @return whether array creation completed
@ -1793,6 +2074,9 @@ class binary_reader
return sax->end_object(); return sax->end_object();
} }
// Note, no reader for UBJSON binary types is implemented because they do
// not exist
/////////////////////// ///////////////////////
// Utility functions // // Utility functions //
/////////////////////// ///////////////////////
@ -1900,6 +2184,38 @@ class binary_reader
return success; 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<typename NumberType>
bool get_binary(const input_format_t format,
const NumberType len,
internal_binary_t& result)
{
bool success = true;
std::generate_n(std::back_inserter(result), len, [this, &success, &format]()
{
get();
if (JSON_HEDLEY_UNLIKELY(not unexpect_eof(format, "binary")))
{
success = false;
}
return static_cast<uint8_t>(current);
});
return success;
}
/*! /*!
@param[in] format the current format (for diagnostics) @param[in] format the current format (for diagnostics)
@param[in] context further context information (for diagnostics) @param[in] context further context information (for diagnostics)

View file

@ -31,6 +31,7 @@ struct json_sax
using number_float_t = typename BasicJsonType::number_float_t; using number_float_t = typename BasicJsonType::number_float_t;
/// type for strings /// type for strings
using string_t = typename BasicJsonType::string_t; using string_t = typename BasicJsonType::string_t;
using binary_t = typename BasicJsonType::binary_t;
/*! /*!
@brief a null value was read @brief a null value was read
@ -75,6 +76,14 @@ struct json_sax
*/ */
virtual bool string(string_t& val) = 0; 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 @brief the beginning of an object was read
@param[in] elements number of object elements or -1 if unknown @param[in] elements number of object elements or -1 if unknown
@ -149,6 +158,7 @@ class json_sax_dom_parser
using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
using number_float_t = typename BasicJsonType::number_float_t; using number_float_t = typename BasicJsonType::number_float_t;
using string_t = typename BasicJsonType::string_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 @param[in, out] r reference to a JSON value that is manipulated while
@ -202,6 +212,12 @@ class json_sax_dom_parser
return true; return true;
} }
bool binary(binary_t& val)
{
handle_binary(val);
return true;
}
bool start_object(std::size_t len) bool start_object(std::size_t len)
{ {
ref_stack.push_back(handle_value(BasicJsonType::value_t::object)); ref_stack.push_back(handle_value(BasicJsonType::value_t::object));
@ -311,6 +327,36 @@ class json_sax_dom_parser
return object_element; return object_element;
} }
/*!
@invariant If the ref stack is empty, then the passed value will be the new
root.
@invariant If the ref stack contains a value, then it is an array or an
object to which we can add elements
*/
template<typename BinaryValue>
JSON_HEDLEY_RETURNS_NON_NULL
BasicJsonType* handle_binary(BinaryValue&& v)
{
if (ref_stack.empty())
{
root = BasicJsonType::binary_array(std::forward<BinaryValue>(v));
return &root;
}
assert(ref_stack.back()->is_array() or ref_stack.back()->is_object());
if (ref_stack.back()->is_array())
{
ref_stack.back()->m_value.array->emplace_back(BasicJsonType::binary_array(std::forward<BinaryValue>(v)));
return &(ref_stack.back()->m_value.array->back());
}
assert(ref_stack.back()->is_object());
assert(object_element);
*object_element = BasicJsonType::binary_array(std::forward<BinaryValue>(v));
return object_element;
}
/// the parsed JSON value /// the parsed JSON value
BasicJsonType& root; BasicJsonType& root;
/// stack to model hierarchy of values /// stack to model hierarchy of values
@ -331,6 +377,7 @@ class json_sax_dom_callback_parser
using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
using number_float_t = typename BasicJsonType::number_float_t; using number_float_t = typename BasicJsonType::number_float_t;
using string_t = typename BasicJsonType::string_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 parser_callback_t = typename BasicJsonType::parser_callback_t;
using parse_event_t = typename BasicJsonType::parse_event_t; using parse_event_t = typename BasicJsonType::parse_event_t;
@ -385,6 +432,12 @@ class json_sax_dom_callback_parser
return true; return true;
} }
bool binary(binary_t& val)
{
handle_value(val);
return true;
}
bool start_object(std::size_t len) bool start_object(std::size_t len)
{ {
// check callback for object start // check callback for object start
@ -635,6 +688,7 @@ class json_sax_acceptor
using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
using number_float_t = typename BasicJsonType::number_float_t; using number_float_t = typename BasicJsonType::number_float_t;
using string_t = typename BasicJsonType::string_t; using string_t = typename BasicJsonType::string_t;
using binary_t = typename BasicJsonType::binary_t;
bool null() bool null()
{ {
@ -666,7 +720,12 @@ class json_sax_acceptor
return true; 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; return true;
} }
@ -681,7 +740,7 @@ class json_sax_acceptor
return true; 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; return true;
} }

View file

@ -18,6 +18,8 @@ template<typename BasicJsonType> struct internal_iterator
typename BasicJsonType::object_t::iterator object_iterator {}; typename BasicJsonType::object_t::iterator object_iterator {};
/// iterator for JSON arrays /// iterator for JSON arrays
typename BasicJsonType::array_t::iterator array_iterator {}; 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 /// generic iterator for all other types
primitive_iterator_t primitive_iterator {}; primitive_iterator_t primitive_iterator {};
}; };

View file

@ -113,9 +113,10 @@
class StringType, class BooleanType, class NumberIntegerType, \ class StringType, class BooleanType, class NumberIntegerType, \
class NumberUnsignedType, class NumberFloatType, \ class NumberUnsignedType, class NumberFloatType, \
template<typename> class AllocatorType, \ template<typename> class AllocatorType, \
template<typename, typename = void> class JSONSerializer> template<typename, typename = void> class JSONSerializer, \
class BinaryType>
#define NLOHMANN_BASIC_JSON_TPL \ #define NLOHMANN_BASIC_JSON_TPL \
basic_json<ObjectType, ArrayType, StringType, BooleanType, \ basic_json<ObjectType, ArrayType, StringType, BooleanType, \
NumberIntegerType, NumberUnsignedType, NumberFloatType, \ NumberIntegerType, NumberUnsignedType, NumberFloatType, \
AllocatorType, JSONSerializer> AllocatorType, JSONSerializer, BinaryType>

View file

@ -26,6 +26,7 @@ template<typename BasicJsonType, typename CharType>
class binary_writer class binary_writer
{ {
using string_t = typename BasicJsonType::string_t; using string_t = typename BasicJsonType::string_t;
using internal_binary_t = typename BasicJsonType::internal_binary_t;
public: public:
/*! /*!
@ -258,6 +259,45 @@ class binary_writer
break; 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<std::uint8_t>(0x40 + N));
}
else if (N <= (std::numeric_limits<std::uint8_t>::max)())
{
oa->write_character(to_char_type(0x58));
write_number(static_cast<std::uint8_t>(N));
}
else if (N <= (std::numeric_limits<std::uint16_t>::max)())
{
oa->write_character(to_char_type(0x59));
write_number(static_cast<std::uint16_t>(N));
}
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
{
oa->write_character(to_char_type(0x5A));
write_number(static_cast<std::uint32_t>(N));
}
// LCOV_EXCL_START
else if (N <= (std::numeric_limits<std::uint64_t>::max)())
{
oa->write_character(to_char_type(0x5B));
write_number(static_cast<std::uint64_t>(N));
}
// LCOV_EXCL_STOP
// step 2: write each element
oa->write_characters(
reinterpret_cast<const CharType*>(j.m_value.binary->data()),
N);
break;
}
case value_t::object: case value_t::object:
{ {
// step 1: write control byte and the object size // step 1: write control byte and the object size
@ -506,6 +546,101 @@ class binary_writer
break; break;
} }
case value_t::binary:
{
// step 0: determine if the binary type has a set subtype to
// determine whether or not to use the ext or fixext types
const bool use_ext = j.m_value.binary->has_subtype;
// step 1: write control byte and the byte string length
const auto N = j.m_value.binary->size();
if (N <= (std::numeric_limits<std::uint8_t>::max)())
{
std::uint8_t output_type;
bool fixed = true;
if (use_ext)
{
switch (N)
{
case 1:
output_type = 0xD4; // fixext 1
break;
case 2:
output_type = 0xD5; // fixext 2
break;
case 4:
output_type = 0xD6; // fixext 4
break;
case 8:
output_type = 0xD7; // fixext 8
break;
case 16:
output_type = 0xD8; // fixext 16
break;
default:
output_type = 0xC7; // ext 8
fixed = false;
break;
}
}
else
{
output_type = 0xC4; // bin 8
fixed = false;
}
oa->write_character(to_char_type(output_type));
if (not fixed)
{
write_number(static_cast<std::uint8_t>(N));
}
}
else if (N <= (std::numeric_limits<std::uint16_t>::max)())
{
std::uint8_t output_type;
if (use_ext)
{
output_type = 0xC8; // ext 16
}
else
{
output_type = 0xC5; // bin 16
}
oa->write_character(to_char_type(output_type));
write_number(static_cast<std::uint16_t>(N));
}
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
{
std::uint8_t output_type;
if (use_ext)
{
output_type = 0xC9; // ext 32
}
else
{
output_type = 0xC6; // bin 32
}
oa->write_character(to_char_type(output_type));
write_number(static_cast<std::uint32_t>(N));
}
// step 1.5: if this is an ext type, write the subtype
if (use_ext)
{
write_number(j.m_value.binary->subtype);
}
// step 2: write the byte string
oa->write_characters(
reinterpret_cast<const CharType*>(j.m_value.binary->data()),
N);
break;
}
case value_t::object: case value_t::object:
{ {
// step 1: write control byte and the object size // step 1: write control byte and the object size
@ -649,6 +784,49 @@ class binary_writer
break; 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);
}
if (use_type)
{
oa->write_characters(
reinterpret_cast<const CharType*>(j.m_value.binary->data()),
j.m_value.binary->size());
}
else
{
for (size_t i = 0; i < j.m_value.binary->size(); ++i)
{
oa->write_character(to_char_type('U'));
oa->write_character(j.m_value.binary->data()[i]);
}
}
if (not use_count)
{
oa->write_character(to_char_type(']'));
}
break;
}
case value_t::object: case value_t::object:
{ {
if (add_prefix) if (add_prefix)
@ -871,6 +1049,14 @@ class binary_writer
return sizeof(std::int32_t) + embedded_document_size + 1ul; 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::internal_binary_t& value)
{
return sizeof(std::int32_t) + value.size() + 1ul;
}
/*! /*!
@brief Writes a BSON element with key @a name and array @a value @brief Writes a BSON element with key @a name and array @a value
*/ */
@ -890,6 +1076,27 @@ class binary_writer
oa->write_character(to_char_type(0x00)); 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 internal_binary_t& value)
{
write_bson_entry_header(name, 0x05);
write_number<std::int32_t, true>(static_cast<std::int32_t>(value.size()));
std::uint8_t subtype = 0x00; // Generic Binary Subtype
if (value.has_subtype)
{
subtype = value.subtype;
}
write_number(subtype);
oa->write_characters(
reinterpret_cast<const CharType*>(value.data()),
value.size());
}
/*! /*!
@brief Calculates the size necessary to serialize the JSON value @a j with its @a name @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. @return The calculated size for the BSON document entry for @a j with the given @a name.
@ -906,6 +1113,9 @@ class binary_writer
case value_t::array: case value_t::array:
return header_size + calc_bson_array_size(*j.m_value.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: case value_t::boolean:
return header_size + 1ul; return header_size + 1ul;
@ -950,6 +1160,9 @@ class binary_writer
case value_t::array: case value_t::array:
return write_bson_array(name, *j.m_value.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: case value_t::boolean:
return write_bson_boolean(name, j.m_value.boolean); return write_bson_boolean(name, j.m_value.boolean);
@ -1230,7 +1443,8 @@ class binary_writer
case value_t::string: case value_t::string:
return 'S'; return 'S';
case value_t::array: case value_t::array: // fallthrough
case value_t::binary:
return '['; return '[';
case value_t::object: case value_t::object:

View file

@ -45,6 +45,7 @@ class serializer
using number_float_t = typename BasicJsonType::number_float_t; using number_float_t = typename BasicJsonType::number_float_t;
using number_integer_t = typename BasicJsonType::number_integer_t; using number_integer_t = typename BasicJsonType::number_integer_t;
using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
using binary_t = typename BasicJsonType::binary_t;
static constexpr std::uint8_t UTF8_ACCEPT = 0; static constexpr std::uint8_t UTF8_ACCEPT = 0;
static constexpr std::uint8_t UTF8_REJECT = 1; static constexpr std::uint8_t UTF8_REJECT = 1;
@ -83,16 +84,19 @@ class serializer
- strings and object keys are escaped using `escape_string()` - strings and object keys are escaped using `escape_string()`
- integer numbers are converted implicitly via `operator<<` - integer numbers are converted implicitly via `operator<<`
- floating-point numbers are converted to a string using `"%g"` format - floating-point numbers are converted to a string using `"%g"` format
- if specified to, binary values are output using the syntax `b[]`, otherwise an exception is thrown
@param[in] val value to serialize @param[in] val value to serialize
@param[in] pretty_print whether the output shall be pretty-printed @param[in] pretty_print whether the output shall be pretty-printed
@param[in] indent_step the indent level @param[in] indent_step the indent level
@param[in] current_indent the current indent level (only used internally) @param[in] current_indent the current indent level (only used internally)
@param[in] serialize_binary whether the output shall include non-standard binary output
*/ */
void dump(const BasicJsonType& val, const bool pretty_print, void dump(const BasicJsonType& val, const bool pretty_print,
const bool ensure_ascii, const bool ensure_ascii,
const unsigned int indent_step, const unsigned int indent_step,
const unsigned int current_indent = 0) const unsigned int current_indent = 0,
const bool serialize_binary = false)
{ {
switch (val.m_type) switch (val.m_type)
{ {
@ -236,6 +240,55 @@ class serializer
return; return;
} }
case value_t::binary:
{
if (not serialize_binary)
{
JSON_THROW(type_error::create(317, "cannot serialize binary data to text JSON"));
}
if (val.m_value.binary->empty())
{
o->write_characters("b[]", 3);
}
else if (pretty_print)
{
o->write_characters("b[", 2);
for (auto i = val.m_value.binary->cbegin();
i != val.m_value.binary->cend() - 1; ++i)
{
dump_integer(*i);
o->write_character(',');
int index = i - val.m_value.binary->cbegin();
if (index % 16 == 0)
{
o->write_character('\n');
}
else
{
o->write_character(' ');
}
}
dump_integer(val.m_value.binary->back());
o->write_character(']');
}
else
{
o->write_characters("b[", 2);
for (auto i = val.m_value.binary->cbegin();
i != val.m_value.binary->cend() - 1; ++i)
{
dump_integer(*i);
o->write_character(',');
}
dump_integer(val.m_value.binary->back());
o->write_character(']');
}
return;
}
case value_t::boolean: case value_t::boolean:
{ {
if (val.m_value.boolean) if (val.m_value.boolean)
@ -592,7 +645,8 @@ class serializer
*/ */
template<typename NumberType, detail::enable_if_t< template<typename NumberType, detail::enable_if_t<
std::is_same<NumberType, number_unsigned_t>::value or std::is_same<NumberType, number_unsigned_t>::value or
std::is_same<NumberType, number_integer_t>::value, std::is_same<NumberType, number_integer_t>::value or
std::is_same<NumberType, typename binary_t::value_type>::value,
int> = 0> int> = 0>
void dump_integer(NumberType x) void dump_integer(NumberType x)
{ {
@ -630,7 +684,7 @@ class serializer
if (is_negative) if (is_negative)
{ {
*buffer_ptr = '-'; *buffer_ptr = '-';
abs_value = remove_sign(x); abs_value = remove_sign(static_cast<number_integer_t>(x));
// account one more byte for the minus sign // account one more byte for the minus sign
n_chars = 1 + count_digits(abs_value); n_chars = 1 + count_digits(abs_value);

View file

@ -48,6 +48,7 @@ enum class value_t : std::uint8_t
number_integer, ///< number value (signed integer) number_integer, ///< number value (signed integer)
number_unsigned, ///< number value (unsigned integer) number_unsigned, ///< number value (unsigned integer)
number_float, ///< number value (floating-point) number_float, ///< number value (floating-point)
binary, ///< binary array (ordered collection of bytes)
discarded ///< discarded by the parser callback function discarded ///< discarded by the parser callback function
}; };
@ -55,17 +56,21 @@ enum class value_t : std::uint8_t
@brief comparison operator for JSON types @brief comparison operator for JSON types
Returns an ordering that is similar to Python: 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 - furthermore, each type is not smaller than itself
- discarded values are not comparable - 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 @since version 1.0.0
*/ */
inline bool operator<(const value_t lhs, const value_t rhs) noexcept inline bool operator<(const value_t lhs, const value_t rhs) noexcept
{ {
static constexpr std::array<std::uint8_t, 8> order = {{ static constexpr std::array<std::uint8_t, 9> order = {{
0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, 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 */
} }
}; };

View file

@ -97,6 +97,9 @@ default; will be used in @ref number_integer_t)
`uint64_t` by default; will be used in @ref number_unsigned_t) `uint64_t` by default; will be used in @ref number_unsigned_t)
@tparam NumberFloatType type for JSON floating-point numbers (`double` by @tparam NumberFloatType type for JSON floating-point numbers (`double` by
default; will be used in @ref number_float_t) default; will be used in @ref number_float_t)
@tparam BinaryType type for packed binary data for compatibility with binary
serialization formats (`std::vector<std::uint8_t>` by default; will be used in
@ref binary_t)
@tparam AllocatorType type of the allocator to use (`std::allocator` by @tparam AllocatorType type of the allocator to use (`std::allocator` by
default) default)
@tparam JSONSerializer the serializer to resolve internal calls to `to_json()` @tparam JSONSerializer the serializer to resolve internal calls to `to_json()`
@ -818,6 +821,85 @@ class basic_json
*/ */
using number_float_t = NumberFloatType; 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.
Additionally, as an implementation detail, the subtype of the binary data is
carried around as a `unint8_t`, which is compatible with both of the binary
data formats that use binary subtyping, (though the specific numbering is
incompatible with each other, and it is up to the user to translate between
them).
[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<std::uint8_t>`, 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<std::uint8_t>`
#### 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.8.0
*/
using binary_t = BinaryType;
/*!
@brief an internal type for a backed binary type
This type is designed to be `binary_t` but with the subtype implementation
detail. This type exists so that the user does not have to specify a struct
his- or herself with a specific naming scheme in order to override the
binary type. I.e. it's for ease of use.
*/
struct internal_binary_t : public BinaryType
{
using BinaryType::BinaryType;
internal_binary_t() : BinaryType() {}
internal_binary_t(BinaryType const& bint) : BinaryType(bint) {}
internal_binary_t(BinaryType&& bint) : BinaryType(std::move(bint)) {}
// TOOD: If minimum C++ version is ever bumped to C++17, this field
// deserves to be a std::optional
std::uint8_t subtype = 0;
bool has_subtype = false;
};
/// @} /// @}
private: private:
@ -860,6 +942,7 @@ class basic_json
number | number_integer | @ref number_integer_t number | number_integer | @ref number_integer_t
number | number_unsigned | @ref number_unsigned_t number | number_unsigned | @ref number_unsigned_t
number | number_float | @ref number_float_t number | number_float | @ref number_float_t
binary | binary | pointer to @ref internal_binary_t
null | null | *no value is stored* null | null | *no value is stored*
@note Variable-length types (objects, arrays, and strings) are stored as @note Variable-length types (objects, arrays, and strings) are stored as
@ -876,6 +959,8 @@ class basic_json
array_t* array; array_t* array;
/// string (stored with pointer to save storage) /// string (stored with pointer to save storage)
string_t* string; string_t* string;
/// binary (stored with pointer to save storage)
internal_binary_t* binary;
/// boolean /// boolean
boolean_t boolean; boolean_t boolean;
/// number (integer) /// number (integer)
@ -918,6 +1003,12 @@ class basic_json
break; break;
} }
case value_t::binary:
{
binary = create<internal_binary_t>();
break;
}
case value_t::boolean: case value_t::boolean:
{ {
boolean = boolean_t(false); boolean = boolean_t(false);
@ -996,6 +1087,18 @@ class basic_json
array = create<array_t>(std::move(value)); array = create<array_t>(std::move(value));
} }
/// constructor for binary arrays
json_value(const binary_t& value)
{
binary = create<internal_binary_t>(value);
}
/// constructor for rvalue binary arrays
json_value(binary_t&& value)
{
binary = create<internal_binary_t>(std::move(value));
}
void destroy(value_t t) noexcept void destroy(value_t t) noexcept
{ {
// flatten the current json_value to a heap-allocated stack // flatten the current json_value to a heap-allocated stack
@ -1071,6 +1174,14 @@ class basic_json
break; break;
} }
case value_t::binary:
{
AllocatorType<internal_binary_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
break;
}
default: default:
{ {
break; break;
@ -1191,6 +1302,7 @@ class basic_json
number | `0` number | `0`
object | `{}` object | `{}`
array | `[]` array | `[]`
binary | empty array
@param[in] v the type of the value to create @param[in] v the type of the value to create
@ -1262,6 +1374,12 @@ class basic_json
@ref number_float_t, and all convertible number types such as `int`, @ref number_float_t, and all convertible number types such as `int`,
`size_t`, `int64_t`, `float` or `double` can be used. `size_t`, `int64_t`, `float` or `double` can be used.
- **boolean**: @ref boolean_t / `bool` can be used. - **boolean**: @ref boolean_t / `bool` can be used.
- **binary**: @ref binary_t / `std::vector<uint8_t>` 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. See the examples below.
@ -1343,6 +1461,7 @@ class basic_json
using other_string_t = typename BasicJsonType::string_t; using other_string_t = typename BasicJsonType::string_t;
using other_object_t = typename BasicJsonType::object_t; using other_object_t = typename BasicJsonType::object_t;
using other_array_t = typename BasicJsonType::array_t; using other_array_t = typename BasicJsonType::array_t;
using other_binary_t = typename BasicJsonType::internal_binary_t;
switch (val.type()) switch (val.type())
{ {
@ -1367,6 +1486,9 @@ class basic_json
case value_t::array: case value_t::array:
JSONSerializer<other_array_t>::to_json(*this, val.template get_ref<const other_array_t&>()); JSONSerializer<other_array_t>::to_json(*this, val.template get_ref<const other_array_t&>());
break; break;
case value_t::binary:
JSONSerializer<other_binary_t>::to_json(*this, val.template get_ref<const other_binary_t&>());
break;
case value_t::null: case value_t::null:
*this = nullptr; *this = nullptr;
break; break;
@ -1505,6 +1627,78 @@ class basic_json
assert_invariant(); assert_invariant();
} }
/*!
@brief explicitly create a binary array from an already constructed copy of
its base type
Creates a JSON binary array value from a given `binary_t`. Binary values are
part of various binary formats, such as CBOR, MsgPack, and BSON. And this
constructor is used to create a value for serialization to those formats.
@note Note, this function exists because of the difficulty in correctly
specifying the correct template overload in the standard value ctor, as both
JSON arrays and JSON binary arrays are backed with some form of a
`std::vector`. Because JSON binary arrays are a non-standard extension it
was decided that it would be best to prevent automatic initialization of a
binary array type, for backwards compatibility and so it does not happen on
accident.
@param[in] init `binary_t` with JSON values to create a binary array from
@return JSON binary array value
@complexity Linear in the size of @a init.
@exceptionsafety Strong guarantee: if an exception is thrown, there are no
changes to any JSON value.
@since version 3.8.0
*/
JSON_HEDLEY_WARN_UNUSED_RESULT
static basic_json binary_array(binary_t const& init)
{
auto res = basic_json();
res.m_type = value_t::binary;
res.m_value = init;
return res;
}
/*!
@brief explicitly create a binary array from an already constructed rvalue
copy of its base type
Creates a JSON binary array value from a given `binary_t`. Binary values are
part of various binary formats, such as CBOR, MsgPack, and BSON. And this
constructor is used to create a value for serialization to those formats.
@note Note, this function exists because of the difficulty in correctly
specifying the correct template overload in the standard value ctor, as both
JSON arrays and JSON binary arrays are backed with some form of a
`std::vector`. Because JSON binary arrays are a non-standard extension it
was decided that it would be best to prevent automatic initialization of a
binary array type, for backwards compatibility and so it doesn't happen on
accident.
@param[in] init `binary_t` with JSON values to create a binary array from
@return JSON binary array value
@complexity Linear in the size of @a init.
@exceptionsafety Strong guarantee: if an exception is thrown, there are no
changes to any JSON value.
@since version 3.8.0
*/
JSON_HEDLEY_WARN_UNUSED_RESULT
static basic_json binary_array(binary_t&& init)
{
auto res = basic_json();
res.m_type = value_t::binary;
res.m_value = std::move(init);
return res;
}
/*! /*!
@brief explicitly create an array from an initializer list @brief explicitly create an array from an initializer list
@ -1760,6 +1954,13 @@ class basic_json
break; break;
} }
case value_t::binary:
{
m_value.binary = create<internal_binary_t>(first.m_it.binary_iterator,
last.m_it.binary_iterator);
break;
}
default: default:
JSON_THROW(invalid_iterator::create(206, "cannot construct with iterators from " + JSON_THROW(invalid_iterator::create(206, "cannot construct with iterators from " +
std::string(first.m_object->type_name()))); std::string(first.m_object->type_name())));
@ -1853,6 +2054,12 @@ class basic_json
break; break;
} }
case value_t::binary:
{
m_value = *other.m_value.binary;
break;
}
default: default:
break; break;
} }
@ -1993,6 +2200,11 @@ class basic_json
possible values: `strict` (throws and exception in case a decoding error possible values: `strict` (throws and exception in case a decoding error
occurs; default), `replace` (replace invalid UTF-8 sequences with U+FFFD), occurs; default), `replace` (replace invalid UTF-8 sequences with U+FFFD),
and `ignore` (ignore invalid UTF-8 sequences during serialization). and `ignore` (ignore invalid UTF-8 sequences during serialization).
@param[in] serialize_binary Whether or not to allow serialization of binary
types to JSON. Because binary types are non-standard, this will produce
non-conformant JSON, and is disabled by default. This flag is primarily
useful for debugging. Will output the binary value as a list of 8-bit
numbers prefixed by "b" (e.g. "bindata" = b[3, 0, 42, 255]).
@return string containing the serialization of the JSON value @return string containing the serialization of the JSON value
@ -2017,18 +2229,19 @@ class basic_json
string_t dump(const int indent = -1, string_t dump(const int indent = -1,
const char indent_char = ' ', const char indent_char = ' ',
const bool ensure_ascii = false, const bool ensure_ascii = false,
const error_handler_t error_handler = error_handler_t::strict) const const error_handler_t error_handler = error_handler_t::strict,
const bool serialize_binary = false) const
{ {
string_t result; string_t result;
serializer s(detail::output_adapter<char, string_t>(result), indent_char, error_handler); serializer s(detail::output_adapter<char, string_t>(result), indent_char, error_handler);
if (indent >= 0) if (indent >= 0)
{ {
s.dump(*this, true, ensure_ascii, static_cast<unsigned int>(indent)); s.dump(*this, true, ensure_ascii, static_cast<unsigned int>(indent), serialize_binary);
} }
else else
{ {
s.dump(*this, false, ensure_ascii, 0); s.dump(*this, false, ensure_ascii, 0, serialize_binary);
} }
return result; return result;
@ -2051,6 +2264,7 @@ class basic_json
number (floating-point) | value_t::number_float number (floating-point) | value_t::number_float
object | value_t::object object | value_t::object
array | value_t::array array | value_t::array
binary | value_t::binary
discarded | value_t::discarded discarded | value_t::discarded
@complexity Constant. @complexity Constant.
@ -2093,12 +2307,13 @@ class basic_json
@sa @ref is_string() -- returns whether JSON value is a string @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_boolean() -- returns whether JSON value is a boolean
@sa @ref is_number() -- returns whether JSON value is a number @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 @since version 1.0.0
*/ */
constexpr bool is_primitive() const noexcept 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();
} }
/*! /*!
@ -2353,6 +2568,28 @@ class basic_json
return m_type == value_t::string; 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.8.0
*/
constexpr bool is_binary() const noexcept
{
return m_type == value_t::binary;
}
/*! /*!
@brief return whether value is discarded @brief return whether value is discarded
@ -2508,6 +2745,18 @@ class basic_json
return is_number_float() ? &m_value.number_float : nullptr; 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() @brief helper function to implement get_ref()
@ -3555,6 +3804,120 @@ class basic_json
return value(ptr, string_t(default_value)); return value(ptr, string_t(default_value));
} }
/*!
@brief return the binary subtype
Returns the numerical subtype of the JSON value, if the JSON value is of
type "binary", and it has a subtype. If it does not have a subtype (or the
object is not of type binary) this function will return size_t(-1) as a
sentinel value.
@return the numerical subtype of the binary JSON value
@complexity Constant.
@exceptionsafety No-throw guarantee: this member function never throws
exceptions.
@sa @ref set_subtype() -- sets the binary subtype
@sa @ref clear_subtype() -- clears the binary subtype
@sa @ref has_subtype() -- returns whether or not the binary value has a
subtype
@since version 3.8.0
*/
std::size_t get_subtype() const noexcept
{
if (is_binary() and m_value.binary->has_subtype)
{
return m_value.binary->subtype;
}
return std::size_t(-1);
}
/*!
@brief sets the binary subtype
Sets the binary subtype of the JSON value, also flags a binary JSON value as
having a subtype, which has implications for serialization to msgpack (will
prefer ext file formats over bin). If the JSON value is not a binary value,
this function does nothing.
@complexity Constant.
@exceptionsafety No-throw guarantee: this member function never throws
exceptions.
@sa @ref get_subtype() -- return the binary subtype
@sa @ref clear_subtype() -- clears the binary subtype
@sa @ref has_subtype() -- returns whether or not the binary value has a
subtype
@since version 3.8.0
*/
void set_subtype(std::uint8_t subtype) noexcept
{
if (is_binary())
{
m_value.binary->has_subtype = true;
m_value.binary->subtype = subtype;
}
}
/*!
@brief clears the binary subtype
Clears the binary subtype of the JSON value, also flags a binary JSON value
as not having a subtype, which has implications for serialization to msgpack
(will prefer bin file formats over ext). If the JSON value is not a binary
value, this function does nothing.
@complexity Constant.
@exceptionsafety No-throw guarantee: this member function never throws
exceptions.
@sa @ref get_subtype() -- return the binary subtype
@sa @ref set_subtype() -- sets the binary subtype
@sa @ref has_subtype() -- returns whether or not the binary value has a
subtype
@since version 3.8.0
*/
void clear_subtype() noexcept
{
if (is_binary())
{
m_value.binary->has_subtype = false;
m_value.binary->subtype = 0;
}
}
/*!
@brief return whether or not the binary subtype has a value
Returns whether or not the binary subtype has a value.
@return whether or not the binary subtype has a value.
@complexity Constant.
@exceptionsafety No-throw guarantee: this member function never throws
exceptions.
@sa @ref get_subtype() -- return the binary subtype
@sa @ref set_subtype() -- sets the binary subtype
@sa @ref clear_subtype() -- clears the binary subtype
@since version 3.8.0
*/
bool has_subtype() const noexcept
{
return is_binary() and m_value.binary->has_subtype;
}
/*! /*!
@brief access the first element @brief access the first element
@ -3562,8 +3925,8 @@ class basic_json
container `c`, the expression `c.front()` is equivalent to `*c.begin()`. 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 @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 first element is returned. In case of number, string, boolean, or binary
reference to the value is returned. values, a reference to the value is returned.
@complexity Constant. @complexity Constant.
@ -3605,8 +3968,8 @@ class basic_json
@endcode @endcode
@return In case of a structured type (array or object), a reference to the @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 last element is returned. In case of number, string, boolean, or binary
reference to the value is returned. values, a reference to the value is returned.
@complexity Constant. @complexity Constant.
@ -3672,7 +4035,7 @@ class basic_json
@complexity The complexity depends on the type: @complexity The complexity depends on the type:
- objects: amortized constant - objects: amortized constant
- arrays: linear in distance between @a pos and the end of the container - 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 - other types: constant
@liveexample{The example shows the result of `erase()` for different JSON @liveexample{The example shows the result of `erase()` for different JSON
@ -3708,6 +4071,7 @@ class basic_json
case value_t::number_integer: case value_t::number_integer:
case value_t::number_unsigned: case value_t::number_unsigned:
case value_t::string: case value_t::string:
case value_t::binary:
{ {
if (JSON_HEDLEY_UNLIKELY(not pos.m_it.primitive_iterator.is_begin())) if (JSON_HEDLEY_UNLIKELY(not pos.m_it.primitive_iterator.is_begin()))
{ {
@ -3721,6 +4085,13 @@ class basic_json
std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_value.string, 1); std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_value.string, 1);
m_value.string = nullptr; m_value.string = nullptr;
} }
else if (is_binary())
{
AllocatorType<internal_binary_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, m_value.binary);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_value.binary, 1);
m_value.binary = nullptr;
}
m_type = value_t::null; m_type = value_t::null;
assert_invariant(); assert_invariant();
@ -3778,7 +4149,7 @@ class basic_json
- objects: `log(size()) + std::distance(first, last)` - objects: `log(size()) + std::distance(first, last)`
- arrays: linear in the distance between @a first and @a last, plus linear - arrays: linear in the distance between @a first and @a last, plus linear
in the distance between @a last and end of the container 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 - other types: constant
@liveexample{The example shows the result of `erase()` for different JSON @liveexample{The example shows the result of `erase()` for different JSON
@ -3813,6 +4184,7 @@ class basic_json
case value_t::number_integer: case value_t::number_integer:
case value_t::number_unsigned: case value_t::number_unsigned:
case value_t::string: case value_t::string:
case value_t::binary:
{ {
if (JSON_HEDLEY_LIKELY(not first.m_it.primitive_iterator.is_begin() if (JSON_HEDLEY_LIKELY(not first.m_it.primitive_iterator.is_begin()
or not last.m_it.primitive_iterator.is_end())) or not last.m_it.primitive_iterator.is_end()))
@ -3827,6 +4199,13 @@ class basic_json
std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_value.string, 1); std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_value.string, 1);
m_value.string = nullptr; m_value.string = nullptr;
} }
else if (is_binary())
{
AllocatorType<internal_binary_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, m_value.binary);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_value.binary, 1);
m_value.binary = nullptr;
}
m_type = value_t::null; m_type = value_t::null;
assert_invariant(); assert_invariant();
@ -4546,6 +4925,7 @@ class basic_json
boolean | `false` boolean | `false`
string | `false` string | `false`
number | `false` number | `false`
binary | `false`
object | result of function `object_t::empty()` object | result of function `object_t::empty()`
array | result of function `array_t::empty()` array | result of function `array_t::empty()`
@ -4617,6 +4997,7 @@ class basic_json
boolean | `1` boolean | `1`
string | `1` string | `1`
number | `1` number | `1`
binary | `1`
object | result of function object_t::size() object | result of function object_t::size()
array | result of function array_t::size() array | result of function array_t::size()
@ -4691,6 +5072,7 @@ class basic_json
boolean | `1` (same as `size()`) boolean | `1` (same as `size()`)
string | `1` (same as `size()`) string | `1` (same as `size()`)
number | `1` (same as `size()`) number | `1` (same as `size()`)
binary | `1` (same as `size()`)
object | result of function `object_t::max_size()` object | result of function `object_t::max_size()`
array | result of function `array_t::max_size()` array | result of function `array_t::max_size()`
@ -4763,6 +5145,7 @@ class basic_json
boolean | `false` boolean | `false`
string | `""` string | `""`
number | `0` number | `0`
binary | An empty byte vector
object | `{}` object | `{}`
array | `[]` array | `[]`
@ -4820,6 +5203,12 @@ class basic_json
break; break;
} }
case value_t::binary:
{
m_value.binary->clear();
break;
}
case value_t::array: case value_t::array:
{ {
m_value.array->clear(); m_value.array->clear();
@ -5712,6 +6101,9 @@ class basic_json
case value_t::number_float: case value_t::number_float:
return lhs.m_value.number_float == rhs.m_value.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: default:
return false; return false;
} }
@ -5872,6 +6264,9 @@ class basic_json
case value_t::number_float: case value_t::number_float:
return (lhs.m_value.number_float) < (rhs.m_value.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: default:
return false; return false;
} }
@ -6436,6 +6831,7 @@ class basic_json
number | `"number"` (for all number types) number | `"number"` (for all number types)
object | `"object"` object | `"object"`
array | `"array"` array | `"array"`
binary | `"binary"`
discarded | `"discarded"` discarded | `"discarded"`
@exceptionsafety No-throw guarantee: this function never throws exceptions. @exceptionsafety No-throw guarantee: this function never throws exceptions.
@ -6467,6 +6863,8 @@ class basic_json
return "string"; return "string";
case value_t::boolean: case value_t::boolean:
return "boolean"; return "boolean";
case value_t::binary:
return "binary";
case value_t::discarded: case value_t::discarded:
return "discarded"; return "discarded";
default: default:
@ -6542,6 +6940,11 @@ class basic_json
object | *size*: 256..65535 | map (2 bytes follow) | 0xB9 object | *size*: 256..65535 | map (2 bytes follow) | 0xB9
object | *size*: 65536..4294967295 | map (4 bytes follow) | 0xBA object | *size*: 65536..4294967295 | map (4 bytes follow) | 0xBA
object | *size*: 4294967296..18446744073709551615 | map (8 bytes follow) | 0xBB 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 @note The mapping is **complete** in the sense that any JSON value type
can be converted to a CBOR value. can be converted to a CBOR value.
@ -6551,10 +6954,10 @@ class basic_json
function which serializes NaN or Infinity to `null`. function which serializes NaN or Infinity to `null`.
@note The following CBOR types are not used in the conversion: @note The following CBOR types are not used in the conversion:
- byte strings (0x40..0x5F)
- UTF-8 strings terminated by "break" (0x7F) - UTF-8 strings terminated by "break" (0x7F)
- arrays terminated by "break" (0x9F) - arrays terminated by "break" (0x9F)
- maps terminated by "break" (0xBF) - maps terminated by "break" (0xBF)
- byte strings terminated by "break" (0x5F)
- date/time (0xC0..0xC1) - date/time (0xC0..0xC1)
- bignum (0xC2..0xC3) - bignum (0xC2..0xC3)
- decimal fraction (0xC4) - decimal fraction (0xC4)
@ -6641,20 +7044,21 @@ class basic_json
object | *size*: 0..15 | fix map | 0x80..0x8F object | *size*: 0..15 | fix map | 0x80..0x8F
object | *size*: 16..65535 | map 16 | 0xDE object | *size*: 16..65535 | map 16 | 0xDE
object | *size*: 65536..4294967295 | map 32 | 0xDF 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 @note The mapping is **complete** in the sense that any JSON value type
can be converted to a MessagePack value. can be converted to a MessagePack value.
@note The following values can **not** be converted to a MessagePack value: @note The following values can **not** be converted to a MessagePack value:
- strings with more than 4294967295 bytes - strings with more than 4294967295 bytes
- byte strings with more than 4294967295 bytes
- arrays with more than 4294967295 elements - arrays with more than 4294967295 elements
- objects with more than 4294967295 elements - objects with more than 4294967295 elements
@note The following MessagePack types are not used in the conversion: @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) - float 32 (0xCA)
- fixext 1 - fixext 16 (0xD4..0xD8)
@note Any MessagePack output created @ref to_msgpack can be successfully @note Any MessagePack output created @ref to_msgpack can be successfully
parsed by @ref from_msgpack. parsed by @ref from_msgpack.
@ -6757,6 +7161,12 @@ class basic_json
the benefit of this parameter is that the receiving side is the benefit of this parameter is that the receiving side is
immediately informed on the number of elements of the container. 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] j JSON value to serialize
@param[in] use_size whether to add size annotations to container types @param[in] use_size whether to add size annotations to container types
@param[in] use_type whether to add type annotations to container types @param[in] use_type whether to add type annotations to container types
@ -6821,6 +7231,7 @@ class basic_json
string | *any value* | string | 0x02 string | *any value* | string | 0x02
array | *any value* | document | 0x04 array | *any value* | document | 0x04
object | *any value* | document | 0x03 object | *any value* | document | 0x03
binary | *any value* | binary | 0x05
@warning The mapping is **incomplete**, since only JSON-objects (and things @warning The mapping is **incomplete**, since only JSON-objects (and things
contained therein) can be serialized to BSON. contained therein) can be serialized to BSON.
@ -6902,7 +7313,11 @@ class basic_json
Negative integer | number_integer | 0x39 Negative integer | number_integer | 0x39
Negative integer | number_integer | 0x3A Negative integer | number_integer | 0x3A
Negative integer | number_integer | 0x3B 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 | 0x60..0x77
UTF-8 string | string | 0x78 UTF-8 string | string | 0x78
UTF-8 string | string | 0x79 UTF-8 string | string | 0x79
@ -6931,7 +7346,6 @@ class basic_json
@warning The mapping is **incomplete** in the sense that not all CBOR @warning The mapping is **incomplete** in the sense that not all CBOR
types can be converted to a JSON value. The following CBOR types types can be converted to a JSON value. The following CBOR types
are not supported and will yield parse errors (parse_error.112): are not supported and will yield parse errors (parse_error.112):
- byte strings (0x40..0x5F)
- date/time (0xC0..0xC1) - date/time (0xC0..0xC1)
- bignum (0xC2..0xC3) - bignum (0xC2..0xC3)
- decimal fraction (0xC4) - decimal fraction (0xC4)
@ -7042,15 +7456,19 @@ class basic_json
array 32 | array | 0xDD array 32 | array | 0xDD
map 16 | object | 0xDE map 16 | object | 0xDE
map 32 | object | 0xDF map 32 | object | 0xDF
bin 8 | binary | 0xC4
bin 16 | binary | 0xC5
bin 32 | binary | 0xC6
ext 8 | binary | 0xC7
ext 16 | binary | 0xC8
ext 32 | binary | 0xC9
fixext 1 | binary | 0xD4
fixext 2 | binary | 0xD5
fixext 4 | binary | 0xD6
fixext 8 | binary | 0xD7
fixext 16 | binary | 0xD8
negative fixint | number_integer | 0xE0-0xFF negative fixint | number_integer | 0xE0-0xFF
@warning The mapping is **incomplete** in the sense that not all
MessagePack types can be converted to a JSON value. The following
MessagePack types are not supported and will yield parse errors:
- bin 8 - bin 32 (0xC4..0xC6)
- ext 8 - ext 32 (0xC7..0xC9)
- fixext 1 - fixext 16 (0xD4..0xD8)
@note Any MessagePack output created @ref to_msgpack can be successfully @note Any MessagePack output created @ref to_msgpack can be successfully
parsed by @ref from_msgpack. parsed by @ref from_msgpack.

View file

@ -33,7 +33,8 @@ template<template<typename U, typename V, typename... Args> class ObjectType =
class NumberFloatType = double, class NumberFloatType = double,
template<typename U> class AllocatorType = std::allocator, template<typename U> class AllocatorType = std::allocator,
template<typename T, typename SFINAE = void> class JSONSerializer = template<typename T, typename SFINAE = void> class JSONSerializer =
adl_serializer> adl_serializer,
class BinaryType = std::vector<std::uint8_t>>
class basic_json; class basic_json;
/*! /*!

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -0,0 +1,17 @@
 













<17>ΘK€ΐxY<59>ζΏ¬?“Έ€π3"η†xU—·όF*RϊΝΟΓΥPΠgω)<29>ΘK€ΐxY<59>ζΏ¬?“Έ€π3"η†xU—·όF*RϊΝΟΓΥPΠgω)<29>ΘK€ΐxY<59>ζΏ¬?“Έ€π3"η†xU—·όF*RϊΝΟΓΥPΠgω)<29>ΘK€ΐxY<59>ζΏ¬?“Έ€π3"η†xU—·όF*RϊΝΟΓΥPΠgω)


View file

@ -492,6 +492,36 @@ TEST_CASE("BSON")
CHECK(json::from_bson(result, true, false) == j); CHECK(json::from_bson(result, true, false) == j);
} }
SECTION("non-empty object with binary member")
{
const size_t N = 10;
const auto s = std::vector<uint8_t>(N, 'x');
json j =
{
{ "entry", json::binary_array(s) }
};
std::vector<uint8_t> expected =
{
0x1B, 0x00, 0x00, 0x00, // size (little endian)
0x05, // entry: binary
'e', 'n', 't', 'r', 'y', '\x00',
0x0A, 0x00, 0x00, 0x00, // size of binary (little endian)
0x00, // Generic binary subtype
0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
0x00 // end marker
};
const auto result = json::to_bson(j);
CHECK(result == expected);
// roundtrip
CHECK(json::from_bson(result) == j);
CHECK(json::from_bson(result, true, false) == j);
}
SECTION("Some more complex document") SECTION("Some more complex document")
{ {
// directly encoding uint64 is not supported in bson (only for timestamp values) // directly encoding uint64 is not supported in bson (only for timestamp values)
@ -646,6 +676,11 @@ class SaxCountdown
return events_left-- > 0; return events_left-- > 0;
} }
bool binary(std::vector<uint8_t>&)
{
return events_left-- > 0;
}
bool start_object(std::size_t) bool start_object(std::size_t)
{ {
return events_left-- > 0; return events_left-- > 0;

View file

@ -36,6 +36,7 @@ using nlohmann::json;
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
#include <iomanip> #include <iomanip>
#include <iostream>
#include <set> #include <set>
namespace namespace
@ -76,6 +77,11 @@ class SaxCountdown
return events_left-- > 0; return events_left-- > 0;
} }
bool binary(std::vector<std::uint8_t>&)
{
return events_left-- > 0;
}
bool start_object(std::size_t) bool start_object(std::size_t)
{ {
return events_left-- > 0; return events_left-- > 0;
@ -1285,10 +1291,156 @@ TEST_CASE("CBOR")
CHECK(json::from_cbor(result, true, false) == j); 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<uint8_t>(N, 'x');
json j = json::binary_array(s);
// create expected byte vector
std::vector<uint8_t> expected;
expected.push_back(static_cast<uint8_t>(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<uint8_t>(N, 'x');
json j = json::binary_array(s);
// create expected byte vector
std::vector<uint8_t> expected;
expected.push_back(0x58);
expected.push_back(static_cast<uint8_t>(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<uint8_t>(N, 'x');
json j = json::binary_array(s);
// create expected byte vector (hack: create string first)
std::vector<uint8_t> expected(N, 'x');
// reverse order of commands, because we insert at begin()
expected.insert(expected.begin(), static_cast<uint8_t>(N & 0xff));
expected.insert(expected.begin(), static_cast<uint8_t>((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<uint8_t>(N, 'x');
json j = json::binary_array(s);
// create expected byte vector (hack: create string first)
std::vector<uint8_t> expected(N, 'x');
// reverse order of commands, because we insert at begin()
expected.insert(expected.begin(), static_cast<uint8_t>(N & 0xff));
expected.insert(expected.begin(), static_cast<uint8_t>((N >> 8) & 0xff));
expected.insert(expected.begin(), static_cast<uint8_t>((N >> 16) & 0xff));
expected.insert(expected.begin(), static_cast<uint8_t>((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("additional deserialization") SECTION("additional deserialization")
{ {
SECTION("0x5b (byte array)")
{
std::vector<uint8_t> given = {0x5b, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x61
};
json j = json::from_cbor(given);
CHECK(j == json::binary_array(std::vector<uint8_t> {'a'}));
}
SECTION("0x7b (string)") SECTION("0x7b (string)")
{ {
std::vector<uint8_t> given = {0x7b, 0x00, 0x00, 0x00, 0x00, std::vector<uint8_t> given = {0x7b, 0x00, 0x00, 0x00, 0x00,
@ -1455,14 +1607,8 @@ TEST_CASE("CBOR")
0x1c, 0x1d, 0x1e, 0x1f, 0x1c, 0x1d, 0x1e, 0x1f,
// ? // ?
0x3c, 0x3d, 0x3e, 0x3f, 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, 0x5c, 0x5d, 0x5e,
// byte string
0x5f,
// ? // ?
0x7c, 0x7d, 0x7e, 0x7c, 0x7d, 0x7e,
// ? // ?
@ -1929,12 +2075,6 @@ TEST_CASE("all CBOR first bytes")
{ {
//// types not supported by this library //// 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 // date/time
0xc0, 0xc1, 0xc0, 0xc1,
// bignum // bignum
@ -2144,6 +2284,20 @@ TEST_CASE("examples from RFC 7049 Appendix A")
CHECK(json::parse("\"streaming\"") == json::from_cbor(std::vector<uint8_t>({0x7f, 0x65, 0x73, 0x74, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x67, 0xff}))); CHECK(json::parse("\"streaming\"") == json::from_cbor(std::vector<uint8_t>({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<uint8_t> packed((std::istreambuf_iterator<char>(f_cbor)),
std::istreambuf_iterator<char>());
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<uint8_t> expected((std::istreambuf_iterator<char>(f_bin)),
std::istreambuf_iterator<char>());
CHECK(j == json::binary_array(expected));
}
SECTION("arrays") SECTION("arrays")
{ {
CHECK(json::to_cbor(json::parse("[]")) == std::vector<uint8_t>({0x80})); CHECK(json::to_cbor(json::parse("[]")) == std::vector<uint8_t>({0x80}));

View file

@ -77,6 +77,21 @@ class SaxEventLogger
return true; return true;
} }
bool binary(std::vector<std::uint8_t>& val)
{
std::string binary_contents = "binary(";
std::string comma_space = "";
for (auto b : val)
{
binary_contents.append(comma_space);
binary_contents.append(std::to_string(static_cast<int>(b)));
comma_space = ", ";
}
binary_contents.append(")");
events.push_back(binary_contents);
return true;
}
bool start_object(std::size_t elements) bool start_object(std::size_t elements)
{ {
if (elements == std::size_t(-1)) if (elements == std::size_t(-1))
@ -168,6 +183,11 @@ class SaxCountdown : public nlohmann::json::json_sax_t
return events_left-- > 0; return events_left-- > 0;
} }
bool binary(std::vector<std::uint8_t>&) override
{
return events_left-- > 0;
}
bool start_object(std::size_t) override bool start_object(std::size_t) override
{ {
return events_left-- > 0; return events_left-- > 0;

View file

@ -76,6 +76,21 @@ struct SaxEventLogger : public nlohmann::json_sax<json>
return true; return true;
} }
bool binary(std::vector<std::uint8_t>& val)
{
std::string binary_contents = "binary(";
std::string comma_space = "";
for (auto b : val)
{
binary_contents.append(comma_space);
binary_contents.append(std::to_string(static_cast<int>(b)));
comma_space = ", ";
}
binary_contents.append(")");
events.push_back(binary_contents);
return true;
}
bool start_object(std::size_t elements) override bool start_object(std::size_t elements) override
{ {
if (elements == std::size_t(-1)) if (elements == std::size_t(-1))

View file

@ -75,6 +75,11 @@ class SaxCountdown
return events_left-- > 0; return events_left-- > 0;
} }
bool binary(std::vector<std::uint8_t>&)
{
return events_left-- > 0;
}
bool start_object(std::size_t) bool start_object(std::size_t)
{ {
return events_left-- > 0; return events_left-- > 0;
@ -1115,6 +1120,261 @@ TEST_CASE("MessagePack")
CHECK(json::from_msgpack(result, true, false) == j); CHECK(json::from_msgpack(result, true, false) == j);
} }
} }
SECTION("extension")
{
SECTION("N = 0..255")
{
for (size_t N = 0; N <= 0xFF; ++N)
{
CAPTURE(N)
// create JSON value with byte array containing of N * 'x'
const auto s = std::vector<uint8_t>(N, 'x');
json j = json::binary_array(s);
std::uint8_t subtype = 42;
j.set_subtype(subtype);
// create expected byte vector
std::vector<uint8_t> expected;
switch (N)
{
case 1:
expected.push_back(static_cast<std::uint8_t>(0xD4));
break;
case 2:
expected.push_back(static_cast<std::uint8_t>(0xD5));
break;
case 4:
expected.push_back(static_cast<std::uint8_t>(0xD6));
break;
case 8:
expected.push_back(static_cast<std::uint8_t>(0xD7));
break;
case 16:
expected.push_back(static_cast<std::uint8_t>(0xD8));
break;
default:
expected.push_back(static_cast<std::uint8_t>(0xC7));
expected.push_back(static_cast<std::uint8_t>(N));
break;
}
expected.push_back(subtype);
for (size_t i = 0; i < N; ++i)
{
expected.push_back(0x78);
}
// compare result + size
const auto result = json::to_msgpack(j);
CHECK(result == expected);
switch (N)
{
case 1:
case 2:
case 4:
case 8:
case 16:
CHECK(result.size() == N + 2);
break;
default:
CHECK(result.size() == N + 3);
break;
}
// check that no null byte is appended
if (N > 0)
{
CHECK(result.back() != '\x00');
}
// roundtrip
CHECK(json::from_msgpack(result) == j);
CHECK(json::from_msgpack(result, true, false) == j);
}
}
SECTION("N = 256..65535")
{
for (std::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<uint8_t>(N, 'x');
json j = json::binary_array(s);
std::uint8_t subtype = 42;
j.set_subtype(subtype);
// create expected byte vector (hack: create string first)
std::vector<uint8_t> expected(N, 'x');
// reverse order of commands, because we insert at begin()
expected.insert(expected.begin(), subtype);
expected.insert(expected.begin(), static_cast<uint8_t>(N & 0xff));
expected.insert(expected.begin(), static_cast<uint8_t>((N >> 8) & 0xff));
expected.insert(expected.begin(), 0xC8);
// compare result + size
const auto result = json::to_msgpack(j);
CHECK(result == expected);
CHECK(result.size() == N + 4);
// check that no null byte is appended
CHECK(result.back() != '\x00');
// roundtrip
CHECK(json::from_msgpack(result) == j);
CHECK(json::from_msgpack(result, true, false) == j);
}
}
SECTION("N = 65536..4294967295")
{
for (std::size_t N :
{
65536u, 77777u, 1048576u
})
{
CAPTURE(N)
// create JSON value with string containing of N * 'x'
const auto s = std::vector<uint8_t>(N, 'x');
json j = json::binary_array(s);
std::uint8_t subtype = 42;
j.set_subtype(subtype);
// create expected byte vector (hack: create string first)
std::vector<uint8_t> expected(N, 'x');
// reverse order of commands, because we insert at begin()
expected.insert(expected.begin(), subtype);
expected.insert(expected.begin(), static_cast<uint8_t>(N & 0xff));
expected.insert(expected.begin(), static_cast<uint8_t>((N >> 8) & 0xff));
expected.insert(expected.begin(), static_cast<uint8_t>((N >> 16) & 0xff));
expected.insert(expected.begin(), static_cast<uint8_t>((N >> 24) & 0xff));
expected.insert(expected.begin(), 0xC9);
// compare result + size
const auto result = json::to_msgpack(j);
CHECK(result == expected);
CHECK(result.size() == N + 6);
// check that no null byte is appended
CHECK(result.back() != '\x00');
// roundtrip
CHECK(json::from_msgpack(result) == j);
CHECK(json::from_msgpack(result, true, false) == j);
}
}
}
SECTION("binary")
{
SECTION("N = 0..255")
{
for (std::size_t N = 0; N <= 0xFF; ++N)
{
CAPTURE(N)
// create JSON value with byte array containing of N * 'x'
const auto s = std::vector<uint8_t>(N, 'x');
json j = json::binary_array(s);
// create expected byte vector
std::vector<std::uint8_t> expected;
expected.push_back(static_cast<std::uint8_t>(0xC4));
expected.push_back(static_cast<std::uint8_t>(N));
for (size_t i = 0; i < N; ++i)
{
expected.push_back(0x78);
}
// compare result + size
const auto result = json::to_msgpack(j);
CHECK(result == expected);
CHECK(result.size() == N + 2);
// check that no null byte is appended
if (N > 0)
{
CHECK(result.back() != '\x00');
}
// roundtrip
CHECK(json::from_msgpack(result) == j);
CHECK(json::from_msgpack(result, true, false) == j);
}
}
SECTION("N = 256..65535")
{
for (std::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<std::uint8_t>(N, 'x');
json j = json::binary_array(s);
// create expected byte vector (hack: create string first)
std::vector<std::uint8_t> expected(N, 'x');
// reverse order of commands, because we insert at begin()
expected.insert(expected.begin(), static_cast<std::uint8_t>(N & 0xff));
expected.insert(expected.begin(), static_cast<std::uint8_t>((N >> 8) & 0xff));
expected.insert(expected.begin(), 0xC5);
// compare result + size
const auto result = json::to_msgpack(j);
CHECK(result == expected);
CHECK(result.size() == N + 3);
// check that no null byte is appended
CHECK(result.back() != '\x00');
// roundtrip
CHECK(json::from_msgpack(result) == j);
CHECK(json::from_msgpack(result, true, false) == j);
}
}
SECTION("N = 65536..4294967295")
{
for (std::size_t N :
{
65536u, 77777u, 1048576u
})
{
CAPTURE(N)
// create JSON value with string containing of N * 'x'
const auto s = std::vector<std::uint8_t>(N, 'x');
json j = json::binary_array(s);
// create expected byte vector (hack: create string first)
std::vector<uint8_t> expected(N, 'x');
// reverse order of commands, because we insert at begin()
expected.insert(expected.begin(), static_cast<std::uint8_t>(N & 0xff));
expected.insert(expected.begin(), static_cast<std::uint8_t>((N >> 8) & 0xff));
expected.insert(expected.begin(), static_cast<std::uint8_t>((N >> 16) & 0xff));
expected.insert(expected.begin(), static_cast<std::uint8_t>((N >> 24) & 0xff));
expected.insert(expected.begin(), 0xC6);
// compare result + size
const auto result = json::to_msgpack(j);
CHECK(result == expected);
CHECK(result.size() == N + 5);
// check that no null byte is appended
CHECK(result.back() != '\x00');
// roundtrip
CHECK(json::from_msgpack(result) == j);
CHECK(json::from_msgpack(result, true, false) == j);
}
}
}
} }
SECTION("from float32") SECTION("from float32")
@ -1226,12 +1486,6 @@ TEST_CASE("MessagePack")
CHECK_THROWS_AS(_ = json::from_msgpack(std::vector<uint8_t>({0xc1})), json::parse_error&); CHECK_THROWS_AS(_ = json::from_msgpack(std::vector<uint8_t>({0xc1})), json::parse_error&);
CHECK_THROWS_WITH(_ = json::from_msgpack(std::vector<uint8_t>({0xc1})), CHECK_THROWS_WITH(_ = json::from_msgpack(std::vector<uint8_t>({0xc1})),
"[json.exception.parse_error.112] parse error at byte 1: syntax error while parsing MessagePack value: invalid byte: 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<uint8_t>({0xc6}), true, false).is_discarded());
CHECK_THROWS_AS(_ = json::from_msgpack(std::vector<uint8_t>({0xc6})), json::parse_error&);
CHECK_THROWS_WITH(_ = json::from_msgpack(std::vector<uint8_t>({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<uint8_t>({0xc6}), true, false).is_discarded());
} }
SECTION("all unsupported bytes") SECTION("all unsupported bytes")
@ -1239,13 +1493,7 @@ TEST_CASE("MessagePack")
for (auto byte : for (auto byte :
{ {
// never used // never used
0xc1, 0xc1
// bin
0xc4, 0xc5, 0xc6,
// ext
0xc7, 0xc8, 0xc9,
// fixext
0xd4, 0xd5, 0xd6, 0xd7, 0xd8
}) })
{ {
json _; json _;

View file

@ -107,7 +107,7 @@ struct foo_serializer < T, typename std::enable_if < !std::is_same<foo, T>::valu
} }
using foo_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, using foo_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t,
std::uint64_t, double, std::allocator, ns::foo_serializer>; std::uint64_t, double, std::allocator, ns::foo_serializer, std::vector<std::uint8_t>>;
///////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////
// for #805 // for #805

View file

@ -73,6 +73,11 @@ class SaxCountdown
return events_left-- > 0; return events_left-- > 0;
} }
bool binary(std::vector<std::uint8_t>&)
{
return events_left-- > 0;
}
bool start_object(std::size_t) bool start_object(std::size_t)
{ {
return events_left-- > 0; return events_left-- > 0;
@ -905,6 +910,231 @@ TEST_CASE("UBJSON")
} }
} }
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<std::uint8_t>(N, 'x');
json j = json::binary_array(s);
// create expected byte vector
std::vector<std::uint8_t> expected;
expected.push_back(static_cast<std::uint8_t>('['));
if (N != 0)
{
expected.push_back(static_cast<std::uint8_t>('$'));
expected.push_back(static_cast<std::uint8_t>('U'));
}
expected.push_back(static_cast<std::uint8_t>('#'));
expected.push_back(static_cast<std::uint8_t>('i'));
expected.push_back(static_cast<std::uint8_t>(N));
for (size_t i = 0; i < N; ++i)
{
expected.push_back(0x78);
}
// compare result + size
const auto result = json::to_ubjson(j, true, true);
CHECK(result == expected);
if (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');
}
// roundtrip only works to an array of numbers
json j_out = s;
CHECK(json::from_ubjson(result) == j_out);
CHECK(json::from_ubjson(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<std::uint8_t>(N, 'x');
json j = json::binary_array(s);
// create expected byte vector
std::vector<uint8_t> expected;
expected.push_back(static_cast<std::uint8_t>('['));
expected.push_back(static_cast<std::uint8_t>('$'));
expected.push_back(static_cast<std::uint8_t>('U'));
expected.push_back(static_cast<std::uint8_t>('#'));
expected.push_back(static_cast<std::uint8_t>('U'));
expected.push_back(static_cast<std::uint8_t>(N));
for (size_t i = 0; i < N; ++i)
{
expected.push_back(0x78);
}
// compare result + size
const auto result = json::to_ubjson(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_ubjson(result) == j_out);
CHECK(json::from_ubjson(result, true, false) == j_out);
}
}
SECTION("N = 256..32767")
{
for (std::size_t N :
{
256u, 999u, 1025u, 3333u, 2048u, 32767u
})
{
CAPTURE(N)
// create JSON value with byte array containing of N * 'x'
const auto s = std::vector<std::uint8_t>(N, 'x');
json j = json::binary_array(s);
// create expected byte vector
std::vector<std::uint8_t> expected(N + 7, 'x');
expected[0] = '[';
expected[1] = '$';
expected[2] = 'U';
expected[3] = '#';
expected[4] = 'I';
expected[5] = static_cast<std::uint8_t>((N >> 8) & 0xFF);
expected[6] = static_cast<std::uint8_t>(N & 0xFF);
// compare result + size
const auto result = json::to_ubjson(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_ubjson(result) == j_out);
CHECK(json::from_ubjson(result, true, false) == j_out);
}
}
SECTION("N = 32768..2147483647")
{
for (std::size_t N :
{
32768u, 77777u, 1048576u
})
{
CAPTURE(N)
// create JSON value with byte array containing of N * 'x'
const auto s = std::vector<std::uint8_t>(N, 'x');
json j = json::binary_array(s);
// create expected byte vector
std::vector<std::uint8_t> expected(N + 9, 'x');
expected[0] = '[';
expected[1] = '$';
expected[2] = 'U';
expected[3] = '#';
expected[4] = 'l';
expected[5] = static_cast<std::uint8_t>((N >> 24) & 0xFF);
expected[6] = static_cast<std::uint8_t>((N >> 16) & 0xFF);
expected[7] = static_cast<std::uint8_t>((N >> 8) & 0xFF);
expected[8] = static_cast<std::uint8_t>(N & 0xFF);
// compare result + size
const auto result = json::to_ubjson(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_ubjson(result) == j_out);
CHECK(json::from_ubjson(result, true, false) == j_out);
}
}
SECTION("Other Serializations")
{
const std::size_t N = 10;
const auto s = std::vector<std::uint8_t>(N, 'x');
json j = json::binary_array(s);
SECTION("No Count No Type")
{
std::vector<uint8_t> expected;
expected.push_back(static_cast<std::uint8_t>('['));
for (std::size_t i = 0; i < N; ++i)
{
expected.push_back(static_cast<std::uint8_t>('U'));
expected.push_back(static_cast<std::uint8_t>(0x78));
}
expected.push_back(static_cast<std::uint8_t>(']'));
// compare result + size
const auto result = json::to_ubjson(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_ubjson(result) == j_out);
CHECK(json::from_ubjson(result, true, false) == j_out);
}
SECTION("Yes Count No Type")
{
std::vector<std::uint8_t> expected;
expected.push_back(static_cast<std::uint8_t>('['));
expected.push_back(static_cast<std::uint8_t>('#'));
expected.push_back(static_cast<std::uint8_t>('i'));
expected.push_back(static_cast<std::uint8_t>(N));
for (size_t i = 0; i < N; ++i)
{
expected.push_back(static_cast<std::uint8_t>('U'));
expected.push_back(static_cast<std::uint8_t>(0x78));
}
// compare result + size
const auto result = json::to_ubjson(j, true, false);
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_ubjson(result) == j_out);
CHECK(json::from_ubjson(result, true, false) == j_out);
}
}
}
SECTION("array") SECTION("array")
{ {
SECTION("empty") SECTION("empty")

View file

@ -653,8 +653,7 @@ TEST_CASE("custom serializer for pods" * doctest::test_suite("udt"))
{ {
using custom_json = using custom_json =
nlohmann::basic_json<std::map, std::vector, std::string, bool, nlohmann::basic_json<std::map, std::vector, std::string, bool,
std::int64_t, std::uint64_t, double, std::allocator, std::int64_t, std::uint64_t, double, std::allocator, pod_serializer>;
pod_serializer>;
auto p = udt::small_pod{42, '/', 42}; auto p = udt::small_pod{42, '/', 42};
custom_json j = p; custom_json j = p;