Как сериализовать объект json, не заключая его в подобъект, используя Cereal

Допустим, у меня есть класс на C++, например:

struct Point {
    int x, y, z;
};

Я хочу использовать Cereal для сериализации этой структуры в JSON. Поэтому я добавил функцию сериализации следующим образом:

struct Point {
    int x, y, z;
    template<class Archive>
    void serialize(Archive& ar) {
        ar(CEREAL_NVP(x),
           CEREAL_NVP(y),
           CEREAL_NVP(z));
    }
};

Это прекрасно работает, когда Point является членом другого объекта или элементом массива. Но если я хочу сделать Point основным объектом всего файла JSON, это не сработает должным образом. Например, со следующим кодом:

Point p { 1, 2, 3 };
cereal::JSONOutputArchive ar(std::cout);
ar(p);

Я получаю следующий вывод:

{
    "value0": {
        "x": 1,
        "y": 2,
        "z": 3
    }
}

Я хотел бы удалить ключ "value0" и поднять объект, чтобы он занимал весь файл, например:

{
    "x": 1,
    "y": 2,
    "z": 3
}

Единственный способ, которым я могу это сделать, - это повторно реализовать функцию сериализации, вручную добавив имена ключей.

Point p {1, 2, 3};
cereal::JSONOutputArchive ar(std::cout);
ar(cereal::make_nvp("x", p.x),
   cereal::make_nvp("y", p.y),
   cereal::make_nvp("z", p.z));

Есть ли способ сделать это, используя функцию сериализации, которую я уже реализовал для класса?


person Benjamin Lindley    schedule 15.11.2015    source источник


Ответы (3)


Ладно, разобрался. Довольно просто, просто нужно было вызывать функцию сериализации непосредственно из объекта, передавая архив, вместо того, чтобы передавать объект в архив.

Point p {1, 2, 3};
cereal::JSONOutputArchive ar(std::cout);
p.serialize(ar);
person Benjamin Lindley    schedule 15.11.2015
comment
К сожалению, это решение не работает для сериализации константных объектов, поскольку метод сериализации не может быть константным. - person Michal Fapso; 06.07.2020
comment
Тот факт, что мне пришлось найти этот ответ, просто говорит о том, насколько ужасна документация и образцы хлопьев. - person cen; 22.09.2020

Ответ Бенджамина - идеальное решение, если вы заранее знаете, что сериализуемый класс имеет метод serialize(). Поскольку Cereal поддерживает внутриклассовые/внеклассовые serialize(), разделение load()/save(), явное управление версиями; это не всегда так. Внутренние классы Cereal cereal::InputArchive и cereal::OutputArchive имеют множество шаблонных методов SFINAE для определения правильного метода сериализации, который следует использовать во время компиляции. Черты типов можно использовать для переключения нашего собственного переключателя шаблонов:

template< typename Class, typename Archive,
          typename std::enable_if< cereal::traits::has_member_serialize<Class, Archive>::value>::type* = nullptr>
inline static void serializeHelper(Class& cl, Archive& ar)
{
    cl.serialize(ar);
}

template< typename Class, typename Archive,
          typename std::enable_if< cereal::traits::has_member_save<Class, Archive>::value>::type* = nullptr>
inline static void serializeHelper(Class& cl, Archive& ar)
{
    cl.save(ar);
}

// More version could follow for remaining serialization types (external, versioned...)

template< typename Class, typename Archive,
          typename std::enable_if< cereal::traits::has_member_serialize<Class, Archive>::value>::type* = nullptr>
inline static void deserializeHelper(Class& cl, Archive& ar)
{
    cl.serialize(ar);
}

template< typename Class, typename Archive,
          typename std::enable_if< cereal::traits::has_member_load<Class, Archive>::value>::type* = nullptr>
inline static void deserializeHelper(Class& cl, Archive& ar)
{
    cl.load(ar);
}

// More version could follow for remaining deserialization types (external, versioned...)

Вызов serializeHelper(p, ar); автоматически выберет метод сериализации, предоставленный Point, так же, как Cereal делает это внутри.

person Gyorgy Szekely    schedule 18.05.2016
comment
Даже это решение не работает: оно не будет работать с константными объектами и выходными архивами. - person void.pointer; 22.06.2016

Вам нужно определить функции epilogue и prologue для вашего собственного типа, чтобы они не выполнялись, чтобы вместо этого не были выбраны функции из выбранного архива.

#include <cereal/archives/json.hpp>
#include <iostream>

struct Point {
    int x, y, z;
    template<class Archive>
    void serialize(Archive& ar) {
        ar(CEREAL_NVP(x),
           CEREAL_NVP(y),
           CEREAL_NVP(z));
    }
};

void epilogue(cereal::JSONOutputArchive&, const Point &){}
void prologue(cereal::JSONOutputArchive&, const Point &){}

int main()
{
    Point p { 1, 2, 3 };
    cereal::JSONOutputArchive ar(std::cout);
    ar(p); 

    return 0;
}
person Fabio A.    schedule 01.05.2020
comment
Идеальный! Это самое чистое решение для меня. - person Michal Fapso; 06.07.2020
comment
Я думал, что для этого можно использовать шаблоны для всех структур, но тогда компилятор жалуется на неоднозначный эпилог()/пролог(). - person Michal Fapso; 06.07.2020
comment
Ваше решение работает для сериализации объекта в строку JSON, но когда я хочу десериализовать его обратно из строки в объект, оно жалуется на rapidjson internal assertion failure: IsObject(). Когда я возвращаю закрывающий узел value0 в строку JSON, десериализация снова работает. Так это решение несовместимо с десериализацией? - person Michal Fapso; 06.07.2020
comment
@MichalFapso, если вы также хотите десериализовать, функции эпилога и пролога также должны быть адаптированы для JSONInputArchive. - person Fabio A.; 07.07.2020
comment
Оооо, конечно, спасибо, что указали на это, @Fabio A. :) - person Michal Fapso; 07.07.2020