C++ JSON序列化

Vic*_*scó 44 c++ json

我想要一种尽可能自动地将对象序列化和反序列化为JSON的方法.

序列化: 对我来说,理想的方法是,如果我在实例中调用JSONSerialize(),它将返回一个带有JSON对象的字符串,该对象具有该对象的所有公共属性"name_of_property": "value".对于那些作为原语的值,它很简单,对于它应该尝试在每个JSONSerialize()或ToString()上调用的对象来递归序列化所有公共属性.对于集合,它也应该正常运行(只需矢量/数组就可以了).

反序列化:只需创建给定对象的实例(让我们说一只狗)并调用JSONDeserialize(json_string),这应该填充所有公共属性,在属性不是基元或需要的集合的情况下创建所需的对象.

一个例子应该像这样运行:

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"
Run Code Online (Sandbox Code Playgroud)

或者像那样:

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"
Run Code Online (Sandbox Code Playgroud)

我该如何轻松地将其拉下来?

Gui*_*cot 76

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};
}
Run Code Online (Sandbox Code Playgroud)

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

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

现在在您的课程中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")
    );
};
Run Code Online (Sandbox Code Playgroud)

我们需要迭代该列表.要迭代元组,有很多方法,但我首选的是:

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};
}
Run Code Online (Sandbox Code Playgroud)

如果编译器中有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>{})), ...);
}
Run Code Online (Sandbox Code Playgroud)

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

如果此方法不起作用或给编译器带来麻烦,则可以始终使用数组扩展技巧.

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

// 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;
}
Run Code Online (Sandbox Code Playgroud)

并序列化:

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;
}
Run Code Online (Sandbox Code Playgroud)

如果您想要递归序列化和反序列化,可以替换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!
Run Code Online (Sandbox Code Playgroud)

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

这段代码可以从一些改进中受益,当然可以在C++ 11中使用一些辅助功能.

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

这是一个完整的,有效的例子,由这个答案的各个代码片段组成.随意使用它.

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

  • 这是我见过的最精彩的解决方案. (3认同)
  • 您可以添加一个辅助宏来避免输入错误。#define PROPERTY(CLASS,MEMBER)属性(&CLASS :: MEMBER,#MEMBER)避免必须在代码中复制/粘贴成员的名称。将`property(&Dog :: barkType,“ barkType”)`替换为`PROPERTY(Dog,barkType)` (2认同)
  • @cppguy 是的,我可以做到这一点。但是,我发现语法非常“陌生”,而且大部分时间我都在处理序列化,其中一些名称以不同的名称进行了序列化。 (2认同)

小智 9

为此,您需要使用C/C++语言进行反射,而这种反映并不存在.您需要一些元数据来描述类的结构(成员,继承的基类).目前,C/C++编译器不会在构建的二进制文件中自动提供该信息.

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

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

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

    // 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 ;
Run Code Online (Sandbox Code Playgroud)

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

    // 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 ;
Run Code Online (Sandbox Code Playgroud)

输出映象:

>:~/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$ 
Run Code Online (Sandbox Code Playgroud)

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


Sig*_*erm 7

有什么比这更容易存在吗?谢谢 :))

C++不会在编译代码中存储类成员名称,也无法发现(在运行时)哪些成员(变量/方法)类包含.换句话说,您无法遍历结构的成员.因为没有这样的机制,您将无法为每个对象自动创建"JSONserialize".

但是,您可以使用任何json库来序列化对象,但是您必须自己为每个类编写序列化/反序列化代码.要么是这样,要么你必须创建类似于QVariantMap的可序列化类,它将被用来代替所有可序列化对象的结构.

换句话说,如果您可以为所有可序列化对象使用特定类型(或者为每个类自己编写序列化例程),那么就可以完成.但是,如果您想自动序列化每个可能的类,您应该忘记它.如果此功能对您很重要,请尝试其他语言.


Dav*_*gel 6

使用快速类型,您可以从 JSON 示例数据生成 C++ 序列化器和反序列化器。

例如,给定示例 JSON:

{
  "breed": "Boxer",
  "age": 5,
  "tail_length": 6.5
}
Run Code Online (Sandbox Code Playgroud)

快速类型生成:

{
  "breed": "Boxer",
  "age": 5,
  "tail_length": 6.5
}
Run Code Online (Sandbox Code Playgroud)

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

Dog dog = nlohmann::json::parse(jsonString);
Run Code Online (Sandbox Code Playgroud)