I'm looking for a library that allows to serialize and deserialize custom types in a few lines of code.
I've already tested this library RTTR, but unluckily it does not fit my purpose (also is poorly documented).
What I wish to achieve in pseudo-code is something like:
void serializeInstance(instance)
{
for (property in instance)
{
serializeProperty("propertyName", property);
}
}
void deserializeInstance()
{
// Getting the type of the component from a string stored in a file
type = getType(component["Type"]);
decltype(type) component;
properties = getProperties(component["Properties"]);
for (propertyName in properties)
{
// need to cast this to the right type
component.property = getPropertyValue(propertyName["Value"]);
}
}
To make it simple I'm trying to do something similar to what Unity does with the serialization of components.
This is a class diagram of my components: ClassDiagram I wish to be able to deserialize from a Yaml file all my components.
YAML file:
Scene Object: 1252896902983049
Component: Mesh
Vertices: {...}
Indices: {...}
Path: ./Assets/Mesh/Cube.obj
.cpp file:
YAML::Emitter node;
// Storing Component Name in a string (in this case "Mesh")
std::string componentName = node["Scene Object"]["Component"].as<std::string>();
// Don't know how to do this so I'll paste some pseudo code
// This auto should return my Mesh component not an instance of IComponent
auto component = generateComponentFromComponentName(componentName);
// Get all the attributes in class component by name
foreach (attribute in component.getAttributes())
// The cast to the attribute type is already implemented by me
attribute = node["Scene Object"]["Component"][attribute.name()].as<attribute.type()>();
The Oops library is doing what you are looking for. It is written for serialization using reflection.
https://bitbucket.org/barczpe/oops
You can find examples and some documentation on the wiki page.
I implemented the classes from your class diagram and added the type description or reflection to all classes. It handles the yaml file for you. Your code does not need to deal with the yaml tree and connect the yaml nodes with the members of the classes. This is all done by Oops. This is faster and simpler. Oops has its own yaml parser, and it does not build that immediate data-structure from the yaml file. However, I do not recommend using yaml file format. It is not the best for serializing C++ objects. Use the text format of the Oops library if you have a choice. It is similar, but designed for storing C++ objects.
Here is the code (what you can also find in the AbstractClasses_test.cpp file of Oops unit test):
#include "SerializationTest.h"
#include <oops/Enum2StringTool.h>
#include <oops/rPropInterface.h>
#include <gtest/gtest.h>
#include <string>
using namespace rOops;
class IComponent
{
public:
virtual ~IComponent() = default;
IComponent() = default;
virtual void init(short) = 0;
rOOPS_ADD_PROPERTY_INTERFACE_ABSTRACT(IComponent)
{
}
};
DECLARE_ENUM_CLASS(CameraType, std::uint8_t,
eNone = 0,
eType1,
eType2
);
rOOPS_DECLARE_ENUM_TYPE_INFO( CameraType, CameraType2String )
class Camera : public IComponent {
public:
Camera() = default;
void init(short p) override
{
height = width = p;
}
private:
CameraType type{CameraType::eNone};
int height{0};
int width{0};
rOOPS_ADD_PROPERTY_INTERFACE(Camera)
{
rOOPS_INHERIT(IComponent);
rOOPS_PROPERTY(type);
rOOPS_PROPERTY(height);
rOOPS_PROPERTY(width);
}
};
rOOPS_DECLARE_STL_LIST_TYPE_INFO(std::vector<long>)
class Mesh : public IComponent
{
public:
Mesh() = default;
void init(short p) override
{
indices.push_back(p);
path = std::to_string(p);
}
private:
std::vector<long> indices;
std::string path;
rOOPS_ADD_PROPERTY_INTERFACE(Mesh)
{
rOOPS_INHERIT(IComponent);
rOOPS_PROPERTY(indices);
rOOPS_PROPERTY(path);
}
};
class ILight : public IComponent
{
public:
~ILight() override = default;
ILight() = default;
rOOPS_ADD_PROPERTY_INTERFACE_ABSTRACT(ILight)
{
rOOPS_INHERIT(IComponent);
}
};
using Vec3 = std::array<double, 3>;
rOOPS_DECLARE_STL_ARRAY_TYPE_INFO(Vec3)
class PointLight : public ILight
{
public:
PointLight() = default;
void init(short p) override
{
AO[0] = Diffuse[1] = Specular[2] = p;
}
private:
Vec3 AO{};
Vec3 Diffuse{};
Vec3 Specular{};
rOOPS_ADD_PROPERTY_INTERFACE(PointLight)
{
rOOPS_INHERIT(ILight);
rOOPS_PROPERTY(AO);
rOOPS_PROPERTY(Diffuse);
rOOPS_PROPERTY(Specular);
}
};
rOOPS_DECLARE_STL_LIST_TYPE_INFO(std::vector<std::unique_ptr<IComponent>>)
TEST(AbstractClassesTest, IComponents)
{
std::vector<std::unique_ptr<IComponent>> v;
v.push_back(std::make_unique<Camera>()); v.back()->init(1);
v.push_back(std::make_unique<Mesh>()); v.back()->init(2);
v.push_back(std::make_unique<PointLight>()); v.back()->init(3);
std::stringstream strm(cStringStreamMode);
rOopsYamlFormat format(strm);
save(format, v, "Components");
std::cout << "========== save ==========" << std::endl;
std::cout << strm.str() << std::endl;
std::cout << "==========================" << std::endl;
rOopsYamlParser parser(strm, "Components");
std::vector<std::unique_ptr<IComponent>> v2;
load(parser, v2);
}
And here is the yaml file:
Components: !std::vector<std::unique_ptr<IComponent>>
- !Camera&94841900126576
IComponent:
type: eNone
height: 1
width: 1
- !Mesh&94841900120480
IComponent:
indices:
- 2
path: "2"
- !PointLight&94841900126768
ILight:
IComponent:
AO:
- 3
- 0
- 0
Diffuse:
- 0
- 3
- 0
Specular:
- 0
- 0
- 3