1、简介
最近项目中需要使用C++ Json序列化和反序列化,顺便调研了下目前比较好用的C++ json库,发现nlohmann/json应该是其中相对较好的json库。nlohmann/json有如下主要优点:
1、语法比较直观,类似于Python。
2、要使用nlohmann/json,只需要引入头文件json.hpp。无需引入lib之类的。
#include <nlohmann/json.hpp>
// for convenience
using json = nlohmann::json;
3、经过非常多的测试,代码质量非常高,没有内存泄漏。
4、内存效率、速度相对其它库较高。
2、实例介绍
2.1 创建Json对象
使用nlohmann/json创建Json对象,无需关心值类型,比如创建如下Json对象:
// create an empty structure (null)
json j;
// add a number that is stored as double (note the implicit conversion of j to an object)
j["pi"] = 3.141;
// add a Boolean that is stored as bool
j["happy"] = true;
// add a string that is stored as std::string
j["name"] = "Niels";
// add another null object by passing nullptr
j["nothing"] = nullptr;
// add an object inside the object
j["answer"]["everything"] = 42;
// add an array that is stored as std::vector (using an initializer list)
j["list"] = { 1, 0, 2 };
// add another object (using an initializer list of pairs)
j["object"] = { {"currency", "USD"}, {"value", 42.99} };
// instead, you could also write (which looks very similar to the JSON above)
json j2 = {
{"pi", 3.141},
{"happy", true},
{"name", "Niels"},
{"nothing", nullptr},
{"answer", {
{"everything", 42}
}},
{"list", {1, 0, 2}},
{"object", {
{"currency", "USD"},
{"value", 42.99}
}}
};
2.2 序列/反序列化
在class/struct内部定义NLOHMANN_DEFINE_TYPE_INTRUSIVE(name, member1, member2, …)
class Address
{
std::string street;
int housenumber;
int postcode;
public:
NLOHMANN_DEFINE_TYPE_INTRUSIVE(Address, street, housenumber, postcode)
};
字符串转成class/struct
std::string strTemp =(R"({"street": "test", "housenumber": 0, "postcode": 0})";
Address temp = nlohmann::json::parse(strTemp).get<Address>();
class/struct转成字符串
Addree temp{"Test", 0, 0};
nlohmann::json jsonTemp = temp;
std::string strTemp = jsonTemp.dump();
为了方便序列/反序列化,可以封装下面两个接口:
template<class T>
static void deserialize(const std::string &str, T &value)
{
if (str.size() <= 0)
return;
value = parse(str).template get<T>();
}
template<class T>
static void serialize(const T &value, std::string &str)
{
str = json(value).dump();
}
3、遇到的问题及解决办法
3.1 strings参数比class/struct的参数少,反序列化崩溃
实际项目中,json使用未必非常标准,有解析strings参数比较少的需求。比如2.2中类Address
std::string strTemp =(R"({"street": "test", "housenumber": 0})";
Address temp = nlohmann::json::parse(strTemp).get<Address>();
strTemp少了postcode,执行第二行代码就会崩溃。
解决办法
打开nlohmann/json.hpp找到源码
#define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1);
将其改成
#define NLOHMANN_JSON_FROM(v1) if(nlohmann_json_j.contains(#v1)){nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1);}
相比源码,就加了contains的判断,若待解析的json没有对应字段,就不作处理。
3.2 strings包含中文、“\"等反序列化崩溃
strings包含中文、“\“等“特殊字符”,用nlohmann::json反序列化就会崩溃。跟大多数json库一样,nlohmann::json是不支持中文、”\"等“特殊字符”的,因此需要先替换strings中的特殊字符,再反序列化。
3.3 类型必须匹配,数字无法转成string
实际项目中协议定的某些字段,经常出现数字、string混用的情况。nlohmann/json默认实现要求反序列化的目标class/struct参数类型和strings中完美匹配,否则解析过程中崩溃。
解决办法
原代码
template<typename BasicJsonType>
void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s)
{
if (JSON_HEDLEY_UNLIKELY(!j.is_string()))
{
JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()), j));
}
s = *j.template get_ptr<const typename BasicJsonType::string_t*>();
}
改造后
template<typename BasicJsonType>
void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s)
{
if (JSON_HEDLEY_UNLIKELY(!j.is_string()))
{
/* 为了提高健壮性,对于json中非string的类型,也将其转成string */
s = to_string(j);
return;
/* JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()), j)); */
}
s = *j.template get_ptr<const typename BasicJsonType::string_t*>();
}
3.4 NLOHMANN_DEFINE_TYPE_INTRUSIVE不支持class/struct参数过多(超过63个)
NLOHMANN_DEFINE_TYPE_INTRUSIVE并不能无限展开,nlohmann/json默认支持63个参数。
解决方案
NLOHMANN_DEFINE_TYPE_INTRUSIVE的定义如下:
#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \
friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }
NLOHMANN_DEFINE_TYPE_INTRUSIVE的原理就是用NLOHMANN_JSON_EXPAND,1个NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, …))可以支持63个,超过可以另起1个,如下:
struct StructTest
{
std::string param1;
std::string param2;
...
std::string param63;
std::string param64;
...
friend void from_json(const nlohmann::json& nlohmann_json_j, StructTest& nlohmann_json_t)
{
NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(func, param1, param2, ..., param63));
NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(func, param64, param65, ...));
}
friend void to_json(nlohmann::json& nlohmann_json_j, const StructTest& nlohmann_json_t)
{
NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(func, param1, param2, ..., param63));
NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(func, param64, param65, ...));
}
};