Loki-Astari / ThorsSerializer

C++ Serialization library for JSON

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How to combine templates and polymorphism?

JakkuSakura opened this issue · comments

I was trying to make a game using ECS framework. It's better to use POD as components, in order to improve performance and increase stability of the game. So I decided to use some wrapper to provide virtual table and to keep my components intact.
However I got some errors. I'm looking for a way to print my wrapped POD, as well as to scan POD using the library's methods.

In file included from /home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.h:425,
                 from /home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/JsonParser.h:21,
                 from /home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/JsonThor.h:15,
                 from /home/jack/IdeaProjects/Escape/src/serialization.cpp:19:
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp: In instantiation of ‘class ThorsAnvil::Serialize::DeSerializeMember<TerrainData, TerrainType, ThorsAnvil::Serialize::TraitType::Invalid>’:
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:508:73:   required from ‘bool ThorsAnvil::Serialize::DeSerializer::scanEachMember(const string&, T&, const Members&, std::index_sequence<_Idx ...>&) [with T = TerrainData; Members = std::tuple<std::pair<const char*, TerrainType TerrainData::*>, std::pair<const char*, float TerrainData::*>, std::pair<const char*, float TerrainData::*>, std::pair<const char*, float TerrainData::*>, std::pair<const char*, float TerrainData::*> >; long unsigned int ...Seq = {0, 1, 2, 3, 4}; std::string = std::__cxx11::basic_string<char>; std::index_sequence<_Idx ...> = std::integer_sequence<long unsigned int, 0, 1, 2, 3, 4>]’
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:515:95:   required from ‘bool ThorsAnvil::Serialize::DeSerializer::scanMembers(const string&, T&, const std::tuple<_Elements ...>&) [with T = TerrainData; Members = {std::pair<const char*, TerrainType TerrainData::*>, std::pair<const char*, float TerrainData::*>, std::pair<const char*, float TerrainData::*>, std::pair<const char*, float TerrainData::*>, std::pair<const char*, float TerrainData::*>}; std::string = std::__cxx11::basic_string<char>]’
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:531:17:   required from ‘bool ThorsAnvil::Serialize::DeSerializer::scanObjectMembers(const I&, T&) [with T = TerrainData; I = std::__cxx11::basic_string<char>]’
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:196:21:   required from ‘void ThorsAnvil::Serialize::DeSerializationForBlock<traitType, T>::scanObject(T&) [with ThorsAnvil::Serialize::TraitType traitType = ThorsAnvil::Serialize::TraitType::Map; T = TerrainData]’
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:541:9:   required from ‘void ThorsAnvil::Serialize::DeSerializer::parse(T&) [with T = TerrainData]’
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:456:9:   [ skipping 3 instantiation contexts, use -ftemplate-backtrace-limit=0 to disable ]
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:515:95:   required from ‘bool ThorsAnvil::Serialize::DeSerializer::scanMembers(const string&, T&, const std::tuple<_Elements ...>&) [with T = Wrapper<TerrainData>; Members = {std::pair<const char*, TerrainData Wrapper<TerrainData>::*>}; std::string = std::__cxx11::basic_string<char>]’
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:531:17:   required from ‘bool ThorsAnvil::Serialize::DeSerializer::scanObjectMembers(const I&, T&) [with T = Wrapper<TerrainData>; I = std::__cxx11::basic_string<char>]’
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:196:21:   required from ‘void ThorsAnvil::Serialize::DeSerializationForBlock<traitType, T>::scanObject(T&) [with ThorsAnvil::Serialize::TraitType traitType = ThorsAnvil::Serialize::TraitType::Parent; T = Wrapper<TerrainData>]’
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:345:5:   required from ‘void ThorsAnvil::Serialize::parsePolyMorphicObject(ThorsAnvil::Serialize::DeSerializer&, ThorsAnvil::Serialize::ParserInterface&, T&) [with T = Wrapper<TerrainData>]’
/home/jack/IdeaProjects/Escape/src/serialization.cpp:68:5:   required from ‘void Wrapper<T>::parsePolyMorphicObject(ThorsAnvil::Serialize::DeSerializer&, ThorsAnvil::Serialize::ParserInterface&) [with T = TerrainData]’
/home/jack/IdeaProjects/Escape/src/serialization.cpp:68:5:   required from here
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:484:7: error: invalid use of incomplete type ‘struct ThorsAnvil::Serialize::TraitsInfo<TerrainData, TerrainType, ThorsAnvil::Serialize::TraitType::Invalid>’
  484 | class DeSerializeMember: public TraitsInfo<T, M, Type>::DeSerializeMember
      |       ^~~~~~~~~~~~~~~~~
In file included from /home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/JsonParser.h:21,
                 from /home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/JsonThor.h:15,
                 from /home/jack/IdeaProjects/Escape/src/serialization.cpp:19:
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.h:301:8: note: declaration of ‘struct ThorsAnvil::Serialize::TraitsInfo<TerrainData, TerrainType, ThorsAnvil::Serialize::TraitType::Invalid>’
  301 | struct TraitsInfo;
      |        ^~~~~~~~~~
In file included from /home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.h:425,
                 from /home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/JsonParser.h:21,
                 from /home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/JsonThor.h:15,
                 from /home/jack/IdeaProjects/Escape/src/serialization.cpp:19:
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:486:11: error: invalid use of incomplete type ‘struct ThorsAnvil::Serialize::TraitsInfo<TerrainData, TerrainType, ThorsAnvil::Serialize::TraitType::Invalid>’
  486 |     using Parent = typename TraitsInfo<T, M, Type>::DeSerializeMember;
      |           ^~~~~~
In file included from /home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/JsonParser.h:21,
                 from /home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/JsonThor.h:15,
                 from /home/jack/IdeaProjects/Escape/src/serialization.cpp:19:
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.h:301:8: note: declaration of ‘struct ThorsAnvil::Serialize::TraitsInfo<TerrainData, TerrainType, ThorsAnvil::Serialize::TraitType::Invalid>’
  301 | struct TraitsInfo;
      |        ^~~~~~~~~~
In file included from /home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.h:425,
                 from /home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/JsonParser.h:21,
                 from /home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/JsonThor.h:15,
                 from /home/jack/IdeaProjects/Escape/src/serialization.cpp:19:
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:488:23: error: using-declaration for non-member at class scope
  488 |         using Parent::Parent;
      |                       ^~~~~~
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp: In instantiation of ‘bool ThorsAnvil::Serialize::DeSerializer::scanEachMember(const string&, T&, const Members&, std::index_sequence<_Idx ...>&) [with T = TerrainData; Members = std::tuple<std::pair<const char*, TerrainType TerrainData::*>, std::pair<const char*, float TerrainData::*>, std::pair<const char*, float TerrainData::*>, std::pair<const char*, float TerrainData::*>, std::pair<const char*, float TerrainData::*> >; long unsigned int ...Seq = {0, 1, 2, 3, 4}; std::string = std::__cxx11::basic_string<char>; std::index_sequence<_Idx ...> = std::integer_sequence<long unsigned int, 0, 1, 2, 3, 4>]’:
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:515:95:   required from ‘bool ThorsAnvil::Serialize::DeSerializer::scanMembers(const string&, T&, const std::tuple<_Elements ...>&) [with T = TerrainData; Members = {std::pair<const char*, TerrainType TerrainData::*>, std::pair<const char*, float TerrainData::*>, std::pair<const char*, float TerrainData::*>, std::pair<const char*, float TerrainData::*>, std::pair<const char*, float TerrainData::*>}; std::string = std::__cxx11::basic_string<char>]’
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:531:17:   required from ‘bool ThorsAnvil::Serialize::DeSerializer::scanObjectMembers(const I&, T&) [with T = TerrainData; I = std::__cxx11::basic_string<char>]’
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:196:21:   required from ‘void ThorsAnvil::Serialize::DeSerializationForBlock<traitType, T>::scanObject(T&) [with ThorsAnvil::Serialize::TraitType traitType = ThorsAnvil::Serialize::TraitType::Map; T = TerrainData]’
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:541:9:   required from ‘void ThorsAnvil::Serialize::DeSerializer::parse(T&) [with T = TerrainData]’
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:456:9:   required from ‘ThorsAnvil::Serialize::DeSerializeMemberContainer<T, M>::DeSerializeMemberContainer(ThorsAnvil::Serialize::DeSerializer&, ThorsAnvil::Serialize::ParserInterface&, const string&, T&, const std::pair<const char*, M T::*>&) [with T = Wrapper<TerrainData>; M = TerrainData; std::string = std::__cxx11::basic_string<char>]’
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:488:23:   [ skipping 2 instantiation contexts, use -ftemplate-backtrace-limit=0 to disable ]
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:515:95:   required from ‘bool ThorsAnvil::Serialize::DeSerializer::scanMembers(const string&, T&, const std::tuple<_Elements ...>&) [with T = Wrapper<TerrainData>; Members = {std::pair<const char*, TerrainData Wrapper<TerrainData>::*>}; std::string = std::__cxx11::basic_string<char>]’
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:531:17:   required from ‘bool ThorsAnvil::Serialize::DeSerializer::scanObjectMembers(const I&, T&) [with T = Wrapper<TerrainData>; I = std::__cxx11::basic_string<char>]’
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:196:21:   required from ‘void ThorsAnvil::Serialize::DeSerializationForBlock<traitType, T>::scanObject(T&) [with ThorsAnvil::Serialize::TraitType traitType = ThorsAnvil::Serialize::TraitType::Parent; T = Wrapper<TerrainData>]’
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:345:5:   required from ‘void ThorsAnvil::Serialize::parsePolyMorphicObject(ThorsAnvil::Serialize::DeSerializer&, ThorsAnvil::Serialize::ParserInterface&, T&) [with T = Wrapper<TerrainData>]’
/home/jack/IdeaProjects/Escape/src/serialization.cpp:68:5:   required from ‘void Wrapper<T>::parsePolyMorphicObject(ThorsAnvil::Serialize::DeSerializer&, ThorsAnvil::Serialize::ParserInterface&) [with T = TerrainData]’
/home/jack/IdeaProjects/Escape/src/serialization.cpp:68:5:   required from here
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:508:33: error: invalid static_cast from type ‘ThorsAnvil::Serialize::DeSerializeMember<TerrainData, TerrainType, ThorsAnvil::Serialize::TraitType::Invalid>’ to type ‘bool’
  508 |     CheckMembers memberCheck = {static_cast<bool>(make_DeSerializeMember(*this, parser, key, object, std::get<Seq>(member)))...};
      |                                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:508:18: error: could not convert ‘{<expression error>}’ from ‘<brace-enclosed initializer list>’ to ‘CheckMembers’ {aka ‘std::initializer_list<bool>’}
  508 |     CheckMembers memberCheck = {static_cast<bool>(make_DeSerializeMember(*this, parser, key, object, std::get<Seq>(member)))...};
      |                  ^~~~~~~~~~~
      |                  |
      |                  <brace-enclosed initializer list>
enum class TerrainType {
    BOX,
    CIRCLE
};

struct TerrainData {
    TerrainType type;
    float argument_1;
    float argument_2;
    float argument_3;
    float argument_4;
};

ThorsAnvil_MakeTrait(TerrainData, type, argument_1, argument_2, argument_3, argument_4);

struct WrapperBase {
    virtual ~WrapperBase() {};

    virtual void *getData() { return 0; };

    ThorsAnvil_PolyMorphicSerializer(WrapperBase);
};

template<typename T>
struct Wrapper : public WrapperBase {
    Wrapper() : data() {}

    Wrapper(const Wrapper<T> &wrapper) : data(wrapper.data) {}
    Wrapper(const T &d) : data(d) {}
    Wrapper(T &&d) : data(std::move(d)) {}
    T data;
    Wrapper &operator=(const Wrapper &wrapper) {
        data = wrapper.data;
        return *this;
    }

    void *getData() {
        return &data;
    }
// The next line will not let the compiler go
    ThorsAnvil_PolyMorphicSerializer(Wrapper<T>);


};
ThorsAnvil_MakeTrait(WrapperBase);
ThorsAnvil_Template_ExpandTrait(1, WrapperBase, Wrapper , data);

Currently not in a place to diagnose.
Will look closer tonight.

BUT: I do see ThorsAnvil::Serialize::TraitType::Invalid in the error messages. Which means one of the types has not been declared as serializable.

// You may need to add this.
ThorsAnvil_MakeEnum(TerrainType, BOX, CIRCLE);

But I am still not sure that would work.
The Polymorphic code would not be able to unwrap the T in Wrapper<T> to plant the correct code and would result in the code generating:

    "__type": "Wrapper<T>"

I might be able to fix that given some time.

In the meantime you may need to do:

class WrapperInt: public Wrapper<int>
{
    ThorsAnvil_PolyMorphicSerializer(WrapperInt);
};

I fixed it by adding MakeEnum(...). It's quite strange because it was working on boost serialization. Unspecified enum/enum class will be simply numbers.
I did some tricks with macros to generate names

template <typename T>
struct Wrapper
{
};
#define MAKE_WRAPPER(T)                                                                        \
    template <>                                                                                \
    struct Wrapper<T> : public WrapperBase                                                     \
    {                                                                                          \
        Wrapper() : data() {}                                                                  \
        Wrapper(const Wrapper<T> &wrapper) : data(wrapper.data) {}                             \
        Wrapper(const T &d) : data(d) {}                                                       \
        Wrapper(T &&d) : data(std::move(d)) {}                                                 \
        T data;                                                                                \
        Wrapper &operator=(const Wrapper &wrapper)                                             \
        {                                                                                      \
            data = wrapper.data;                                                               \
            return *this;                                                                      \
        }                                                                                      \
        void *getData()                                                                        \
        {                                                                                      \
            return &data;                                                                      \
        }                                                                                      \
        virtual void printPolyMorphicObject(ThorsAnvil::Serialize::Serializer &parent,         \
                                            ThorsAnvil::Serialize::PrinterInterface &printer)  \
        {                                                                                      \
            ThorsAnvil::Serialize::printPolyMorphicObject<Wrapper<T>>(parent, printer, *this); \
        }                                                                                      \
        virtual void parsePolyMorphicObject(ThorsAnvil::Serialize::DeSerializer &parent,       \
                                            ThorsAnvil::Serialize::ParserInterface &parser)    \
        {                                                                                      \
            ThorsAnvil::Serialize::parsePolyMorphicObject<Wrapper<T>>(parent, parser, *this);  \
        }                                                                                      \
        static constexpr char const *polyMorphicSerializerName() { return #T; };               \
    }


ThorsAnvil_MakeTrait(WrapperBase);
ThorsAnvil_Template_ExpandTrait(1, WrapperBase, Wrapper, data);
FOREACH_COMPONENT_TYPE(MAKE_WRAPPER);

where

#define FOREACH_COMPONENT_TYPE(func) \
    func(Position);          \
    func(Name);              \
    ...

By doing so,

  1. I could have vtables, which is vital in some cases
  2. I could have template arguments outside the wrapper, which is useful to obtain the underlaying data type, i.e. , typeid(Wrapper) == typeid(*some_pointer). Moreover, typeid could be obtained at both runtime and compiling time
  3. I could have name of type, in const char * format

Now I have the result of exportJSON(new Wrapper(Position(42, 6)));

 { 
	"__type": "Position", 
	"data": 
	{ 
		"x": 42, 
		"y": 6
	},
},

But I would like it to be

{ 
		"__type": "Position", 
		"x": 42, 
		"y": 6
}

or preferably

"Position" : {
		"x": 42, 
		"y": 6
}

since any component will be not appear multiple times in one entity.
Is there a way to hack the procedure of containers like std::vector<WrapperBase *>?

Another problem while parsing json file:

static constexpr char const *polyMorphicSerializerName() { return "Wrapped"#T; };  

terminate called after throwing an instance of 'std::runtime_error'
  what():  ThorsAnvil::Serialize::PolyMorphicRegistry::getNamedTypeConvertedTo: Non polymorphic type Wrapper<TimeServerInfo>

Here my TimeServerInfo is just a POD containing one integer, and Wrapper is obviously my wrapper and the name I assigned.
It seems that my name doens't match the typename name I typed in MakeTrait or TemplateMakeTrait

Problem partially solved:
I used macro in argument of ExpandTrait, instead of TemplateExpandTrait. Now I can serialize and deserialize. One problem remains: I have to implement my own version of ThorsAnvil_ExpandTrait_Base() to specify arbitrary register type name, instead of Wrapper<my_inner_type_name>

struct WrapperBase {
    virtual ~WrapperBase() {};

    virtual void *getData() { return 0; };

    ThorsAnvil_PolyMorphicSerializer(WrapperBase);
};

template<typename T>
struct Wrapper {
};
#define MAKE_WRAPPER(T, name)                                                                        \
    template <>                                                                                \
    struct Wrapper<T> : public WrapperBase                                                     \
    {                                                                                          \
        Wrapper() : data() {}                                                                  \
        Wrapper(const Wrapper<T> &wrapper) : data(wrapper.data) {}                             \
        Wrapper(const T &d) : data(d) {}                                                       \
        Wrapper(T &&d) : data(std::move(d)) {}                                                 \
        T data;                                                                                \
        Wrapper &operator=(const Wrapper &wrapper)                                             \
        {                                                                                      \
            data = wrapper.data;                                                               \
            return *this;                                                                      \
        }                                                                                      \
        void *getData()                                                                        \
        {                                                                                      \
            return &data;                                                                      \
        }                                                                                      \
        virtual void printPolyMorphicObject(ThorsAnvil::Serialize::Serializer &parent,         \
                                            ThorsAnvil::Serialize::PrinterInterface &printer)  \
        {                                                                                      \
            ThorsAnvil::Serialize::printPolyMorphicObject<Wrapper<T>>(parent, printer, *this); \
        }                                                                                      \
        virtual void parsePolyMorphicObject(ThorsAnvil::Serialize::DeSerializer &parent,       \
                                            ThorsAnvil::Serialize::ParserInterface &parser)    \
        {                                                                                      \
            ThorsAnvil::Serialize::parsePolyMorphicObject<Wrapper<T>>(parent, parser, *this);  \
        }                                                                                      \
        static constexpr char const *polyMorphicSerializerName() { return #name; };            \
    };                                                                                         \
    ThorsAnvil_ExpandTrait(WrapperBase, Wrapper<T>, data);


ThorsAnvil_MakeTrait(WrapperBase);
#define MAKE_WRAPPER2(type) MAKE_WRAPPER(type, Wrapper<type>)
FOREACH_COMPONENT_TYPE(MAKE_WRAPPER2);
 
	{ 
		"0": [ 
			{ 
				"__type": "Wrapper<TimeServerInfo>", 
				"data": 
				{ 
					"tick": 87
				}
			}], 
		"1": [ 
			{ 
				"__type": "Wrapper<Position>", 
				"data": 
				{ 
					"x": -13.5215, 
					"y": 0.0
				}
			}, 
			{ 
				"__type": "Wrapper<Name>", 
				"data": 
				{ 
					"name": "agent"
				}
			}, 
			{ 
				"__type": "Wrapper<Velocity>", 
				"data": 
				{ 
					"x": -0.895253, 
					"y": 0.0
				}
			}, 
			{ 
				"__type": "Wrapper<Health>", 
				"data": 
				{ 
					"max_health": 100
				}
			}, 
			{ 
				"__type": "Wrapper<Weapon>", 
				"data": 
				{ 
					"weapon": "HANDGUN", 
					"last": 0.0, 
					"next": 0.0
				}
			}, 
			{ 
				"__type": "Wrapper<Hitbox>", 
				"data": 
				{ 
					"radius": 1
				}
			}, 
			{ 
				"__type": "Wrapper<AgentData>", 
				"data": 
				{ 
					"id": 1, 
					"player": 1
				}
			}], 
		"2": [ 
			{ 
				"__type": "Wrapper<Position>", 
				"data": 
				{ 
					"x": 5, 
					"y": 20
				}
			}, 
			{ 
				"__type": "Wrapper<Name>", 
				"data": 
				{ 
					"name": "agent"
				}
			}, 
			{ 
				"__type": "Wrapper<Velocity>", 
				"data": 
				{ 
					"x": 0.0, 
					"y": 0.0
				}
			}, 
			{ 
				"__type": "Wrapper<Health>", 
				"data": 
				{ 
					"max_health": 100
				}
			}, 
			{ 
				"__type": "Wrapper<Weapon>", 
				"data": 
				{ 
					"weapon": "HANDGUN", 
					"last": 0.0, 
					"next": 0.0
				}
			}, 
			{ 
				"__type": "Wrapper<Hitbox>", 
				"data": 
				{ 
					"radius": 1
				}
			}, 
			{ 
				"__type": "Wrapper<AgentData>", 
				"data": 
				{ 
					"id": 2, 
					"player": 0
				}
			}], 
		"3": [ 
			{ 
				"__type": "Wrapper<Position>", 
				"data": 
				{ 
					"x": 0.0, 
					"y": 20
				}
			}, 
			{ 
				"__type": "Wrapper<Name>", 
				"data": 
				{ 
					"name": "agent"
				}
			}, 
			{ 
				"__type": "Wrapper<Velocity>", 
				"data": 
				{ 
					"x": 0.0, 
					"y": 0.0
				}
			}, 
			{ 
				"__type": "Wrapper<Health>", 
				"data": 
				{ 
					"max_health": 100
				}
			}, 
			{ 
				"__type": "Wrapper<Weapon>", 
				"data": 
				{ 
					"weapon": "HANDGUN", 
					"last": 0.0, 
					"next": 0.0
				}
			}, 
			{ 
				"__type": "Wrapper<Hitbox>", 
				"data": 
				{ 
					"radius": 1
				}
			}, 
			{ 
				"__type": "Wrapper<AgentData>", 
				"data": 
				{ 
					"id": 3, 
					"player": 0
				}
			}], 
		"4": [ 
			{ 
				"__type": "Wrapper<Position>", 
				"data": 
				{ 
					"x": -5, 
					"y": 20
				}
			}, 
			{ 
				"__type": "Wrapper<Name>", 
				"data": 
				{ 
					"name": "agent"
				}
			}, 
			{ 
				"__type": "Wrapper<Velocity>", 
				"data": 
				{ 
					"x": 0.0, 
					"y": 0.0
				}
			}, 
			{ 
				"__type": "Wrapper<Health>", 
				"data": 
				{ 
					"max_health": 100
				}
			}, 
			{ 
				"__type": "Wrapper<Weapon>", 
				"data": 
				{ 
					"weapon": "HANDGUN", 
					"last": 0.0, 
					"next": 0.0
				}
			}, 
			{ 
				"__type": "Wrapper<Hitbox>", 
				"data": 
				{ 
					"radius": 1
				}
			}, 
			{ 
				"__type": "Wrapper<AgentData>", 
				"data": 
				{ 
					"id": 4, 
					"player": 0
				}
			}], 
		"5": [ 
			{ 
				"__type": "Wrapper<Position>", 
				"data": 
				{ 
					"x": 0.0, 
					"y": 10
				}
			}, 
			{ 
				"__type": "Wrapper<Name>", 
				"data": 
				{ 
					"name": "box"
				}
			}, 
			{ 
				"__type": "Wrapper<Rotation>", 
				"data": 
				{ 
					"radian": 0.523599
				}
			}, 
			{ 
				"__type": "Wrapper<TerrainData>", 
				"data": 
				{ 
					"type": "BOX", 
					"argument_1": 20, 
					"argument_2": 2, 
					"argument_3": 0.0, 
					"argument_4": 0.0
				}
			}], 
		"6": [ 
			{ 
				"__type": "Wrapper<Position>", 
				"data": 
				{ 
					"x": 0.0, 
					"y": 10
				}
			}, 
			{ 
				"__type": "Wrapper<Name>", 
				"data": 
				{ 
					"name": "box"
				}
			}, 
			{ 
				"__type": "Wrapper<Rotation>", 
				"data": 
				{ 
					"radian": 2.0944
				}
			}, 
			{ 
				"__type": "Wrapper<TerrainData>", 
				"data": 
				{ 
					"type": "BOX", 
					"argument_1": 20, 
					"argument_2": 2, 
					"argument_3": 0.0, 
					"argument_4": 0.0
				}
			}]
	}

Finally I combined templates and polymorphism with user-friendly type names by doing these steps.

  1. Define a basic class with vtable.
  2. Define a templated wrapper/smart pointer class(or what ever), derived from basic class. (It's like boost::any, but we need the vtable, not typeid). Define Traits<Wrapper> as pointer manually. This is not needed if you do step 5 in another way)
  3. Use macro to partially specialize the templated class to generate user-friendly type names. Unwrap several macros manually to set up type names.
  4. Define InlinedSerializer<T> and InlinedDeSerializer<T> for POD class.
  5. Define SerializerForBlock<TraitType::Pointer, Wrapper<T>> and DeSerializationForBlock<TraitType::Pointer, Wrapper<T>> to call InlinedSerializer<T>/InlinedDeserializer<T>

That's it. The core step is to use pointers with virtual table to wrap PODs, use macro to generate type names in string format, and specialize [De]Serializers and Inline[De]Serializers.
It's quite annoying but the result is very satisfying.

 
	{ 
		"0": [ 
			{ 
				"__type": "TimeServerInfo", 
				"tick": 69
			}], 
		"1": [ 
			{ 
				"__type": "Position", 
				"x": 0.0, 
				"y": 0.0
			}, 
			{ 
				"__type": "Name", 
				"name": "agent"
			}, 
			{ 
				"__type": "Velocity", 
				"x": 0.0, 
				"y": 0.0
			}, 
			{ 
				"__type": "Health", 
				"max_health": 100
			}, 
			{ 
				"__type": "Weapon", 
				"weapon": "HANDGUN", 
				"last": 0.0, 
				"next": 0.0
			}, 
			{ 
				"__type": "Hitbox", 
				"radius": 1
			}, 
			{ 
				"__type": "AgentData", 
				"id": 1, 
				"player": 1
			}], 
		"2": [ 
			{ 
				"__type": "Position", 
				"x": 5, 
				"y": 20
			}, 
			{ 
				"__type": "Name", 
				"name": "agent"
			}, 
			{ 
				"__type": "Velocity", 
				"x": 0.0, 
				"y": 0.0
			}, 
			{ 
				"__type": "Health", 
				"max_health": 100
			}, 
			{ 
				"__type": "Weapon", 
				"weapon": "HANDGUN", 
				"last": 0.0, 
				"next": 0.0
			}, 
			{ 
				"__type": "Hitbox", 
				"radius": 1
			}, 
			{ 
				"__type": "AgentData", 
				"id": 2, 
				"player": 0
			}], 
		"3": [ 
			{ 
				"__type": "Position", 
				"x": 0.0, 
				"y": 20
			}, 
			{ 
				"__type": "Name", 
				"name": "agent"
			}, 
			{ 
				"__type": "Velocity", 
				"x": 0.0, 
				"y": 0.0
			}, 
			{ 
				"__type": "Health", 
				"max_health": 100
			}, 
			{ 
				"__type": "Weapon", 
				"weapon": "HANDGUN", 
				"last": 0.0, 
				"next": 0.0
			}, 
			{ 
				"__type": "Hitbox", 
				"radius": 1
			}, 
			{ 
				"__type": "AgentData", 
				"id": 3, 
				"player": 0
			}], 
		"4": [ 
			{ 
				"__type": "Position", 
				"x": -5, 
				"y": 20
			}, 
			{ 
				"__type": "Name", 
				"name": "agent"
			}, 
			{ 
				"__type": "Velocity", 
				"x": 0.0, 
				"y": 0.0
			}, 
			{ 
				"__type": "Health", 
				"max_health": 100
			}, 
			{ 
				"__type": "Weapon", 
				"weapon": "HANDGUN", 
				"last": 0.0, 
				"next": 0.0
			}, 
			{ 
				"__type": "Hitbox", 
				"radius": 1
			}, 
			{ 
				"__type": "AgentData", 
				"id": 4, 
				"player": 0
			}], 
		"5": [ 
			{ 
				"__type": "Position", 
				"x": 0.0, 
				"y": 10
			}, 
			{ 
				"__type": "Name", 
				"name": "box"
			}, 
			{ 
				"__type": "Rotation", 
				"radian": 0.523599
			}, 
			{ 
				"__type": "TerrainData", 
				"type": "BOX", 
				"argument_1": 20, 
				"argument_2": 2, 
				"argument_3": 0.0, 
				"argument_4": 0.0
			}],
	}
struct WrapperBase {
    WrapperBase() {};

    virtual ~WrapperBase() {};

    ThorsAnvil_PolyMorphicSerializer(WrapperBase);

};

#define My_RegisterPolyMorphicType(DataType, name)                    \
    namespace ThorsAnvil                                              \
    {                                                                 \
    namespace Serialize                                               \
    {                                                                 \
    namespace                                                         \
    {                                                                 \
    ThorsAnvil_InitPolyMorphicType<DataType> THOR_UNIQUE_NAME(#name); \
    }                                                                 \
    }                                                                 \
    }

template<typename T>
struct Wrapper {
};
#define MAKE_WRAPPER(T, name)                                                                       \
    template <>                                                                                     \
    struct Wrapper<T> : public WrapperBase                                                          \
    {                                                                                               \
        using element_type = T;                                                                     \
        T *pointer;                                                                                 \
        Wrapper() { pointer = nullptr; }                                                            \
        Wrapper(T *p) {pointer = p;}                                                                \
        Wrapper(const T *p) {pointer = const_cast<T *>(p);}                                         \
        Wrapper &operator=(const Wrapper &wrapper)                                                  \
        {                                                                                           \
            pointer = wrapper.pointer;                                                              \
            return *this;                                                                           \
        }                                                                                           \
        Wrapper &operator=(std::nullptr_t nil)                                                      \
        {                                                                                           \
            return *this;                                                                           \
        }                                                                                           \
        T &operator*() const {return *pointer;}                                                     \
        T* operator->(){return pointer;}                                                            \
        virtual void printPolyMorphicObject(ThorsAnvil::Serialize::Serializer &parent,              \
                                            ThorsAnvil::Serialize::PrinterInterface &printer)       \
        {                                                                                           \
            ThorsAnvil::Serialize::printPolyMorphicObject<Wrapper<T>>(parent, printer, *this);      \
        }                                                                                           \
        virtual void parsePolyMorphicObject(ThorsAnvil::Serialize::DeSerializer &parent,            \
                                            ThorsAnvil::Serialize::ParserInterface &parser)         \
        {                                                                                           \
            ThorsAnvil::Serialize::parsePolyMorphicObject<Wrapper<T>>(parent, parser, *this);       \
        }                                                                                           \
        static constexpr char const *polyMorphicSerializerName() { return #name; };                 \
    };                                                                                              \
    My_RegisterPolyMorphicType(Wrapper<T>, name);

ThorsAnvil_MakeTrait(WrapperBase);
#define MAKE_WRAPPER2(type) MAKE_WRAPPER(type, type)

FOREACH_COMPONENT_TYPE(MAKE_WRAPPER2);


namespace ThorsAnvil {
    namespace Serialize {

        template<typename T>
        class InlinedSerializer {
            Serializer &parent;
            PrinterInterface &printer;
            T const &object;
        public:
            InlinedSerializer(Serializer &parent, PrinterInterface &printer, T const &object)
                    : parent(parent), printer(printer), object(object) {
            }

            ~InlinedSerializer() {
            }

            void printMembers() {
                parent.printObjectMembers(object);
            }
        };

        template<typename T>
        class Traits<Wrapper<T>> {
        public:
            static constexpr TraitType type = TraitType::Pointer;

            static Wrapper<T> alloc() { return Wrapper<T>(new T()); }

            static void release(Wrapper<T> &p) {
                delete p.pointer;
                p.pointer = nullptr;
            }
        };

        template<typename T>
        class DeSerializationForBlock<TraitType::Pointer, Wrapper<T>> {
            DeSerializer &parent;
            ParserInterface &parser;
        public:
            DeSerializationForBlock(DeSerializer &parent, ParserInterface &parser)
                    : parent(parent), parser(parser) {}

            void scanObject(Wrapper<T> &object) {
                T *t = new T();
                try {
                    parent.parse(*t);
                } catch (...) {
                    delete t;
                    throw;
                }
                object.pointer = t;
            }
        };

        template<typename T>
        class SerializerForBlock<TraitType::Pointer, Wrapper<T>> {
            Serializer &parent;
            PrinterInterface &printer;
            Wrapper<T> const &object;
        public:
            SerializerForBlock(Serializer &parent, PrinterInterface &printer, Wrapper<T> const &object)
                    : parent(parent), printer(printer), object(object) {
                printer.openMap();
            }

            ~SerializerForBlock() {
                printer.closeMap();
            }

            void printMembers() {
                if (object.pointer == nullptr) {
                    printer.addNull();
                } else {
                    InlinedSerializer inlined(parent, printer, *object);
                    inlined.printMembers();
                }
            }

            void printPolyMorphicMembers(std::string const &type) {
                printer.addKey(printer.config.polymorphicMarker);
                printer.addValue(type);
                printMembers();
            }

        };

    }
}

Would you like to add the wrapper, or let me do it by craete a PR?
Currently it relies on the inner implemtation. Without proper hacking(c++ template system is hackable), it can't be done.