Json 库:NlohmannJson 可以根据实际情况进行修改

前言

在很多场景中,使用 Json 作为配置保存和数据传输是一个很好的选择,比如 ProtoBuf3 就自带了两者之间的转换。同时这个方式能有效的解决前面所提到的数据类型不一致导致业务逻辑异常的问题

接口&实现

内部转换接口设计&实现

原理和前文一样,也是为需要的结构体定义属性,然后循环进行转换。因此,需要和二进制数据转换一样需要定义两个正反转换的接口:

namespace _impl {
/**
* 序列化至Json数据核心类
* @tparam T 源类型
*/
template <typename T>
struct ToJson {
void operator()(nlohmann::json& json, const T& value);
};

/**
* 反序列化Json数据核心类
* @tparam T 目标类型
*/
template <typename T>
struct FromJson {
bool operator()(T& val, const nlohmann::json& json);
};
} // _impl

相对于二进制数据转换来说只需要完善对 vector 支持即可,为什么?因为 struct 对于 Json 来说可以视为一个 Object ,所以对于这种字典结构来说只针对数组进行处理是可行的
Json 的数组中存放着不同类型的数据该如何支持?说实话,这种情况是很少见的,因为这样对于对接采用这样数据定义接口的用户来说是一个非常糟糕的方式,所以通常情况下是同一种类型
综上,对 vector 和基本类型的转换实现如下:

namespace _impl {
/**
* 序列化至Json数据核心类
* @tparam T 源类型
*/
template <typename T>
struct ToJson {
void operator()(nlohmann::json& json, const T& value) {
json = value;
}
};

// vector偏特化
template <typename T>
struct ToJson<std::vector<T> > {
void operator()(nlohmann::json& json, const std::vector<T>& value) {
for (const auto& i : value) {
nlohmann::json _json;
ToJson<T>()(_json, i);
json.push_back(_json);
}
}
};

/**
* 反序列化Json数据核心类
* @tparam T 目标类型
*/
template <typename T>
struct FromJson {
bool operator()(T& val, const nlohmann::json& json) {
val = json.get<T>();
return true;
}
};

// vector偏特化
template <typename T>
struct FromJson<std::vector<T> > {
bool operator()(std::vector<T>& val, const nlohmann::json& json) {
if (!json.is_array()) {
return false;
}
for (const auto& i : json) {
T _val;
if (!FromJson<T>()(_val, i)) {
return false;
}
val.push_back(_val);
}
return true;
}
};
} // _impl

用户接口设计&实现

提供给用户调用的接口可以设计为:

struct Json {
template<typename T>
static void Serialized(nlohmann::json& json, const T& value);
template<typename T>
static bool Deserialized(T& value, const nlohmann::json& json);
};

具体实现和 Binary 部分类似,只是这里对 Json 库转换的异常做了拦截返回,因为有可能在某些项目中不会使用异常捕获来进行处理,所以这里可以按需进行修改:

struct Json {
template<typename T>
static void Serialized(nlohmann::json& json, const T& value) {
// 遍历属性中的成员数据, 按照顺序进行转换
PropertyForeach(T::__Property, [&](auto elem) {
nlohmann::json json_value;
_impl::ToJson<typename decltype(elem)::Type>()(json_value, value.*(elem.Member));
json[elem.Name] = json_value;
});
}
template<typename T>
static bool Deserialized(T& value, const nlohmann::json& json) {
try {
// 遍历属性中的成员数据, 按照顺序进行转换
PropertyForeach(T::__Property, [&](auto elem) {
using ValueType = typename decltype(elem)::Type;
auto res = json.find(elem.Name);
if (res == json.end()) {
value.*(elem.Member) = ValueType {};
} else {
_impl::FromJson<ValueType>()(value.*(elem.Member), *res);
}
});
}
catch (...) {
return false;
}
return true;
}
};

更进一步?

Json 提供了很好的可读性,因此可以增加一个接口用于转换成字符串:

struct Json {
...
template<typename T>
static std::string ToString(T& value);
};

在有了转换的接口和第三方库提供了 to_string 的接口基础上就不难实现了:

template <typename T>
static std::string ToString(const T& value) {
nlohmann::json json;
Serialized(json, value);
return nlohmann::to_string(json);
}

测试

还是和前文一样进行互相转换测试,但由于字符串互转在不同的场景下 KV 顺序并不是一定的,所以需要自身去判断下