问题? 在使用 JsonCpp 相关的库对配置数据进行读写时,有可能会出现这样的问题,首先写入数据到文件:
uint32_t width = 1920 ;uint32_t height = 1080 ;uint32_t fps = 25 ;Json::Value value; value["width" ] = width; value["height" ] = height; value["fps" ] = fps; ...
然后创建一个一模一样的变量来和文件中读取出来的数据做比对:
Json::Value value_from_file; ... uint32_t width = 1920 ;uint32_t height = 1080 ;uint32_t fps = 25 ;Json::Value value; value["width" ] = width; value["height" ] = height; value["fps" ] = fps; if (value == value_from_file) { printf ("equal!\n" ); } else { printf ("not equal!\n" ); }
然后在输出的时候你会发现,这两个变量其实是不等的,那么为什么 kv 一致的情况下判断出来却不相等呢? 查询 JsonCpp 源码 后可知晓:
enum ValueType { nullValue = 0 , intValue, uintValue, realValue, stringValue, booleanValue, arrayValue, objectValue };
JsonCpp 中对整型数据进行了细分,然而 Json 并没有特殊的规范针对文件中的整型进行有无符号的区分,所以 JsonCpp 读取到的整型均以有符号的方式来读取,这就导致了上面的问题,即 kv 虽然相等了,但是 v 的类型不相等 这个情况同时也导致了某些实现监听配置变动时,在回调中对新老值的比对上出现问题:
... void XX::OnCfgChanged (const listener_id id, const Json::Value& old_value, const Json::Value& new_value) { ... if (old_value["resolution" ] != new_value["resolution" ]) { ... } ... } ...
如只需要简单的使用,那么不需要在意很多,但对于复杂的项目来说就应该尽量避免这样实现
如何更好的设计? 其实可以将配置视为一个对象来进行设计,以对象来管理资源一直是 C++ 的处理方式,这样就可以定义好 value
的类型:
"obj" : { "text" : "_text_" , "num" : 1 }
➡
struct Obj { std: : string text; int32_t num; } ;
这样既可以确认对应配置所具有的 kv 也可以保证类型安全。当然我们的目的是将 Json 与目标值进行转换,所以可以基于 Json 库来定义接口来编写,这里使用 nlohmann 库来做为 Json 的转换:
namespace tools {namespace var {class Base { public : virtual ~Base () = default ; virtual void FromJson (const nlohmann::json& json) = 0 ; virtual nlohmann::json ToJson () = 0 ; virtual std::string ToJsonString () { return nlohmann::to_string (ToJson ()); } virtual void FromJsonString (const std::string& json_str) { FromJson (nlohmann::json::parse (json_str)); } }; } }
那么 Obj
就可以继承 tools::var::Base
进行实现:
using namespace tools;struct Obj : public var::Base { std::string text; int32_t num{}; void FromJson (const nlohmann::json& json) override { const auto cvt = [&](auto & dst, const char * key, auto def_value) { dst = json.contains (key) ? json[key].get<std::remove_reference_t <decltype (dst)> >() : def_value; }; cvt (text, "text" , "(null)" ); cvt (num, "num" , 0 ); } nlohmann::json ToJson () override { nlohmann::json json; json["text" ] = text; json["num" ] = num; return json; } };
这样对于调用者来说就无需关注如何使用 Json 库转换出自己想要的数据,同时也可以在转换的函数里对数据进行对应的数据解析转换成对应的目标成员值
总结 其实无论是 Json 还是 Yaml 或者 ini 等其他配置格式都可以考虑这样的实现方式,毕竟这样在大型的项目中是十分有利于其他模块的对接和处理,同时也可以减少代码互相耦合的情况