diff --git a/docs/mkdocs/docs/api/basic_json/binary_t.md b/docs/mkdocs/docs/api/basic_json/binary_t.md index 2c61ddb28..e273d138f 100644 --- a/docs/mkdocs/docs/api/basic_json/binary_t.md +++ b/docs/mkdocs/docs/api/basic_json/binary_t.md @@ -51,6 +51,38 @@ represent a byte array in modern C++. The default values for `BinaryType` is `#!cpp std::vector`. +#### Custom BinaryType behavior + +When a custom `BinaryType` is configured (other than the default `#!cpp std::vector`), 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 // Custom BinaryType +>; + +std::vector 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>(); +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` as arrays for backward compatibility. + #### Storage Binary Arrays are stored as pointers in a `basic_json` type. That is, for any access to array values, a pointer of the diff --git a/include/nlohmann/detail/conversions/from_json.hpp b/include/nlohmann/detail/conversions/from_json.hpp index 65065035f..6856a0965 100644 --- a/include/nlohmann/detail/conversions/from_json.hpp +++ b/include/nlohmann/detail/conversions/from_json.hpp @@ -19,6 +19,7 @@ #include // unordered_map #include // pair, declval #include // valarray +#include // vector #include #include @@ -332,6 +333,7 @@ template < typename BasicJsonType, typename ConstructibleArrayType, !is_constructible_object_type::value&& !is_constructible_string_type::value&& !std::is_same::value&& + !is_compatible_binary_type::value&& !is_basic_json::value, int > = 0 > 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(); } +template < typename BasicJsonType, typename CompatibleArrayType, + enable_if_t < is_compatible_binary_type::value, + int > = 0 > +inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin) +{ + if (j.is_binary()) + { + bin = static_cast(*j.template get_ptr()); + } + 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::value, int> = 0> inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index aef1b1a25..ee6164c5a 100644 --- a/include/nlohmann/detail/conversions/to_json.hpp +++ b/include/nlohmann/detail/conversions/to_json.hpp @@ -355,6 +355,7 @@ template < typename BasicJsonType, typename CompatibleArrayType, !is_compatible_object_type::value&& !is_compatible_string_type::value&& !std::is_same::value&& + !is_compatible_binary_type::value&& !is_basic_json::value, int > = 0 > inline void to_json(BasicJsonType& j, const CompatibleArrayType& arr) @@ -368,6 +369,14 @@ inline void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bi external_constructor::construct(j, bin); } +template < typename BasicJsonType, typename CompatibleArrayType, + enable_if_t < is_compatible_binary_type::value, + int > = 0 > +inline void to_json(BasicJsonType& j, const CompatibleArrayType& bin) +{ + external_constructor::construct(j, typename BasicJsonType::binary_t(bin)); +} + template::value, int> = 0> inline void to_json(BasicJsonType& j, const std::valarray& arr) diff --git a/include/nlohmann/detail/meta/type_traits.hpp b/include/nlohmann/detail/meta/type_traits.hpp index e4551d1ba..9091fbe37 100644 --- a/include/nlohmann/detail/meta/type_traits.hpp +++ b/include/nlohmann/detail/meta/type_traits.hpp @@ -559,6 +559,14 @@ template struct is_compatible_type : is_compatible_type_impl {}; +template +struct is_compatible_binary_type +{ + static constexpr bool value = + std::is_same::value && + !std::is_same>::value; +}; + template struct is_compatible_reference_type_impl { diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 5395609f0..674794bc7 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -189,6 +189,7 @@ #include // unordered_map #include // pair, declval #include // valarray +#include // vector // #include // __ _____ _____ _____ @@ -4321,6 +4322,14 @@ template struct is_compatible_type : is_compatible_type_impl {}; +template +struct is_compatible_binary_type +{ + static constexpr bool value = + std::is_same::value && + !std::is_same>::value; +}; + template struct is_compatible_reference_type_impl { @@ -5464,6 +5473,7 @@ template < typename BasicJsonType, typename ConstructibleArrayType, !is_constructible_object_type::value&& !is_constructible_string_type::value&& !std::is_same::value&& + !is_compatible_binary_type::value&& !is_basic_json::value, int > = 0 > auto from_json(const BasicJsonType& j, ConstructibleArrayType& arr) @@ -5509,6 +5519,25 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin = *j.template get_ptr(); } +template < typename BasicJsonType, typename CompatibleArrayType, + enable_if_t < is_compatible_binary_type::value, + int > = 0 > +inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin) +{ + if (j.is_binary()) + { + bin = static_cast(*j.template get_ptr()); + } + 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::value, int> = 0> inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) @@ -6383,6 +6412,7 @@ template < typename BasicJsonType, typename CompatibleArrayType, !is_compatible_object_type::value&& !is_compatible_string_type::value&& !std::is_same::value&& + !is_compatible_binary_type::value&& !is_basic_json::value, int > = 0 > inline void to_json(BasicJsonType& j, const CompatibleArrayType& arr) @@ -6396,6 +6426,14 @@ inline void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bi external_constructor::construct(j, bin); } +template < typename BasicJsonType, typename CompatibleArrayType, + enable_if_t < is_compatible_binary_type::value, + int > = 0 > +inline void to_json(BasicJsonType& j, const CompatibleArrayType& bin) +{ + external_constructor::construct(j, typename BasicJsonType::binary_t(bin)); +} + template::value, int> = 0> inline void to_json(BasicJsonType& j, const std::valarray& arr) diff --git a/tests/src/unit-regression2.cpp b/tests/src/unit-regression2.cpp index 62ef843dd..0035ecfb4 100644 --- a/tests/src/unit-regression2.cpp +++ b/tests/src/unit-regression2.cpp @@ -1136,6 +1136,40 @@ TEST_CASE("regression tests 2") 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 original{std::byte{1}, std::byte{2}, std::byte{3}}; + 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>(); + CHECK(extracted == original); + + // Test that the default json alias behavior is unchanged: std::vector -> array + json default_json = std::vector {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) + 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>(); + 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") { const json jval{};