C++ JSON 序列化
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/17549906/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
C++ JSON Serialization
提问by Vicen? Gascó
I want a way to serialize and deserialize Objects to JSON, as automatic as possible.
我想要一种尽可能自动地将对象序列化和反序列化为 JSON 的方法。
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).
序列化:对我来说,理想的方式是,如果我在一个实例中调用 JSONSerialize() ,它会返回一个字符串,其中包含一个 JSON 对象,该对象具有该对象的所有公共属性"name_of_property": "value"
。对于那些原始值,很简单,对于对象,它应该尝试调用每个 JSONSerialize() 或 ToString() 或类似的东西来递归序列化所有公共属性。对于集合,它也应该正确运行(只有向量/数组就可以了)。
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.
反序列化:只需创建给定对象的实例(比如一只狗)并调用JSONDeserialize(json_string)
,这应该填充所有公共属性,在属性不是基元或所需集合的情况下创建所需的对象。
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?
我怎样才能轻松解决这个问题?
采纳答案by JBV06
For that you need reflection in C/C++ language, that doesn't exists. You need to have some meta data describing the structure of your classes (members, inherited base classes). For the moment C/C++ compilers doesn't provide automatically that information in built binaries.
为此,您需要在 C/C++ 语言中进行反射,而这并不存在。您需要一些描述类结构的元数据(成员、继承的基类)。目前,C/C++ 编译器不会在构建的二进制文件中自动提供该信息。
I had the same idea in mind, and I used GCC XMLproject to get this information. It outputs XML data describing class structures. I have built a project and I'm explaining some key points in this page:
我有同样的想法,我使用GCC XML项目来获取这些信息。它输出描述类结构的 XML 数据。我已经建立了一个项目,我正在解释这个页面中的一些关键点:
Serialization is easy, but we have to deal with complex data structure implementations (std::string, std::map for example) that play with allocated buffers. Deserialization is more complex and you need to rebuild your object with all its members, plus references to vtables ... a painful implementation.
序列化很容易,但我们必须处理复杂的数据结构实现(例如 std::string、std::map),这些实现与分配的缓冲区有关。反序列化更复杂,您需要使用其所有成员以及对 vtable 的引用来重建对象……这是一个痛苦的实现。
For example you can serialize like that :
例如,您可以像这样序列化:
// 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 ;
To deserialize data it works like that:
要反序列化数据,它的工作方式如下:
// 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 ;
Ouptuts:
输出:
>:~/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$
Usually these implementations are compiler dependent (ABI Specification for example), and requires external description to work (GCCXML output), such are not really easy to integrate to projects.
通常这些实现依赖于编译器(例如 ABI 规范),并且需要外部描述才能工作(GCCXML 输出),因此很难集成到项目中。
回答by Guillaume Racicot
There is no reflection in C++. True. But if the compiler can't provide you the metadata you need, you can provide it yourself.
C++ 中没有反射。真的。但是如果编译器不能为你提供你需要的元数据,你可以自己提供。
Let's start by making a property struct:
让我们从创建一个属性结构开始:
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};
}
Of course, you also can have a property
that takes a setter and getter instead of a pointer to member, and maybe read only properties for calculated value you'd like to serialize. If you use C++17, you can extend it further to make a property that works with lambdas.
当然,您也可以property
使用 setter 和 getter 而不是指向成员的指针,并且可能只读取要序列化的计算值的属性。如果您使用 C++17,您可以进一步扩展它以创建一个适用于 lambda 的属性。
Ok, now we have the building block of our compile-time introspection system.
好的,现在我们有了编译时自省系统的构建块。
Now in your class Dog
, add your metadata:
现在在您的班级中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")
);
};
We will need to iterate on that list. To iterate on a tuple, there are many ways, but my preferred one is this:
我们将需要迭代该列表。要迭代元组,有很多方法,但我更喜欢的是:
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};
}
If C++17 fold expressions are available in your compiler, then for_sequence
can be simplified to:
如果您的编译器中提供 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>{})), ...);
}
This will call a function for each constant in the integer sequence.
这将为整数序列中的每个常量调用一个函数。
If this method don't work or gives trouble to your compiler, you can always use the array expansion trick.
如果此方法不起作用或给您的编译器带来麻烦,您始终可以使用数组扩展技巧。
Now that you have the desired metadata and tools, you can iterate through the properties to unserialize:
现在您拥有所需的元数据和工具,您可以遍历属性以反序列化:
// 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;
}
And for serialize:
对于序列化:
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;
}
If you want recursive serialization and unserialization, you can replace asAny
by fromJson
.
如果要递归序列化和反序列化,可以替换asAny
为fromJson
.
Now you can use your functions like this:
现在你可以像这样使用你的函数:
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!
Done! No need for run-time reflection, just some C++14 goodness!
完毕!不需要运行时反射,只需要一些 C++14 的优点!
This code could benefit from some improvement, and could of course work with C++11 with some ajustements.
这段代码可以从一些改进中受益,当然可以通过一些调整与 C++11 一起使用。
Note that one would need to write the asAny
function. It's just a function that takes a Json::Value
and call the right as...
function, or another fromJson
.
请注意,需要编写该asAny
函数。它只是一个函数,它接受 aJson::Value
并调用正确的as...
函数,或另一个fromJson
.
Here's a complete, working examplemade from the various code snippet of this answer. Feel free to use it.
这是根据此答案的各种代码片段制作的完整的工作示例。随意使用它。
As mentionned in the comments, this code won't work with msvc. Please refer to this question if you want a compatible code: Pointer to member: works in GCC but not in VS2015
正如评论中提到的,此代码不适用于 msvc。如果你想要一个兼容的代码,请参考这个问题:指向成员的指针:适用于 GCC 但不适用于 VS2015
回答by SigTerm
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.
C++ 不在编译代码中存储类成员名称,并且无法(在运行时)发现类包含哪些成员(变量/方法)。换句话说,您不能遍历结构的成员。因为没有这样的机制,您将无法为每个对象自动创建“JSONserialize”。
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 QVariantMapthat'll be used instead of structs for all serializeable objects.
但是,您可以使用任何 json 库来序列化对象,但您必须自己为每个类编写序列化/反序列化代码。要么,要么您必须创建类似于QVariantMap 的可序列化类,该类将用于代替所有可序列化对象的结构。
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.
换句话说,如果您可以为所有可序列化对象使用特定类型(或为每个类自己编写序列化例程),则可以完成。但是,如果您想自动序列化每个可能的类,您应该忘记它。如果此功能对您很重要,请尝试另一种语言。
回答by David Siegel
Using quicktype, you can generate C++ serializers and deserializers from JSON sample data.
使用quicktype,您可以从 JSON 示例数据生成 C++ 序列化器和反序列化器。
For example, given the sample JSON:
例如,给定示例 JSON:
{
"breed": "Boxer",
"age": 5,
"tail_length": 6.5
}
quicktypegenerates:
快速类型生成:
#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}};
}
}
To parse the Dog JSON data, include the code above, install Boostand json.hpp, then do:
要解析 Dog JSON 数据,请包含上面的代码,安装Boost和json.hpp,然后执行:
Dog dog = nlohmann::json::parse(jsonString);
回答by Siyuan Ren
In case that anyone still has this need (I have), I have written a library myself to deal with this problem. See here. It isn't completely automatic in that you have to describe all the fields in your classes, but it is as close as what we can get as C++ lacks reflection.
如果有人仍然有这个需求(我有),我自己写了一个库来处理这个问题。见这里。它不是完全自动的,因为您必须描述类中的所有字段,但由于 C++ 缺乏反射,因此它与我们所能得到的一样接近。
回答by Andrew
Not yet mentioned, though it was the first in my search result: https://github.com/nlohmann/json
还没有提到,虽然它是我搜索结果中的第一个:https: //github.com/nlohmann/json
Perks listed:
福利列出:
- Intuitive syntax (looks great!)
- Single header file to include, nothing else
- Ridiculously tested
- 直观的语法(看起来很棒!)
- 要包含的单个头文件,仅此而已
- 可笑的测试
Also, it's under the MIT License.
此外,它在 MIT 许可证下。
I'll be honest: I have yet to use it, but through some experience I have a knack for determining when I come across a really well-made c++ library.
老实说:我还没有使用过它,但通过一些经验,我有一种诀窍,可以确定何时遇到一个真正制作精良的 c++ 库。
回答by Daniel
The jsonconsC++ header-only library also supports conversion between JSON text and C++ objects. Decode and encode are defined for all C++ classes that have json_type_traits defined. The standard library containers are already supported, and json_type_traits can be specialized for user types in the jsoncons namespace.
所述jsonconsC ++仅标头库还支持JSON文本和C ++对象之间的转换。为所有定义了 json_type_traits 的 C++ 类定义了解码和编码。已经支持标准库容器,并且 json_type_traits 可以专门用于 jsoncons 命名空间中的用户类型。
Below is an example:
下面是一个例子:
#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";
}
Output:
输出:
(1)
Marilyn C, 0.9
(2)
{
"application": "hiking",
"reputons": [
{
"assertion": "advanced",
"rated": "Marilyn C",
"rater": "HikingAsylum",
"rating": 0.9
}
]
}
回答by Martin York
Using 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"
I think that is pretty close to your original.
There is a tiny bit of set up. You need to declare your class is Serializable.
我认为这与您的原版非常接近。
有一点点设置。您需要声明您的类是可序列化的。
#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);
No other coding is required.
不需要其他编码。
Complete Examples can be found:
可以找到完整的示例:
回答by kola
Try json_dto. It is header-only and easy to use.
试试json_dto。它仅包含标题且易于使用。
Simple example:
简单的例子:
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 );
}
};
This will be convertable toand fromJSON:
这将是可转换到和来自JSON:
{ "from" : "json_dto", "text" : "Hello world!" }