From 0bb269605886f410f70cb9659d33ac3fa20fa522 Mon Sep 17 00:00:00 2001 From: Emil Dotchevski Date: Sun, 1 Feb 2026 13:57:16 -0500 Subject: [PATCH] Improved SFINAE in the JSON encoders --- doc/exception.adoc | 27 ++++++++------ .../serialization/boost_json_encoder.hpp | 37 ++++++++----------- .../serialization/nlohmann_json_encoder.hpp | 9 +++-- test/boost_json_test.cpp | 15 +++++++- 4 files changed, 49 insertions(+), 39 deletions(-) diff --git a/doc/exception.adoc b/doc/exception.adoc index 900bb1f..a7c08c5 100644 --- a/doc/exception.adoc +++ b/doc/exception.adoc @@ -1084,8 +1084,9 @@ struct boost_json_encoder { boost::json::value & v_; - // Enabled if x is assignable to boost::json::value, or - // if tag_invoke is defined for boost::json::value_from_tag. + // Uses unspecified SFINAE expression designed to make the + // overload selected only if no other compatible overload is found. + // Implemented in terms of boost::json::value_from. template friend void output( boost_json_encoder &, T const & x ); @@ -1114,7 +1115,9 @@ struct nlohmann_json_encoder { Json & j_; - // Enabled if to_json is available for Json and T. + // Uses unspecified SFINAE expression designed to make the + // overload selected only if no other compatible overload is found. + // Implemented in terms of to_json. template friend void output( nlohmann_json_encoder &, T const & x ); @@ -1156,8 +1159,9 @@ struct boost_json_encoder { boost::json::value & v_; - // Enabled if x is assignable to boost::json::value, or - // if tag_invoke is defined for boost::json::value_from_tag. + // Uses unspecified SFINAE expression designed to make the + // overload selected only if no other compatible overload is found. + // Implemented in terms of boost::json::value_from. template friend void output( boost_json_encoder &, T const & x ); @@ -1168,10 +1172,7 @@ struct boost_json_encoder } } ---- -The `boost_json_encoder` type serializes objects to JSON format using https://www.boost.org/doc/libs/release/libs/json/[Boost.JSON]. The `output` function is enabled for: - -* Types directly assignable to `boost::json::value` -* Types for which a `tag_invoke` overload for `value_from_tag` can be found via ADL +The `boost_json_encoder` type serializes objects to JSON format using https://www.boost.org/doc/libs/release/libs/json/[Boost.JSON]. The `output` function is implemented in terms of `boost::json::value_from`. See <>. @@ -1592,7 +1593,9 @@ struct nlohmann_json_encoder { Json & j_; - // Enabled if to_json is available for Json and T. + // Uses unspecified SFINAE expression designed to make the + // overload selected only if no other compatible overload is found. + // Implemented in terms of to_json. template friend void output( nlohmann_json_encoder &, T const & x ); @@ -1603,7 +1606,9 @@ struct nlohmann_json_encoder } } ---- -The `nlohmann_json_encoder` type serializes objects to JSON format based on ADL calls to `to_json`. This is compatible with https://github.com/nlohmann/json[nlohmann/json]. See <>. +The `nlohmann_json_encoder` type serializes objects to JSON format based on ADL calls to `to_json`. This is compatible with https://github.com/nlohmann/json[nlohmann/json]. + +See <>. ''' diff --git a/include/boost/exception/serialization/boost_json_encoder.hpp b/include/boost/exception/serialization/boost_json_encoder.hpp index 6e96808..10ac0ec 100644 --- a/include/boost/exception/serialization/boost_json_encoder.hpp +++ b/include/boost/exception/serialization/boost_json_encoder.hpp @@ -6,43 +6,36 @@ #ifndef BOOST_EXCEPTION_SERIALIZATION_BOOST_JSON_ENCODER_HPP_INCLUDED #define BOOST_EXCEPTION_SERIALIZATION_BOOST_JSON_ENCODER_HPP_INCLUDED +#include #include +namespace boost { namespace json { + +class value; + +template +void value_from(T &&, value &); + +} } + namespace boost { - namespace - json - { - class value; - struct value_from_tag; - } - namespace exception_serialization { - // Baast.JSON does not provide ADL interface for serializing user-defined types. - // This limits the functionality of boost_json_encoder to only types that provide tag_invoke. - template + template struct boost_json_encoder_ { Value & v_; - template + template friend - auto - output(boost_json_encoder_ & e, T const & x) -> decltype(std::declval() = x, void()) + typename std::enable_if::value>::type + output(Encoder & e, T const & x, Deprioritize...) { - e.v_ = x; - } - - template - friend - auto - output(boost_json_encoder_ & e, T const & x) -> decltype(tag_invoke(std::declval(), std::declval(), x), void()) - { - tag_invoke(ValueFromTag{}, e.v_, x); + boost::json::value_from(x, e.v_); } template diff --git a/include/boost/exception/serialization/nlohmann_json_encoder.hpp b/include/boost/exception/serialization/nlohmann_json_encoder.hpp index 003cafd..3417d03 100644 --- a/include/boost/exception/serialization/nlohmann_json_encoder.hpp +++ b/include/boost/exception/serialization/nlohmann_json_encoder.hpp @@ -6,6 +6,7 @@ #ifndef BOOST_EXCEPTION_SERIALIZATION_NLOHMANN_JSON_ENCODER_HPP_INCLUDED #define BOOST_EXCEPTION_SERIALIZATION_NLOHMANN_JSON_ENCODER_HPP_INCLUDED +#include #include namespace @@ -20,12 +21,12 @@ boost { Json & j_; - template - friend - auto - output(nlohmann_json_encoder & e, T const & x) -> decltype(to_json(std::declval(), x)) + template + friend typename std::enable_if::value, Json *>::type + output(Encoder & e, T const & x, Deprioritize...) { to_json(e.j_, x); + return 0; } template diff --git a/test/boost_json_test.cpp b/test/boost_json_test.cpp index eef9d6c..d733ae5 100644 --- a/test/boost_json_test.cpp +++ b/test/boost_json_test.cpp @@ -18,6 +18,7 @@ #include #include #include +#include using output_encoder = boost::exception_serialization::boost_json_encoder; @@ -39,6 +40,7 @@ boost typedef boost::error_info my_error1; typedef boost::error_info my_error2; +typedef boost::error_info> my_error4; struct my_info @@ -97,6 +99,13 @@ check_output(boost::json::value const & j, bool has_source_location) auto const & mij = obj.at("my_error3_").as_object(); BOOST_TEST_EQ(mij.at("code").as_int64(), 1); BOOST_TEST_EQ(mij.at("message").as_string(), "error one"); + + BOOST_TEST(obj.contains("my_error4_")); + auto const & vec = obj.at("my_error4_").as_array(); + BOOST_TEST_EQ(vec.size(), 3u); + BOOST_TEST_EQ(vec[0].as_int64(), 1); + BOOST_TEST_EQ(vec[1].as_int64(), 2); + BOOST_TEST_EQ(vec[2].as_int64(), 3); } int @@ -111,7 +120,8 @@ main() e << my_error1(42) << my_error2("hello") << - my_error3({1, "error one"}); + my_error3({1, "error one"}) << + my_error4({1, 2, 3}); BOOST_THROW_EXCEPTION(e); } catch( test_exception & e ) @@ -132,7 +142,8 @@ main() e << my_error1(42) << my_error2("hello") << - my_error3({1, "error one"}); + my_error3({1, "error one"}) << + my_error4({1, 2, 3}); BOOST_THROW_EXCEPTION(e); } catch( ... )