59

I want a way to serialize and deserialize Objects to JSON, as automatic as possible.

Serialize: For me, the ideal way is that if I call in an instance JSONSerialize() it returns an string with a JSON object that has all the public properties of the object as "name_of_property": "value". For those values that are primitives, it is straightforward, for objects it should try to call on each JSONSerialize() or ToString() or something like that to recursively serialize all the public properties. For collections it should also behave correctly (just vectors/arrays will be ok).

Deserialize: Just make an instance of the given object (let's say a dog) and call JSONDeserialize(json_string), and that should fill all the public properties, creating the needed objects in case that the properties are not primitives, or the needed collections.

An example should run like that:

Dog *d1 = new Dog();
d1->name = "myDog";

string serialized = d1->JSONSerialize();

Dog *d2 = new Dog();
d2->JSONDeserialize(serialized);
std::cout << d2->name; // This will print "myDog"

Or like that:

Dog *d1 = new Dog();
d1->name = "myDog";

string serialized = JSONSerializer.Serialize(d1);

Dog *d2 = JSONSerializer.Deserialize(serialized, Dog);
std::cout << d2->name; // This will print "myDog"

How can I pull this off easily?

4

10 回答 10

100

C++ 中没有反射。真的。但是如果编译器不能为你提供你需要的元数据,你可以自己提供。

让我们从创建一个属性结构开始:

template<typename Class, typename T>
struct PropertyImpl {
    constexpr PropertyImpl(T Class::*aMember, const char* aName) : member{aMember}, name{aName} {}

    using Type = T;

    T Class::*member;
    const char* name;
};

template<typename Class, typename T>
constexpr auto property(T Class::*member, const char* name) {
    return PropertyImpl<Class, T>{member, name};
}

当然,您也可以使用property一个 setter 和 getter 而不是指向成员的指针,并且可能只读取您想要序列化的计算值的属性。如果您使用 C++17,您可以进一步扩展它以创建一个适用于 lambda 的属性。

好的,现在我们有了编译时自省系统的构建块。

现在在您的班级Dog中,添加您的元数据:

struct Dog {
    std::string barkType;
    std::string color;
    int weight = 0;
    
    bool operator==(const Dog& rhs) const {
        return std::tie(barkType, color, weight) == std::tie(rhs.barkType, rhs.color, rhs.weight);
    }
    
    constexpr static auto properties = std::make_tuple(
        property(&Dog::barkType, "barkType"),
        property(&Dog::color, "color"),
        property(&Dog::weight, "weight")
    );
};

我们将需要在该列表上进行迭代。要迭代一个元组,有很多方法,但我更喜欢这样:

template <typename T, T... S, typename F>
constexpr void for_sequence(std::integer_sequence<T, S...>, F&& f) {
    using unpack_t = int[];
    (void)unpack_t{(static_cast<void>(f(std::integral_constant<T, S>{})), 0)..., 0};
}

如果 C++17 折叠表达式在您的编译器中可用,则for_sequence可以简化为:

template <typename T, T... S, typename F>
constexpr void for_sequence(std::integer_sequence<T, S...>, F&& f) {
    (static_cast<void>(f(std::integral_constant<T, S>{})), ...);
}

这将为整数序列中的每个常量调用一个函数。

如果此方法不起作用或给您的编译器带来麻烦,您可以随时使用数组扩展技巧

现在您有了所需的元数据和工具,您可以遍历属性以反序列化:

// unserialize function
template<typename T>
T fromJson(const Json::Value& data) {
    T object;

    // We first get the number of properties
    constexpr auto nbProperties = std::tuple_size<decltype(T::properties)>::value;
    
    // We iterate on the index sequence of size `nbProperties`
    for_sequence(std::make_index_sequence<nbProperties>{}, [&](auto i) {
        // get the property
        constexpr auto property = std::get<i>(T::properties);

        // get the type of the property
        using Type = typename decltype(property)::Type;

        // set the value to the member
        // you can also replace `asAny` by `fromJson` to recursively serialize
        object.*(property.member) = Json::asAny<Type>(data[property.name]);
    });

    return object;
}

对于序列化:

template<typename T>
Json::Value toJson(const T& object) {
    Json::Value data;

    // We first get the number of properties
    constexpr auto nbProperties = std::tuple_size<decltype(T::properties)>::value;
    
    // We iterate on the index sequence of size `nbProperties`
    for_sequence(std::make_index_sequence<nbProperties>{}, [&](auto i) {
        // get the property
        constexpr auto property = std::get<i>(T::properties);

        // set the value to the member
        data[property.name] = object.*(property.member);
    });

    return data;
}

如果你想要递归序列化和反序列化,你可以替换asAnyfromJson.

现在你可以像这样使用你的函数:

Dog dog;

dog.color = "green";
dog.barkType = "whaf";
dog.weight = 30;

Json::Value jsonDog = toJson(dog); // produces {"color":"green", "barkType":"whaf", "weight": 30}
auto dog2 = fromJson<Dog>(jsonDog);

std::cout << std::boolalpha << (dog == dog2) << std::endl; // pass the test, both dog are equal!

完毕!不需要运行时反射,只需一些 C++14 的优点!

这段代码可以从一些改进中受益,当然可以通过一些调整与 C++11 一起使用。

请注意,需要编写asAny函数。它只是一个接受Json::Value并调用正确as...函数或另一个的函数fromJson

这是一个完整的工作示例,由该答案的各种代码片段组成。随意使用它。

如评论中所述,此代码不适用于 msvc。如果你想要一个兼容的代码,请参考这个问题:指向成员的指针:在 GCC 中工作,但在 VS2015 中不工作

于 2015-12-08T20:26:32.287 回答
14

为此,您需要在不存在的 C/C++ 中进行反射。您需要有一些描述类结构的元数据(成员、继承的基类)。目前,C/C++ 编译器不会在构建的二进制文件中自动提供该信息。

我也有同样的想法,我使用GCC XML项目来获取这些信息。它输出描述类结构的 XML 数据。我已经建立了一个项目,并在此页面中解释了一些关键点:

序列化很容易,但我们必须处理复杂的数据结构实现(例如 std::string,std::map),这些实现使用分配的缓冲区。反序列化更复杂,您需要重建对象及其所有成员,以及对 vtables 的引用……这是一个痛苦的实现。

例如,您可以像这样序列化:

    // Random class initialization
    com::class1* aObject = new com::class1();

    for (int i=0; i<10; i++){
            aObject->setData(i,i);
    }      

    aObject->pdata = new char[7];
    for (int i=0; i<7; i++){
            aObject->pdata[i] = 7-i;
    }
    // dictionary initialization
    cjson::dictionary aDict("./data/dictionary.xml");

    // json transformation
    std::string aJson = aDict.toJson<com::class1>(aObject);

    // print encoded class
    cout << aJson << std::endl ;

要反序列化数据,它的工作方式如下:

    // decode the object
    com::class1* aDecodedObject = aDict.fromJson<com::class1>(aJson);

    // modify data
    aDecodedObject->setData(4,22);

    // json transformation
    aJson = aDict.toJson<com::class1>(aDecodedObject);
   
    // print encoded class
    cout << aJson << std::endl ;

输出:

>:~/cjson$ ./main
{"_index":54,"_inner":  {"_ident":"test","pi":3.141593},"_name":"first","com::class0::_type":"type","com::class0::data":[0,1,2,3,4,5,6,7,8,9],"com::classb::_ref":"ref","com::classm1::_type":"typem1","com::classm1::pdata":[7,6,5,4,3,2,1]}
{"_index":54,"_inner":{"_ident":"test","pi":3.141593},"_name":"first","com::class0::_type":"type","com::class0::data":[0,1,2,3,22,5,6,7,8,9],"com::classb::_ref":"ref","com::classm1::_type":"typem1","com::classm1::pdata":[7,6,5,4,3,2,1]}
>:~/cjson$ 

通常这些实现依赖于编译器(例如 ABI 规范),并且需要外部描述才能工作(GCCXML 输出),因此不太容易集成到项目中。

于 2013-12-15T19:01:05.297 回答
9

Does anything, easy like that, exists?? THANKS :))

C++ does not store class member names in compiled code, and there's no way to discover (at runtime) which members (variables/methods) class contains. In other words, you cannot iterate through members of a struct. Because there's no such mechanism, you won't be able to automatically create "JSONserialize" for every object.

You can, however, use any json library to serialize objects, BUT you'll have to write serialization/deserialization code yourself for every class. Either that, or you'll have to create serializeable class similar to QVariantMap that'll be used instead of structs for all serializeable objects.

In other words, if you're okay with using specific type for all serializeable objects (or writing serialization routines yourself for every class), it can be done. However, if you want to automatically serialize every possible class, you should forget about it. If this feature is important to you, try another language.

于 2013-07-09T14:25:04.297 回答
6

使用quicktype,您可以从 JSON 样本数据生成 C++ 序列化器和反序列化器。

例如,给定示例 JSON:

{
  "breed": "Boxer",
  "age": 5,
  "tail_length": 6.5
}

快速类型生成:

#include "json.hpp"

namespace quicktype {
    using nlohmann::json;

    struct Dog {
        int64_t age;
        std::string breed;
        double tail_length;
    };


    inline json get_untyped(const json &j, const char *property) {
        if (j.find(property) != j.end()) {
            return j.at(property).get<json>();
        }
        return json();
    }
}

namespace nlohmann {

    inline void from_json(const json& _j, struct quicktype::Dog& _x) {
        _x.age = _j.at("age").get<int64_t>();
        _x.breed = _j.at("breed").get<std::string>();
        _x.tail_length = _j.at("tail_length").get<double>();
    }

    inline void to_json(json& _j, const struct quicktype::Dog& _x) {
        _j = json{{"age", _x.age}, {"breed", _x.breed}, {"tail_length", _x.tail_length}};
    }
}

要解析 Dog JSON 数据,请包含上面的代码,安装Boostjson.hpp,然后执行以下操作:

Dog dog = nlohmann::json::parse(jsonString);
于 2017-10-31T16:43:27.887 回答
4

如果有人仍然有这个需求(我有),我自己写了一个库来处理这个问题。见这里。它不是完全自动的,因为您必须描述类中的所有字段,但它与我们所能得到的一样接近,因为 C++ 缺乏反射。

于 2014-10-04T09:21:51.807 回答
2

jsoncons C++标头库还支持 JSON 文本和 C++ 对象之间的转换。为所有定义了 json_type_traits 的 C++ 类定义了解码和编码。已经支持标准库容器,并且 json_type_traits 可以专门用于 jsoncons 命名空间中的用户类型。

下面是一个例子:

#include <iostream>
#include <jsoncons/json.hpp>

namespace ns {
    enum class hiking_experience {beginner,intermediate,advanced};

    class hiking_reputon
    {
        std::string rater_;
        hiking_experience assertion_;
        std::string rated_;
        double rating_;
    public:
        hiking_reputon(const std::string& rater,
                       hiking_experience assertion,
                       const std::string& rated,
                       double rating)
            : rater_(rater), assertion_(assertion), rated_(rated), rating_(rating)
        {
        }

        const std::string& rater() const {return rater_;}
        hiking_experience assertion() const {return assertion_;}
        const std::string& rated() const {return rated_;}
        double rating() const {return rating_;}
    };

    class hiking_reputation
    {
        std::string application_;
        std::vector<hiking_reputon> reputons_;
    public:
        hiking_reputation(const std::string& application, 
                          const std::vector<hiking_reputon>& reputons)
            : application_(application), 
              reputons_(reputons)
        {}

        const std::string& application() const { return application_;}
        const std::vector<hiking_reputon>& reputons() const { return reputons_;}
    };

} // namespace ns

// Declare the traits using convenience macros. Specify which data members need to be serialized.

JSONCONS_ENUM_TRAITS_DECL(ns::hiking_experience, beginner, intermediate, advanced)
JSONCONS_ALL_CTOR_GETTER_TRAITS(ns::hiking_reputon, rater, assertion, rated, rating)
JSONCONS_ALL_CTOR_GETTER_TRAITS(ns::hiking_reputation, application, reputons)

using namespace jsoncons; // for convenience

int main()
{

std::string data = R"(
    {
       "application": "hiking",
       "reputons": [
       {
           "rater": "HikingAsylum",
           "assertion": "advanced",
           "rated": "Marilyn C",
           "rating": 0.90
         }
       ]
    }
)";

    // Decode the string of data into a c++ structure
    ns::hiking_reputation v = decode_json<ns::hiking_reputation>(data);

    // Iterate over reputons array value
    std::cout << "(1)\n";
    for (const auto& item : v.reputons())
    {
        std::cout << item.rated() << ", " << item.rating() << "\n";
    }

    // Encode the c++ structure into a string
    std::string s;
    encode_json<ns::hiking_reputation>(v, s, indenting::indent);
    std::cout << "(2)\n";
    std::cout << s << "\n";
}    

输出:

(1)
Marilyn C, 0.9
(2)
{
    "application": "hiking",
    "reputons": [
        {
            "assertion": "advanced",
            "rated": "Marilyn C",
            "rater": "HikingAsylum",
            "rating": 0.9
        }
    ]
}
于 2019-03-20T01:35:38.943 回答
2

使用ThorsSerializer

Dog *d1 = new Dog();
d1->name = "myDog";

std::stringstream  stream << ThorsAnvil::Serialize::jsonExport(d1);
string serialized = stream.str();

Dog *d2 = nullptr;
stream >> ThorsAnvil::Serialize::jsonImport(d2);
std::cout << d2->name; // This will print "myDog"

我认为这与您的原作非常接近。
有一点点设置。您需要声明您的类是可序列化的。

#include "ThorSerialize/Traits.h"
#include "ThorSerialize/JsonThor.h"

struct Dog
{
    std::string  name;
};

// Declare the "Dog" class is Serializable; Serialize the member "name"
ThorsAnvil_MakeTrait(Dog, name);

不需要其他编码。

可以找到完整的示例:

于 2018-08-07T19:39:46.250 回答
1

尚未提及,尽管它是我搜索结果中的第一个:https ://github.com/nlohmann/json

列出的特权:

  • 直观的语法(看起来很棒!)
  • 要包含的单个头文件,仅此而已
  • 荒谬的测试

此外,它在 MIT 许可证下。

老实说:我还没有使用过它,但是通过一些经验,我有一个诀窍来确定何时遇到一个制作精良的 c++ 库。

于 2018-08-23T22:33:53.630 回答
1

尝试json_dto。它只有标题且易于使用。

简单的例子:

struct message_t
{
  std::string m_from;
  std::string m_text;

  // Entry point for json_dto.
  template < typename JSON_IO >
  void
  json_io( JSON_IO & io )
  {
    io
      & json_dto::mandatory( "from", m_from )
      & json_dto::mandatory( "text", m_text );
  }
};

这将可以JSON相互转换:

{ "from" : "json_dto", "text" : "Hello world!" }
于 2016-10-06T19:38:42.953 回答
0

这是我使用 Qt 的尝试:https ://github.com/carlonluca/lqobjectserializer 。像这样的 JSON:

{"menu": {
    "header": "SVG Viewer",
    "items": [
        {"id": "Open"},
        {"id": "OpenNew", "label": "Open New"},
        null,
        {"id": "ZoomIn", "label": "Zoom In"},
        {"id": "ZoomOut", "label": "Zoom Out"},
        {"id": "OriginalView", "label": "Original View"},
        null,
        {"id": "Quality"},
        {"id": "Pause"},
        {"id": "Mute"},
        null,
        {"id": "Find", "label": "Find..."},
        {"id": "FindAgain", "label": "Find Again"},
        {"id": "Copy"},
        {"id": "CopyAgain", "label": "Copy Again"},
        {"id": "CopySVG", "label": "Copy SVG"},
        {"id": "ViewSVG", "label": "View SVG"},
        {"id": "ViewSource", "label": "View Source"},
        {"id": "SaveAs", "label": "Save As"},
        null,
        {"id": "Help"},
        {"id": "About", "label": "About Adobe CVG Viewer..."}
    ]
}}

可以通过声明如下类来反序列化:

L_BEGIN_CLASS(Item)
L_RW_PROP(QString, id, setId, QString())
L_RW_PROP(QString, label, setLabel, QString())
L_END_CLASS

L_BEGIN_CLASS(Menu)
L_RW_PROP(QString, header, setHeader)
L_RW_PROP_ARRAY_WITH_ADDER(Item*, items, setItems)
L_END_CLASS

L_BEGIN_CLASS(MenuRoot)
L_RW_PROP(Menu*, menu, setMenu, nullptr)
L_END_CLASS

和写作:

LDeserializer<MenuRoot> deserializer;
QScopedPointer<MenuRoot> g(deserializer.deserialize(jsonString));

您还需要为元对象注入一次映射:

QHash<QString, QMetaObject> factory {
    { QSL("Item*"), Item::staticMetaObject },
    { QSL("Menu*"), Menu::staticMetaObject }
};

我正在寻找一种方法来避免这种情况。

于 2020-07-18T08:35:02.027 回答