mirror of
https://github.com/nlohmann/json.git
synced 2026-07-12 05:25:09 +00:00
Custom BinaryType direct assignment (#5266)
* Fix discussion #4209: custom BinaryType direct assignment and extraction When a custom BinaryType is configured (other than the default std::vector<uint8_t>), users can now: 1. Assign values of that type directly to create binary values (not arrays) 2. Extract binary values back to that type with get<>() 3. Extract arrays to that type (for backward compatibility) Implementation: - Add is_compatible_binary_type trait to centralize SFINAE condition - Update to_json to accept custom BinaryType values directly - Update from_json to handle both binary and array inputs for custom BinaryType - Add #include <vector> with IWYU comment to from_json.hpp - Add comprehensive tests for assignment and array extraction - Update binary_t documentation with example This is purely additive and invisible to the default nlohmann::json alias, which continues to treat std::vector<uint8_t> as arrays. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix CI: missing include and const-correctness - Add #include <vector> to type_traits.hpp for the new is_compatible_binary_type trait's std::vector<std::uint8_t> reference (caught by cpplint's include-what-you-use check) - Mark test-local json variables const where never reassigned (caught by clang-tidy's misc-const-correctness check) Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
@@ -51,6 +51,38 @@ represent a byte array in modern C++.
|
|||||||
|
|
||||||
The default values for `BinaryType` is `#!cpp std::vector<std::uint8_t>`.
|
The default values for `BinaryType` is `#!cpp std::vector<std::uint8_t>`.
|
||||||
|
|
||||||
|
#### Custom BinaryType behavior
|
||||||
|
|
||||||
|
When a custom `BinaryType` is configured (other than the default `#!cpp std::vector<std::uint8_t>`), you can assign
|
||||||
|
values of that type directly to a `basic_json` instance, and they will automatically be recognized as binary values
|
||||||
|
rather than arrays:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using custom_json = nlohmann::basic_json<
|
||||||
|
nlohmann::ordered_map, // ObjectType
|
||||||
|
std::vector, // ArrayType
|
||||||
|
std::string, // StringType
|
||||||
|
bool, // BooleanType
|
||||||
|
std::int64_t, // NumberIntegerType
|
||||||
|
std::uint64_t, // NumberUnsignedType
|
||||||
|
double, // NumberFloatType
|
||||||
|
std::allocator, // AllocatorType
|
||||||
|
nlohmann::adl_serializer,
|
||||||
|
std::vector<std::byte> // Custom BinaryType
|
||||||
|
>;
|
||||||
|
|
||||||
|
std::vector<std::byte> data{std::byte{1}, std::byte{2}, std::byte{3}};
|
||||||
|
custom_json j = data; // Creates a binary value, not an array
|
||||||
|
assert(j.is_binary());
|
||||||
|
|
||||||
|
// Round-tripping works seamlessly
|
||||||
|
auto extracted = j.get<std::vector<std::byte>>();
|
||||||
|
assert(extracted == data);
|
||||||
|
```
|
||||||
|
|
||||||
|
This automatic type detection is a convenience feature that only applies to custom (non-default) `BinaryType` configurations.
|
||||||
|
The default `nlohmann::json` continues to treat `#!cpp std::vector<std::uint8_t>` as arrays for backward compatibility.
|
||||||
|
|
||||||
#### Storage
|
#### Storage
|
||||||
|
|
||||||
Binary Arrays are stored as pointers in a `basic_json` type. That is, for any access to array values, a pointer of the
|
Binary Arrays are stored as pointers in a `basic_json` type. That is, for any access to array values, a pointer of the
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
#include <unordered_map> // unordered_map
|
#include <unordered_map> // unordered_map
|
||||||
#include <utility> // pair, declval
|
#include <utility> // pair, declval
|
||||||
#include <valarray> // valarray
|
#include <valarray> // valarray
|
||||||
|
#include <vector> // vector
|
||||||
|
|
||||||
#include <nlohmann/detail/exceptions.hpp>
|
#include <nlohmann/detail/exceptions.hpp>
|
||||||
#include <nlohmann/detail/macro_scope.hpp>
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
@@ -332,6 +333,7 @@ template < typename BasicJsonType, typename ConstructibleArrayType,
|
|||||||
!is_constructible_object_type<BasicJsonType, ConstructibleArrayType>::value&&
|
!is_constructible_object_type<BasicJsonType, ConstructibleArrayType>::value&&
|
||||||
!is_constructible_string_type<BasicJsonType, ConstructibleArrayType>::value&&
|
!is_constructible_string_type<BasicJsonType, ConstructibleArrayType>::value&&
|
||||||
!std::is_same<ConstructibleArrayType, typename BasicJsonType::binary_t>::value&&
|
!std::is_same<ConstructibleArrayType, typename BasicJsonType::binary_t>::value&&
|
||||||
|
!is_compatible_binary_type<BasicJsonType, ConstructibleArrayType>::value&&
|
||||||
!is_basic_json<ConstructibleArrayType>::value,
|
!is_basic_json<ConstructibleArrayType>::value,
|
||||||
int > = 0 >
|
int > = 0 >
|
||||||
auto from_json(const BasicJsonType& j, ConstructibleArrayType& arr)
|
auto from_json(const BasicJsonType& j, ConstructibleArrayType& arr)
|
||||||
@@ -377,6 +379,25 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t&
|
|||||||
bin = *j.template get_ptr<const typename BasicJsonType::binary_t*>();
|
bin = *j.template get_ptr<const typename BasicJsonType::binary_t*>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template < typename BasicJsonType, typename CompatibleArrayType,
|
||||||
|
enable_if_t < is_compatible_binary_type<BasicJsonType, CompatibleArrayType>::value,
|
||||||
|
int > = 0 >
|
||||||
|
inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin)
|
||||||
|
{
|
||||||
|
if (j.is_binary())
|
||||||
|
{
|
||||||
|
bin = static_cast<CompatibleArrayType>(*j.template get_ptr<const typename BasicJsonType::binary_t*>());
|
||||||
|
}
|
||||||
|
else if (j.is_array())
|
||||||
|
{
|
||||||
|
from_json_array_impl(j, bin, priority_tag<3> {});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
JSON_THROW(type_error::create(302, concat("type must be binary or array, but is ", j.type_name()), &j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template<typename BasicJsonType, typename ConstructibleObjectType,
|
template<typename BasicJsonType, typename ConstructibleObjectType,
|
||||||
enable_if_t<is_constructible_object_type<BasicJsonType, ConstructibleObjectType>::value, int> = 0>
|
enable_if_t<is_constructible_object_type<BasicJsonType, ConstructibleObjectType>::value, int> = 0>
|
||||||
inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
|
inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
|
||||||
|
|||||||
@@ -377,6 +377,7 @@ template < typename BasicJsonType, typename CompatibleArrayType,
|
|||||||
!is_compatible_object_type<BasicJsonType, CompatibleArrayType>::value&&
|
!is_compatible_object_type<BasicJsonType, CompatibleArrayType>::value&&
|
||||||
!is_compatible_string_type<BasicJsonType, CompatibleArrayType>::value&&
|
!is_compatible_string_type<BasicJsonType, CompatibleArrayType>::value&&
|
||||||
!std::is_same<typename BasicJsonType::binary_t, CompatibleArrayType>::value&&
|
!std::is_same<typename BasicJsonType::binary_t, CompatibleArrayType>::value&&
|
||||||
|
!is_compatible_binary_type<BasicJsonType, CompatibleArrayType>::value&&
|
||||||
!is_basic_json<CompatibleArrayType>::value
|
!is_basic_json<CompatibleArrayType>::value
|
||||||
#if JSON_HAS_RANGES && !defined(__MINGW32__)
|
#if JSON_HAS_RANGES && !defined(__MINGW32__)
|
||||||
&& !is_compatible_range_view<CompatibleArrayType>::value
|
&& !is_compatible_range_view<CompatibleArrayType>::value
|
||||||
@@ -406,6 +407,14 @@ inline void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bi
|
|||||||
external_constructor<value_t::binary>::construct(j, bin);
|
external_constructor<value_t::binary>::construct(j, bin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template < typename BasicJsonType, typename CompatibleArrayType,
|
||||||
|
enable_if_t < is_compatible_binary_type<BasicJsonType, CompatibleArrayType>::value,
|
||||||
|
int > = 0 >
|
||||||
|
inline void to_json(BasicJsonType& j, const CompatibleArrayType& bin)
|
||||||
|
{
|
||||||
|
external_constructor<value_t::binary>::construct(j, typename BasicJsonType::binary_t(bin));
|
||||||
|
}
|
||||||
|
|
||||||
template<typename BasicJsonType, typename T,
|
template<typename BasicJsonType, typename T,
|
||||||
enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>
|
enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>
|
||||||
inline void to_json(BasicJsonType& j, const std::valarray<T>& arr)
|
inline void to_json(BasicJsonType& j, const std::valarray<T>& arr)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include <tuple> // tuple
|
#include <tuple> // tuple
|
||||||
#include <type_traits> // false_type, is_constructible, is_integral, is_same, true_type
|
#include <type_traits> // false_type, is_constructible, is_integral, is_same, true_type
|
||||||
#include <utility> // declval
|
#include <utility> // declval
|
||||||
|
#include <vector> // vector
|
||||||
#if defined(__cpp_lib_byte) && __cpp_lib_byte >= 201603L
|
#if defined(__cpp_lib_byte) && __cpp_lib_byte >= 201603L
|
||||||
#include <cstddef> // byte
|
#include <cstddef> // byte
|
||||||
#endif
|
#endif
|
||||||
@@ -632,6 +633,14 @@ template<typename BasicJsonType, typename CompatibleType>
|
|||||||
struct is_compatible_type
|
struct is_compatible_type
|
||||||
: is_compatible_type_impl<BasicJsonType, CompatibleType> {};
|
: is_compatible_type_impl<BasicJsonType, CompatibleType> {};
|
||||||
|
|
||||||
|
template<typename BasicJsonType, typename CompatibleArrayType>
|
||||||
|
struct is_compatible_binary_type
|
||||||
|
{
|
||||||
|
static constexpr bool value =
|
||||||
|
std::is_same<typename BasicJsonType::binary_t::container_type, CompatibleArrayType>::value &&
|
||||||
|
!std::is_same<typename BasicJsonType::binary_t::container_type, std::vector<std::uint8_t>>::value;
|
||||||
|
};
|
||||||
|
|
||||||
template<typename BasicJsonType, typename CompatibleReferenceType>
|
template<typename BasicJsonType, typename CompatibleReferenceType>
|
||||||
struct is_compatible_reference_type_impl
|
struct is_compatible_reference_type_impl
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -189,6 +189,7 @@
|
|||||||
#include <unordered_map> // unordered_map
|
#include <unordered_map> // unordered_map
|
||||||
#include <utility> // pair, declval
|
#include <utility> // pair, declval
|
||||||
#include <valarray> // valarray
|
#include <valarray> // valarray
|
||||||
|
#include <vector> // vector
|
||||||
|
|
||||||
// #include <nlohmann/detail/exceptions.hpp>
|
// #include <nlohmann/detail/exceptions.hpp>
|
||||||
// __ _____ _____ _____
|
// __ _____ _____ _____
|
||||||
@@ -3592,6 +3593,7 @@ NLOHMANN_JSON_NAMESPACE_END
|
|||||||
#include <tuple> // tuple
|
#include <tuple> // tuple
|
||||||
#include <type_traits> // false_type, is_constructible, is_integral, is_same, true_type
|
#include <type_traits> // false_type, is_constructible, is_integral, is_same, true_type
|
||||||
#include <utility> // declval
|
#include <utility> // declval
|
||||||
|
#include <vector> // vector
|
||||||
#if defined(__cpp_lib_byte) && __cpp_lib_byte >= 201603L
|
#if defined(__cpp_lib_byte) && __cpp_lib_byte >= 201603L
|
||||||
#include <cstddef> // byte
|
#include <cstddef> // byte
|
||||||
#endif
|
#endif
|
||||||
@@ -4394,6 +4396,14 @@ template<typename BasicJsonType, typename CompatibleType>
|
|||||||
struct is_compatible_type
|
struct is_compatible_type
|
||||||
: is_compatible_type_impl<BasicJsonType, CompatibleType> {};
|
: is_compatible_type_impl<BasicJsonType, CompatibleType> {};
|
||||||
|
|
||||||
|
template<typename BasicJsonType, typename CompatibleArrayType>
|
||||||
|
struct is_compatible_binary_type
|
||||||
|
{
|
||||||
|
static constexpr bool value =
|
||||||
|
std::is_same<typename BasicJsonType::binary_t::container_type, CompatibleArrayType>::value &&
|
||||||
|
!std::is_same<typename BasicJsonType::binary_t::container_type, std::vector<std::uint8_t>>::value;
|
||||||
|
};
|
||||||
|
|
||||||
template<typename BasicJsonType, typename CompatibleReferenceType>
|
template<typename BasicJsonType, typename CompatibleReferenceType>
|
||||||
struct is_compatible_reference_type_impl
|
struct is_compatible_reference_type_impl
|
||||||
{
|
{
|
||||||
@@ -5537,6 +5547,7 @@ template < typename BasicJsonType, typename ConstructibleArrayType,
|
|||||||
!is_constructible_object_type<BasicJsonType, ConstructibleArrayType>::value&&
|
!is_constructible_object_type<BasicJsonType, ConstructibleArrayType>::value&&
|
||||||
!is_constructible_string_type<BasicJsonType, ConstructibleArrayType>::value&&
|
!is_constructible_string_type<BasicJsonType, ConstructibleArrayType>::value&&
|
||||||
!std::is_same<ConstructibleArrayType, typename BasicJsonType::binary_t>::value&&
|
!std::is_same<ConstructibleArrayType, typename BasicJsonType::binary_t>::value&&
|
||||||
|
!is_compatible_binary_type<BasicJsonType, ConstructibleArrayType>::value&&
|
||||||
!is_basic_json<ConstructibleArrayType>::value,
|
!is_basic_json<ConstructibleArrayType>::value,
|
||||||
int > = 0 >
|
int > = 0 >
|
||||||
auto from_json(const BasicJsonType& j, ConstructibleArrayType& arr)
|
auto from_json(const BasicJsonType& j, ConstructibleArrayType& arr)
|
||||||
@@ -5582,6 +5593,25 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t&
|
|||||||
bin = *j.template get_ptr<const typename BasicJsonType::binary_t*>();
|
bin = *j.template get_ptr<const typename BasicJsonType::binary_t*>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template < typename BasicJsonType, typename CompatibleArrayType,
|
||||||
|
enable_if_t < is_compatible_binary_type<BasicJsonType, CompatibleArrayType>::value,
|
||||||
|
int > = 0 >
|
||||||
|
inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin)
|
||||||
|
{
|
||||||
|
if (j.is_binary())
|
||||||
|
{
|
||||||
|
bin = static_cast<CompatibleArrayType>(*j.template get_ptr<const typename BasicJsonType::binary_t*>());
|
||||||
|
}
|
||||||
|
else if (j.is_array())
|
||||||
|
{
|
||||||
|
from_json_array_impl(j, bin, priority_tag<3> {});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
JSON_THROW(type_error::create(302, concat("type must be binary or array, but is ", j.type_name()), &j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template<typename BasicJsonType, typename ConstructibleObjectType,
|
template<typename BasicJsonType, typename ConstructibleObjectType,
|
||||||
enable_if_t<is_constructible_object_type<BasicJsonType, ConstructibleObjectType>::value, int> = 0>
|
enable_if_t<is_constructible_object_type<BasicJsonType, ConstructibleObjectType>::value, int> = 0>
|
||||||
inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
|
inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
|
||||||
@@ -6478,6 +6508,7 @@ template < typename BasicJsonType, typename CompatibleArrayType,
|
|||||||
!is_compatible_object_type<BasicJsonType, CompatibleArrayType>::value&&
|
!is_compatible_object_type<BasicJsonType, CompatibleArrayType>::value&&
|
||||||
!is_compatible_string_type<BasicJsonType, CompatibleArrayType>::value&&
|
!is_compatible_string_type<BasicJsonType, CompatibleArrayType>::value&&
|
||||||
!std::is_same<typename BasicJsonType::binary_t, CompatibleArrayType>::value&&
|
!std::is_same<typename BasicJsonType::binary_t, CompatibleArrayType>::value&&
|
||||||
|
!is_compatible_binary_type<BasicJsonType, CompatibleArrayType>::value&&
|
||||||
!is_basic_json<CompatibleArrayType>::value
|
!is_basic_json<CompatibleArrayType>::value
|
||||||
#if JSON_HAS_RANGES && !defined(__MINGW32__)
|
#if JSON_HAS_RANGES && !defined(__MINGW32__)
|
||||||
&& !is_compatible_range_view<CompatibleArrayType>::value
|
&& !is_compatible_range_view<CompatibleArrayType>::value
|
||||||
@@ -6507,6 +6538,14 @@ inline void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bi
|
|||||||
external_constructor<value_t::binary>::construct(j, bin);
|
external_constructor<value_t::binary>::construct(j, bin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template < typename BasicJsonType, typename CompatibleArrayType,
|
||||||
|
enable_if_t < is_compatible_binary_type<BasicJsonType, CompatibleArrayType>::value,
|
||||||
|
int > = 0 >
|
||||||
|
inline void to_json(BasicJsonType& j, const CompatibleArrayType& bin)
|
||||||
|
{
|
||||||
|
external_constructor<value_t::binary>::construct(j, typename BasicJsonType::binary_t(bin));
|
||||||
|
}
|
||||||
|
|
||||||
template<typename BasicJsonType, typename T,
|
template<typename BasicJsonType, typename T,
|
||||||
enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>
|
enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>
|
||||||
inline void to_json(BasicJsonType& j, const std::valarray<T>& arr)
|
inline void to_json(BasicJsonType& j, const std::valarray<T>& arr)
|
||||||
|
|||||||
@@ -1136,6 +1136,40 @@ TEST_CASE("regression tests 2")
|
|||||||
CHECK((decoded == json_4804::array()));
|
CHECK((decoded == json_4804::array()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("discussion #4209 - custom BinaryType direct assignment and round-tripping")
|
||||||
|
{
|
||||||
|
// Test that assigning a custom BinaryType directly creates a binary value, not an array
|
||||||
|
const std::vector<std::byte> original{std::byte{1}, std::byte{2}, std::byte{3}};
|
||||||
|
const json_4804 j = original;
|
||||||
|
CHECK(j.is_binary());
|
||||||
|
CHECK(!j.is_array());
|
||||||
|
|
||||||
|
// Test round-tripping: extracting the binary value back as the custom container type
|
||||||
|
const auto extracted = j.get<std::vector<std::byte>>();
|
||||||
|
CHECK(extracted == original);
|
||||||
|
|
||||||
|
// Test that the default json alias behavior is unchanged: std::vector<uint8_t> -> array
|
||||||
|
const json default_json = std::vector<std::uint8_t> {1, 2, 3};
|
||||||
|
CHECK(default_json.is_array());
|
||||||
|
CHECK(!default_json.is_binary());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("discussion #4209 - custom BinaryType extraction from parsed array")
|
||||||
|
{
|
||||||
|
// Test that extracting a custom BinaryType from a parsed JSON array still works
|
||||||
|
// (not just from a binary-typed node)
|
||||||
|
const auto j = json_4804::parse("[1,2,3]");
|
||||||
|
CHECK(j.is_array());
|
||||||
|
CHECK(!j.is_binary());
|
||||||
|
|
||||||
|
// Extracting as custom BinaryType should work from arrays
|
||||||
|
const auto extracted = j.get<std::vector<std::byte>>();
|
||||||
|
CHECK(extracted.size() == 3);
|
||||||
|
CHECK(extracted[0] == std::byte{1});
|
||||||
|
CHECK(extracted[1] == std::byte{2});
|
||||||
|
CHECK(extracted[2] == std::byte{3});
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("issue #5046 - implicit conversion of return json to std::optional no longer implicit")
|
SECTION("issue #5046 - implicit conversion of return json to std::optional no longer implicit")
|
||||||
{
|
{
|
||||||
const json jval{};
|
const json jval{};
|
||||||
|
|||||||
Reference in New Issue
Block a user