【问题标题】:C++ JSON SerializationC++ JSON 序列化
【发布时间】:2013-07-07 03:48:48
【问题描述】:

我想要一种将对象序列化和反序列化为 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"

或者这样:

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"

我怎样才能轻松解决这个问题?

【问题讨论】:

  • @rhughes:我看过那个帖子……但我没有找到我要找的东西……这是否意味着我无法做到要吗?
  • 至少不直接。您所要求的需要反射,即在运行时枚举和评估类型属性的能力。 C++ 不能直接做到这一点。您也许可以找到一个库,但是如果没有您在某些时候明确指定可用字段以及它们的名称和类型,就无法做到这一点。
  • 你知道Java中是否有类似的东西吗?该项目正在评估中,我只想知道所有选项!
  • @VicençGascó:“类似的东西”应该在 python 中可用(虽然我非常不喜欢它,在 python 中使用 json 很容易)、lisp 和类似的语言。如果语言支持结构并具有“解释器”,您可以在其中立即键入和执行命令,那么此功能可能可用。

标签: c++ json


【解决方案1】:

有什么东西,像那样简单,存在吗?谢谢:))

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

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

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

【讨论】:

  • @TevoD:我不使用托管 C++,所以我不知道。自己测试一下。
  • 更多的是一种好奇心。我也不用。
【解决方案2】:

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

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

序列化很容易,但我们必须处理复杂的数据结构实现(例如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 输出),因此不太容易集成到项目中。

【讨论】:

  • 如果您想在启用调试符号的情况下执行此操作怎么办?
【解决方案3】:

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

【讨论】:

  • 链接已失效,答案无关紧要。
【解决方案4】:

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,您可以进一步扩展它以创建一个适用于 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")
    );
};

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

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>{})), ...);
}

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

如果此方法不起作用或给您的编译器带来麻烦,您可以随时使用array expansion trick

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

// 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;
}

如果要递归序列化和反序列化,可以将asAny替换为fromJson

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

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 的函数。

这是由该答案的各种代码 sn-p 制成的complete, working example。随意使用。

如 cmets 中所述,此代码不适用于 msvc。如果你想要一个兼容的代码,请参考这个问题:Pointer to member: works in GCC but not in VS2015

【讨论】:

  • @FanZhang 我假设使用的 json 库对于Json::Value 有一个重载的operator[](std::string)。如果您查看第 22 行的实时示例(coliru 链接),您会发现我的基本 json 结构有一个。
  • 这是我见过的最出色的解决方案。
  • 您可以添加一个辅助宏以避免拼写错误。 #define PROPERTY(CLASS, MEMBER) property(&amp;CLASS::MEMBER, #MEMBER) 以避免在代码中复制/粘贴成员的名称。将property(&amp;Dog::barkType, "barkType") 替换为PROPERTY(Dog, barkType)
  • @cppguy 是的,我本可以这样做。但是,我发现语法很“异类”,而且在我处理序列化的大部分时间里,一些名称被序列化为不同的名称。
  • @akshaydhule 在我为一家公司做的代码中,是的,它有效。秘密在于重现性:例如。为forJson 中循环中的每个值调用fromJson。它将递归地序列化/反序列化。
【解决方案5】:

试试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!" }

【讨论】:

  • 这个项目是否也存在于 Github 上?
  • @pudelwudel 现在不行。
  • 您是该库的维护者或开发者吗?
  • @pudelwudel 是的,我是。您希望它在 Github 上用作子模块还是个人喜好?如果有人需要,我可以在 github 上创建一个镜像存储库。如果有,请告诉我。
  • 那太好了。
【解决方案6】:

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

例如,给定示例 JSON:

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

quicktype 生成:

#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);

【讨论】:

  • 由于我已经得出结论,boost 和 nlohmann 的 JSON 库非常物有所值,所以这个解决方案是 perfect for me
【解决方案7】:

使用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);

不需要其他编码。

可以找到完整的例子:

【讨论】:

    【解决方案8】:

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

    列出的福利:

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

    此外,它在 MIT 许可证下。

    老实说:我还没有使用过它,但通过一些经验,我知道什么时候会遇到一个制作精良的 c++ 库。

    【讨论】:

      【解决方案9】:

      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
              }
          ]
      }
      

      【讨论】:

        【解决方案10】:

        这是我使用 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 }
        };
        

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

        【讨论】:

          猜你喜欢
          • 2012-08-26
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-01-06
          相关资源
          最近更新 更多