8000 RTTR and Cereal bridge code (support for serialization) · Issue #380 · rttrorg/rttr · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

RTTR and Cereal bridge code (support for serialization) #380

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
Belfer opened this issue Feb 23, 2025 · 0 comments
Open

RTTR and Cereal bridge code (support for serialization) #380

Belfer opened this issue Feb 23, 2025 · 0 comments

Comments

@Belfer
Copy link
Belfer commented Feb 23, 2025

So this isn't an issue per se, but I've found myself struggling with serialization of polymorphic types using RTTR and ideally the cereal lib. I'm happy that I have a first working version and I wanted to share it in case anyone trying to do the same has a good starting point. I plan to eventually make this into its own repo or maybe RTTR would like to use this in place of their current JSON example.

Anyhow, as a disclaimer I have to say that the code isn't very well tested and probably not a good idea to use in production, since its meant to be a starting point with some of the challenges already solved, I'm happy to use this issue thread for further improvements.

serial.hpp
namespace rttr
{
    namespace serial
    {
        namespace tags
        {
            static constexpr const char* no_serialize = "NO_SERIALIZE";
        }

        struct ObjectWrapper
        {
            rttr::variant data{};
        };

        struct EnumWrapper
        {
            rttr::variant data{};
        };
    }
}

namespace rttr
{
    namespace serial
    {
        template <class Archive, typename T>
        void save_val(Archive& archive, const rttr::string_view& name, const T& val)
        {
            if (!name.empty())
                archive(cereal::make_nvp(name.data(), val));
            else
                archive(val);
        }

        template <class Archive>
        void save_enum(Archive& archive, const rttr::variant& obj)
        {
            bool enum_is_str = false;
            auto enum_str = obj.to_string(&enum_is_str);
            archive(cereal::make_nvp("enum_str", enum_is_str ? enum_str : ""));

            bool enum_is_val = false;
            auto enum_val = obj.to_int32(&enum_is_val);
            archive(cereal::make_nvp("enum_val", enum_is_val ? enum_val : 0));
        }

        template<class Archive>
        bool save_basic_type(Archive& archive, const rttr::string_view& name, const rttr::variant& obj)
        {
            auto value_type = obj.get_type();
            auto wrapped_type = value_type.is_wrapper() ? value_type.get_wrapped_type() : value_type;
            bool is_wrapper = wrapped_type != value_type;

            const rttr::type& type = is_wrapper ? wrapped_type : value_type;
            const rttr::variant& var = is_wrapper ? obj.extract_wrapped_value() : obj;

            if (type.is_arithmetic())
            {
                if (type == rttr::type::get<bool>())
                    save_val(archive, name, var.to_bool());
                else if (type == rttr::type::get<char>())
                    save_val(archive, name, var.to_bool());
                else if (type == rttr::type::get<i8>())
                    save_val(archive, name, var.to_int8());
                else if (type == rttr::type::get<i16>())
                    save_val(archive, name, var.to_int16());
                else if (type == rttr::type::get<i32>())
                    save_val(archive, name, var.to_int32());
                else if (type == rttr::type::get<i64>())
                    save_val(archive, name, var.to_int64());
                else if (type == rttr::type::get<u8>())
                    save_val(archive, name, var.to_uint8());
                else if (type == rttr::type::get<u16>())
                    save_val(archive, name, var.to_uint16());
                else if (type == rttr::type::get<u32>())
                    save_val(archive, name, var.to_uint32());
                else if (type == rttr::type::get<u64>())
                    save_val(archive, name, var.to_uint64());
                else if (type == rttr::type::get<f32>())
                    save_val(archive, name, var.to_float());
                else if (type == rttr::type::get<f64>())
                    save_val(archive, name, var.to_double());

                return true;
            }
            else if (type.is_enumeration())
            {
                save_val(archive, name, EnumWrapper{ var });
                return true;
            }
            else if (type == rttr::type::get<String>())
            {
                save_val(archive, name, var.to_string());
                return true;
            }

            return false;
        }
        
        template<class Archive>
        void save_array(Archive& archive, const rttr::variant_sequential_view& view)
        {
            archive(cereal::make_size_tag(static_cast<cereal::size_type>(view.get_size())));
            
            for (const auto& item : view)
            {
                if (save_basic_type(archive, "", item))
                    continue;

                archive(ObjectWrappe
8000
r{ item });
            }
        }

        template<class Archive>
        void save_map(Archive& archive, const rttr::variant_associative_view& view)
        {
            static const rttr::string_view key_name{ "key" };
            static const rttr::string_view value_name{ "value" };

            archive(cereal::make_size_tag(static_cast<cereal::size_type>(view.get_size())));

            if (view.is_key_only_type())
            {
                for (const auto& item : view)
                {
                    if (save_basic_type(archive, "", item.first))
                        continue;

                    archive(ObjectWrapper{ item.first });
                }
            }
            else
            {
                for (const auto& item : view)
                {
                    archive(cereal::make_map_item(ObjectWrapper{ item.first }, ObjectWrapper{ item.second }));
                }
            }
        }

        inline rttr::instance unwrap_instance(const rttr::variant& obj)
        {
            if (obj.get_type().get_raw_type().is_wrapper())
            {
                rttr::variant unwrapped_inst{ obj };
                while (unwrapped_inst.get_type().get_raw_type().is_wrapper())
                {
                    unwrapped_inst = unwrapped_inst.extract_wrapped_value();
                }
                return unwrapped_inst;
            }

            return obj;
        }

        template<class Archive>
        void save_instance(Archive& archive, const rttr::variant& obj)
        {
            const rttr::instance inst = unwrap_instance(obj);
            ENSURE(inst.is_valid());

            bool is_wrapper = obj.get_type().get_raw_type().is_wrapper();
            bool is_derived = inst.get_type().get_raw_type() != inst.get_derived_type();
            if (is_wrapper && is_derived)
            {
                rttr::type actual_type = inst.get_derived_type();
                rttr::string_view type_name = actual_type.get_name();
                archive(cereal::make_nvp("@type", type_name.to_string()));
            }

            // Now write the properties.
            // (Use the underlying non-wrapper object, if applicable.)
            auto prop_list = inst.get_derived_type().get_properties();

            if (prop_list.empty())
            {
                bool ok = false;
                auto text = obj.to_string(&ok);
                archive(ok ? text : "unknown");
                return;
            }

            for (auto prop : prop_list)
            {
                if (prop.get_metadata(rttr::serial::tags::no_serialize))
                    continue;

                rttr::variant prop_value = prop.get_value(inst);
                if (!prop_value)
                    continue; // cannot serialize, because we cannot retrieve the value

                const auto prop_name = prop.get_name();
                if (save_basic_type(archive, prop_name, prop_value))
                    continue;

                archive(cereal::make_nvp(prop_name.data(), ObjectWrapper{ prop_value }));
            }
        }

        template<class Archive>
        void save_variant(Archive& archive, const rttr::variant& obj)
        {
            if (save_basic_type(archive, "", obj))
                return;

            if (obj.is_sequential_container())
            {
                save_array(archive, obj.create_sequential_view());
            }
            else if (obj.is_associative_container())
            {
                save_map(archive, obj.create_associative_view());
            }
            else
            {
                save_instance(archive, obj);
            }
        }

        template<class Archive>
        void save(Archive& archive, const EnumWrapper& wrapper)
        {
            save_enum(archive, wrapper.data);
        }

        template<class Archive>
        void save(Archive& archive, const ObjectWrapper& wrapper)
        {
            save_variant(archive, wrapper.data);
        }
    }
}

template<class Archive, typename T>
void save(Archive& archive, const T& data)
{
    rttr::serial::save_variant(archive, data);
}

namespace rttr
{
    namespace serial
    {
        template <class Archive, typename T>
        void load_val(Archive& archive, const rttr::string_view& name, rttr::variant& obj)
        {
            T val{};
            if (!name.empty())
                archive(cereal::make_nvp(name.data(), val));
            else
                archive(val);
            obj = val;
        }

        template <class Archive>
        void load_enum(Archive& archive, rttr::variant& obj)
        {
            String enum_str{};
            i32 enum_val{};

            archive(cereal::make_nvp("enum_str", enum_str));
            archive(cereal::make_nvp("enum_val", enum_val));

            if (!enum_str.empty())
                obj = enum_str;
            else
                obj = enum_val;
        }

        template <class Archive>
        bool load_basic_type(Archive& archive, const rttr::string_view& name, rttr::variant& obj)
        {
            auto value_type = obj.get_type();
            auto wrapped_type = value_type.is_wrapper() ? value_type.get_wrapped_type() : value_type;
            bool is_wrapper = wrapped_type != value_type;

            rttr::type type = is_wrapper ? wrapped_type : value_type;
            rttr::variant var = is_wrapper ? obj.extract_wrapped_value() : obj;

            if (type.is_arithmetic())
            {
                if (type == rttr::type::get<bool>())
                    load_val<Archive, bool>(archive, name, var);
                else if (type == rttr::type::get<char>())
                    load_val<Archive, bool>(archive, name, var);
                else if (type == rttr::type::get<i8>())
                    load_val<Archive, i8>(archive, name, var);
                else if (type == rttr::type::get<i16>())
                    load_val<Archive, i16>(archive, name, var);
                else if (type == rttr::type::get<i32>())
                    load_val<Archive, i32>(archive, name, var);
                else if (type == rttr::type::get<i64>())
                    load_val<Archive, i64>(archive, name, var);
                else if (type == rttr::type::get<u8>())
                    load_val<Archive, u8>(archive, name, var);
                else if (type == rttr::type::get<u16>())
                    load_val<Archive, u16>(archive, name, var);
                else if (type == rttr::type::get<u32>())
                    load_val<Archive, u32>(archive, name, var);
                else if (type == rttr::type::get<u64>())
                    load_val<Archive, u64>(archive, name, var);
                else if (type == rttr::type::get<f32>())
                    load_val<Archive, f32>(archive, name, var);
                else if (type == rttr::type::get<f64>())
                    load_val<Archive, f64>(archive, name, var);

                obj = var;
                return true;
            }
            else if (type.is_enumeration())
            {
                EnumWrapper wrapper{ var };

                if (!name.empty())
                    archive(cereal::make_nvp(name.data(), wrapper));
                else
                    archive(wrapper);

                obj = wrapper.data;
                return true;
            }
            else if (type == rttr::type::get<String>())
            {
                load_val<Archive, String>(archive, name, var);
                
                obj = var;
                return true;
            }

            return false;
        }

        template<class Archive>
        void load_array(Archive& archive, rttr::variant_sequential_view& view)
        {
            cereal::size_type size{};
            archive(cereal::make_size_tag(size));

            view.set_size(size);
            const rttr::type array_value_type = view.get_rank_type(1);

            for (cereal::size_type i = 0; i < size; ++i)
            {
                auto item = view.get_value(i);

                if (load_basic_type(archive, "", item))
                {
                    ENSURE(item.convert(array_value_type));
                    ENSURE(view.set_value(i, item));
                    continue;
                }

                rttr::variant wrapped_var = item.is_sequential_container() ? item : item.extract_wrapped_value();
                ObjectWrapper wrapper{ wrapped_var };
                archive(wrapper);
                ENSURE(wrapper.data.convert(array_value_type));
                ENSURE(view.set_value(i, wrapper.data));
            }
        }

        template <class Archive>
        void validate_variant(Archive& archive, rttr::variant& obj)
        {
            const rttr::instance inst{ obj };
            bool is_wrapper = obj.get_type().get_raw_type().is_wrapper();
            bool is_valid = is_wrapper ? inst.get_wrapped_instance().is_valid() : inst.is_valid();

            if (!is_valid)
            {
                if (is_wrapper)
                {
                    String type_name{};
                    archive(cereal::make_nvp("@type", type_name));

                    rttr::type actual_type = rttr::type::get_by_name(type_name);
                    obj = actual_type.create();
                }
                else
                {
                    obj = obj.get_type().create();
                }
            }
        }

        inline rttr::variant create_map_item(rttr::type type)
        {
            if (type.is_arithmetic())
            {
                if (type == rttr::type::get<bool>())
                    return false;
                else if (type == rttr::type::get<char>())
                    return false;
                else if (type == rttr::type::get<i8>())
                    return (i8)0;
                else if (type == rttr::type::get<i16>())
                    return (i16)0;
                else if (type == rttr::type::get<i32>())
                    return (i32)0;
                else if (type == rttr::type::get<i64>())
                    return (i64)0;
                else if (type == rttr::type::get<u8>())
                    return (u8)0;
                else if (type == rttr::type::get<u16>())
                    return (u16)0;
                else if (type == rttr::type::get<u32>())
                    return (u32)0;
                else if (type == rttr::type::get<u64>())
                    return (u64)0;
                else if (type == rttr::type::get<f32>())
                    return (f32)0;
                else if (type == rttr::type::get<f64>())
                    return (f64)0;
            }

            if (type.is_enumeration())
            {
                auto val = *type.get_enumeration().get_values().begin();
                return val;
            }

            if (type == rttr::type::get<String>())
                return String{};

            if (type.is_wrapper())
            {
                auto wrapped_type = type.get_wrapped_type().get_raw_type();
                auto val = wrapped_type.create();
                auto t = val.get_type();
                return val;
            }

            rttr::constructor ctor = type.get_constructor();
            for (auto& item : type.get_constructors())
            {
                if (item.get_instantiated_type() == type)
                    ctor = item;
            }
            return ctor.invoke();
        }

        template <class Archive>
        void load_map(Archive& archive, rttr::variant_associative_view& view)
        {
            cereal::size_type size{};
            archive(cereal::make_size_tag(size));

            for (cereal::size_type i = 0; i < size; ++i)
            {
                if (view.is_key_only_type())
                {
                    auto item = create_map_item(view.get_key_type());

                    if (load_basic_type(archive, "", item))
                    {
                        ENSURE(item.convert(view.get_key_type()));
                        auto ret = view.insert(item);
                        ENSURE(ret.second);
                        continue;
                    }

                    rttr::variant wrapped_var = item;// .is_sequential_container() ? item : item.extract_wrapped_value();
                    ObjectWrapper wrapper{ wrapped_var };
                    archive(wrapper);
                    ENSURE(wrapper.data.convert(view.get_key_type()));
                    auto ret = view.insert(wrapper.data);
                    ENSURE(ret.second);
                }
                else
                {
                    auto item_key = create_map_item(view.get_key_type());
                    auto item_value = create_map_item(view.get_value_type());

                    rttr::variant wrapped_key_var = item_key;// .is_sequential_container() ? item_key : item_key.extract_wrapped_value();
                    auto t1 = wrapped_key_var.get_type();
                    auto key_wrapper = ObjectWrapper{ wrapped_key_var };

                    rttr::variant wrapped_value_var = item_value;// .is_sequential_container() ? item_value : item_value.extract_wrapped_value();
                    auto t2 = wrapped_value_var.get_type();
                    auto value_wrapper = ObjectWrapper{ wrapped_value_var };

                    archive(cereal::make_map_item(key_wrapper, value_wrapper));

                    ENSURE(key_wrapper.data.convert(view.get_key_type()));
                    ENSURE(value_wrapper.data.convert(view.get_value_type()));

                    if (key_wrapper.data && value_wrapper.data)
                    {
                        auto ret = view.insert(key_wrapper.data, value_wrapper.data);
                        ENSURE(ret.second);
                    }
                }
            }
        }

        template<class Archive>
        void load_instance(Archive& archive, const rttr::instance& obj)
        {
            rttr::instance inst = obj.get_type().get_raw_type().is_wrapper() ? obj.get_wrapped_instance() : obj;
            const auto prop_list = inst.get_derived_type().get_properties();

            for (auto prop : prop_list)
            {
                if (prop.get_metadata(rttr::serial::tags::no_serialize))
                    continue;

                const rttr::type prop_type = prop.get_type();

                rttr::variant prop_value = prop.get_value(inst);
                if (!prop_value)
                    continue;

                const auto prop_name = prop.get_name();
                if (load_basic_type(archive, prop_name, prop_value))
                {
                    ENSURE(prop_value.convert(prop_type));
                    ENSURE(prop.set_value(inst, prop_value));
                    continue;
                }

                ObjectWrapper wrapper{ prop_value };
                archive(cereal::make_nvp(prop_name.data(), wrapper));
                ENSURE(prop.set_value(obj, wrapper.data));
            }
        }

        template <class Archive>
        void load_variant(Archive& archive, rttr::variant& obj)
        {
            if (load_basic_type(archive, "", obj))
                return;

            auto value_type = obj.get_type();
            auto wrapped_type = value_type.is_wrapper() ? value_type.get_wrapped_type() : value_type;
            bool is_wrapper = wrapped_type != value_type;

            if (obj.is_sequential_container())
            {
                load_array(archive, obj.create_sequential_view());
            }
            else if (obj.is_associative_container())
            {
                load_map(archive, obj.create_associative_view());
            }
            else
            {
                validate_variant(archive, obj);

                auto child_props = is_wrapper ? wrapped_type.get_properties() : value_type.get_properties();
                if (!child_props.empty())
                {
                    load_instance(archive, obj);
                }
                else
                {
                    String text{};
                    archive(text);
                }
            }
        }

        template<class Archive>
        void load(Archive& archive, EnumWrapper& wrapper)
        {
            load_enum(archive, wrapper.data);
        }

        template<class Archive>
        void load(Archive& archive, ObjectWrapper& wrapper)
        {
            load_variant(archive, wrapper.data);
        }
    }
}

template<class Archive, typename T>
void load(Archive& archive, T& data)
{
    rttr::variant var{ data };
    rttr::serial::load_variant(archive, var);
    var.convert(data);
}
example.cpp
enum class color
{
    red = 1,
    green = 2,
    blue = 3
};

struct point2d
{
    point2d() {}
    point2d(int x_, int y_) : x(x_), y(y_) {}
    int x = 0;
    int y = 0;
};

struct shape
{
    shape() {}
    shape(std::string n) : name(n) {}

    void set_visible(bool v) { visible = v; }
    bool get_visible() const { return visible; }

    color color_ = color::blue;
    std::string name = "";
    point2d position;
    std::map<color, point2d> dictionary;

    RTTR_ENABLE()
private:
    bool visible = false;
};

struct circle : shape
{
    circle() {}
    circle(std::string n) : shape(n) {}

    double radius = 5.2;
    std::vector<point2d> points;

    int no_serialize = 100;

    RTTR_ENABLE(shape)
};

struct rect : shape
{
    rect() {}
    rect(std::string n) : shape(n) {}

    std::map<std::string, std::shared_ptr<shape>> children;

    double width = 5.2;
    double height = 5.2;

    RTTR_ENABLE(shape)
};

RTTR_REGISTRATION
{
    rttr::registration::class_<shape>("shape")
        .constructor()(rttr::policy::ctor::as_std_shared_ptr)
        .property("visible", &shape::get_visible, &shape::set_visible)
        .property("color", &shape::color_)
        .property("name", &shape::name)
        .property("position", &shape::position)
        .property("dictionary", &shape::dictionary)
    ;

    rttr::registration::class_<circle>("circle")
        .constructor()(rttr::policy::ctor::as_std_shared_ptr)
        .property("radius", &circle::radius)
        .property("points", &circle::points)
        .property("no_serialize", &circle::no_serialize)
        (
            metadata(rttr::serial::tags::no_serialize, true)
        )
        ;

    rttr::registration::class_<rect>("rect")
        .constructor()(rttr::policy::ctor::as_std_shared_ptr)
        .property("width", &rect::width)
        .property("height", &rect::height)
        .property("children", &rect::children)
        ;

    rttr::registration::class_<point2d>("point2d")
        .constructor()(rttr::policy::ctor::as_object)
        .property("x", &point2d::x)
        .property("y", &point2d::y)
        ;


    rttr::registration::enumeration<color>("color")
        (
            value("red", color::red),
            value("blue", color::blue),
            value("green", color::green)
        );

    //https://github.com/rttrorg/rttr/issues/81
    //https://github.com/rttrorg/rttr/pull/132
    rttr::type::register_wrapper_converter_for_base_classes<std::shared_ptr<circle>>();
    rttr::type::register_wrapper_converter_for_base_classes<std::shared_ptr<rect>>();
}

void Example()
{
    {
        auto stream = std::ofstream("data.json");
        cereal::JSONOutputArchive archive(stream);

        std::shared_ptr<circle> s1{ new circle("Circle #1") };
        s1->set_visible(true);
        s1->points = std::vector<point2d>(2, point2d(1, 1));
        s1->points[1].x = 23;
        s1->points[1].y = 42;
        s1->position.x = 12;
        s1->position.y = 66;
        s1->radius = 15.123;
        s1->color_ = color::red;
        // additional braces are needed for a VS 2013 bug
        s1->dictionary = { { {color::green, {1, 2} }, {color::blue, {3, 4} }, {color::red, {5, 6} } } };
        s1->no_serialize = 12345;

        std::shared_ptr<rect> s2{ new rect("Rect #2") };
        s2->width = 10;
        s2->height = 20;
        s2->children.insert(std::make_pair("Child 1", new circle()));
        s2->children.insert(std::make_pair("Child 2", new rect()));

        std::vector<std::shared_ptr<shape>> obj{};
        obj.emplace_back(s1);
        obj.emplace_back(s2);
        archive(cereal::make_nvp("shapes", obj));
        archive(cereal::make_nvp("shape", s1));
    }

    {
        auto stream = std::ifstream("data.json");
        cereal::JSONInputArchive archive(stream);

        std::vector<std::shared_ptr<shape>> obj{};
        archive(cereal::make_nvp("shapes", obj));

        std::shared_ptr<shape> s1{};
        archive(cereal::make_nvp("shape", s1));
    }
}
data.json
{
    "shapes": [
        {
            "@type": "circle",
            "visible": true,
            "color": {
                "enum_str": "red",
                "enum_val": 1
            },
            "name": "Circle #1",
            "position": {
                "x": 12,
                "y": 66
            },
            "dictionary": [
                {
                    "key": {
                        "value0": {
                            "enum_str": "red",
                            "enum_val": 1
                        }
                    },
                    "value": {
                        "@type": "point2d",
                        "x": 5,
                        "y": 6
                    }
                },
                {
                    "key": {
                        "value0": {
                            "enum_str": "green",
                            "enum_val": 2
                        }
                    },
                    "value": {
                        "@type": "point2d",
                        "x": 1,
                        "y": 2
                    }
                },
                {
                    "key": {
                        "value0": {
                            "enum_str": "blue",
                            "enum_val": 3
                        }
                    },
                    "value": {
                        "@type": "point2d",
                        "x": 3,
                        "y": 4
                    }
                }
            ],
            "radius": 15.123,
            "points": [
                {
                    "@type": "point2d",
                    "x": 1,
                    "y": 1
                },
                {
                    "@type": "point2d",
                    "x": 23,
                    "y": 42
                }
            ]
        },
        {
            "@type": "rect",
            "visible": false,
            "color": {
                "enum_str": "blue",
                "enum_val": 3
            },
            "name": "Rect #2",
            "position": {
                "x": 0,
                "y": 0
            },
            "dictionary": [],
            "width": 10.0,
            "height": 20.0,
            "children": [
                {
                    "key": {
                        "value0": "Child 1"
                    },
                    "value": {
                        "@type": "circle",
                        "visible": false,
                        "color": {
                            "enum_str": "blue",
                            "enum_val": 3
                        },
                        "name": "",
                        "position": {
                            "x": 0,
                            "y": 0
                        },
                        "dictionary": [],
                        "radius": 5.2,
                        "points": []
                    }
                },
                {
                    "key": {
                        "value0": "Child 2"
                    },
                    "value": {
                        "@type": "rect",
                        "visible": false,
                        "color": {
                            "enum_str": "blue",
                            "enum_val": 3
                        },
                        "name": "",
                        "position": {
                            "x": 0,
                            "y": 0
                        },
                        "dictionary": [],
                        "width": 5.2,
                        "height": 5.2,
                        "children": []
                    }
                }
            ]
        }
    ],
    "shape": {
        "@type": "circle",
        "visible": true,
        "color": {
            "enum_str": "red",
            "enum_val": 1
        },
        "name": "Circle #1",
        "position": {
            "x": 12,
            "y": 66
        },
        "dictionary": [
            {
                "key": {
                    "value0": {
                        "enum_str": "red",
                        "enum_val": 1
                    }
                },
                "value": {
                    "@type": "point2d",
                    "x": 5,
                    "y": 6
                }
            },
            {
                "key": {
                    "value0": {
                        "enum_str": "green",
                        "enum_val": 2
                    }
                },
                "value": {
                    "@type": "point2d",
                    "x": 1,
                    "y": 2
                }
            },
            {
                "key": {
                    "value0": {
                        "enum_str": "blue",
                        "enum_val": 3
                    }
                },
                "value": {
                    "@type": "point2d",
                    "x": 3,
                    "y": 4
                }
            }
        ],
        "radius": 15.123,
        "points": [
            {
                "@type": "point2d",
                "x": 1,
                "y": 1
            },
            {
                "@type": "point2d",
                "x": 23,
                "y": 42
            }
        ]
    }
}
@Belfer Belfer changed the title RTTR and Cereal bridge code RTTR and Cereal bridge code (support for serialization) Feb 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant
0