【问题标题】:Converting C++ class to JSON将 C++ 类转换为 JSON
【发布时间】:2023-04-07 22:51:02
【问题描述】:

我想创建一个包含我的类的实例变量的 JSON 字符串。

例如,

class Example {  
    std::string string;  
    std::map<std::string, std:string> map;  
    std::vector<int> vector;  
};

会变成:

{
    "string":"the-string-value",
    "map": {
        "key1":"val1",
        "key2":"val2"
    },
    "vector":[1,2,3,4]
}

我研究了几个用于创建 JSON 的 C++ 库,它们看起来都非常复杂。我想要类似于 Javascript 的 JSON.stringify(object) 的东西。换句话说,只需将 std::map 传递给它并接收一个字符串。该映射可以包含其他映射、向量、列表、字符串、数字和布尔值。

最好的方法是什么?

感谢您的帮助。

编辑

我研究了以下内容:

json精神、jsoncpp、zoolib、JOST、CAJUN、libjson、nosjob、JsonBox、jsonme--

据我所知,我可以在下面的答案中构造一个单独的 JSON 对象并转换为 JSON 我希望能够将我的东西存储在标准集合中并进行转换。

编辑 2

好的,放弃对类进行序列化的想法,因为 C++ 缺乏反射,这似乎是不可能的。

有没有一种很好的方法可以将包含 std:maps、std::vectors、std::lists、数字、字符串和布尔值的 std::map 转换为 JSON,而无需更改数据类型或将数据复制到新的数据类型?

谢谢。

【问题讨论】:

  • 你看过哪些库?所以我们知道什么你觉得复杂
  • 我已经更新了我的帖子。复杂性在于需要做很多工作才能做一些我认为非常简单的事情。我真的觉得我错过了一些东西,可能是一些明显的东西。
  • 你没有遗漏任何东西,这在 C++ 中是不可能的(以你描述的形式)。
  • 您可能会以错误的方式处理此问题。这样的“仅数据”类可能应该是std::tuple;并且您应该能够很容易地构建一些模板操作来输出元组的 JSON 表示。

标签: c++ json serialization


【解决方案1】:

这是一个解决方案,允许您使用现有的类/结构,而无需在自定义字典中手动创建和维护您的键和值。它使用 nlohmann json。

nlohmann::json 允许您进行任意类型转换。这里https://nlohmann.github.io/json/features/arbitrary_types/有更详细的描述。

代码:

// example.cc
#include <string>
#include <iostream>
#include <map>
#include <vector>

#include "third_party/nlohmann/json.hpp"

struct Example {
    std::string name;
    std::map<std::string, int> dict;
    std::vector<int> vec;
    std::string this_member_is_not_part_of_the_json;
};

// Use NLOHMANN_DEFINE_TYPE_INTRUSIVE if your members are private.
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Example,
    name,
    dict,
    vec);

int main() {
  Example example;
  example.name = "Example";
  example.dict = {{"AnyKey", 42}};
  example.vec = {1, 2, 3};

  nlohmann::json json;
  to_json(json, example);  // "generated" function

  std::cout << json.dump(1) << std::endl;

  return 0;
}

编译代码:

g++ -I third_party/nlohmann/ example.cc -o example

运行./example 输出:

{
 "dict": {
  "AnyKey": 42
 },
 "name": "Example",
 "vec": [
  1,
  2,
  3
 ]
}

解释:

注意我如何在结构定义之外使用NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE,这会在同一命名空间中生成函数to_json(...)

  • json.dump(1) 将 json 转换为格式化字符串。

【讨论】:

    【解决方案2】:

    我是https://github.com/beached/daw_json_link 的作者。你是对的,C++ 目前没有反射,如果能把简单的东西排除在外,那会很好。但是通过定义一个简单的声明式映射,JSON Link 将为您的类型提供一个类型检查的解析器。例如,您指定的类可以映射为:

        class Example {  
          std::string string;  
          std::map<std::string, std:string> map;  
          std::vector<int> vector;  
        };
            
        namespace daw::json {
          template<> 
          struct json_data_contract<Example> {
            using type = json_member_list<
              json_link<"string", std::string>, 
              json_link<"map", std::map<std::string, std::string>>
              json_link<"vector", std::vector<int>>>;
            
            static inline auto to_json_data( Example const & v ) {
              return std::forward_as_tuple( v.string, v.map, v.vector );
            }
          };
        }
    

    从这里你可以在另一个内部使用这个映射作为json_class&lt;"Name", Example&gt;。按照您的要求进行序列化只是auto json_document = daw::json::to_json( MyExampleValue ) 或解析它daw::json::from_json&lt;Example&gt;( json_document ); 的问题。该库的美妙之处在于它为您的类型生成自定义解析器,并在解析时对数据进行类型检查。

    【讨论】:

      【解决方案3】:

      RareCpp 中,我在反射实现之上创建了一个非常有效的JSON Library。它是为 C++17 编写的,可与 Visual Studios、g++ 和 Clang 一起使用。该库仅是标头,这意味着您只需将反射和 json 标头复制到您的项目中即可使用它。

      JSON 库只要求您在 REFLECT 宏中列出一次字段;从那里它自动识别用于读取或写入的适当 JSON 输出表示,并且可以递归地遍历任何反射字段,以及数组、STL 容器、引用、指针等。

      struct MyOtherObject { int myOtherInt; REFLECT(MyOtherObject, myOtherInt) };
      struct MyObject
      {
          int myInt;
          std::string myString;
          MyOtherObject myOtherObject;
          std::vector<int> myIntCollection;
      
          REFLECT(MyObject, myInt, myString, myOtherObject, myIntCollection)
      };
      
      int main()
      {
          MyObject myObject = {};
          std::cout << "Enter MyObject:" << std::endl;
          std::cin >> Json::in(myObject);
          std::cout << std::endl << std::endl << "You entered:" << std::endl;
          std::cout << Json::pretty(myObject);
      }
      

      上面可以这样运行...

      Enter MyObject:
      {
        "myInt": 1337, "myString": "stringy", "myIntCollection": [2,4,6],
        "myOtherObject": {
          "myOtherInt": 9001
        }
      }
      
      
      You entered:
      {
        "myInt": 1337,
        "myString": "stringy",
        "myOtherObject": {
          "myOtherInt": 9001
        },
        "myIntCollection": [ 2, 4, 6 ]
      }
      

      您还可以对字段进行注释以执行诸如在 JSON 表示中给它们一个不同的名称、强制它们为字符串等操作。

      struct Point
      {
          NOTE(latitude, Json::Name{"lat"})
          double latitude;
      
          NOTE(longitude, Json::Name{"long"})
          double longitude;
      
          REFLECT(Point, latitude, longitude)
      };
      

      更多示例请参见here,还有许多其他功能,例如捕获超类、支持读取、遍历和写入编译时未知的 JSON,进一步自定义特定字段或类型的 JSON 表示等。

      【讨论】:

      【解决方案4】:

      你可以使用Boost.PropertyTree

      #include <map>
      #include <vector>
      #include <string>
      #include <iostream>
      #include <boost/property_tree/ptree.hpp>
      #include <boost/property_tree/json_parser.hpp>
      
      namespace pt = boost::property_tree;
      
      int main() {
          // Create an empty property tree object.
          pt::ptree tree;
      
          // Put a string value into the tree.
          tree.put("string", "the-string-value");
      
          // Put a map object into the tree.
          pt::ptree child1;
          std::map<std::string, std::string> map = {{"key1", "val1"},
                                                    {"key2", "val2"}};
          for (auto &p : map) {
              child1.add(p.first, p.second);
          }
          tree.add_child("map", child1);
      
          // Put a vector of numbers into the tree
          pt::ptree child2;
          std::vector<int> vector = {1, 2, 3, 4};
          for (auto &v : vector) {
              pt::ptree item;
              item.put("", v);
              child2.push_back(std::make_pair("", item));
          }
          tree.add_child("vector", child2);
      
          // Write property tree to JSON file
          pt::write_json("output.json", tree);
      
          return 0;
      }
      

      输出:

      {
          "string": "the-string-value",
          "map": {
              "key1": "val1",
              "key2": "val2"
          },
          "vector": [
              "1",
              "2",
              "3",
              "4"
          ]
      }
      

      【讨论】:

        【解决方案5】:

        JSON Spirit 允许您这样做:

        Object addr_obj;
        
        addr_obj.push_back( Pair( "house_number", 42 ) );
        addr_obj.push_back( Pair( "road",         "East Street" ) );
        addr_obj.push_back( Pair( "town",         "Newtown" ) );
        
        ofstream os( "address.txt" );
        os.write( addr_obj, os, pretty_print );
        os.close();
        

        输出:

        {
            "house_number" : 42,
            "road" : "East Street",
            "town" : "Newtown"
        }
        

        我想json_map_demo.cpp 将是一个不错的起点。

        【讨论】:

        • 我希望能够做到这一点,而不必将每个值都复制到单独的 JSON 对象中。
        • @Ca1icoJack:你不必复制。您可以调整您现有的结构。正如 Tamás 所指出的,这需要工作,因为 C++ 没有反射
        • 您是否介意详细说明 adapt 的含义。您的意思是使用 JSON Spirit Object 而不是 std::map?我是 C++ 新手,习惯了几种必须使用反射的语言(我目前正在阅读)。谢谢。
        【解决方案6】:

        如果问题仍然存在,请查看json_dto 库,这是一个仅用于在 JSON 表示和 c++ 结构之间转换数据的小型标头助手。

        例如具有以下结构:

        struct message_source_t
        {
          // Worker thread.
          std::int32_t m_thread_id;
        
          // Sender.
          std::string m_subsystem;
        };
        
        struct message_t
        {
          // Who sent a message.
          message_source_t m_from;
        
          // When the message was sent (unixtime).
          std::tm m_when;
        
          // Message text.
          std::string m_text;
        };
        

        json_dto 的帮助下,您可以创建以下 JSON:

        {
          "from" : 
            {
              "thread_id" : 4242,
              "sybsystem" : "json_dto"
            },
          "when" : "2016.09.28 19:55:00",
          "text" : "Hello world!"
        }  
        

        并且给定这样的 JSON 字符串,您可以将其转换为结构。

        【讨论】:

        • 别忘了你必须实现 json_io 成员函数,你将结构成员映射到 JSON 中的成员。这是由于 C++ 中缺乏反射。
        【解决方案7】:

        您想对地图或对象进行 JSON 化吗? (您的示例显示了一个类,但您说的是地图)。如需地图,请查看此库 - JSON Spirit

        对于对象:C++ 中没有反射支持(除了非常有限的 RTTI),因此也没有序列化的“一键式”解决方案。任何解决方案都需要您为要序列化和反序列化的类编写额外的、可能紧密耦合的代码(这取决于您是否要序列化非公共数据)。

        【讨论】:

        • 很抱歉给您带来了困惑。最好我想对 JSON 做对象,但构建地图并存储它是另一种选择。我一生都无法弄清楚如何使用 JSON Spirit 映射到 JSON。它适用于 JSON 的向量/列表。
        • 这个答案是正确的,C++ 永远不会有像 Jackson 这样的库,可以在不编写与指定类紧密耦合的代码的情况下将对象转换为 JSON。
        【解决方案8】:

        任何好的 C++ JSON 库都应该这样做,但很遗憾他们没有这样做——除了ThorsSerializer 和显然是Nosjob,正如question 中提到的那样。

        当然,C++ 不像 Java 那样具有反射,因此您必须显式注释您的类型:
        (从 ThorsSerializer 文档中复制)

        #include "ThorSerialize/JsonThor.h"
        #include "ThorSerialize/SerUtil.h"
        #include <map>
        #include <vector>
        #include <string>
        #include <iostream>
        
        class Example {
            std::string string;
            std::map<std::string, std::string> map;
            std::vector<int> vector;
        
            // Allow access to the class by the serialization library.
            friend class ThorsAnvil::Serialize::Traits<Example>;
        
            public:
                Example(std::string const& s, std::map<std::string, std::string> const& m, std::vector<int> const& v)
                    : string(s), map(m), vector(v)
                {}
        };
        
        // Define what members need to be serilizable
        ThorsAnvil_MakeTrait(Example, string, map, vector);
        

        示例用法:

        int main()
        {
            using ThorsAnvil::Serialize::jsonExport;
            using ThorsAnvil::Serialize::jsonImport;
        
        
            Example     e1 {"Some Text", {{"ace", "the best"}, {"king", "second best"}}, {1 ,2 ,3, 4}};
        
            // Simply serialize object to json using a stream.
            std::cout << jsonExport(e1) << "\n";
        
            // Deserialize json text from a stream into object.
            std::cin  >> jsonImport(e1);
        }
        

        跑步:

        {
            "string": "Some Text",
            "map":
            {
                "ace": "the best",
                "king": "second best"
            },
            "vector": [ 1, 2, 3, 4]
        }
        

        在 C++ 中你不能做得比这更好。

        【讨论】:

        • 对于其他人:如果您像我一样是新手,请不要尝试这个库。使用起来很糟糕。我花了将近 4 个小时尝试运行这个简单的示例,但没有任何效果。即使在作者给出的简单示例中,也缺少声明。我用新安装的 Ubuntu 尝试了自制软件,但在 brew install xxx 期间失败了。然后我尝试下载仅标头分支并成功编译,但链接出错。最后我尝试下载整个 repo 并进行安装,但在 .configure 部分,我无法以简单的方式安装所有依赖项。
        • 我最终放弃了,决定使用另一种语言来完成我的任务。如果有人可以编写有关如何使其运行的分步教程。请告诉我。
        • @Rick 如果您仍然关心,我是作者,非常乐意提供帮助。在此处添加问题:github.com/Loki-Astari/ThorsSerializer/issues
        • @MartinYork 谢谢马丁。当我学习 C++ 中的反射或相关的东西时,我可能会回到这个:P。现在我主要在 Python 上工作。
        【解决方案9】:

        你看过麦片 (http://uscilab.github.io/cereal/) 吗?它有 JSON 档案,用于使用 C++ 与 JSON 进行序列化。

        可以在 SO 上找到开销最小的示例(来自谷物):https://stackoverflow.com/a/22587527/255635

        【讨论】:

        • 虽然这在理论上可以回答问题,it would be preferable 在这里包含答案的基本部分,并提供链接以供参考。
        • 好吧,您只需通过链接前往麦片粥即可快速找到答案。不需要为此投票,真的吗?但是很好,更新了另一个类似问题的链接,但与谷物有关。
        • 不,我没有投反对票。一定有人将您的帖子标记为 VLQ,因此编辑会自动触发反对票。如果我当天还有选票,我也会投票
        • 真的很难得罪我。一些改进。 1. 不要以你看过...开头,你是在回复帖子而不是要求澄清。 2.添加另一个答案的示例(或自己制作一个),并在底部添加链接以供参考。 (如果你这样做,我一定会回来为答案投票)
        • @罗伯特。我赞成弥补谁反对你的答案。明天我将研究谷物和示例,看看它是否对我有用。谢谢。
        【解决方案10】:

        thispython 脚本生成 c++ pod 类,每个 json 属性都有一个成员

        你想要完全相反的东西,但是生成一个既可以加载又可以保存的映射类是微不足道的

        生成的代码依赖于外部 json 解析器库

        【讨论】:

          【解决方案11】:

          我已经编写了一个实验库,可以完成这项工作,但它需要对类结构和层次结构进行外部描述。它使用GCCXML构建一个xml字典,用于序列化反序列化:

          http://code.google.com/p/cjson/

          目前是一个实验项目,可以处理基本类型(int、float double)、指向基本类型的指针、类、继承的成员等......它实现了基本的 std::vector 和 std::map 序列化,还有 std::string 实例。

          具体实现见here

          【讨论】:

            【解决方案12】:

            我写了一个库来解决你的问题。 但是,这是一个非常新的项目,还不够稳定。 随便看看,主页在这里::

            https://github.com/Mizuchi/acml

            在您的示例中,您必须像这样添加一行:

            ACML_REGISTER(Example, ,(string)(map)(vector));
            

            为了告诉库您要转储哪个成员。 由于 C++ 没有反射。 而且您必须提供访问会员的方法, 使用公共成员级别或使用朋友类。

            然后你只需要这样做:

            字符串结果 = acml::json::dumps(any_object);

            会变成::

            {
                "string": "the-string-value",
                "map":
                {
                    "key1": "val1",
                    "key2": "val2"
                },
                "vector":
                {
                    "type": "std::vector",
                    "size": "4",
                    "0": "1",
                    "1": "2",
                    "2": "3",
                    "3": "4"
                }
            }
            

            如您所见,JSON 数组尚未实现。 现在一切都变成了字符串。

            【讨论】:

            • @Gelldur 我没有计划支持 JSON 数组。由于 ACML 非常小(而且很干净!?),您可以自己实现此功能,欢迎您的贡献。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2022-12-12
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多