C++ JSON Serialization
C++ JSON Serialization
我想要一种方法,尽可能自动地将对象序列化和反序列化为 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"
我怎样才能轻松做到这一点?
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};
}
当然,您也可以有一个采用资源库和 getter 而不是指向成员的指针的property
,并且可能只读取要序列化的计算值的属性。如果使用 C++17,则可以进一步扩展它以创建适用于 lambda 的属性。
好的,现在我们有了编译时自省系统的构建块。
现在在您的类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>{})), ...);
}
这将为整数序列中的每个常量调用一个函数。
如果此方法不起作用或给编译器带来麻烦,则始终可以使用数组扩展技巧。
现在,您已经有了所需的元数据和工具,可以循环访问属性以取消序列化:
// 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
。
这是一个完整的工作示例,由此答案的各种代码片段组成。随意使用它。
如注释中所述,此代码不适用于 msvc。如果您想要兼容的代码,请参考这个问题: 指向成员的指针:适用于 GCC
,您需要在 C/C++ 中进行反射,而 C/中不存在。您需要一些元数据来描述类(成员、继承的基类(的结构。目前,C/C++编译器不会在构建的二进制文件中自动提供该信息。
我也有同样的想法,我使用GCC XML项目来获取这些信息。它输出描述类结构的 XML 数据。我已经建立了一个项目,我正在解释此页面中的一些关键点:
序列化很容易,但我们必须处理复杂的数据结构实现(例如 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输出(,因此并不容易集成到项目中。
像这样容易的事情存在吗?谢谢:)(
C++ 不会在编译的代码中存储类成员名称,并且无法(在运行时(发现类包含哪些成员(变量/方法(。换句话说,不能循环访问结构的成员。由于没有这样的机制,因此无法为每个对象自动创建"JSONserialize"。
但是,您可以使用任何 json 库来序列化对象,但您必须为每个类自己编写序列化/反序列化代码。 要么这样,要么你必须创建类似于 QVariantMap 的可序列化类,该类将用于所有可序列化对象的结构。
换句话说,如果您愿意对所有可序列化对象使用特定类型(或者为每个类自己编写序列化例程(,则可以这样做。但是,如果要自动序列化每个可能的类,则应忘记它。如果此功能对您很重要,请尝试使用其他语言。
使用快速类型,可以从 JSON 示例数据生成C++序列化程序和反序列化程序。
例如,给定示例 JSON:
{
"breed": "Boxer",
"age": 5,
"tail_length": 6.5
}
快速类型生成:
#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 数据,请包含上面的代码,安装 Boost 和 json.hpp,然后执行以下操作:
Dog dog = nlohmann::json::parse(jsonString);
如果有人仍然有这个需求(我有(,我自己写了一个库来处理这个问题。看这里。它不是完全自动的,因为您必须描述类中的所有字段,但它与我们可以得到的一样接近C++缺乏反射。
试试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!" }
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
}
]
}
使用 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);
不需要其他编码。
可以找到完整的示例:
- 简单示例
- 容器示例
- 多态示例
尚未提及,尽管这是我搜索结果中的第一个:https://github.com/nlohmann/json
列出的福利:
- 直观的语法(看起来很棒!
- 要包含的单个头文件,没有其他内容
- 可笑的测试
此外,它还在 MIT 许可证下。
老实说:我还没有使用它,但通过一些经验,我有一个诀窍来确定我何时遇到一个制作精良的 c++ 库。
使用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 }
};
我正在寻找一种避免这种情况的方法。
- 在c++中使用nlohmann从类到json的转换
- C++json插入数组
- nlohmann-json将一个数组插入到另一个数组中
- 如何使用curlpp通过POST方法上传文件和json数据
- JSON转换为nlohmann JSON-lib中的结构数组
- NLOHMANN 的 JSON 库将数组转换为结构向量
- 在nlohmann json中,如何将嵌套对象的数组转换为嵌套结构的向量
- 如何在 nlohmann 的 json 库中获取数组长度?
- UE4 C++ 我无法将 JSON 嵌套值获取到 TArray
- 如何在特定位置追加 json 文件
- 有没有办法让编译器在我放置字符串而不是 nlohmann::json 对象时抛出错误?
- 使用 jsoncpp 解析 json 数组字符串
- 将 FlatBuffer 对象序列化为 JSON,而不使用其架构文件
- 如何在 c++ 非托管代码中反序列化 byte[] 的 json 字符串?
- 使用 cmake 生成 compile_commands.json
- 如何使用 c++ 和提升库生成 json
- 使用 minijson-reader 库读取 JSON 字符串时出现问题
- 使用 Qt 解析 JSON 数组
- Qt Json serialization
- C++ JSON Serialization