<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
C/C++使用者端需要接收和傳送JSON格式的資料到後端以實現通訊和資料互動。C++沒有現成的處理JSON格式資料的介面,直接參照第三方庫還是避免不了拆解拼接。考慮到此專案將會有大量JSON資料需要處理,避免不了重複性的拆分拼接。所以打算封裝一套C++結構體物件轉JSON資料、JSON資料直接裝C++結構體物件的介面,類似於資料傳輸中常見的序列化和反序列化,以方便後續處理資料,提高開發效率。
Json2Object(inJsonString, outStructObject)
,或者Object2Json(inStructObject, outJsonString)
先上單元測試程式碼
TEST_CASE("解析結構體陣列到JSON串", "[json]") { struct DemoChildrenObject { bool boolValue; int intValue; std::string strValue; /*JSON相互轉換成員變數宣告(必需)*/ JSONCONVERT2OBJECT_MEMEBER_REGISTER(boolValue, intValue, strValue) }; struct DemoObjct { bool boolValue; int intValue; std::string strValue; /*巢狀的支援JSON轉換的結構體成員變數,陣列形式*/ std::vector< DemoChildrenObject> children; /*JSON相互轉換成員變數宣告(必需)*/ JSONCONVERT2OBJECT_MEMEBER_REGISTER(boolValue, intValue, strValue, children) }; DemoObjct demoObj; /*開始對demoObj物件的成員變數進行賦值*/ demoObj.boolValue = true; demoObj.intValue = 321; demoObj.strValue = "hello worLd"; DemoChildrenObject child1; child1.boolValue = true; child1.intValue = 1000; child1.strValue = "hello worLd child1"; DemoChildrenObject child2; child2.boolValue = true; child2.intValue = 30005; child2.strValue = "hello worLd child2"; demoObj.children.push_back(child1); demoObj.children.push_back(child2); /*結束對demoObj物件的成員變數的賦值*/ std::string jsonStr; /*關鍵轉換函數*/ REQUIRE(Object2Json(jsonStr, demoObj)); std::cout << "returned json format: " << jsonStr << std::endl; /*列印的內容如下: returned json format: { "boolValue" : true, "children" : [ { "boolValue" : true, "intValue" : 1000, "strValue" : "hello worLd child1" }, { "boolValue" : true, "intValue" : 30005, "strValue" : "hello worLd child2" } ], "intValue" : 321, "strValue" : "hello worLd" } */ DemoObjct demoObj2; /*關鍵轉換函數*/ REQUIRE(Json2Object(demoObj2, jsonStr)); /*校驗轉換後的結構體變數中各成員變數的內容是否如預期*/ REQUIRE(demoObj2.boolValue == true); REQUIRE(demoObj2.intValue == 321); REQUIRE(demoObj2.strValue == "hello worLd"); REQUIRE(demoObj2.children.size() == 2); REQUIRE(demoObj.children[0].boolValue == true); REQUIRE(demoObj.children[0].intValue == 1000); REQUIRE(demoObj.children[0].strValue == "hello worLd child1"); REQUIRE(demoObj.children[1].boolValue == true); REQUIRE(demoObj.children[1].intValue == 30005); REQUIRE(demoObj.children[1].strValue == "hello worLd child2"); }
本次我們只關注怎麼友好地在結構體與Json字串之間進行轉換,而不深入關注JSon字串具體如何與基本資料型別進行轉換。這個已經有不少的第三方庫幫我們解決這個問題,如cJSON、Jsoncpp、rapidjson,不必再重複造輪子。
此次我們選擇了JsonCPP作為底層的JSON解析支援,如果想替換成其他三方庫也比較簡單,修改對應嵌入的內容即可。
我們的目標是實現兩個介面:
Json2Object(inJsonString, outStructObject)
Object2Json(inStructObject, outJsonString)
結合JsonCPP自身定義的型別,我們進一步需要實現的是:
Json2Object(const Json::Value& jsonTypeValue, outStructObject)
Object2Json(inStructObject, const std::string& key, Json::Value& jsonTypeValue)
對於如bool、int、double、string等基本資料型別,該實現均較為簡單:
/*int 型別支援*/ static bool Json2Object(int& aimObj, const Json::Value& jsonTypeValue) { if (jsonTypeValue.isNull() || !jsonTypeValue.isInt()) { return false; } else { aimObj = jsonTypeValue.asInt(); return true; } } static bool Object2Json(Json::Value& jsonTypeValue, const std::string& key, const int& value) { jsonTypeValue[key] = value; return true; } /*std::string 字串型別支援*/ static bool Json2Object(std::string& aimObj, const Json::Value& jsonTypeValue) { if (jsonTypeValue.isNull() || !jsonTypeValue.isString()) { return false; } else { aimObj = jsonTypeValue.asString(); return true; } } static bool Object2Json(Json::Value& jsonTypeValue, const std::string& key, const std::string& value) { jsonTypeValue[key] = value; return true; }
對於自定義的結構體型別,我們要做的就是要保證其成員變數能夠與JSON節點一一對應,並能夠匹配進行資料填充。
/*Json字串: { "boolValue" : true, "intValue" : 1234, "strValue" : "demo object!" }*/ struct DemoObjct { bool boolValue; int intValue; std::string strValue; };
如上面範例,在相互轉換過程中,"boolValue"能與DemoObjct物件中名為boolValue的成員變數對應,"strValue"與DemoObjct物件中名為strValue的成員變數對應。
正常情況下,對於這種場景,我們只能對DemoObjct結構體額外實現處理常式進行資料轉換,因不同的結構體宣告定義的成員變數都不一樣,所以針對每個結構體均需要單獨實現,工作繁瑣,不通用。
從這裡下手,我們要做的就是“隱藏”針對類結構體實現的轉換函數,利用語言自身的特性(函數模板等)讓他們幫我們去做這些事情。
Json2Object
和Object2Json
函數時,觸發呼叫該轉換成員函數,以便填充或輸出成員變數的內容。把大象裝進冰箱裡只需要三步,我們來看這三步怎麼走。
Json2Object
時),不能簡單採用陣列列舉方式處理,可以採用C++11的特性——可變引數模板,從裡到外遍歷處理每個成員變數template <typename T> static bool JsonParse(const std::vector<std::string>& names, int index, const Json::Value& jsonTypeValue, T& arg) { const auto key = names[index]; if (!jsonTypeValue.isMember(key) || Json2Object(arg, jsonTypeValue[key])) { return true; } else { return false; } } template <typename T, typename... Args> static bool JsonParse(const std::vector<std::string>& names, int index, const Json::Value& jsonTypeValue, T& arg, Args&... args) { if (!JsonParse(names, index, jsonTypeValue, arg)) { return false; } else { return JsonParse(names, index + 1, jsonTypeValue, args...); } }
#define JSONCONVERT2OBJECT_MEMEBER_REGISTER(...) bool ParseHelpImpl(const Json::Value& jsonTypeValue) { std::vector<std::string> names = Member2KeyParseWithStr(#__VA_ARGS__); return JsonParse(names, 0, jsonTypeValue, __VA_ARGS__); }
例如DemoObjct
這個類結構體,新增JSONCONVERT2OBJECT_MEMEBER_REGISTER
並帶上成員變數的註冊宣告:
struct DemoObjct { bool boolValue; int intValue; std::string strValue; JSONCONVERT2OBJECT_MEMEBER_REGISTER(boolValue, intValue, strValue) };
等同於:
struct DemoObjct { bool boolValue; int intValue; std::string strValue; bool ParseHelpImpl(const Json::Value& jsonTypeValue, std::vector<std::string> &names) { names = Member2KeyParseWithStr("boolValue, intValue, strValue"); //names 得到 ["boolValue","intValue", "strValue"] //然後帶著這些key逐一從Json中取值賦值到成員變數中 return JsonParse(names, 0, jsonTypeValue, boolValue, intValue, strValue); } };
到目前為止,核心的功能已經實現。如果目標結構體類未新增JSON轉換的宣告註冊,外部在使用Json2Object
介面時會導致編譯報錯,提示找不到ParseHelpImpl
這個成員函數的宣告定義……,我們可以採用enable_if
來給未宣告註冊宏的結構體提供預設函數。
template <typename TClass, typename enable_if<HasConverFunction<TClass>::has, int>::type = 0> static inline bool Json2Object(TClass& aimObj, const Json::Value& jsonTypeValue) { std::vector<std::string> names = PreGetCustomMemberNameIfExists(aimObj); return aimObj.JSONCONVERT2OBJECT_MEMEBER_REGISTER_RESERVERD_IMPLE(jsonTypeValue, names); } template <typename TClass, typename enable_if<!HasConverFunction<TClass>::has, int>::type = 0> static inline bool Json2Object(TClass& aimObj, const Json::Value& jsonTypeValue) { return false; }
目前的實現均為將成員變數的名稱作為JSON串中的Key名稱,為靈活處理,再補充一個宏用於重新宣告結構體成員變數中對應到JSON串中的key,例如:
struct DemoObjct { bool boolValue; int intValue; std::string strValue; JSONCONVERT2OBJECT_MEMEBER_REGISTER(boolValue, intValue, strValue) /*重新宣告成員變數對應到JSON串的key,注意順序一致*/ JSONCONVERT2OBJECT_MEMEBER_RENAME_REGISTER("bValue", "iValue", "sValue") }; DemoObjct demoObj; /*boolValue <--> bValue; intValue <--> iValue; ...*/ REQUIRE(Json2Object(demoObj, std::string("{"bValue":true, "iValue":1234, "sValue":"demo object!"}"))); REQUIRE(demoObj.boolValue == true); REQUIRE(demoObj.intValue == 1234); REQUIRE(demoObj.strValue == "demo object!");
上面提到為大多為實現Json2Object
介面所提供的操作,從結構體物件轉成Json也是類似的操作,這裡就不再闡述,詳細可參考原始碼。
#include "json/json.h" #include <string> #include <vector> #include <initializer_list> #define JSONCONVERT2OBJECT_MEMEBER_REGISTER(...) bool JSONCONVERT2OBJECT_MEMEBER_REGISTER_RESERVERD_IMPLE(const Json::Value& jsonTypeValue, std::vector<std::string> &names) { if(names.size() <= 0) { names = Member2KeyParseWithStr(#__VA_ARGS__); } return JsonParse(names, 0, jsonTypeValue, __VA_ARGS__); } bool OBJECTCONVERT2JSON_MEMEBER_REGISTER_RESERVERD_IMPLE(Json::Value& jsonTypeValue, std::vector<std::string> &names) const { if(names.size() <= 0) { names = Member2KeyParseWithStr(#__VA_ARGS__); } return ParseJson(names, 0, jsonTypeValue, __VA_ARGS__); } #define JSONCONVERT2OBJECT_MEMEBER_RENAME_REGISTER(...) std::vector<std::string> JSONCONVERT2OBJECT_MEMEBER_RENAME_REGISTER_RESERVERD_IMPLE() const { return Member2KeyParseWithMultiParam({ __VA_ARGS__ }); } namespace JSON { template <bool, class TYPE = void> struct enable_if { }; template <class TYPE> struct enable_if<true, TYPE> { typedef TYPE type; }; } //JSON template <typename T> struct HasConverFunction { template <typename TT> static char func(decltype(&TT::JSONCONVERT2OBJECT_MEMEBER_REGISTER_RESERVERD_IMPLE)); //@1 template <typename TT> static int func(...); //@2 const static bool has = (sizeof(func<T>(NULL)) == sizeof(char)); template <typename TT> static char func2(decltype(&TT::JSONCONVERT2OBJECT_MEMEBER_RENAME_REGISTER_RESERVERD_IMPLE)); //@1 template <typename TT> static int func2(...); //@2 const static bool has2 = (sizeof(func2<T>(NULL)) == sizeof(char)); }; static std::vector<std::string> Member2KeyParseWithMultiParam(std::initializer_list<std::string> il) { std::vector<std::string> result; for (auto it = il.begin(); it != il.end(); it++) { result.push_back(*it); } return result; } inline static std::string NormalStringTrim(std::string const& str) { static char const* whitespaceChars = "nrt "; std::string::size_type start = str.find_first_not_of(whitespaceChars); std::string::size_type end = str.find_last_not_of(whitespaceChars); return start != std::string::npos ? str.substr(start, 1 + end - start) : std::string(); } inline static std::vector<std::string> NormalStringSplit(std::string str, char splitElem) { std::vector<std::string> strs; std::string::size_type pos1, pos2; pos2 = str.find(splitElem); pos1 = 0; while (std::string::npos != pos2) { strs.push_back(str.substr(pos1, pos2 - pos1)); pos1 = pos2 + 1; pos2 = str.find(splitElem, pos1); } strs.push_back(str.substr(pos1)); return strs; } static std::vector<std::string> Member2KeyParseWithStr(const std::string& values) { std::vector<std::string> result; auto enumValues = NormalStringSplit(values, ','); result.reserve(enumValues.size()); for (auto const& enumValue : enumValues) { result.push_back(NormalStringTrim(enumValue)); } return result; } ////////////////////////////////////////////////////////////////////////////// static bool Json2Object(bool& aimObj, const Json::Value& jsonTypeValue) { if (jsonTypeValue.isNull() || !jsonTypeValue.isBool()) { return false; } else { aimObj = jsonTypeValue.asBool(); return true; } } static bool Object2Json(Json::Value& jsonTypeValue, const std::string& key, bool value) { jsonTypeValue[key] = value; return true; } static bool Json2Object(int& aimObj, const Json::Value& jsonTypeValue) { if (jsonTypeValue.isNull() || !jsonTypeValue.isInt()) { return false; } else { aimObj = jsonTypeValue.asInt(); return true; } } static bool Object2Json(Json::Value& jsonTypeValue, const std::string& key, const int& value) { jsonTypeValue[key] = value; return true; } static bool Json2Object(unsigned int& aimObj, const Json::Value& jsonTypeValue) { if (jsonTypeValue.isNull() || !jsonTypeValue.isUInt()) { return false; } else { aimObj = jsonTypeValue.asUInt(); return true; } } static bool Object2Json(Json::Value& jsonTypeValue, const std::string& key, const unsigned int& value) { jsonTypeValue[key] = value; return true; } static bool Json2Object(double& aimObj, const Json::Value& jsonTypeValue) { if (jsonTypeValue.isNull() || !jsonTypeValue.isDouble()) { return false; } else { aimObj = jsonTypeValue.asDouble(); return true; } } static bool Object2Json(Json::Value& jsonTypeValue, const std::string& key, const double& value) { jsonTypeValue[key] = value; return true; } static bool Json2Object(std::string& aimObj, const Json::Value& jsonTypeValue) { if (jsonTypeValue.isNull() || !jsonTypeValue.isString()) { return false; } else { aimObj = jsonTypeValue.asString(); return true; } } static bool Object2Json(Json::Value& jsonTypeValue, const std::string& key, const std::string& value) { jsonTypeValue[key] = value; return true; } template <typename TClass, typename JSON::enable_if<HasConverFunction<TClass>::has2, int>::type = 0> static inline std::vector<std::string> PreGetCustomMemberNameIfExists(const TClass& aimObj) { return aimObj.JSONCONVERT2OBJECT_MEMEBER_RENAME_REGISTER_RESERVERD_IMPLE(); } template <typename TClass, typename JSON::enable_if<!HasConverFunction<TClass>::has2, int>::type = 0> static inline std::vector<std::string> PreGetCustomMemberNameIfExists(const TClass& aimObj) { return std::vector<std::string>(); } template <typename TClass, typename JSON::enable_if<HasConverFunction<TClass>::has, int>::type = 0> static inline bool Json2Object(TClass& aimObj, const Json::Value& jsonTypeValue) { std::vector<std::string> names = PreGetCustomMemberNameIfExists(aimObj); return aimObj.JSONCONVERT2OBJECT_MEMEBER_REGISTER_RESERVERD_IMPLE(jsonTypeValue, names); } template <typename TClass, typename JSON::enable_if<!HasConverFunction<TClass>::has, int>::type = 0> static inline bool Json2Object(TClass& aimObj, const Json::Value& jsonTypeValue) { return false; } template <typename T> static bool Json2Object(std::vector<T>& aimObj, const Json::Value& jsonTypeValue) { if (jsonTypeValue.isNull() || !jsonTypeValue.isArray()) { return false; } else { aimObj.clear(); bool result(true); for (int i = 0; i < jsonTypeValue.size(); ++i) { T item; if (!Json2Object(item, jsonTypeValue[i])) { result = false; } aimObj.push_back(item); } return result; } } template <typename T> static bool JsonParse(const std::vector<std::string>& names, int index, const Json::Value& jsonTypeValue, T& arg) { const auto key = names[index]; if (!jsonTypeValue.isMember(key) || Json2Object(arg, jsonTypeValue[key])) { return true; } else { return false; } } template <typename T, typename... Args> static bool JsonParse(const std::vector<std::string>& names, int index, const Json::Value& jsonTypeValue, T& arg, Args&... args) { if (!JsonParse(names, index, jsonTypeValue, arg)) { return false; } else { return JsonParse(names, index + 1, jsonTypeValue, args...); } } /** Provider interface*/ template<typename TClass> bool Json2Object(TClass& aimObj, const std::string& jsonTypeStr) { Json::Reader reader; Json::Value root; if (!reader.parse(jsonTypeStr, root) || root.isNull()) { return false; } return Json2Object(aimObj, root); } static bool GetJsonRootObject(Json::Value& root, const std::string& jsonTypeStr) { Json::Reader reader; if (!reader.parse(jsonTypeStr, root)) { return false; } return true; } //////////////////////////////////////////////////////////////////////// template <typename TClass, typename JSON::enable_if<HasConverFunction<TClass>::has, int>::type = 0> static inline bool Object2Json(Json::Value& jsonTypeOutValue, const std::string& key, const TClass& objValue) { std::vector<std::string> names = PreGetCustomMemberNameIfExists(objValue); if (key.empty()) { return objValue.OBJECTCONVERT2JSON_MEMEBER_REGISTER_RESERVERD_IMPLE(jsonTypeOutValue, names); } else { Json::Value jsonTypeNewValue; const bool result = objValue.OBJECTCONVERT2JSON_MEMEBER_REGISTER_RESERVERD_IMPLE(jsonTypeNewValue, names); if (result) { jsonTypeOutValue[key] = jsonTypeNewValue; } return result; } } template <typename TClass, typename JSON::enable_if<!HasConverFunction<TClass>::has, int>::type = 0> static inline bool Object2Json(Json::Value& jsonTypeOutValue, const std::string& key, const TClass& objValue) { return false; } template <typename T> static bool Object2Json(Json::Value& jsonTypeOutValue, const std::string& key, const std::vector<T>& objValue) { bool result(true); for (int i = 0; i < objValue.size(); ++i) { Json::Value item; if (!Object2Json(item, "", objValue[i])) { result = false; } else { if (key.empty()) jsonTypeOutValue.append(item); else jsonTypeOutValue[key].append(item); } } return result; } template <typename T> static bool ParseJson(const std::vector<std::string>& names, int index, Json::Value& jsonTypeValue, const T& arg) { if (names.size() > index) { const std::string key = names[index]; return Object2Json(jsonTypeValue, key, arg); } else { return false; } } template <typename T, typename... Args> static bool ParseJson(const std::vector<std::string>& names, int index, Json::Value& jsonTypeValue, T& arg, Args&... args) { if (names.size() - (index + 0) != 1 + sizeof...(Args)) { return false; } const std::string key = names[index]; Object2Json(jsonTypeValue, key, arg); return ParseJson(names, index + 1, jsonTypeValue, args...); } /** Provider interface*/ template<typename T> bool Object2Json(std::string& jsonTypeStr, const T& obj) { //std::function<Json::Value()>placehoder = [&]()->Json::Value { return Json::Value(); }; //auto func = [&](std::function<Json::Value()>f) { return f(); }; //Json::Value val = func(placehoder); Json::StyledWriter writer; Json::Value root; const bool result = Object2Json(root, "", obj); if (result) { jsonTypeStr = writer.write(root); } return result; }
到此這篇關於C++對Json資料的友好處理的文章就介紹到這了,更多相關C++對Json資料的處理內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45