Enable classes to customize their serialization

Add support for classes to define members to customize their
serialization from the default from reflection.
This commit is contained in:
Nathaniel 2022-03-16 15:47:17 -05:00
parent 6171e973c7
commit 9a1ea8a0bf
No known key found for this signature in database
GPG key ID: B4344309A110851E
3 changed files with 153 additions and 5 deletions

View file

@ -417,13 +417,35 @@ namespace fc {
}
};
template<>
struct if_reflected<fc::true_type> {
template<typename Stream, typename T>
struct if_reflected<std::true_type> {
private:
template<typename...> using void_t = void;
template<typename T, typename=void>
struct has_custom_packing : std::false_type {};
template<typename T>
struct has_custom_packing<T,
void_t<decltype(std::declval<const T>().fc_pack(std::declval<fc::datastream<size_t>&>(), 1)),
decltype(std::declval<T>().fc_unpack(std::declval<fc::datastream<size_t>&>(), 1))>>
: std::true_type {};
public:
template<typename Stream, typename T, std::enable_if_t<has_custom_packing<T>::value, bool> = true>
static inline void pack( Stream& s, const T& v, uint32_t _max_depth ) {
FC_ASSERT( _max_depth > 0 );
v.fc_pack( s, _max_depth - 1 );
}
template<typename Stream, typename T, std::enable_if_t<!has_custom_packing<T>::value, bool> = true>
static inline void pack( Stream& s, const T& v, uint32_t _max_depth ) {
FC_ASSERT( _max_depth > 0 );
if_enum< typename fc::reflector<T>::is_enum >::pack( s, v, _max_depth - 1 );
}
template<typename Stream, typename T>
template<typename Stream, typename T, std::enable_if_t<has_custom_packing<T>::value, bool> = true>
static inline void unpack( Stream& s, T& v, uint32_t _max_depth ) {
FC_ASSERT( _max_depth > 0 );
v.fc_unpack( s, _max_depth - 1 );
}
template<typename Stream, typename T, std::enable_if_t<!has_custom_packing<T>::value, bool> = true>
static inline void unpack( Stream& s, T& v, uint32_t _max_depth ) {
FC_ASSERT( _max_depth > 0 );
if_enum< typename fc::reflector<T>::is_enum >::unpack( s, v, _max_depth - 1 );

View file

@ -99,16 +99,49 @@ namespace fc
}
};
namespace detail {
template<typename...> using void_t = void;
template<typename C, typename=void>
struct has_custom_variant_conversion : std::false_type {};
template<typename C>
struct has_custom_variant_conversion<C,
void_t<decltype(std::declval<const C>().fc_to_variant(std::declval<variant&>(), 1)),
decltype(std::declval<C>().fc_from_variant(std::declval<const variant&>(), 1))>>
: std::true_type {};
template<typename T, std::enable_if_t<!detail::has_custom_variant_conversion<T>::value, bool> = true>
void to_variant( const T& o, variant& v, uint32_t max_depth )
{
if_enum<T>::to_variant( o, v, max_depth );
}
template<typename T, std::enable_if_t<detail::has_custom_variant_conversion<T>::value, bool> = true>
void to_variant( const T& o, variant& v, uint32_t max_depth )
{
o.fc_to_variant(v, max_depth);
}
template<typename T, std::enable_if_t<!detail::has_custom_variant_conversion<T>::value, bool> = true>
void from_variant( const variant& v, T& o, uint32_t max_depth )
{
if_enum<T>::from_variant( v, o, max_depth );
}
template<typename T, std::enable_if_t<detail::has_custom_variant_conversion<T>::value, bool> = true>
void from_variant( const variant& v, T& o, uint32_t max_depth )
{
o.fc_from_variant(v, max_depth);
}
} // namespace detail
template<typename T>
void to_variant( const T& o, variant& v, uint32_t max_depth )
{
if_enum<typename fc::reflector<T>::is_enum>::to_variant( o, v, max_depth );
detail::to_variant(o, v, max_depth);
}
template<typename T>
void from_variant( const variant& v, T& o, uint32_t max_depth )
{
if_enum<typename fc::reflector<T>::is_enum>::from_variant( v, o, max_depth );
detail::from_variant(v, o, max_depth);
}
}

View file

@ -3,6 +3,8 @@
#include <fc/container/flat.hpp>
#include <fc/io/raw.hpp>
#include <fc/io/json.hpp>
#include <fc/reflect/variant.hpp>
namespace fc { namespace test {
@ -44,6 +46,35 @@ namespace fc { namespace test {
FC_REFLECT( fc::test::item_wrapper, (v) );
FC_REFLECT( fc::test::item, (level)(w) );
// These are used in the custom_serialization test
static bool custom_serialization_used = false;
// Custom serialized type with a fake serializer that does nothing but set the above bool to true
struct custom_serialized_type {
int a = 1;
std::string b = "Hi!";
bool c = true;
// Override default serialization for reflected types
template<typename Stream>
void fc_pack(Stream&, uint32_t) const {
custom_serialization_used = true;
}
template<typename Stream>
void fc_unpack(Stream&, uint32_t) {
custom_serialization_used = true;
}
void fc_to_variant(fc::variant&, uint32_t) const {
custom_serialization_used = true;
}
void fc_from_variant(const fc::variant&, uint32_t) {
custom_serialization_used = true;
}
};
FC_REFLECT(custom_serialized_type, (a)(b)(c))
BOOST_AUTO_TEST_SUITE(fc_serialization)
BOOST_AUTO_TEST_CASE( nested_objects_test )
@ -105,4 +136,66 @@ BOOST_AUTO_TEST_CASE( nested_objects_test )
} FC_CAPTURE_LOG_AND_RETHROW ( (0) ) }
BOOST_AUTO_TEST_CASE( unpack_recursion_test )
{
try
{
std::stringstream ss;
int recursion_level = 100000;
uint64_t allocation_per_level = 500000;
for ( int i = 0; i < recursion_level; i++ )
{
fc::raw::pack( ss, fc::unsigned_int( allocation_per_level ) );
fc::raw::pack( ss, static_cast< uint8_t >( fc::variant::array_type ) );
}
std::vector< fc::variant > v;
BOOST_REQUIRE_THROW( fc::raw::unpack( ss, v ), fc::assert_exception );
}
FC_LOG_AND_RETHROW();
}
BOOST_AUTO_TEST_CASE( custom_serialization )
{ try {
// Create our custom serialized type and serialize it; check that only our fake serializer ran
custom_serialized_type obj;
auto packed = fc::raw::pack(obj);
BOOST_CHECK(packed.empty());
BOOST_CHECK(custom_serialization_used);
// Deserializer won't run on an empty vector; put a byte in so deserialization tests can run
packed = {'a'};
// Reset the flag, alter the object, and deserialize it, again checking that only the fake serializer ran
custom_serialization_used = false;
obj.c = false;
fc::raw::unpack(packed, obj);
BOOST_CHECK(obj.c == false);
BOOST_CHECK(custom_serialization_used);
// Reset the flag, deserialize again without existing ref, check if fake serializer ran
custom_serialization_used = false;
auto obj2 = fc::raw::unpack<custom_serialized_type>(packed);
BOOST_CHECK(custom_serialization_used);
// All over again, but for variants instead of binary serialization
fc::variant v(obj, 10);
BOOST_CHECK(v.is_null());
BOOST_CHECK(custom_serialization_used);
custom_serialization_used = false;
fc::to_variant(obj, v, 10);
BOOST_CHECK(custom_serialization_used);
// JSON should be affected too. Check it is.
custom_serialization_used = false;
BOOST_CHECK_EQUAL(fc::json::to_string(obj), "null");
BOOST_CHECK(custom_serialization_used);
custom_serialization_used = false;
obj.c = true;
fc::from_variant(v, obj, 10);
BOOST_CHECK(custom_serialization_used);
BOOST_CHECK(obj.c);
} FC_LOG_AND_RETHROW() }
BOOST_AUTO_TEST_SUITE_END()