Examples » Core » Data » Deserialization » Custom Deserializer

Implementing your own Deserializer.

To define your own deserializer type, you'll need to include core/data/des/deserializer.hpp. For simplicity, in this sample we'll use the following aliases:

#include <cubos/core/data/des/deserializer.hpp>

using cubos::core::data::Deserializer;
using cubos::core::reflection::Type;

We'll define a deserializer that will print the data to the standard output.

class MyDeserializer : public Deserializer
{
public:
    MyDeserializer();

protected:
    bool decompose(const Type& type, void* value) override;
};

In the constructor, we should set hooks to be called for deserializing primitive types or any other type we want to handle specifically.

In this example, we'll only handle int32_t, but usually you should at least cover all primitive types.

#include <cubos/core/reflection/external/primitives.hpp>

using cubos::core::reflection::reflect;

MyDeserializer::MyDeserializer()
{
    this->hook<int32_t>([](int32_t& value) {
        Stream::stdOut.print("enter an int32_t: ");
        Stream::stdIn.parse(value);
        return true;
    });
}

The only other thing you need to do is implement the deserializer::decompose method, which acts as a catch-all for any type without a specific hook.

Here, we can use traits such as FieldsTrait to access the fields of a type and write to them.

In this sample, we'll only be handling fields and arrays, but you should try to cover as many kinds of data as possible.

#include <cubos/core/reflection/traits/array.hpp>
#include <cubos/core/reflection/traits/fields.hpp>
#include <cubos/core/reflection/type.hpp>

using cubos::core::reflection::ArrayTrait;
using cubos::core::reflection::FieldsTrait;

bool MyDeserializer::decompose(const Type& type, void* value)
{
    if (type.has<ArrayTrait>())
    {
        const auto& arrayTrait = type.get<ArrayTrait>();
        auto arrayView = arrayTrait.view(value);

        auto length = static_cast<uint64_t>(arrayView.length());
        Stream::stdOut.printf("enter array size: ", length);
        Stream::stdIn.parse(length);

        for (std::size_t i = 0; i < static_cast<std::size_t>(length); ++i)
        {
            if (i == arrayView.length())
            {
                arrayView.insertDefault(i);
            }

            Stream::stdOut.printf("writing array[{}]: ", i);
            this->read(arrayTrait.elementType(), arrayView.get(i));
        }

        while (arrayView.length() > static_cast<std::size_t>(length))
        {
            arrayView.erase(static_cast<std::size_t>(length));
        }

        return true;
    }

We start by checking if the type can be viewed as an array. If it can, we'll ask the user how many elements they want the array to have. We resize it, and then, we recurse into the elements. If the type doesn't have this trait, we'll fallback into checking if it has fields.

    if (type.has<FieldsTrait>())
    {
        for (const auto& [field, fieldValue] : type.get<FieldsTrait>().view(value))
        {
            Stream::stdOut.printf("writing field '{}': ", field->name());
            if (!this->read(field->type(), fieldValue))
            {
                return false;
            }
        }

        return true;
    }

    CUBOS_WARN("Cannot decompose {}", type.name());
    return false;
}

If the type has fields, we'll iterate over them and ask the user to enter values for them. Otherwise, we'll fail by returning false.

Using our deserializer is as simple as constructing it and calling Deserializer::read on the data we want to deserialize.

In this case, we'll be deserializing a std::vector<glm::ivec3>, which is an array of objects with three int32_t fields.

#include <glm/vec3.hpp>

#include <cubos/core/reflection/external/glm.hpp>
#include <cubos/core/reflection/external/vector.hpp>

int main()
{
    std::vector<glm::ivec3> vec{};
    MyDeserializer des{};
    des.read(vec);

    Stream::stdOut.print("-----------\n");
    Stream::stdOut.print("Resulting vec: [ ");
    for (const auto& v : vec)
    {
        Stream::stdOut.printf("({}, {}, {}) ", v.x, v.y, v.z);
    }
    Stream::stdOut.print("]\n");
}

This should output the values you enter when you execute it.