将 C++ 类转换为 JSON
Posted
技术标签:
【中文标题】将 C++ 类转换为 JSON【英文标题】:Converting C++ class to JSON 【发布时间】:2012-01-03 10:37:51 【问题描述】:我想创建一个包含我的类的实例变量的 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,而无需更改数据类型或将数据复制到新的数据类型?
谢谢。
【问题讨论】:
你看过哪些库?所以我们知道什么你觉得复杂 参见 ***.com/questions/245973/whats-the-best-c-json-parser 和 ***.com/questions/6538725/…(现已删除;仅 10k+) 我已经更新了我的帖子。复杂性在于需要做很多工作才能做一些我认为非常简单的事情。我真的觉得我错过了一些东西,可能是一些明显的东西。 你没有遗漏任何东西,这在 C++ 中是不可能的(以你描述的形式)。 您可能会以错误的方式处理此问题。这样的“仅数据”类可能应该是std::tuple
;并且您应该能够很容易地构建一些模板操作来输出元组的 JSON 表示。
【参考方案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<"Name", Example>
。按照您的要求进行序列化只是auto json_document = daw::json::to_json( MyExampleValue )
或解析它daw::json::from_json<Example>( 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 表示等。
【讨论】:
如何将其添加到 VisualStudio 2019 项目中? 只需将github.com/TheNitesWhoSay/RareCpp/blob/master/RareCppLib/… 和github.com/TheNitesWhoSay/RareCpp/blob/master/RareCppLib/Json.h heders 添加到您的项目中,并在需要的地方包含它们;还可以右键单击您的项目 -> 属性并在“常规”下确保 C++ 语言标准设置为 C++17 或更高版本【参考方案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://***.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 非常小(而且很干净!?),您可以自己实现此功能,欢迎您的贡献。以上是关于将 C++ 类转换为 JSON的主要内容,如果未能解决你的问题,请参考以下文章