Merge branch 'develop' into issues/1457

This commit is contained in:
Niels Lohmann 2020-05-13 12:48:46 +02:00 committed by GitHub
commit a4266bbb7d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
1178 changed files with 6590 additions and 6298894 deletions

View file

@ -990,11 +990,11 @@ inline char* format_buffer(char* buf, int len, int decimal_exponent,
// digits[000]
// len <= max_exp + 2
std::memset(buf + k, '0', static_cast<size_t>(n - k));
std::memset(buf + k, '0', static_cast<size_t>(n) - static_cast<size_t>(k));
// Make it look like a floating-point number (#362, #378)
buf[n + 0] = '.';
buf[n + 1] = '0';
return buf + (n + 2);
return buf + (static_cast<size_t>(n) + 2);
}
if (0 < n and n <= max_exp)
@ -1004,9 +1004,9 @@ inline char* format_buffer(char* buf, int len, int decimal_exponent,
assert(k > n);
std::memmove(buf + (n + 1), buf + n, static_cast<size_t>(k - n));
std::memmove(buf + (static_cast<size_t>(n) + 1), buf + n, static_cast<size_t>(k) - static_cast<size_t>(n));
buf[n] = '.';
return buf + (k + 1);
return buf + (static_cast<size_t>(k) + 1U);
}
if (min_exp < n and n <= 0)
@ -1014,11 +1014,11 @@ inline char* format_buffer(char* buf, int len, int decimal_exponent,
// 0.[000]digits
// len <= 2 + (-min_exp - 1) + max_digits10
std::memmove(buf + (2 + -n), buf, static_cast<size_t>(k));
std::memmove(buf + (2 + static_cast<size_t>(-n)), buf, static_cast<size_t>(k));
buf[0] = '0';
buf[1] = '.';
std::memset(buf + 2, '0', static_cast<size_t>(-n));
return buf + (2 + (-n) + k);
return buf + (2U + static_cast<size_t>(-n) + static_cast<size_t>(k));
}
if (k == 1)
@ -1033,9 +1033,9 @@ inline char* format_buffer(char* buf, int len, int decimal_exponent,
// d.igitsE+123
// len <= max_digits10 + 1 + 5
std::memmove(buf + 2, buf + 1, static_cast<size_t>(k - 1));
std::memmove(buf + 2, buf + 1, static_cast<size_t>(k) - 1);
buf[1] = '.';
buf += 1 + k;
buf += 1 + static_cast<size_t>(k);
}
*buf++ = 'e';

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<>
struct external_constructor<value_t::number_float>
{

View file

@ -52,6 +52,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 internal_binary_t = typename BasicJsonType::internal_binary_t;
using json_sax_t = SAX;
public:
@ -208,6 +209,30 @@ class binary_reader
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.
@param[in] element_type The BSON element type, c.f. http://bsonspec.org/spec.html
@ -246,6 +271,13 @@ class binary_reader
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
{
return sax->boolean(get() != 0);
@ -292,6 +324,7 @@ class binary_reader
bool parse_bson_element_list(const bool is_array)
{
string_t key;
while (int element_type = get())
{
if (JSON_HEDLEY_UNLIKELY(not unexpect_eof(input_format_t::bson, "element list")))
@ -466,6 +499,41 @@ class binary_reader
- 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)
case 0x60:
case 0x61:
@ -781,6 +849,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
array of indefinite size
@ -1101,6 +1264,22 @@ class binary_reader
case 0xC3: // 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
{
float number;
@ -1310,6 +1489,100 @@ 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)
{
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: // LCOV_EXCL_LINE
assert(false); // LCOV_EXCL_LINE
}
}
/*!
@param[in] len the length of the array
@return whether array creation completed
@ -1794,6 +2067,9 @@ class binary_reader
return sax->end_object();
}
// Note, no reader for UBJSON binary types is implemented because they do
// not exist
///////////////////////
// Utility functions //
///////////////////////
@ -1901,6 +2177,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<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<std::uint8_t>(current);
});
return success;
}
/*!
@param[in] format the current format (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;
/// type for strings
using string_t = typename BasicJsonType::string_t;
using binary_t = typename BasicJsonType::binary_t;
/*!
@brief a null value was read
@ -75,6 +76,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
@ -149,6 +158,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
@ -202,6 +212,12 @@ class json_sax_dom_parser
return true;
}
bool binary(binary_t& val)
{
handle_value(BasicJsonType::binary_array(std::move(val)));
return true;
}
bool start_object(std::size_t len)
{
ref_stack.push_back(handle_value(BasicJsonType::value_t::object));
@ -331,6 +347,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;
@ -385,6 +402,12 @@ class json_sax_dom_callback_parser
return true;
}
bool binary(binary_t& val)
{
handle_value(BasicJsonType::binary_array(val));
return true;
}
bool start_object(std::size_t len)
{
// check callback for object start
@ -635,6 +658,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()
{
@ -666,7 +690,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;
}
@ -681,7 +710,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;
}

View file

@ -344,13 +344,13 @@ class lexer : public lexer_base<BasicJsonType>
}
else
{
error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF";
error_message = "invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF";
return token_type::parse_error;
}
}
else
{
error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF";
error_message = "invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF";
return token_type::parse_error;
}
}

View file

@ -18,6 +18,8 @@ template<typename BasicJsonType> 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 {};
};

View file

@ -126,8 +126,8 @@ class json_pointer
/*!
@brief append an array index at the end of this JSON pointer
@param[in] array_index array index to append
@return JSON pointer with @a array_index appended
@param[in] array_idx array index to append
@return JSON pointer with @a array_idx appended
@liveexample{The example shows the usage of `operator/=`.,json_pointer__operator_add}
@ -139,9 +139,9 @@ class json_pointer
@since version 3.6.0
*/
json_pointer& operator/=(std::size_t array_index)
json_pointer& operator/=(std::size_t array_idx)
{
return *this /= std::to_string(array_index);
return *this /= std::to_string(array_idx);
}
/*!
@ -189,8 +189,8 @@ class json_pointer
@brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer
@param[in] ptr JSON pointer
@param[in] array_index array index
@return a new JSON pointer with @a array_index appended to @a ptr
@param[in] array_idx array index
@return a new JSON pointer with @a array_idx appended to @a ptr
@liveexample{The example shows the usage of `operator/`.,json_pointer__operator_add_binary}
@ -200,9 +200,9 @@ class json_pointer
@since version 3.6.0
*/
friend json_pointer operator/(const json_pointer& ptr, std::size_t array_index)
friend json_pointer operator/(const json_pointer& ptr, std::size_t array_idx)
{
return json_pointer(ptr) /= array_index;
return json_pointer(ptr) /= array_idx;
}
/*!
@ -329,8 +329,30 @@ class json_pointer
*/
static int array_index(const std::string& s)
{
// error condition (cf. RFC 6901, Sect. 4)
if (JSON_HEDLEY_UNLIKELY(s.size() > 1 and s[0] == '0'))
{
JSON_THROW(detail::parse_error::create(106, 0,
"array index '" + s +
"' must not begin with '0'"));
}
// error condition (cf. RFC 6901, Sect. 4)
if (JSON_HEDLEY_UNLIKELY(s.size() > 1 and not (s[0] >= '1' and s[0] <= '9')))
{
JSON_THROW(detail::parse_error::create(109, 0, "array index '" + s + "' is not a number"));
}
std::size_t processed_chars = 0;
const int res = std::stoi(s, &processed_chars);
int res = 0;
JSON_TRY
{
res = std::stoi(s, &processed_chars);
}
JSON_CATCH(std::out_of_range&)
{
JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'"));
}
// check if the string was completely read
if (JSON_HEDLEY_UNLIKELY(processed_chars != s.size()))
@ -397,14 +419,7 @@ class json_pointer
case detail::value_t::array:
{
// create an entry in the array
JSON_TRY
{
result = &result->operator[](static_cast<size_type>(array_index(reference_token)));
}
JSON_CATCH(std::invalid_argument&)
{
JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
}
result = &result->operator[](static_cast<size_type>(array_index(reference_token)));
break;
}
@ -474,14 +489,6 @@ class json_pointer
case detail::value_t::array:
{
// error condition (cf. RFC 6901, Sect. 4)
if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
{
JSON_THROW(detail::parse_error::create(106, 0,
"array index '" + reference_token +
"' must not begin with '0'"));
}
if (reference_token == "-")
{
// explicitly treat "-" as index beyond the end
@ -490,15 +497,8 @@ class json_pointer
else
{
// convert array index to number; unchecked access
JSON_TRY
{
ptr = &ptr->operator[](
static_cast<size_type>(array_index(reference_token)));
}
JSON_CATCH(std::invalid_argument&)
{
JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
}
ptr = &ptr->operator[](
static_cast<size_type>(array_index(reference_token)));
}
break;
}
@ -541,23 +541,8 @@ class json_pointer
") is out of range"));
}
// error condition (cf. RFC 6901, Sect. 4)
if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
{
JSON_THROW(detail::parse_error::create(106, 0,
"array index '" + reference_token +
"' must not begin with '0'"));
}
// note: at performs range check
JSON_TRY
{
ptr = &ptr->at(static_cast<size_type>(array_index(reference_token)));
}
JSON_CATCH(std::invalid_argument&)
{
JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
}
ptr = &ptr->at(static_cast<size_type>(array_index(reference_token)));
break;
}
@ -606,24 +591,9 @@ class json_pointer
") is out of range"));
}
// error condition (cf. RFC 6901, Sect. 4)
if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
{
JSON_THROW(detail::parse_error::create(106, 0,
"array index '" + reference_token +
"' must not begin with '0'"));
}
// use unchecked array access
JSON_TRY
{
ptr = &ptr->operator[](
static_cast<size_type>(array_index(reference_token)));
}
JSON_CATCH(std::invalid_argument&)
{
JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
}
ptr = &ptr->operator[](
static_cast<size_type>(array_index(reference_token)));
break;
}
@ -665,23 +635,8 @@ class json_pointer
") is out of range"));
}
// error condition (cf. RFC 6901, Sect. 4)
if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
{
JSON_THROW(detail::parse_error::create(106, 0,
"array index '" + reference_token +
"' must not begin with '0'"));
}
// note: at performs range check
JSON_TRY
{
ptr = &ptr->at(static_cast<size_type>(array_index(reference_token)));
}
JSON_CATCH(std::invalid_argument&)
{
JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
}
ptr = &ptr->at(static_cast<size_type>(array_index(reference_token)));
break;
}
@ -723,31 +678,36 @@ class json_pointer
// "-" always fails the range check
return false;
}
// error condition (cf. RFC 6901, Sect. 4)
if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
if (JSON_HEDLEY_UNLIKELY(reference_token.size() == 1 and not ("0" <= reference_token and reference_token <= "9")))
{
JSON_THROW(detail::parse_error::create(106, 0,
"array index '" + reference_token +
"' must not begin with '0'"));
// invalid char
return false;
}
JSON_TRY
if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1))
{
const auto idx = static_cast<size_type>(array_index(reference_token));
if (idx >= ptr->size())
if (JSON_HEDLEY_UNLIKELY(not ('1' <= reference_token[0] and reference_token[0] <= '9')))
{
// index out of range
// first char should be between '1' and '9'
return false;
}
for (std::size_t i = 1; i < reference_token.size(); i++)
{
if (JSON_HEDLEY_UNLIKELY(not ('0' <= reference_token[i] and reference_token[i] <= '9')))
{
// other char should be between '0' and '9'
return false;
}
}
}
ptr = &ptr->operator[](idx);
break;
}
JSON_CATCH(std::invalid_argument&)
const auto idx = static_cast<size_type>(array_index(reference_token));
if (idx >= ptr->size())
{
JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
// index out of range
return false;
}
ptr = &ptr->operator[](idx);
break;
}

View file

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

View file

@ -41,6 +41,19 @@ template<typename> struct is_basic_json : std::false_type {};
NLOHMANN_BASIC_JSON_TPL_DECLARATION
struct is_basic_json<NLOHMANN_BASIC_JSON_TPL> : std::true_type {};
//////////////////////
// json_ref helpers //
//////////////////////
template <typename>
class json_ref;
template<typename>
struct is_json_ref : std::false_type {};
template <typename T>
struct is_json_ref<json_ref<T>> : std::true_type {};
//////////////////////////
// aliases for detected //
//////////////////////////

View file

@ -26,6 +26,7 @@ template<typename BasicJsonType, typename CharType>
class binary_writer
{
using string_t = typename BasicJsonType::string_t;
using internal_binary_t = typename BasicJsonType::internal_binary_t;
public:
/*!
@ -258,6 +259,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<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:
{
// step 1: write control byte and the object size
@ -506,6 +546,101 @@ class binary_writer
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:
{
// step 1: write control byte and the object size
@ -649,6 +784,49 @@ 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);
}
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:
{
if (add_prefix)
@ -871,6 +1049,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::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
*/
@ -890,6 +1076,27 @@ 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 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
@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:
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;
@ -950,6 +1160,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);
@ -1230,7 +1443,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:
@ -1300,7 +1514,7 @@ class binary_writer
static CharType to_char_type(std::uint8_t x) noexcept
{
static_assert(sizeof(std::uint8_t) == sizeof(CharType), "size of CharType must be equal to std::uint8_t");
static_assert(std::is_pod<CharType>::value, "CharType must be POD");
static_assert(std::is_trivial<CharType>::value, "CharType must be trivial");
CharType result;
std::memcpy(&result, &x, sizeof(x));
return result;

View file

@ -45,6 +45,7 @@ class serializer
using number_float_t = typename BasicJsonType::number_float_t;
using number_integer_t = typename BasicJsonType::number_integer_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_REJECT = 1;
@ -83,16 +84,19 @@ class serializer
- strings and object keys are escaped using `escape_string()`
- integer numbers are converted implicitly via `operator<<`
- 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] pretty_print whether the output shall be pretty-printed
@param[in] indent_step the indent level
@param[in] current_indent the current indent level (only used internally)
@param[in] val value to serialize
@param[in] pretty_print whether the output shall be pretty-printed
@param[in] indent_step the indent level
@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,
const bool ensure_ascii,
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)
{
@ -123,7 +127,7 @@ class serializer
o->write_character('\"');
dump_escaped(i->first, ensure_ascii);
o->write_characters("\": ", 3);
dump(i->second, true, ensure_ascii, indent_step, new_indent);
dump(i->second, true, ensure_ascii, indent_step, new_indent, serialize_binary);
o->write_characters(",\n", 2);
}
@ -134,7 +138,7 @@ class serializer
o->write_character('\"');
dump_escaped(i->first, ensure_ascii);
o->write_characters("\": ", 3);
dump(i->second, true, ensure_ascii, indent_step, new_indent);
dump(i->second, true, ensure_ascii, indent_step, new_indent, serialize_binary);
o->write_character('\n');
o->write_characters(indent_string.c_str(), current_indent);
@ -151,7 +155,7 @@ class serializer
o->write_character('\"');
dump_escaped(i->first, ensure_ascii);
o->write_characters("\":", 2);
dump(i->second, false, ensure_ascii, indent_step, current_indent);
dump(i->second, false, ensure_ascii, indent_step, current_indent, serialize_binary);
o->write_character(',');
}
@ -161,7 +165,7 @@ class serializer
o->write_character('\"');
dump_escaped(i->first, ensure_ascii);
o->write_characters("\":", 2);
dump(i->second, false, ensure_ascii, indent_step, current_indent);
dump(i->second, false, ensure_ascii, indent_step, current_indent, serialize_binary);
o->write_character('}');
}
@ -193,14 +197,14 @@ class serializer
i != val.m_value.array->cend() - 1; ++i)
{
o->write_characters(indent_string.c_str(), new_indent);
dump(*i, true, ensure_ascii, indent_step, new_indent);
dump(*i, true, ensure_ascii, indent_step, new_indent, serialize_binary);
o->write_characters(",\n", 2);
}
// last element
assert(not val.m_value.array->empty());
o->write_characters(indent_string.c_str(), new_indent);
dump(val.m_value.array->back(), true, ensure_ascii, indent_step, new_indent);
dump(val.m_value.array->back(), true, ensure_ascii, indent_step, new_indent, serialize_binary);
o->write_character('\n');
o->write_characters(indent_string.c_str(), current_indent);
@ -214,13 +218,13 @@ class serializer
for (auto i = val.m_value.array->cbegin();
i != val.m_value.array->cend() - 1; ++i)
{
dump(*i, false, ensure_ascii, indent_step, current_indent);
dump(*i, false, ensure_ascii, indent_step, current_indent, serialize_binary);
o->write_character(',');
}
// last element
assert(not val.m_value.array->empty());
dump(val.m_value.array->back(), false, ensure_ascii, indent_step, current_indent);
dump(val.m_value.array->back(), false, ensure_ascii, indent_step, current_indent, serialize_binary);
o->write_character(']');
}
@ -236,6 +240,33 @@ class serializer
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
{
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:
{
if (val.m_value.boolean)
@ -592,7 +623,8 @@ class serializer
*/
template<typename NumberType, detail::enable_if_t<
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>
void dump_integer(NumberType x)
{
@ -630,7 +662,7 @@ class serializer
if (is_negative)
{
*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
n_chars = 1 + count_digits(abs_value);
@ -807,7 +839,9 @@ class serializer
? (byte & 0x3fu) | (codep << 6u)
: (0xFFu >> type) & (byte);
state = utf8d[256u + state * 16u + type];
std::size_t index = 256u + static_cast<size_t>(state) * 16u + static_cast<size_t>(type);
assert(index < 400);
state = utf8d[index];
return state;
}

View file

@ -48,6 +48,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 parser callback function
};
@ -55,17 +56,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<std::uint8_t, 8> order = {{
static constexpr std::array<std::uint8_t, 9> 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 */
}
};