Merge pull request #2908 from nlohmann/issue2863

Fix binary subtypes
pull/2936/head
Niels Lohmann 2021-08-14 14:27:58 +02:00 committed by GitHub
commit 1300d2e4c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 378 additions and 116 deletions

View File

@ -43,7 +43,7 @@ jobs:
run: cmake -S . -B build -G "Visual Studio 15 2017" -A ${{ matrix.architecture }} -DJSON_BuildTests=On -DCMAKE_CXX_FLAGS="/W4 /WX"
if: matrix.build_type == 'Release' && matrix.architecture != 'x64'
- name: cmake
run: cmake -S . -B build -G "Visual Studio 15 2017" -A ${{ matrix.architecture }} -DJSON_BuildTests=On -DJSON_FastTests=ON -DCMAKE_CXX_FLAGS="/W4 /WX"
run: cmake -S . -B build -G "Visual Studio 15 2017" -A ${{ matrix.architecture }} -DJSON_BuildTests=On -DJSON_FastTests=ON -DCMAKE_EXE_LINKER_FLAGS="/STACK:4000000" -DCMAKE_CXX_FLAGS="/W4 /WX"
if: matrix.build_type == 'Debug'
- name: build
run: cmake --build build --config ${{ matrix.build_type }} --parallel 10
@ -75,7 +75,7 @@ jobs:
run: cmake -S . -B build -G "Visual Studio 16 2019" -A ${{ matrix.architecture }} -DJSON_BuildTests=On -DCMAKE_CXX_FLAGS="/W4 /WX"
if: matrix.build_type == 'Release'
- name: cmake
run: cmake -S . -B build -G "Visual Studio 16 2019" -A ${{ matrix.architecture }} -DJSON_BuildTests=On -DJSON_FastTests=ON -DCMAKE_CXX_FLAGS="/W4 /WX"
run: cmake -S . -B build -G "Visual Studio 16 2019" -A ${{ matrix.architecture }} -DJSON_BuildTests=On -DJSON_FastTests=ON -DCMAKE_EXE_LINKER_FLAGS="/STACK:4000000" -DCMAKE_CXX_FLAGS="/W4 /WX"
if: matrix.build_type == 'Debug'
- name: build
run: cmake --build build --config ${{ matrix.build_type }} --parallel 10
@ -88,7 +88,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: cmake
run: cmake -S . -B build -G "Visual Studio 16 2019" -DJSON_BuildTests=On -DCMAKE_CXX_FLAGS="/permissive- /std:c++latest /utf-8 /W4 /WX"
run: cmake -S . -B build -G "Visual Studio 16 2019" -DJSON_BuildTests=On -DCMAKE_CXX_FLAGS="/permissive- /std:c++latest /utf-8 /W4 /WX" -DCMAKE_EXE_LINKER_FLAGS="/STACK:4000000"
- name: build
run: cmake --build build --config Release --parallel 10
- name: test

View File

@ -64,4 +64,4 @@ type `#!cpp binary_t*` must be dereferenced.
## Version history
- Added in version 3.8.0.
- Added in version 3.8.0. Changed type of subtype to `std::uint64_t` in version 3.9.2.

View File

@ -4,7 +4,8 @@
enum class cbor_tag_handler_t
{
error,
ignore
ignore,
store
};
```
@ -16,6 +17,9 @@ error
ignore
: ignore tags
store
: store tagged values as binary container with subtype (for bytes 0xd8..0xdb)
## Version history
- Added in version 3.9.0.
- Added in version 3.9.0. Added value `store` in 3.9.2.

View File

@ -55,6 +55,8 @@ binary | *size*: 256..65535 | byte string (2 by
binary | *size*: 65536..4294967295 | byte string (4 bytes follow) | 0x5A
binary | *size*: 4294967296..18446744073709551615 | byte string (8 bytes follow) | 0x5B
Binary values with subtype are mapped to tagged values (0xD8..0xDB) depending on the subtype, followed by a byte string,
see "binary" cells in the table above.
!!! success "Complete mapping"
@ -162,7 +164,7 @@ Double-Precision Float | number_float | 0xFB
!!! warning "Tagged items"
Tagged items will throw a parse error by default. However, they can be ignored by passing `cbor_tag_handler_t::ignore` to function `from_cbor`.
Tagged items will throw a parse error by default. They can be ignored by passing `cbor_tag_handler_t::ignore` to function `from_cbor`. They can be stored by passing `cbor_tag_handler_t::store` to function `from_cbor`.
??? example

View File

@ -25,7 +25,7 @@ to efficiently encode JSON values to byte vectors and to decode such vectors.
| Format | Binary values | Binary subtypes |
| ----------- | ------------- | --------------- |
| BSON | supported | supported |
| CBOR | supported | not supported |
| CBOR | supported | supported |
| MessagePack | supported | supported |
| UBJSON | not supported | not supported |

View File

@ -9,10 +9,10 @@ JSON itself does not have a binary value. As such, binary values are an extensio
```plantuml
class json::binary_t {
-- setters --
+void set_subtype(std::uint8_t subtype)
+void set_subtype(std::uint64_t subtype)
+void clear_subtype()
-- getters --
+std::uint8_t subtype() const
+std::uint64_t subtype() const
+bool has_subtype() const
}
@ -68,7 +68,7 @@ j.get_binary().has_subtype(); // returns true
j.get_binary().size(); // returns 4
```
For convencience, binary JSON values can be constructed via `json::binary`:
For convenience, binary JSON values can be constructed via `json::binary`:
```cpp
auto j2 = json::binary({0xCA, 0xFE, 0xBA, 0xBE}, 23);
@ -76,6 +76,7 @@ auto j3 = json::binary({0xCA, 0xFE, 0xBA, 0xBE});
j2 == j; // returns true
j3.get_binary().has_subtype(); // returns false
j3.get_binary().subtype(); // returns std::uint64_t(-1) as j3 has no subtype
```
@ -184,7 +185,7 @@ JSON does not have a binary type, and this library does not introduce a new type
0xCA 0xFE 0xBA 0xBE // content
```
Note that the subtype is serialized as tag. However, parsing tagged values yield a parse error unless `json::cbor_tag_handler_t::ignore` is passed to `json::from_cbor`.
Note that the subtype is serialized as tag. However, parsing tagged values yield a parse error unless `json::cbor_tag_handler_t::ignore` or `json::cbor_tag_handler_t::store` is passed to `json::from_cbor`.
```json
{

View File

@ -1,6 +1,6 @@
#pragma once
#include <cstdint> // uint8_t
#include <cstdint> // uint8_t, uint64_t
#include <tuple> // tie
#include <utility> // move
@ -18,7 +18,7 @@ order to override the binary type.
@tparam BinaryType container to store bytes (`std::vector<std::uint8_t>` by
default)
@since version 3.8.0
@since version 3.8.0; changed type of subtypes to std::uint64_t in 3.9.2.
*/
template<typename BinaryType>
class byte_container_with_subtype : public BinaryType
@ -26,6 +26,8 @@ class byte_container_with_subtype : public BinaryType
public:
/// the type of the underlying container
using container_type = BinaryType;
/// the type of the subtype
using subtype_type = std::uint64_t;
byte_container_with_subtype() noexcept(noexcept(container_type()))
: container_type()
@ -39,13 +41,13 @@ class byte_container_with_subtype : public BinaryType
: container_type(std::move(b))
{}
byte_container_with_subtype(const container_type& b, std::uint8_t subtype_) noexcept(noexcept(container_type(b)))
byte_container_with_subtype(const container_type& b, subtype_type subtype_) noexcept(noexcept(container_type(b)))
: container_type(b)
, m_subtype(subtype_)
, m_has_subtype(true)
{}
byte_container_with_subtype(container_type&& b, std::uint8_t subtype_) noexcept(noexcept(container_type(std::move(b))))
byte_container_with_subtype(container_type&& b, subtype_type subtype_) noexcept(noexcept(container_type(std::move(b))))
: container_type(std::move(b))
, m_subtype(subtype_)
, m_has_subtype(true)
@ -80,7 +82,7 @@ class byte_container_with_subtype : public BinaryType
@since version 3.8.0
*/
void set_subtype(std::uint8_t subtype_) noexcept
void set_subtype(subtype_type subtype_) noexcept
{
m_subtype = subtype_;
m_has_subtype = true;
@ -90,7 +92,7 @@ class byte_container_with_subtype : public BinaryType
@brief return the binary subtype
Returns the numerical subtype of the value if it has a subtype. If it does
not have a subtype, this function will return size_t(-1) as a sentinel
not have a subtype, this function will return subtype_type(-1) as a sentinel
value.
@return the numerical subtype of the binary value
@ -105,11 +107,12 @@ class byte_container_with_subtype : public BinaryType
@sa see @ref has_subtype() -- returns whether or not the binary value has a
subtype
@since version 3.8.0
@since version 3.8.0; fixed return value to properly return
subtype_type(-1) as documented in version 3.9.2
*/
constexpr std::uint8_t subtype() const noexcept
constexpr subtype_type subtype() const noexcept
{
return m_subtype;
return m_has_subtype ? m_subtype : subtype_type(-1);
}
/*!
@ -159,7 +162,7 @@ class byte_container_with_subtype : public BinaryType
}
private:
std::uint8_t m_subtype = 0;
subtype_type m_subtype = 0;
bool m_has_subtype = false;
};

View File

@ -103,7 +103,7 @@ std::size_t hash(const BasicJsonType& j)
auto seed = combine(type, j.get_binary().size());
const auto h = std::hash<bool> {}(j.get_binary().has_subtype());
seed = combine(seed, h);
seed = combine(seed, j.get_binary().subtype());
seed = combine(seed, static_cast<std::size_t>(j.get_binary().subtype()));
for (const auto byte : j.get_binary())
{
seed = combine(seed, std::hash<std::uint8_t> {}(byte));

View File

@ -30,8 +30,9 @@ namespace detail
/// how to treat CBOR tags
enum class cbor_tag_handler_t
{
error, ///< throw a parse_error exception in case of a tag
ignore ///< ignore tags
error, ///< throw a parse_error exception in case of a tag
ignore, ///< ignore tags
store ///< store tags as binary type
};
/*!
@ -723,30 +724,31 @@ class binary_reader
case cbor_tag_handler_t::ignore:
{
// ignore binary subtype
switch (current)
{
case 0xD8:
{
std::uint8_t len{};
get_number(input_format_t::cbor, len);
std::uint8_t subtype_to_ignore{};
get_number(input_format_t::cbor, subtype_to_ignore);
break;
}
case 0xD9:
{
std::uint16_t len{};
get_number(input_format_t::cbor, len);
std::uint16_t subtype_to_ignore{};
get_number(input_format_t::cbor, subtype_to_ignore);
break;
}
case 0xDA:
{
std::uint32_t len{};
get_number(input_format_t::cbor, len);
std::uint32_t subtype_to_ignore{};
get_number(input_format_t::cbor, subtype_to_ignore);
break;
}
case 0xDB:
{
std::uint64_t len{};
get_number(input_format_t::cbor, len);
std::uint64_t subtype_to_ignore{};
get_number(input_format_t::cbor, subtype_to_ignore);
break;
}
default:
@ -755,6 +757,47 @@ class binary_reader
return parse_cbor_internal(true, tag_handler);
}
case cbor_tag_handler_t::store:
{
binary_t b;
// use binary subtype and store in binary container
switch (current)
{
case 0xD8:
{
std::uint8_t subtype{};
get_number(input_format_t::cbor, subtype);
b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype));
break;
}
case 0xD9:
{
std::uint16_t subtype{};
get_number(input_format_t::cbor, subtype);
b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype));
break;
}
case 0xDA:
{
std::uint32_t subtype{};
get_number(input_format_t::cbor, subtype);
b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype));
break;
}
case 0xDB:
{
std::uint64_t subtype{};
get_number(input_format_t::cbor, subtype);
b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype));
break;
}
default:
return parse_cbor_internal(true, tag_handler);
}
get();
return get_cbor_binary(b) && sax->binary(b);
}
default: // LCOV_EXCL_LINE
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
return false; // LCOV_EXCL_LINE

View File

@ -23,7 +23,7 @@ namespace detail
// parser //
////////////
enum class parse_event_t : uint8_t
enum class parse_event_t : std::uint8_t
{
/// the parser read `{` and started to process a JSON object
object_start,

View File

@ -291,8 +291,26 @@ class binary_writer
{
if (j.m_value.binary->has_subtype())
{
write_number(static_cast<std::uint8_t>(0xd8));
write_number(j.m_value.binary->subtype());
if (j.m_value.binary->subtype() <= (std::numeric_limits<std::uint8_t>::max)())
{
write_number(static_cast<std::uint8_t>(0xd8));
write_number(static_cast<std::uint8_t>(j.m_value.binary->subtype()));
}
else if (j.m_value.binary->subtype() <= (std::numeric_limits<std::uint16_t>::max)())
{
write_number(static_cast<std::uint8_t>(0xd9));
write_number(static_cast<std::uint16_t>(j.m_value.binary->subtype()));
}
else if (j.m_value.binary->subtype() <= (std::numeric_limits<std::uint32_t>::max)())
{
write_number(static_cast<std::uint8_t>(0xda));
write_number(static_cast<std::uint32_t>(j.m_value.binary->subtype()));
}
else if (j.m_value.binary->subtype() <= (std::numeric_limits<std::uint64_t>::max)())
{
write_number(static_cast<std::uint8_t>(0xdb));
write_number(static_cast<std::uint64_t>(j.m_value.binary->subtype()));
}
}
// step 1: write control byte and the binary array size
@ -1109,7 +1127,7 @@ class binary_writer
write_bson_entry_header(name, 0x05);
write_number<std::int32_t, true>(static_cast<std::int32_t>(value.size()));
write_number(value.has_subtype() ? value.subtype() : std::uint8_t(0x00));
write_number(value.has_subtype() ? static_cast<std::uint8_t>(value.subtype()) : std::uint8_t(0x00));
oa->write_characters(reinterpret_cast<const CharType*>(value.data()), value.size());
}

View File

@ -389,7 +389,7 @@ class serializer
for (std::size_t i = 0; i < s.size(); ++i)
{
const auto byte = static_cast<uint8_t>(s[i]);
const auto byte = static_cast<std::uint8_t>(s[i]);
switch (decode(state, codepoint, byte))
{
@ -674,6 +674,7 @@ class serializer
@tparam NumberType either @a number_integer_t or @a number_unsigned_t
*/
template < typename NumberType, detail::enable_if_t <
std::is_integral<NumberType>::value ||
std::is_same<NumberType, number_unsigned_t>::value ||
std::is_same<NumberType, number_integer_t>::value ||
std::is_same<NumberType, binary_char_t>::value,
@ -706,7 +707,7 @@ class serializer
// use a pointer to fill the buffer
auto buffer_ptr = number_buffer.begin(); // NOLINT(llvm-qualified-auto,readability-qualified-auto,cppcoreguidelines-pro-type-vararg,hicpp-vararg)
const bool is_negative = std::is_same<NumberType, number_integer_t>::value && !(x >= 0); // see issue #755
const bool is_negative = std::is_signed<NumberType>::value && !(x >= 0); // see issue #755
number_unsigned_t abs_value;
unsigned int n_chars{};

View File

@ -902,8 +902,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
#### Notes on subtypes
- CBOR
- Binary values are represented as byte strings. No subtypes are
supported and will be ignored when CBOR is written.
- Binary values are represented as byte strings. Subtypes are serialized
as tagged values.
- MessagePack
- If a subtype is given and the binary array contains exactly 1, 2, 4, 8,
or 16 elements, the fixext family (fixext1, fixext2, fixext4, fixext8)
@ -1512,7 +1512,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
@ref number_float_t, and all convertible number types such as `int`,
`size_t`, `int64_t`, `float` or `double` can be used.
- **boolean**: @ref boolean_t / `bool` can be used.
- **binary**: @ref binary_t / `std::vector<uint8_t>` may be used,
- **binary**: @ref binary_t / `std::vector<std::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
@ -1832,7 +1832,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
@since version 3.8.0
*/
JSON_HEDLEY_WARN_UNUSED_RESULT
static basic_json binary(const typename binary_t::container_type& init, std::uint8_t subtype)
static basic_json binary(const typename binary_t::container_type& init, typename binary_t::subtype_type subtype)
{
auto res = basic_json();
res.m_type = value_t::binary;
@ -1850,9 +1850,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return res;
}
/// @copydoc binary(const typename binary_t::container_type&, std::uint8_t)
/// @copydoc binary(const typename binary_t::container_type&, typename binary_t::subtype_type)
JSON_HEDLEY_WARN_UNUSED_RESULT
static basic_json binary(typename binary_t::container_type&& init, std::uint8_t subtype)
static basic_json binary(typename binary_t::container_type&& init, typename binary_t::subtype_type subtype)
{
auto res = basic_json();
res.m_type = value_t::binary;
@ -7248,6 +7248,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
binary | *size*: 65536..4294967295 | byte string (4 bytes follow) | 0x5A
binary | *size*: 4294967296..18446744073709551615 | byte string (8 bytes follow) | 0x5B
Binary values with subtype are mapped to tagged values (0xD8..0xDB)
depending on the subtype, followed by a byte string, see "binary" cells
in the table above.
@note The mapping is **complete** in the sense that any JSON value type
can be converted to a CBOR value.
@ -7288,16 +7292,16 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
@since version 2.0.9; compact representation of floating-point numbers
since version 3.8.0
*/
static std::vector<uint8_t> to_cbor(const basic_json& j)
static std::vector<std::uint8_t> to_cbor(const basic_json& j)
{
std::vector<uint8_t> result;
std::vector<std::uint8_t> result;
to_cbor(j, result);
return result;
}
static void to_cbor(const basic_json& j, detail::output_adapter<uint8_t> o)
static void to_cbor(const basic_json& j, detail::output_adapter<std::uint8_t> o)
{
binary_writer<uint8_t>(o).write_cbor(j);
binary_writer<std::uint8_t>(o).write_cbor(j);
}
static void to_cbor(const basic_json& j, detail::output_adapter<char> o)
@ -7383,16 +7387,16 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
@since version 2.0.9
*/
static std::vector<uint8_t> to_msgpack(const basic_json& j)
static std::vector<std::uint8_t> to_msgpack(const basic_json& j)
{
std::vector<uint8_t> result;
std::vector<std::uint8_t> result;
to_msgpack(j, result);
return result;
}
static void to_msgpack(const basic_json& j, detail::output_adapter<uint8_t> o)
static void to_msgpack(const basic_json& j, detail::output_adapter<std::uint8_t> o)
{
binary_writer<uint8_t>(o).write_msgpack(j);
binary_writer<std::uint8_t>(o).write_msgpack(j);
}
static void to_msgpack(const basic_json& j, detail::output_adapter<char> o)
@ -7486,19 +7490,19 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
@since version 3.1.0
*/
static std::vector<uint8_t> to_ubjson(const basic_json& j,
const bool use_size = false,
const bool use_type = false)
static std::vector<std::uint8_t> to_ubjson(const basic_json& j,
const bool use_size = false,
const bool use_type = false)
{
std::vector<uint8_t> result;
std::vector<std::uint8_t> result;
to_ubjson(j, result, use_size, use_type);
return result;
}
static void to_ubjson(const basic_json& j, detail::output_adapter<uint8_t> o,
static void to_ubjson(const basic_json& j, detail::output_adapter<std::uint8_t> o,
const bool use_size = false, const bool use_type = false)
{
binary_writer<uint8_t>(o).write_ubjson(j, use_size, use_type);
binary_writer<std::uint8_t>(o).write_ubjson(j, use_size, use_type);
}
static void to_ubjson(const basic_json& j, detail::output_adapter<char> o,
@ -7564,9 +7568,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
@sa see @ref to_cbor(const basic_json&) for the related CBOR format
@sa see @ref to_msgpack(const basic_json&) for the related MessagePack format
*/
static std::vector<uint8_t> to_bson(const basic_json& j)
static std::vector<std::uint8_t> to_bson(const basic_json& j)
{
std::vector<uint8_t> result;
std::vector<std::uint8_t> result;
to_bson(j, result);
return result;
}
@ -7579,13 +7583,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
@pre The input `j` shall be an object: `j.is_object() == true`
@sa see @ref to_bson(const basic_json&)
*/
static void to_bson(const basic_json& j, detail::output_adapter<uint8_t> o)
static void to_bson(const basic_json& j, detail::output_adapter<std::uint8_t> o)
{
binary_writer<uint8_t>(o).write_bson(j);
binary_writer<std::uint8_t>(o).write_bson(j);
}
/*!
@copydoc to_bson(const basic_json&, detail::output_adapter<uint8_t>)
@copydoc to_bson(const basic_json&, detail::output_adapter<std::uint8_t>)
*/
static void to_bson(const basic_json& j, detail::output_adapter<char> o)
{

View File

@ -4985,7 +4985,7 @@ struct adl_serializer
// #include <nlohmann/byte_container_with_subtype.hpp>
#include <cstdint> // uint8_t
#include <cstdint> // uint8_t, uint64_t
#include <tuple> // tie
#include <utility> // move
@ -5003,7 +5003,7 @@ order to override the binary type.
@tparam BinaryType container to store bytes (`std::vector<std::uint8_t>` by
default)
@since version 3.8.0
@since version 3.8.0; changed type of subtypes to std::uint64_t in 3.9.2.
*/
template<typename BinaryType>
class byte_container_with_subtype : public BinaryType
@ -5011,6 +5011,8 @@ class byte_container_with_subtype : public BinaryType
public:
/// the type of the underlying container
using container_type = BinaryType;
/// the type of the subtype
using subtype_type = std::uint64_t;
byte_container_with_subtype() noexcept(noexcept(container_type()))
: container_type()
@ -5024,13 +5026,13 @@ class byte_container_with_subtype : public BinaryType
: container_type(std::move(b))
{}
byte_container_with_subtype(const container_type& b, std::uint8_t subtype_) noexcept(noexcept(container_type(b)))
byte_container_with_subtype(const container_type& b, subtype_type subtype_) noexcept(noexcept(container_type(b)))
: container_type(b)
, m_subtype(subtype_)
, m_has_subtype(true)
{}
byte_container_with_subtype(container_type&& b, std::uint8_t subtype_) noexcept(noexcept(container_type(std::move(b))))
byte_container_with_subtype(container_type&& b, subtype_type subtype_) noexcept(noexcept(container_type(std::move(b))))
: container_type(std::move(b))
, m_subtype(subtype_)
, m_has_subtype(true)
@ -5065,7 +5067,7 @@ class byte_container_with_subtype : public BinaryType
@since version 3.8.0
*/
void set_subtype(std::uint8_t subtype_) noexcept
void set_subtype(subtype_type subtype_) noexcept
{
m_subtype = subtype_;
m_has_subtype = true;
@ -5075,7 +5077,7 @@ class byte_container_with_subtype : public BinaryType
@brief return the binary subtype
Returns the numerical subtype of the value if it has a subtype. If it does
not have a subtype, this function will return size_t(-1) as a sentinel
not have a subtype, this function will return subtype_type(-1) as a sentinel
value.
@return the numerical subtype of the binary value
@ -5090,11 +5092,12 @@ class byte_container_with_subtype : public BinaryType
@sa see @ref has_subtype() -- returns whether or not the binary value has a
subtype
@since version 3.8.0
@since version 3.8.0; fixed return value to properly return
subtype_type(-1) as documented in version 3.9.2
*/
constexpr std::uint8_t subtype() const noexcept
constexpr subtype_type subtype() const noexcept
{
return m_subtype;
return m_has_subtype ? m_subtype : subtype_type(-1);
}
/*!
@ -5144,7 +5147,7 @@ class byte_container_with_subtype : public BinaryType
}
private:
std::uint8_t m_subtype = 0;
subtype_type m_subtype = 0;
bool m_has_subtype = false;
};
@ -5263,7 +5266,7 @@ std::size_t hash(const BasicJsonType& j)
auto seed = combine(type, j.get_binary().size());
const auto h = std::hash<bool> {}(j.get_binary().has_subtype());
seed = combine(seed, h);
seed = combine(seed, j.get_binary().subtype());
seed = combine(seed, static_cast<std::size_t>(j.get_binary().subtype()));
for (const auto byte : j.get_binary())
{
seed = combine(seed, std::hash<std::uint8_t> {}(byte));
@ -8296,8 +8299,9 @@ namespace detail
/// how to treat CBOR tags
enum class cbor_tag_handler_t
{
error, ///< throw a parse_error exception in case of a tag
ignore ///< ignore tags
error, ///< throw a parse_error exception in case of a tag
ignore, ///< ignore tags
store ///< store tags as binary type
};
/*!
@ -8989,30 +8993,31 @@ class binary_reader
case cbor_tag_handler_t::ignore:
{
// ignore binary subtype
switch (current)
{
case 0xD8:
{
std::uint8_t len{};
get_number(input_format_t::cbor, len);
std::uint8_t subtype_to_ignore{};
get_number(input_format_t::cbor, subtype_to_ignore);
break;
}
case 0xD9:
{
std::uint16_t len{};
get_number(input_format_t::cbor, len);
std::uint16_t subtype_to_ignore{};
get_number(input_format_t::cbor, subtype_to_ignore);
break;
}
case 0xDA:
{
std::uint32_t len{};
get_number(input_format_t::cbor, len);
std::uint32_t subtype_to_ignore{};
get_number(input_format_t::cbor, subtype_to_ignore);
break;
}
case 0xDB:
{
std::uint64_t len{};
get_number(input_format_t::cbor, len);
std::uint64_t subtype_to_ignore{};
get_number(input_format_t::cbor, subtype_to_ignore);
break;
}
default:
@ -9021,6 +9026,47 @@ class binary_reader
return parse_cbor_internal(true, tag_handler);
}
case cbor_tag_handler_t::store:
{
binary_t b;
// use binary subtype and store in binary container
switch (current)
{
case 0xD8:
{
std::uint8_t subtype{};
get_number(input_format_t::cbor, subtype);
b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype));
break;
}
case 0xD9:
{
std::uint16_t subtype{};
get_number(input_format_t::cbor, subtype);
b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype));
break;
}
case 0xDA:
{
std::uint32_t subtype{};
get_number(input_format_t::cbor, subtype);
b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype));
break;
}
case 0xDB:
{
std::uint64_t subtype{};
get_number(input_format_t::cbor, subtype);
b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype));
break;
}
default:
return parse_cbor_internal(true, tag_handler);
}
get();
return get_cbor_binary(b) && sax->binary(b);
}
default: // LCOV_EXCL_LINE
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
return false; // LCOV_EXCL_LINE
@ -10783,7 +10829,7 @@ namespace detail
// parser //
////////////
enum class parse_event_t : uint8_t
enum class parse_event_t : std::uint8_t
{
/// the parser read `{` and started to process a JSON object
object_start,
@ -13792,8 +13838,26 @@ class binary_writer
{
if (j.m_value.binary->has_subtype())
{
write_number(static_cast<std::uint8_t>(0xd8));
write_number(j.m_value.binary->subtype());
if (j.m_value.binary->subtype() <= (std::numeric_limits<std::uint8_t>::max)())
{
write_number(static_cast<std::uint8_t>(0xd8));
write_number(static_cast<std::uint8_t>(j.m_value.binary->subtype()));
}
else if (j.m_value.binary->subtype() <= (std::numeric_limits<std::uint16_t>::max)())
{
write_number(static_cast<std::uint8_t>(0xd9));
write_number(static_cast<std::uint16_t>(j.m_value.binary->subtype()));
}
else if (j.m_value.binary->subtype() <= (std::numeric_limits<std::uint32_t>::max)())
{
write_number(static_cast<std::uint8_t>(0xda));
write_number(static_cast<std::uint32_t>(j.m_value.binary->subtype()));
}
else if (j.m_value.binary->subtype() <= (std::numeric_limits<std::uint64_t>::max)())
{
write_number(static_cast<std::uint8_t>(0xdb));
write_number(static_cast<std::uint64_t>(j.m_value.binary->subtype()));
}
}
// step 1: write control byte and the binary array size
@ -14610,7 +14674,7 @@ class binary_writer
write_bson_entry_header(name, 0x05);
write_number<std::int32_t, true>(static_cast<std::int32_t>(value.size()));
write_number(value.has_subtype() ? value.subtype() : std::uint8_t(0x00));
write_number(value.has_subtype() ? static_cast<std::uint8_t>(value.subtype()) : std::uint8_t(0x00));
oa->write_characters(reinterpret_cast<const CharType*>(value.data()), value.size());
}
@ -16629,7 +16693,7 @@ class serializer
for (std::size_t i = 0; i < s.size(); ++i)
{
const auto byte = static_cast<uint8_t>(s[i]);
const auto byte = static_cast<std::uint8_t>(s[i]);
switch (decode(state, codepoint, byte))
{
@ -16914,6 +16978,7 @@ class serializer
@tparam NumberType either @a number_integer_t or @a number_unsigned_t
*/
template < typename NumberType, detail::enable_if_t <
std::is_integral<NumberType>::value ||
std::is_same<NumberType, number_unsigned_t>::value ||
std::is_same<NumberType, number_integer_t>::value ||
std::is_same<NumberType, binary_char_t>::value,
@ -16946,7 +17011,7 @@ class serializer
// use a pointer to fill the buffer
auto buffer_ptr = number_buffer.begin(); // NOLINT(llvm-qualified-auto,readability-qualified-auto,cppcoreguidelines-pro-type-vararg,hicpp-vararg)
const bool is_negative = std::is_same<NumberType, number_integer_t>::value && !(x >= 0); // see issue #755
const bool is_negative = std::is_signed<NumberType>::value && !(x >= 0); // see issue #755
number_unsigned_t abs_value;
unsigned int n_chars{};
@ -18217,8 +18282,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
#### Notes on subtypes
- CBOR
- Binary values are represented as byte strings. No subtypes are
supported and will be ignored when CBOR is written.
- Binary values are represented as byte strings. Subtypes are serialized
as tagged values.
- MessagePack
- If a subtype is given and the binary array contains exactly 1, 2, 4, 8,
or 16 elements, the fixext family (fixext1, fixext2, fixext4, fixext8)
@ -18827,7 +18892,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
@ref number_float_t, and all convertible number types such as `int`,
`size_t`, `int64_t`, `float` or `double` can be used.
- **boolean**: @ref boolean_t / `bool` can be used.
- **binary**: @ref binary_t / `std::vector<uint8_t>` may be used,
- **binary**: @ref binary_t / `std::vector<std::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
@ -19147,7 +19212,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
@since version 3.8.0
*/
JSON_HEDLEY_WARN_UNUSED_RESULT
static basic_json binary(const typename binary_t::container_type& init, std::uint8_t subtype)
static basic_json binary(const typename binary_t::container_type& init, typename binary_t::subtype_type subtype)
{
auto res = basic_json();
res.m_type = value_t::binary;
@ -19165,9 +19230,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return res;
}
/// @copydoc binary(const typename binary_t::container_type&, std::uint8_t)
/// @copydoc binary(const typename binary_t::container_type&, typename binary_t::subtype_type)
JSON_HEDLEY_WARN_UNUSED_RESULT
static basic_json binary(typename binary_t::container_type&& init, std::uint8_t subtype)
static basic_json binary(typename binary_t::container_type&& init, typename binary_t::subtype_type subtype)
{
auto res = basic_json();
res.m_type = value_t::binary;
@ -24563,6 +24628,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
binary | *size*: 65536..4294967295 | byte string (4 bytes follow) | 0x5A
binary | *size*: 4294967296..18446744073709551615 | byte string (8 bytes follow) | 0x5B
Binary values with subtype are mapped to tagged values (0xD8..0xDB)
depending on the subtype, followed by a byte string, see "binary" cells
in the table above.
@note The mapping is **complete** in the sense that any JSON value type
can be converted to a CBOR value.
@ -24603,16 +24672,16 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
@since version 2.0.9; compact representation of floating-point numbers
since version 3.8.0
*/
static std::vector<uint8_t> to_cbor(const basic_json& j)
static std::vector<std::uint8_t> to_cbor(const basic_json& j)
{
std::vector<uint8_t> result;
std::vector<std::uint8_t> result;
to_cbor(j, result);
return result;
}
static void to_cbor(const basic_json& j, detail::output_adapter<uint8_t> o)
static void to_cbor(const basic_json& j, detail::output_adapter<std::uint8_t> o)
{
binary_writer<uint8_t>(o).write_cbor(j);
binary_writer<std::uint8_t>(o).write_cbor(j);
}
static void to_cbor(const basic_json& j, detail::output_adapter<char> o)
@ -24698,16 +24767,16 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
@since version 2.0.9
*/
static std::vector<uint8_t> to_msgpack(const basic_json& j)
static std::vector<std::uint8_t> to_msgpack(const basic_json& j)
{
std::vector<uint8_t> result;
std::vector<std::uint8_t> result;
to_msgpack(j, result);
return result;
}
static void to_msgpack(const basic_json& j, detail::output_adapter<uint8_t> o)
static void to_msgpack(const basic_json& j, detail::output_adapter<std::uint8_t> o)
{
binary_writer<uint8_t>(o).write_msgpack(j);
binary_writer<std::uint8_t>(o).write_msgpack(j);
}
static void to_msgpack(const basic_json& j, detail::output_adapter<char> o)
@ -24801,19 +24870,19 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
@since version 3.1.0
*/
static std::vector<uint8_t> to_ubjson(const basic_json& j,
const bool use_size = false,
const bool use_type = false)
static std::vector<std::uint8_t> to_ubjson(const basic_json& j,
const bool use_size = false,
const bool use_type = false)
{
std::vector<uint8_t> result;
std::vector<std::uint8_t> result;
to_ubjson(j, result, use_size, use_type);
return result;
}
static void to_ubjson(const basic_json& j, detail::output_adapter<uint8_t> o,
static void to_ubjson(const basic_json& j, detail::output_adapter<std::uint8_t> o,
const bool use_size = false, const bool use_type = false)
{
binary_writer<uint8_t>(o).write_ubjson(j, use_size, use_type);
binary_writer<std::uint8_t>(o).write_ubjson(j, use_size, use_type);
}
static void to_ubjson(const basic_json& j, detail::output_adapter<char> o,
@ -24879,9 +24948,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
@sa see @ref to_cbor(const basic_json&) for the related CBOR format
@sa see @ref to_msgpack(const basic_json&) for the related MessagePack format
*/
static std::vector<uint8_t> to_bson(const basic_json& j)
static std::vector<std::uint8_t> to_bson(const basic_json& j)
{
std::vector<uint8_t> result;
std::vector<std::uint8_t> result;
to_bson(j, result);
return result;
}
@ -24894,13 +24963,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
@pre The input `j` shall be an object: `j.is_object() == true`
@sa see @ref to_bson(const basic_json&)
*/
static void to_bson(const basic_json& j, detail::output_adapter<uint8_t> o)
static void to_bson(const basic_json& j, detail::output_adapter<std::uint8_t> o)
{
binary_writer<uint8_t>(o).write_bson(j);
binary_writer<std::uint8_t>(o).write_bson(j);
}
/*!
@copydoc to_bson(const basic_json&, detail::output_adapter<uint8_t>)
@copydoc to_bson(const basic_json&, detail::output_adapter<std::uint8_t>)
*/
static void to_bson(const basic_json& j, detail::output_adapter<char> o)
{

View File

@ -0,0 +1,97 @@
/*
__ _____ _____ _____
__| | __| | | | JSON for Modern C++ (test suite)
| | |__ | | | | | | version 3.9.1
|_____|_____|_____|_|___| https://github.com/nlohmann/json
Licensed under the MIT License <http://opensource.org/licenses/MIT>.
SPDX-License-Identifier: MIT
Copyright (c) 2013-2019 Niels Lohmann <http://nlohmann.me>.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "doctest_compatibility.h"
#include <nlohmann/json.hpp>
using nlohmann::json;
TEST_CASE("byte_container_with_subtype")
{
using subtype_type = nlohmann::byte_container_with_subtype<std::vector<std::uint8_t>>::subtype_type;
SECTION("empty container")
{
nlohmann::byte_container_with_subtype<std::vector<std::uint8_t>> container;
CHECK(!container.has_subtype());
CHECK(container.subtype() == subtype_type(-1));
container.clear_subtype();
CHECK(!container.has_subtype());
CHECK(container.subtype() == subtype_type(-1));
container.set_subtype(42);
CHECK(container.has_subtype());
CHECK(container.subtype() == 42);
}
SECTION("subtyped container")
{
nlohmann::byte_container_with_subtype<std::vector<std::uint8_t>> container({}, 42);
CHECK(container.has_subtype());
CHECK(container.subtype() == 42);
container.clear_subtype();
CHECK(!container.has_subtype());
CHECK(container.subtype() == subtype_type(-1));
}
SECTION("comparisons")
{
std::vector<std::uint8_t> bytes = {{0xCA, 0xFE, 0xBA, 0xBE}};
nlohmann::byte_container_with_subtype<std::vector<std::uint8_t>> container1;
nlohmann::byte_container_with_subtype<std::vector<std::uint8_t>> container2({}, 42);
nlohmann::byte_container_with_subtype<std::vector<std::uint8_t>> container3(bytes);
nlohmann::byte_container_with_subtype<std::vector<std::uint8_t>> container4(bytes, 42);
CHECK(container1 == container1);
CHECK(container1 != container2);
CHECK(container1 != container3);
CHECK(container1 != container4);
CHECK(container2 != container1);
CHECK(container2 == container2);
CHECK(container2 != container3);
CHECK(container2 != container4);
CHECK(container3 != container1);
CHECK(container3 != container2);
CHECK(container3 == container3);
CHECK(container3 != container4);
CHECK(container4 != container1);
CHECK(container4 != container2);
CHECK(container4 != container3);
CHECK(container4 == container4);
container3.clear();
container4.clear();
CHECK(container1 == container3);
CHECK(container2 == container4);
}
}

View File

@ -2486,7 +2486,22 @@ TEST_CASE("examples from RFC 7049 Appendix A")
auto expected = utils::read_binary_file(TEST_DATA_DIRECTORY "/binary_data/cbor_binary.out");
CHECK(j == json::binary(expected));
// 0xd8
CHECK(json::to_cbor(json::binary(std::vector<uint8_t> {}, 0x42)) == std::vector<uint8_t> {0xd8, 0x42, 0x40});
CHECK(!json::from_cbor(json::to_cbor(json::binary(std::vector<uint8_t> {}, 0x42)), true, true, json::cbor_tag_handler_t::ignore).get_binary().has_subtype());
CHECK(json::from_cbor(json::to_cbor(json::binary(std::vector<uint8_t> {}, 0x42)), true, true, json::cbor_tag_handler_t::store).get_binary().subtype() == 0x42);
// 0xd9
CHECK(json::to_cbor(json::binary(std::vector<uint8_t> {}, 1000)) == std::vector<uint8_t> {0xd9, 0x03, 0xe8, 0x40});
CHECK(!json::from_cbor(json::to_cbor(json::binary(std::vector<uint8_t> {}, 1000)), true, true, json::cbor_tag_handler_t::ignore).get_binary().has_subtype());
CHECK(json::from_cbor(json::to_cbor(json::binary(std::vector<uint8_t> {}, 1000)), true, true, json::cbor_tag_handler_t::store).get_binary().subtype() == 1000);
// 0xda
CHECK(json::to_cbor(json::binary(std::vector<uint8_t> {}, 394216)) == std::vector<uint8_t> {0xda, 0x00, 0x06, 0x03, 0xe8, 0x40});
CHECK(!json::from_cbor(json::to_cbor(json::binary(std::vector<uint8_t> {}, 394216)), true, true, json::cbor_tag_handler_t::ignore).get_binary().has_subtype());
CHECK(json::from_cbor(json::to_cbor(json::binary(std::vector<uint8_t> {}, 394216)), true, true, json::cbor_tag_handler_t::store).get_binary().subtype() == 394216);
// 0xdb
CHECK(json::to_cbor(json::binary(std::vector<uint8_t> {}, 8589934590)) == std::vector<uint8_t> {0xdb, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xfe, 0x40});
CHECK(!json::from_cbor(json::to_cbor(json::binary(std::vector<uint8_t> {}, 8589934590)), true, true, json::cbor_tag_handler_t::ignore).get_binary().has_subtype());
CHECK(json::from_cbor(json::to_cbor(json::binary(std::vector<uint8_t> {}, 8589934590)), true, true, json::cbor_tag_handler_t::store).get_binary().subtype() == 8589934590);
}
SECTION("arrays")
@ -2545,6 +2560,8 @@ TEST_CASE("Tagged values")
0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4
})
{
CAPTURE(b);
// add tag to value
auto v_tagged = v;
v_tagged.insert(v_tagged.begin(), b);
@ -2557,6 +2574,9 @@ TEST_CASE("Tagged values")
// check that parsing succeeds and gets original value in ignore mode
auto j_tagged = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::ignore);
CHECK(j_tagged == j);
auto j_tagged_stored = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::store);
CHECK(j_tagged_stored == j);
}
}