Search code examples
c++jsonserializationcereal

Serialization and inheritance in Cereal


I would like my program to save and read a Config structure in a JSON file.

However, I have a problem with generating the correct JSON file. Probably the problem is inheritance.

JSON Output (Incorrect):

  {
        "config": {
            "confVector": [
                {
                    "common": "a"
                },
                {
                    "common": "b"
                }
            ]
        }
    }

Expected (correct) JSON:

  {
        "config": {
            "confVector": [
                {
                    "common": "a",
                    "a" : 1
                },
                {
                    "common": "b",
                    "b" : "b"
                }
            ]
        }
    }

Code :

Base struct with common element

struct Base
{
    std::string common;

    template <class Archive>
    void serialize(Archive &ar)
    {
        ar(CEREAL_NVP(common));
    }
};

Two specific structures

struct A : public Base
{
    int a;

    template <class Archive>
    void serialize(Archive &ar)
    {
        ar(cereal::make_nvp("Base", cereal::base_class<Base>(this)));
        ar(cereal::make_nvp("a", a));
    }
};

struct B : public Base
{
    std::string b;

    template <class Archive>
    void serialize(Archive &ar)
    {
        ar(cereal::make_nvp("Base", cereal::base_class<Base>(this)));
        ar(cereal::make_nvp("b", b));
    }
};

struct Config
{
    std::vector<Base> confVector;

    template <class Archive>
    void serialize(Archive &ar)
    {
        ar(CEREAL_NVP(confVector));
    }
};
CEREAL_REGISTER_POLYMORPHIC_RELATION(Base, A)
CEREAL_REGISTER_POLYMORPHIC_RELATION(Base, B)

Main: test save to json file

int main()
{
    std::string workPath = MAKE_STR(PLC_PROGRAM);

    Config config;

    A a;
    a.a      = 1;
    a.common = "a";

    B b;
    b.b      = "b";
    b.common = "b";

    config.confVector.push_back(a);
    config.confVector.push_back(b);

    std::ofstream outstream;
    outstream.open(workPath + "/test.json");

    {
        cereal::JSONOutputArchive ar(outstream);
        ar(cereal::make_nvp("config", config));
    }

    outstream.close();
}

Solution

  • I solved this problem.

    struct Base
    {
        Base()          = default;
        virtual ~Base() = default;
    
        std::string common;
    
        template <class Archive>
        void serialize(Archive &ar)
        {
            ar(CEREAL_NVP(common));
        }
    };
    
    struct A : public Base
    {
        A() = default;
    
        A(int v)
        {
            a = v;
        }
    
        int a;
    
        template <class Archive>
        void serialize(Archive &ar)
        {
            ar(cereal::make_nvp("Base", cereal::base_class<Base>(this)));
            ar(a);
        }
    };
    
    struct B : public Base
    {
        B() = default;
    
        B(std::string text)
        {
            b = text;
        }
    
        std::string b;
    
        template <class Archive>
        void serialize(Archive &ar)
        {
            ar(cereal::make_nvp("Base", cereal::base_class<Base>(this)));
            ar(b);
        }
    };
    
    struct Config
    {
        std::vector<std::shared_ptr<Base>> vector;
    
        template <class Archive>
        void serialize(Archive &ar)
        {
            ar(vector);
        }
    };
    
    CEREAL_REGISTER_TYPE(A)
    
    CEREAL_REGISTER_TYPE_WITH_NAME(B, "ClassB")
    
    CEREAL_REGISTER_POLYMORPHIC_RELATION(Base, A)
    CEREAL_REGISTER_POLYMORPHIC_RELATION(Base, B)
    
    int main()
    {
    
        std::string workPath = "/home/user/"
    
        {
            std::ofstream os(workPath + "polymorphism_test.json");
            cereal::JSONOutputArchive oarchive(os);
    
            std::shared_ptr<Base> ptr1 = std::make_shared<A>(123);
            std::shared_ptr<Base> ptr2 = std::make_shared<B>("foobar");
    
            Config op;
            op.vector.push_back(ptr1);
            op.vector.push_back(ptr2);
    
            oarchive(op);
        }
    
        {
            std::ifstream is(workPath + "polymorphism_test.json");
            cereal::JSONInputArchive iarchive(is);
            Config ip;
            iarchive(ip);
        }
    
        return 0;
    }
    

    Output:

    {
        "value0": {
            "value0": [
                {
                    "polymorphic_id": 2147483649,
                    "polymorphic_name": "A",
                    "ptr_wrapper": {
                        "id": 2147483649,
                        "data": {
                            "Base": {
                                "common": ""
                            },
                            "value0": 123
                        }
                    }
                },
                {
                    "polymorphic_id": 2147483650,
                    "polymorphic_name": "ClassB",
                    "ptr_wrapper": {
                        "id": 2147483650,
                        "data": {
                            "Base": {
                                "common": ""
                            },
                            "value0": "foobar"
                        }
                    }
                }
            ]
        }
    }