support UBJSON-derived Binary JData (BJData) format

This commit is contained in:
Qianqian Fang 2022-02-16 18:05:31 -05:00
parent 1a90c9463a
commit 3c820dd215
7 changed files with 3883 additions and 76 deletions

View File

@ -95,12 +95,13 @@ class binary_reader
@return whether parsing was successful
*/
JSON_HEDLEY_NON_NULL(3)
bool sax_parse(const input_format_t format,
bool sax_parse(const input_format_t format_,
json_sax_t* sax_,
const bool strict = true,
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)
{
sax = sax_;
format = format_;
bool result = false;
switch (format)
@ -118,6 +119,7 @@ class binary_reader
break;
case input_format_t::ubjson:
case input_format_t::bjdata:
result = parse_ubjson_internal();
break;
@ -129,7 +131,7 @@ class binary_reader
// strict mode: next byte must be EOF
if (result && strict)
{
if (format == input_format_t::ubjson)
if (format == input_format_t::ubjson || format == input_format_t::bjdata)
{
get_ignore_noop();
}
@ -1844,7 +1846,7 @@ class binary_reader
get(); // TODO(niels): may we ignore N here?
}
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "value")))
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "value")))
{
return false;
}
@ -1854,52 +1856,141 @@ class binary_reader
case 'U':
{
std::uint8_t len{};
return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result);
return get_number(format, len) && get_string(format, len, result);
}
case 'i':
{
std::int8_t len{};
return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result);
return get_number(format, len) && get_string(format, len, result);
}
case 'I':
{
std::int16_t len{};
return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result);
return get_number(format, len) && get_string(format, len, result);
}
case 'l':
{
std::int32_t len{};
return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result);
return get_number(format, len) && get_string(format, len, result);
}
case 'L':
{
std::int64_t len{};
return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result);
return get_number(format, len) && get_string(format, len, result);
}
default:
if (format == input_format_t::bjdata)
{
switch (current)
{
case 'u':
{
uint16_t len;
return get_number(format, len) and get_string(format, len, result);
}
case 'm':
{
uint32_t len;
return get_number(format, len) and get_string(format, len, result);
}
case 'M':
{
uint64_t len;
return get_number(format, len) and get_string(format, len, result);
}
}
}
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::ubjson, concat("expected length type specification (U, i, I, l, L); last byte: 0x", last_token), "string"), nullptr));
exception_message(format, concat("expected length type specification (U, i, I, l, L); last byte: 0x", last_token), "string"), nullptr));
}
}
/*!
@param[out] dim an integer vector storing the ND array dimensions
@return whether array creation completed
*/
bool get_ubjson_ndarray_size(std::vector<size_t>& dim)
{
std::pair<std::size_t, char_int_type> size_and_type;
size_t dimlen;
bool is_optimized = get_ubjson_size_type(size_and_type);
if (size_and_type.first != string_t::npos)
{
if (size_and_type.second != 0)
{
if (size_and_type.second != 'N')
{
for (std::size_t i = 0; i < size_and_type.first; ++i)
{
if (JSON_HEDLEY_UNLIKELY(not get_ubjson_size_value(dimlen, size_and_type.second)))
{
return false;
}
else
{
dim.push_back(dimlen);
}
}
}
}
else
{
for (std::size_t i = 0; i < size_and_type.first; ++i)
{
if (JSON_HEDLEY_UNLIKELY(not get_ubjson_size_value(dimlen)))
{
return false;
}
else
{
dim.push_back(dimlen);
}
}
}
}
else
{
while (current != ']')
{
if (JSON_HEDLEY_UNLIKELY(not get_ubjson_size_value(dimlen)))
{
return false;
}
else
{
dim.push_back(dimlen);
}
get_ignore_noop();
}
}
return true;
}
/*!
@param[out] result determined size
@return whether size determination completed
*/
bool get_ubjson_size_value(std::size_t& result)
bool get_ubjson_size_value(std::size_t& result, int prefix = 0)
{
switch (get_ignore_noop())
if (prefix == 0)
{
prefix = get_ignore_noop();
}
switch (prefix)
{
case 'U':
{
std::uint8_t number{};
if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number)))
if (JSON_HEDLEY_UNLIKELY(!get_number(format, number)))
{
return false;
}
@ -1910,7 +2001,7 @@ class binary_reader
case 'i':
{
std::int8_t number{};
if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number)))
if (JSON_HEDLEY_UNLIKELY(!get_number(format, number)))
{
return false;
}
@ -1921,7 +2012,7 @@ class binary_reader
case 'I':
{
std::int16_t number{};
if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number)))
if (JSON_HEDLEY_UNLIKELY(!get_number(format, number)))
{
return false;
}
@ -1932,7 +2023,7 @@ class binary_reader
case 'l':
{
std::int32_t number{};
if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number)))
if (JSON_HEDLEY_UNLIKELY(!get_number(format, number)))
{
return false;
}
@ -1943,7 +2034,7 @@ class binary_reader
case 'L':
{
std::int64_t number{};
if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number)))
if (JSON_HEDLEY_UNLIKELY(!get_number(format, number)))
{
return false;
}
@ -1953,9 +2044,62 @@ class binary_reader
default:
{
if (format == input_format_t::bjdata)
{
switch (prefix)
{
case 'u':
{
uint16_t number;
if (JSON_HEDLEY_UNLIKELY(not get_number(format, number)))
{
return false;
}
result = static_cast<std::size_t>(number);
return true;
}
case 'm':
{
uint32_t number;
if (JSON_HEDLEY_UNLIKELY(not get_number(format, number)))
{
return false;
}
result = static_cast<std::size_t>(number);
return true;
}
case 'M':
{
uint64_t number;
if (JSON_HEDLEY_UNLIKELY(not get_number(format, number)))
{
return false;
}
result = static_cast<std::size_t>(number);
return true;
}
case '[':
{
std::vector<size_t> dim;
if (JSON_HEDLEY_UNLIKELY(not get_ubjson_ndarray_size(dim)))
{
return false;
}
else
{
result = 1;
for (std::size_t i = 0; i < dim.size(); ++i)
{
result *= dim.at(i);
}
}
return true;
}
}
}
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::ubjson, concat("expected length type specification (U, i, I, l, L) after '#'; last byte: 0x", last_token), "size"), nullptr));
exception_message(format, concat("expected length type specification (U, i, I, l, L) after '#'; last byte: 0x", last_token), "size"), nullptr));
}
}
}
@ -1980,7 +2124,7 @@ class binary_reader
if (current == '$')
{
result.second = get(); // must not ignore 'N', because 'N' maybe the type
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "type")))
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "type")))
{
return false;
}
@ -1988,13 +2132,13 @@ class binary_reader
get_ignore_noop();
if (JSON_HEDLEY_UNLIKELY(current != '#'))
{
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "value")))
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "value")))
{
return false;
}
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::ubjson, concat("expected '#' after type information; last byte: 0x", last_token), "size"), nullptr));
exception_message(format, concat("expected '#' after type information; last byte: 0x", last_token), "size"), nullptr));
}
return get_ubjson_size_value(result.first);
@ -2017,7 +2161,7 @@ class binary_reader
switch (prefix)
{
case std::char_traits<char_type>::eof(): // EOF
return unexpect_eof(input_format_t::ubjson, "value");
return unexpect_eof(format, "value");
case 'T': // true
return sax->boolean(true);
@ -2030,43 +2174,43 @@ class binary_reader
case 'U':
{
std::uint8_t number{};
return get_number(input_format_t::ubjson, number) && sax->number_unsigned(number);
return get_number(format, number) && sax->number_unsigned(number);
}
case 'i':
{
std::int8_t number{};
return get_number(input_format_t::ubjson, number) && sax->number_integer(number);
return get_number(format, number) && sax->number_integer(number);
}
case 'I':
{
std::int16_t number{};
return get_number(input_format_t::ubjson, number) && sax->number_integer(number);
return get_number(format, number) && sax->number_integer(number);
}
case 'l':
{
std::int32_t number{};
return get_number(input_format_t::ubjson, number) && sax->number_integer(number);
return get_number(format, number) && sax->number_integer(number);
}
case 'L':
{
std::int64_t number{};
return get_number(input_format_t::ubjson, number) && sax->number_integer(number);
return get_number(format, number) && sax->number_integer(number);
}
case 'd':
{
float number{};
return get_number(input_format_t::ubjson, number) && sax->number_float(static_cast<number_float_t>(number), "");
return get_number(format, number) && sax->number_float(static_cast<number_float_t>(number), "");
}
case 'D':
{
double number{};
return get_number(input_format_t::ubjson, number) && sax->number_float(static_cast<number_float_t>(number), "");
return get_number(format, number) && sax->number_float(static_cast<number_float_t>(number), "");
}
case 'H':
@ -2077,7 +2221,7 @@ class binary_reader
case 'C': // char
{
get();
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "char")))
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "char")))
{
return false;
}
@ -2085,7 +2229,7 @@ class binary_reader
{
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::ubjson, concat("byte after 'C' must be in range 0x00..0x7F; last byte: 0x", last_token), "char"), nullptr));
exception_message(format, concat("byte after 'C' must be in range 0x00..0x7F; last byte: 0x", last_token), "char"), nullptr));
}
string_t s(1, static_cast<typename string_t::value_type>(current));
return sax->string(s);
@ -2105,9 +2249,74 @@ class binary_reader
default: // anything else
{
if (format == input_format_t::bjdata)
{
switch (prefix)
{
case 'u':
{
uint16_t number;
return get_number(format, number) and sax->number_integer(number);
}
case 'm':
{
uint32_t number;
return get_number(format, number) and sax->number_integer(number);
}
case 'M':
{
uint64_t number;
return get_number(format, number) and sax->number_integer(number);
}
case 'h':
{
const int byte2 = get();
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "half")))
{
return false;
}
const int byte1 = get();
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "half")))
{
return false;
}
// code from RFC 7049, Appendix D, Figure 3:
// As half-precision floating-point numbers were only added
// to IEEE 754 in 2008, today's programming platforms often
// still only have limited support for them. It is very
// easy to include at least decoding support for them even
// without such support. An example of a small decoder for
// half-precision floating-point numbers in the C language
// is shown in Fig. 3.
const int half = (byte1 << 8) + byte2;
const double val = [&half]
{
const int exp = (half >> 10) & 0x1F;
const int mant = half & 0x3FF;
JSON_ASSERT(0 <= exp and exp <= 32);
JSON_ASSERT(0 <= mant and mant <= 1024);
switch (exp)
{
case 0:
return std::ldexp(mant, -24);
case 31:
return (mant == 0)
? std::numeric_limits<double>::infinity()
: std::numeric_limits<double>::quiet_NaN();
default:
return std::ldexp(mant + 1024, exp - 25);
}
}();
return sax->number_float((half & 0x8000) != 0
? static_cast<number_float_t>(-val)
: static_cast<number_float_t>(val), "");
}
}
}
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::ubjson, concat("invalid byte: 0x", last_token), "value"), nullptr));
exception_message(format, concat("invalid byte: 0x", last_token), "value"), nullptr));
}
}
}
@ -2267,7 +2476,7 @@ class binary_reader
for (std::size_t i = 0; i < size; ++i)
{
get();
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "number")))
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "number")))
{
return false;
}
@ -2286,7 +2495,7 @@ class binary_reader
if (JSON_HEDLEY_UNLIKELY(result_remainder != token_type::end_of_input))
{
return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read,
exception_message(input_format_t::ubjson, concat("invalid number text: ", number_lexer.get_token_string()), "high-precision number"), nullptr));
exception_message(format, concat("invalid number text: ", number_lexer.get_token_string()), "high-precision number"), nullptr));
}
switch (result_number)
@ -2313,7 +2522,7 @@ class binary_reader
case token_type::literal_or_value:
default:
return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read,
exception_message(input_format_t::ubjson, concat("invalid number text: ", number_lexer.get_token_string()), "high-precision number"), nullptr));
exception_message(format, concat("invalid number text: ", number_lexer.get_token_string()), "high-precision number"), nullptr));
}
}
@ -2377,7 +2586,8 @@ class binary_reader
}
// reverse byte order prior to conversion if necessary
if (is_little_endian != InputIsLittleEndian)
if ((is_little_endian != InputIsLittleEndian and format != input_format_t::bjdata) or
(is_little_endian == InputIsLittleEndian and format == input_format_t::bjdata))
{
vec[sizeof(NumberType) - i - 1] = static_cast<std::uint8_t>(current);
}
@ -2514,6 +2724,10 @@ class binary_reader
error_msg += "BSON";
break;
case input_format_t::bjdata:
error_msg += "BJData";
break;
case input_format_t::json: // LCOV_EXCL_LINE
default: // LCOV_EXCL_LINE
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
@ -2535,6 +2749,9 @@ class binary_reader
/// whether we can assume little endianness
const bool is_little_endian = little_endianness();
/// sax parser format
input_format_t format;
/// the SAX parser
json_sax_t* sax = nullptr;
};

View File

@ -23,7 +23,7 @@ namespace nlohmann
namespace detail
{
/// the supported input formats
enum class input_format_t { json, cbor, msgpack, ubjson, bson };
enum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata };
////////////////////
// input adapters //

View File

@ -38,9 +38,10 @@ class binary_writer
@param[in] adapter output adapter to write to
*/
explicit binary_writer(output_adapter_t<CharType> adapter) : oa(std::move(adapter))
explicit binary_writer(output_adapter_t<CharType> adapter, const bool is_bjdata_ = false) : oa(std::move(adapter))
{
JSON_ASSERT(oa);
is_bjdata = is_bjdata_;
}
/*!
@ -1334,6 +1335,14 @@ class binary_writer
}
write_number(static_cast<std::int16_t>(n));
}
else if (is_bjdata and n <= static_cast<uint64_t>((std::numeric_limits<uint16_t>::max)()))
{
if (add_prefix)
{
oa->write_character(to_char_type('u')); // uint16 - bjdata only
}
write_number(static_cast<std::uint16_t>(n));
}
else if (n <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)()))
{
if (add_prefix)
@ -1342,6 +1351,14 @@ class binary_writer
}
write_number(static_cast<std::int32_t>(n));
}
else if (is_bjdata and n <= static_cast<uint64_t>((std::numeric_limits<uint32_t>::max)()))
{
if (add_prefix)
{
oa->write_character(to_char_type('m')); // uint32 - bjdata only
}
write_number(static_cast<std::uint32_t>(n));
}
else if (n <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))
{
if (add_prefix)
@ -1350,6 +1367,14 @@ class binary_writer
}
write_number(static_cast<std::int64_t>(n));
}
else if (is_bjdata and n <= static_cast<uint64_t>((std::numeric_limits<uint64_t>::max)()))
{
if (add_prefix)
{
oa->write_character(to_char_type('M')); // uint64 - bjdata only
}
write_number(static_cast<std::uint64_t>(n));
}
else
{
if (add_prefix)
@ -1397,6 +1422,14 @@ class binary_writer
}
write_number(static_cast<std::int16_t>(n));
}
else if (is_bjdata and (static_cast<std::int64_t>((std::numeric_limits<std::uint16_t>::min)()) <= n && n <= static_cast<std::int64_t>((std::numeric_limits<std::uint16_t>::max)())))
{
if (add_prefix)
{
oa->write_character(to_char_type('u')); // uint16 - bjdata only
}
write_number(static_cast<uint16_t>(n));
}
else if ((std::numeric_limits<std::int32_t>::min)() <= n && n <= (std::numeric_limits<std::int32_t>::max)())
{
if (add_prefix)
@ -1405,6 +1438,14 @@ class binary_writer
}
write_number(static_cast<std::int32_t>(n));
}
else if (is_bjdata and (static_cast<std::int64_t>((std::numeric_limits<std::uint32_t>::min)()) <= n && n <= static_cast<std::int64_t>((std::numeric_limits<std::uint32_t>::max)())))
{
if (add_prefix)
{
oa->write_character(to_char_type('m')); // uint32 - bjdata only
}
write_number(static_cast<uint32_t>(n));
}
else if ((std::numeric_limits<std::int64_t>::min)() <= n && n <= (std::numeric_limits<std::int64_t>::max)())
{
if (add_prefix)
@ -1413,6 +1454,14 @@ class binary_writer
}
write_number(static_cast<std::int64_t>(n));
}
else if (is_bjdata and (static_cast<std::uint64_t>((std::numeric_limits<std::uint64_t>::min)()) <= n && n <= static_cast<std::uint64_t>((std::numeric_limits<std::uint64_t>::max)())))
{
if (add_prefix)
{
oa->write_character(to_char_type('M')); // uint64 - bjdata only
}
write_number(static_cast<std::uint64_t>(n));
}
// LCOV_EXCL_START
else
{
@ -1458,14 +1507,26 @@ class binary_writer
{
return 'I';
}
if (is_bjdata and ((std::numeric_limits<std::uint16_t>::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits<std::uint16_t>::max)()))
{
return 'u';
}
if ((std::numeric_limits<std::int32_t>::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)())
{
return 'l';
}
if (is_bjdata and ((std::numeric_limits<std::uint32_t>::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits<std::uint32_t>::max)()))
{
return 'm';
}
if ((std::numeric_limits<std::int64_t>::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits<std::int64_t>::max)())
{
return 'L';
}
if (is_bjdata and ((std::numeric_limits<std::uint64_t>::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits<std::uint64_t>::max)()))
{
return 'M';
}
// anything else is treated as high-precision number
return 'H'; // LCOV_EXCL_LINE
}
@ -1484,14 +1545,26 @@ class binary_writer
{
return 'I';
}
if (is_bjdata and j.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::uint16_t>::max)()))
{
return 'u';
}
if (j.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)()))
{
return 'l';
}
if (is_bjdata and j.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::uint32_t>::max)()))
{
return 'm';
}
if (j.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))
{
return 'L';
}
if (is_bjdata and j.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::uint64_t>::max)()))
{
return 'M';
}
// anything else is treated as high-precision number
return 'H'; // LCOV_EXCL_LINE
}
@ -1539,6 +1612,7 @@ class binary_writer
@note This function needs to respect the system's endianness, because bytes
in CBOR, MessagePack, and UBJSON are stored in network order (big
endian) and therefore need reordering on little endian systems.
BJData is similar to UBJSON but uses little endian by default.
*/
template<typename NumberType, bool OutputIsLittleEndian = false>
void write_number(const NumberType n)
@ -1548,7 +1622,7 @@ class binary_writer
std::memcpy(vec.data(), &n, sizeof(NumberType));
// step 2: write array to output (with possible reordering)
if (is_little_endian != OutputIsLittleEndian)
if ((!is_bjdata and (is_little_endian != OutputIsLittleEndian)) or (is_bjdata and !is_little_endian))
{
// reverse byte order prior to conversion if necessary
std::reverse(vec.begin(), vec.end());
@ -1629,6 +1703,9 @@ class binary_writer
/// whether we can assume little endianness
const bool is_little_endian = little_endianness();
/// whether to write in bjdata format
bool is_bjdata = false;
/// the output
output_adapter_t<CharType> oa = nullptr;
};

View File

@ -3965,6 +3965,27 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
binary_writer<char>(o).write_ubjson(j, use_size, use_type);
}
static std::vector<std::uint8_t> to_bjdata(const basic_json& j,
const bool use_size = false,
const bool use_type = false)
{
std::vector<std::uint8_t> result;
to_bjdata(j, result, use_size, use_type);
return result;
}
static void to_bjdata(const basic_json& j, detail::output_adapter<std::uint8_t> o,
const bool use_size = false, const bool use_type = false)
{
binary_writer<std::uint8_t>(o, true).write_ubjson(j, use_size, use_type);
}
static void to_bjdata(const basic_json& j, detail::output_adapter<char> o,
const bool use_size = false, const bool use_type = false)
{
binary_writer<char>(o, true).write_ubjson(j, use_size, use_type);
}
/// @brief create a BSON serialization of a given JSON value
/// @sa https://json.nlohmann.me/api/basic_json/to_bson/
static std::vector<std::uint8_t> to_bson(const basic_json& j)
@ -4155,6 +4176,62 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return res ? result : basic_json(value_t::discarded);
}
/// @brief create a JSON value from an input in UBJSON format
/// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/
template<typename InputType>
JSON_HEDLEY_WARN_UNUSED_RESULT
static basic_json from_bjdata(InputType&& i,
const bool strict = true,
const bool allow_exceptions = true)
{
basic_json result;
detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);
auto ia = detail::input_adapter(std::forward<InputType>(i));
const bool res = binary_reader<decltype(ia)>(std::move(ia)).sax_parse(input_format_t::bjdata, &sdp, strict);
return res ? result : basic_json(value_t::discarded);
}
/// @brief create a JSON value from an input in BJData format
/// @sa https://github.com/NeuroJSON/bjdata/blob/master/Binary_JData_Specification.md
template<typename IteratorType>
JSON_HEDLEY_WARN_UNUSED_RESULT
static basic_json from_bjdata(IteratorType first, IteratorType last,
const bool strict = true,
const bool allow_exceptions = true)
{
basic_json result;
detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);
auto ia = detail::input_adapter(std::move(first), std::move(last));
const bool res = binary_reader<decltype(ia)>(std::move(ia)).sax_parse(input_format_t::bjdata, &sdp, strict);
return res ? result : basic_json(value_t::discarded);
}
template<typename T>
JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bjdata(ptr, ptr + len))
static basic_json from_bjdata(const T* ptr, std::size_t len,
const bool strict = true,
const bool allow_exceptions = true)
{
return from_bjdata(ptr, ptr + len, strict, allow_exceptions);
}
JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bjdata(ptr, ptr + len))
static basic_json from_bjdata(detail::span_input_adapter&& i,
const bool strict = true,
const bool allow_exceptions = true)
{
basic_json result;
detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);
auto ia = i.get();
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
const bool res = binary_reader<decltype(ia)>(std::move(ia)).sax_parse(input_format_t::bjdata, &sdp, strict);
return res ? result : basic_json(value_t::discarded);
}
/// @brief create a JSON value from an input in BSON format
/// @sa https://json.nlohmann.me/api/basic_json/from_bson/
template<typename InputType>

View File

@ -5433,7 +5433,7 @@ namespace nlohmann
namespace detail
{
/// the supported input formats
enum class input_format_t { json, cbor, msgpack, ubjson, bson };
enum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata };
////////////////////
// input adapters //
@ -8471,12 +8471,13 @@ class binary_reader
@return whether parsing was successful
*/
JSON_HEDLEY_NON_NULL(3)
bool sax_parse(const input_format_t format,
bool sax_parse(const input_format_t format_,
json_sax_t* sax_,
const bool strict = true,
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)
{
sax = sax_;
format = format_;
bool result = false;
switch (format)
@ -8494,6 +8495,7 @@ class binary_reader
break;
case input_format_t::ubjson:
case input_format_t::bjdata:
result = parse_ubjson_internal();
break;
@ -8505,7 +8507,7 @@ class binary_reader
// strict mode: next byte must be EOF
if (result && strict)
{
if (format == input_format_t::ubjson)
if (format == input_format_t::ubjson || format == input_format_t::bjdata)
{
get_ignore_noop();
}
@ -10220,7 +10222,7 @@ class binary_reader
get(); // TODO(niels): may we ignore N here?
}
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "value")))
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "value")))
{
return false;
}
@ -10230,52 +10232,141 @@ class binary_reader
case 'U':
{
std::uint8_t len{};
return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result);
return get_number(format, len) && get_string(format, len, result);
}
case 'i':
{
std::int8_t len{};
return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result);
return get_number(format, len) && get_string(format, len, result);
}
case 'I':
{
std::int16_t len{};
return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result);
return get_number(format, len) && get_string(format, len, result);
}
case 'l':
{
std::int32_t len{};
return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result);
return get_number(format, len) && get_string(format, len, result);
}
case 'L':
{
std::int64_t len{};
return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result);
return get_number(format, len) && get_string(format, len, result);
}
default:
if (format == input_format_t::bjdata)
{
switch (current)
{
case 'u':
{
uint16_t len;
return get_number(format, len) and get_string(format, len, result);
}
case 'm':
{
uint32_t len;
return get_number(format, len) and get_string(format, len, result);
}
case 'M':
{
uint64_t len;
return get_number(format, len) and get_string(format, len, result);
}
}
}
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::ubjson, concat("expected length type specification (U, i, I, l, L); last byte: 0x", last_token), "string"), nullptr));
exception_message(format, concat("expected length type specification (U, i, I, l, L); last byte: 0x", last_token), "string"), nullptr));
}
}
/*!
@param[out] dim an integer vector storing the ND array dimensions
@return whether array creation completed
*/
bool get_ubjson_ndarray_size(std::vector<size_t>& dim)
{
std::pair<std::size_t, char_int_type> size_and_type;
size_t dimlen;
bool is_optimized = get_ubjson_size_type(size_and_type);
if (size_and_type.first != string_t::npos)
{
if (size_and_type.second != 0)
{
if (size_and_type.second != 'N')
{
for (std::size_t i = 0; i < size_and_type.first; ++i)
{
if (JSON_HEDLEY_UNLIKELY(not get_ubjson_size_value(dimlen, size_and_type.second)))
{
return false;
}
else
{
dim.push_back(dimlen);
}
}
}
}
else
{
for (std::size_t i = 0; i < size_and_type.first; ++i)
{
if (JSON_HEDLEY_UNLIKELY(not get_ubjson_size_value(dimlen)))
{
return false;
}
else
{
dim.push_back(dimlen);
}
}
}
}
else
{
while (current != ']')
{
if (JSON_HEDLEY_UNLIKELY(not get_ubjson_size_value(dimlen)))
{
return false;
}
else
{
dim.push_back(dimlen);
}
get_ignore_noop();
}
}
return true;
}
/*!
@param[out] result determined size
@return whether size determination completed
*/
bool get_ubjson_size_value(std::size_t& result)
bool get_ubjson_size_value(std::size_t& result, int prefix = 0)
{
switch (get_ignore_noop())
if (prefix == 0)
{
prefix = get_ignore_noop();
}
switch (prefix)
{
case 'U':
{
std::uint8_t number{};
if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number)))
if (JSON_HEDLEY_UNLIKELY(!get_number(format, number)))
{
return false;
}
@ -10286,7 +10377,7 @@ class binary_reader
case 'i':
{
std::int8_t number{};
if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number)))
if (JSON_HEDLEY_UNLIKELY(!get_number(format, number)))
{
return false;
}
@ -10297,7 +10388,7 @@ class binary_reader
case 'I':
{
std::int16_t number{};
if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number)))
if (JSON_HEDLEY_UNLIKELY(!get_number(format, number)))
{
return false;
}
@ -10308,7 +10399,7 @@ class binary_reader
case 'l':
{
std::int32_t number{};
if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number)))
if (JSON_HEDLEY_UNLIKELY(!get_number(format, number)))
{
return false;
}
@ -10319,7 +10410,7 @@ class binary_reader
case 'L':
{
std::int64_t number{};
if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number)))
if (JSON_HEDLEY_UNLIKELY(!get_number(format, number)))
{
return false;
}
@ -10329,9 +10420,62 @@ class binary_reader
default:
{
if (format == input_format_t::bjdata)
{
switch (prefix)
{
case 'u':
{
uint16_t number;
if (JSON_HEDLEY_UNLIKELY(not get_number(format, number)))
{
return false;
}
result = static_cast<std::size_t>(number);
return true;
}
case 'm':
{
uint32_t number;
if (JSON_HEDLEY_UNLIKELY(not get_number(format, number)))
{
return false;
}
result = static_cast<std::size_t>(number);
return true;
}
case 'M':
{
uint64_t number;
if (JSON_HEDLEY_UNLIKELY(not get_number(format, number)))
{
return false;
}
result = static_cast<std::size_t>(number);
return true;
}
case '[':
{
std::vector<size_t> dim;
if (JSON_HEDLEY_UNLIKELY(not get_ubjson_ndarray_size(dim)))
{
return false;
}
else
{
result = 1;
for (std::size_t i = 0; i < dim.size(); ++i)
{
result *= dim.at(i);
}
}
return true;
}
}
}
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::ubjson, concat("expected length type specification (U, i, I, l, L) after '#'; last byte: 0x", last_token), "size"), nullptr));
exception_message(format, concat("expected length type specification (U, i, I, l, L) after '#'; last byte: 0x", last_token), "size"), nullptr));
}
}
}
@ -10356,7 +10500,7 @@ class binary_reader
if (current == '$')
{
result.second = get(); // must not ignore 'N', because 'N' maybe the type
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "type")))
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "type")))
{
return false;
}
@ -10364,13 +10508,13 @@ class binary_reader
get_ignore_noop();
if (JSON_HEDLEY_UNLIKELY(current != '#'))
{
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "value")))
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "value")))
{
return false;
}
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::ubjson, concat("expected '#' after type information; last byte: 0x", last_token), "size"), nullptr));
exception_message(format, concat("expected '#' after type information; last byte: 0x", last_token), "size"), nullptr));
}
return get_ubjson_size_value(result.first);
@ -10393,7 +10537,7 @@ class binary_reader
switch (prefix)
{
case std::char_traits<char_type>::eof(): // EOF
return unexpect_eof(input_format_t::ubjson, "value");
return unexpect_eof(format, "value");
case 'T': // true
return sax->boolean(true);
@ -10406,43 +10550,43 @@ class binary_reader
case 'U':
{
std::uint8_t number{};
return get_number(input_format_t::ubjson, number) && sax->number_unsigned(number);
return get_number(format, number) && sax->number_unsigned(number);
}
case 'i':
{
std::int8_t number{};
return get_number(input_format_t::ubjson, number) && sax->number_integer(number);
return get_number(format, number) && sax->number_integer(number);
}
case 'I':
{
std::int16_t number{};
return get_number(input_format_t::ubjson, number) && sax->number_integer(number);
return get_number(format, number) && sax->number_integer(number);
}
case 'l':
{
std::int32_t number{};
return get_number(input_format_t::ubjson, number) && sax->number_integer(number);
return get_number(format, number) && sax->number_integer(number);
}
case 'L':
{
std::int64_t number{};
return get_number(input_format_t::ubjson, number) && sax->number_integer(number);
return get_number(format, number) && sax->number_integer(number);
}
case 'd':
{
float number{};
return get_number(input_format_t::ubjson, number) && sax->number_float(static_cast<number_float_t>(number), "");
return get_number(format, number) && sax->number_float(static_cast<number_float_t>(number), "");
}
case 'D':
{
double number{};
return get_number(input_format_t::ubjson, number) && sax->number_float(static_cast<number_float_t>(number), "");
return get_number(format, number) && sax->number_float(static_cast<number_float_t>(number), "");
}
case 'H':
@ -10453,7 +10597,7 @@ class binary_reader
case 'C': // char
{
get();
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "char")))
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "char")))
{
return false;
}
@ -10461,7 +10605,7 @@ class binary_reader
{
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::ubjson, concat("byte after 'C' must be in range 0x00..0x7F; last byte: 0x", last_token), "char"), nullptr));
exception_message(format, concat("byte after 'C' must be in range 0x00..0x7F; last byte: 0x", last_token), "char"), nullptr));
}
string_t s(1, static_cast<typename string_t::value_type>(current));
return sax->string(s);
@ -10481,9 +10625,74 @@ class binary_reader
default: // anything else
{
if (format == input_format_t::bjdata)
{
switch (prefix)
{
case 'u':
{
uint16_t number;
return get_number(format, number) and sax->number_integer(number);
}
case 'm':
{
uint32_t number;
return get_number(format, number) and sax->number_integer(number);
}
case 'M':
{
uint64_t number;
return get_number(format, number) and sax->number_integer(number);
}
case 'h':
{
const int byte2 = get();
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "half")))
{
return false;
}
const int byte1 = get();
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "half")))
{
return false;
}
// code from RFC 7049, Appendix D, Figure 3:
// As half-precision floating-point numbers were only added
// to IEEE 754 in 2008, today's programming platforms often
// still only have limited support for them. It is very
// easy to include at least decoding support for them even
// without such support. An example of a small decoder for
// half-precision floating-point numbers in the C language
// is shown in Fig. 3.
const int half = (byte1 << 8) + byte2;
const double val = [&half]
{
const int exp = (half >> 10) & 0x1F;
const int mant = half & 0x3FF;
JSON_ASSERT(0 <= exp and exp <= 32);
JSON_ASSERT(0 <= mant and mant <= 1024);
switch (exp)
{
case 0:
return std::ldexp(mant, -24);
case 31:
return (mant == 0)
? std::numeric_limits<double>::infinity()
: std::numeric_limits<double>::quiet_NaN();
default:
return std::ldexp(mant + 1024, exp - 25);
}
}();
return sax->number_float((half & 0x8000) != 0
? static_cast<number_float_t>(-val)
: static_cast<number_float_t>(val), "");
}
}
}
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::ubjson, concat("invalid byte: 0x", last_token), "value"), nullptr));
exception_message(format, concat("invalid byte: 0x", last_token), "value"), nullptr));
}
}
}
@ -10643,7 +10852,7 @@ class binary_reader
for (std::size_t i = 0; i < size; ++i)
{
get();
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "number")))
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "number")))
{
return false;
}
@ -10662,7 +10871,7 @@ class binary_reader
if (JSON_HEDLEY_UNLIKELY(result_remainder != token_type::end_of_input))
{
return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read,
exception_message(input_format_t::ubjson, concat("invalid number text: ", number_lexer.get_token_string()), "high-precision number"), nullptr));
exception_message(format, concat("invalid number text: ", number_lexer.get_token_string()), "high-precision number"), nullptr));
}
switch (result_number)
@ -10689,7 +10898,7 @@ class binary_reader
case token_type::literal_or_value:
default:
return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read,
exception_message(input_format_t::ubjson, concat("invalid number text: ", number_lexer.get_token_string()), "high-precision number"), nullptr));
exception_message(format, concat("invalid number text: ", number_lexer.get_token_string()), "high-precision number"), nullptr));
}
}
@ -10753,7 +10962,8 @@ class binary_reader
}
// reverse byte order prior to conversion if necessary
if (is_little_endian != InputIsLittleEndian)
if ((is_little_endian != InputIsLittleEndian and format != input_format_t::bjdata) or
(is_little_endian == InputIsLittleEndian and format == input_format_t::bjdata))
{
vec[sizeof(NumberType) - i - 1] = static_cast<std::uint8_t>(current);
}
@ -10890,6 +11100,10 @@ class binary_reader
error_msg += "BSON";
break;
case input_format_t::bjdata:
error_msg += "BJData";
break;
case input_format_t::json: // LCOV_EXCL_LINE
default: // LCOV_EXCL_LINE
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
@ -10911,6 +11125,9 @@ class binary_reader
/// whether we can assume little endianness
const bool is_little_endian = little_endianness();
/// sax parser format
input_format_t format;
/// the SAX parser
json_sax_t* sax = nullptr;
};
@ -13606,9 +13823,10 @@ class binary_writer
@param[in] adapter output adapter to write to
*/
explicit binary_writer(output_adapter_t<CharType> adapter) : oa(std::move(adapter))
explicit binary_writer(output_adapter_t<CharType> adapter, const bool is_bjdata_ = false) : oa(std::move(adapter))
{
JSON_ASSERT(oa);
is_bjdata = is_bjdata_;
}
/*!
@ -14902,6 +15120,14 @@ class binary_writer
}
write_number(static_cast<std::int16_t>(n));
}
else if (is_bjdata and n <= static_cast<uint64_t>((std::numeric_limits<uint16_t>::max)()))
{
if (add_prefix)
{
oa->write_character(to_char_type('u')); // uint16 - bjdata only
}
write_number(static_cast<std::uint16_t>(n));
}
else if (n <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)()))
{
if (add_prefix)
@ -14910,6 +15136,14 @@ class binary_writer
}
write_number(static_cast<std::int32_t>(n));
}
else if (is_bjdata and n <= static_cast<uint64_t>((std::numeric_limits<uint32_t>::max)()))
{
if (add_prefix)
{
oa->write_character(to_char_type('m')); // uint32 - bjdata only
}
write_number(static_cast<std::uint32_t>(n));
}
else if (n <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))
{
if (add_prefix)
@ -14918,6 +15152,14 @@ class binary_writer
}
write_number(static_cast<std::int64_t>(n));
}
else if (is_bjdata and n <= static_cast<uint64_t>((std::numeric_limits<uint64_t>::max)()))
{
if (add_prefix)
{
oa->write_character(to_char_type('M')); // uint64 - bjdata only
}
write_number(static_cast<std::uint64_t>(n));
}
else
{
if (add_prefix)
@ -14965,6 +15207,14 @@ class binary_writer
}
write_number(static_cast<std::int16_t>(n));
}
else if (is_bjdata and (static_cast<std::int64_t>((std::numeric_limits<std::uint16_t>::min)()) <= n && n <= static_cast<std::int64_t>((std::numeric_limits<std::uint16_t>::max)())))
{
if (add_prefix)
{
oa->write_character(to_char_type('u')); // uint16 - bjdata only
}
write_number(static_cast<uint16_t>(n));
}
else if ((std::numeric_limits<std::int32_t>::min)() <= n && n <= (std::numeric_limits<std::int32_t>::max)())
{
if (add_prefix)
@ -14973,6 +15223,14 @@ class binary_writer
}
write_number(static_cast<std::int32_t>(n));
}
else if (is_bjdata and (static_cast<std::int64_t>((std::numeric_limits<std::uint32_t>::min)()) <= n && n <= static_cast<std::int64_t>((std::numeric_limits<std::uint32_t>::max)())))
{
if (add_prefix)
{
oa->write_character(to_char_type('m')); // uint32 - bjdata only
}
write_number(static_cast<uint32_t>(n));
}
else if ((std::numeric_limits<std::int64_t>::min)() <= n && n <= (std::numeric_limits<std::int64_t>::max)())
{
if (add_prefix)
@ -14981,6 +15239,14 @@ class binary_writer
}
write_number(static_cast<std::int64_t>(n));
}
else if (is_bjdata and (static_cast<std::uint64_t>((std::numeric_limits<std::uint64_t>::min)()) <= n && n <= static_cast<std::uint64_t>((std::numeric_limits<std::uint64_t>::max)())))
{
if (add_prefix)
{
oa->write_character(to_char_type('M')); // uint64 - bjdata only
}
write_number(static_cast<std::uint64_t>(n));
}
// LCOV_EXCL_START
else
{
@ -15026,14 +15292,26 @@ class binary_writer
{
return 'I';
}
if (is_bjdata and ((std::numeric_limits<std::uint16_t>::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits<std::uint16_t>::max)()))
{
return 'u';
}
if ((std::numeric_limits<std::int32_t>::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)())
{
return 'l';
}
if (is_bjdata and ((std::numeric_limits<std::uint32_t>::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits<std::uint32_t>::max)()))
{
return 'm';
}
if ((std::numeric_limits<std::int64_t>::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits<std::int64_t>::max)())
{
return 'L';
}
if (is_bjdata and ((std::numeric_limits<std::uint64_t>::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits<std::uint64_t>::max)()))
{
return 'M';
}
// anything else is treated as high-precision number
return 'H'; // LCOV_EXCL_LINE
}
@ -15052,14 +15330,26 @@ class binary_writer
{
return 'I';
}
if (is_bjdata and j.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::uint16_t>::max)()))
{
return 'u';
}
if (j.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)()))
{
return 'l';
}
if (is_bjdata and j.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::uint32_t>::max)()))
{
return 'm';
}
if (j.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))
{
return 'L';
}
if (is_bjdata and j.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::uint64_t>::max)()))
{
return 'M';
}
// anything else is treated as high-precision number
return 'H'; // LCOV_EXCL_LINE
}
@ -15107,6 +15397,7 @@ class binary_writer
@note This function needs to respect the system's endianness, because bytes
in CBOR, MessagePack, and UBJSON are stored in network order (big
endian) and therefore need reordering on little endian systems.
BJData is similar to UBJSON but uses little endian by default.
*/
template<typename NumberType, bool OutputIsLittleEndian = false>
void write_number(const NumberType n)
@ -15116,7 +15407,7 @@ class binary_writer
std::memcpy(vec.data(), &n, sizeof(NumberType));
// step 2: write array to output (with possible reordering)
if (is_little_endian != OutputIsLittleEndian)
if ((!is_bjdata and (is_little_endian != OutputIsLittleEndian)) or (is_bjdata and !is_little_endian))
{
// reverse byte order prior to conversion if necessary
std::reverse(vec.begin(), vec.end());
@ -15197,6 +15488,9 @@ class binary_writer
/// whether we can assume little endianness
const bool is_little_endian = little_endianness();
/// whether to write in bjdata format
bool is_bjdata = false;
/// the output
output_adapter_t<CharType> oa = nullptr;
};
@ -21413,6 +21707,27 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
binary_writer<char>(o).write_ubjson(j, use_size, use_type);
}
static std::vector<std::uint8_t> to_bjdata(const basic_json& j,
const bool use_size = false,
const bool use_type = false)
{
std::vector<std::uint8_t> result;
to_bjdata(j, result, use_size, use_type);
return result;
}
static void to_bjdata(const basic_json& j, detail::output_adapter<std::uint8_t> o,
const bool use_size = false, const bool use_type = false)
{
binary_writer<std::uint8_t>(o, true).write_ubjson(j, use_size, use_type);
}
static void to_bjdata(const basic_json& j, detail::output_adapter<char> o,
const bool use_size = false, const bool use_type = false)
{
binary_writer<char>(o, true).write_ubjson(j, use_size, use_type);
}
/// @brief create a BSON serialization of a given JSON value
/// @sa https://json.nlohmann.me/api/basic_json/to_bson/
static std::vector<std::uint8_t> to_bson(const basic_json& j)
@ -21603,6 +21918,62 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return res ? result : basic_json(value_t::discarded);
}
/// @brief create a JSON value from an input in UBJSON format
/// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/
template<typename InputType>
JSON_HEDLEY_WARN_UNUSED_RESULT
static basic_json from_bjdata(InputType&& i,
const bool strict = true,
const bool allow_exceptions = true)
{
basic_json result;
detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);
auto ia = detail::input_adapter(std::forward<InputType>(i));
const bool res = binary_reader<decltype(ia)>(std::move(ia)).sax_parse(input_format_t::bjdata, &sdp, strict);
return res ? result : basic_json(value_t::discarded);
}
/// @brief create a JSON value from an input in BJData format
/// @sa https://github.com/NeuroJSON/bjdata/blob/master/Binary_JData_Specification.md
template<typename IteratorType>
JSON_HEDLEY_WARN_UNUSED_RESULT
static basic_json from_bjdata(IteratorType first, IteratorType last,
const bool strict = true,
const bool allow_exceptions = true)
{
basic_json result;
detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);
auto ia = detail::input_adapter(std::move(first), std::move(last));
const bool res = binary_reader<decltype(ia)>(std::move(ia)).sax_parse(input_format_t::bjdata, &sdp, strict);
return res ? result : basic_json(value_t::discarded);
}
template<typename T>
JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bjdata(ptr, ptr + len))
static basic_json from_bjdata(const T* ptr, std::size_t len,
const bool strict = true,
const bool allow_exceptions = true)
{
return from_bjdata(ptr, ptr + len, strict, allow_exceptions);
}
JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bjdata(ptr, ptr + len))
static basic_json from_bjdata(detail::span_input_adapter&& i,
const bool strict = true,
const bool allow_exceptions = true)
{
basic_json result;
detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);
auto ia = i.get();
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
const bool res = binary_reader<decltype(ia)>(std::move(ia)).sax_parse(input_format_t::bjdata, &sdp, strict);
return res ? result : basic_json(value_t::discarded);
}
/// @brief create a JSON value from an input in BSON format
/// @sa https://json.nlohmann.me/api/basic_json/from_bson/
template<typename InputType>

View File

@ -0,0 +1,84 @@
/*
__ _____ _____ _____
__| | __| | | | JSON for Modern C++ (fuzz test support)
| | |__ | | | | | | version 3.10.5
|_____|_____|_____|_|___| https://github.com/nlohmann/json
This file implements a parser test suitable for fuzz testing. Given a byte
array data, it performs the following steps:
- j1 = from_bjdata(data)
- vec = to_bjdata(j1)
- j2 = from_bjdata(vec)
- assert(j1 == j2)
- vec2 = to_bjdata(j1, use_size = true, use_type = false)
- j3 = from_bjdata(vec2)
- assert(j1 == j3)
- vec3 = to_bjdata(j1, use_size = true, use_type = true)
- j4 = from_bjdata(vec3)
- assert(j1 == j4)
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
drivers.
Licensed under the MIT License <http://opensource.org/licenses/MIT>.
*/
#include <iostream>
#include <sstream>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
// see http://llvm.org/docs/LibFuzzer.html
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
{
try
{
// step 1: parse input
std::vector<uint8_t> vec1(data, data + size);
json j1 = json::from_bjdata(vec1);
try
{
// step 2.1: round trip without adding size annotations to container types
std::vector<uint8_t> vec2 = json::to_bjdata(j1, false, false);
// step 2.2: round trip with adding size annotations but without adding type annonations to container types
std::vector<uint8_t> vec3 = json::to_bjdata(j1, true, false);
// step 2.3: round trip with adding size as well as type annotations to container types
std::vector<uint8_t> vec4 = json::to_bjdata(j1, true, true);
// parse serialization
json j2 = json::from_bjdata(vec2);
json j3 = json::from_bjdata(vec3);
json j4 = json::from_bjdata(vec4);
// serializations must match
assert(json::to_bjdata(j2, false, false) == vec2);
assert(json::to_bjdata(j3, true, false) == vec3);
assert(json::to_bjdata(j4, true, true) == vec4);
}
catch (const json::parse_error&)
{
// parsing a BJData serialization must not fail
assert(false);
}
}
catch (const json::parse_error&)
{
// parse errors are ok, because input may be random bytes
}
catch (const json::type_error&)
{
// type errors can occur during parsing, too
}
catch (const json::out_of_range&)
{
// out of range errors may happen if provided sizes are excessive
}
// return 0 - non-zero return values are reserved for future use
return 0;
}

2981
test/src/unit-bjdata.cpp Normal file

File diff suppressed because it is too large Load Diff