C++ 輕量級(jí)對(duì)象JSON序列化實(shí)現(xiàn)詳情
背景:
在項(xiàng)目里經(jīng)常遇到對(duì)象和json
字符串的相互轉(zhuǎn)換這類(lèi)問(wèn)題,在大多數(shù)程序里,一般這個(gè)問(wèn)題都比較有比較好的解決方法,往往一個(gè)函數(shù)搞定。但是到了c++
這邊卻需要我們手?jǐn)]json庫(kù)一個(gè)一個(gè)字段初始化/序列化。如果這時(shí)候有一個(gè)函數(shù) 可以一行代碼 unmarshal /marshal
對(duì)象豈不是很方便?本文以jsoncpp
庫(kù)為基礎(chǔ),設(shè)計(jì)這樣一個(gè)可以支持這種功能的函數(shù),下面進(jìn)入正題~
1、設(shè)計(jì)思路
以unmarshal
為例,我們最終的函數(shù)是打造兩個(gè)這樣的模板函數(shù) :
一個(gè)從string
的josn
直接反序列化對(duì)象,一個(gè)從jsoncpp
庫(kù)的json
對(duì)象,反序列化對(duì)象。
template<typename T> bool Unmarshal(T& obj,const string& json_str); template<typename T> bool Unmarshal(T& obj,const Json::Value& json_obj_root);
由于json
是具有自遞歸結(jié)構(gòu)的,所以在設(shè)計(jì)時(shí),應(yīng)該也是以遞歸的方式解決復(fù)雜的組合類(lèi),我們可以簡(jiǎn)單的把程序中的變量分為下面幾類(lèi):
這樣我們只需要把這幾個(gè)場(chǎng)景的 Unmarshal
實(shí)現(xiàn)了,整體的Unmarshal
也就實(shí)現(xiàn)了。模板設(shè)計(jì)的類(lèi)圖應(yīng)該和我們的分類(lèi)相對(duì)應(yīng):
在實(shí)現(xiàn)中要注意以下幾點(diǎn):
- 每個(gè)分類(lèi)的
Unmarshal
模板具有排他性,也就是說(shuō)基本類(lèi)型在編譯期間只能匹配解析基本類(lèi)型的模板 - 由于1的保證和這些模板函數(shù)名稱(chēng)都是
Unmarshal
,所以他們之間可以相互嵌套調(diào)用對(duì)方。 - 指針、和原生數(shù)組會(huì)涉及到空間分配和長(zhǎng)度檢測(cè)會(huì)使得情況變得復(fù)雜,本次設(shè)計(jì)支持的范圍不包含對(duì)指針、原生數(shù)組的支持。
2、匹配基本類(lèi)型的Unmarshal模板
//只能解析基本類(lèi)型 int long bool float double string 的一組模板 /* * 其實(shí)可聲明為重載函數(shù),聲明為模板只是為了可以放在頭文件中 * 不能聲明為 template <typename T> bool Unmarshal(int& obj,const Json::Value &root);是因?yàn)? * 在編譯時(shí)編譯器不能推斷出T的類(lèi)型,導(dǎo)致編譯失敗.所以將模板類(lèi)型列表設(shè)置為template <int = 0> (Nontype * Parameters) 此處int并無(wú)實(shí)際意義 */ template <int = 0> inline bool Unmarshal(int& obj,const Json::Value &root){ if(!root.isIntegral()) return false; obj = root.asInt(); return true; } template <int = 0> inline bool Unmarshal(long& obj,const Json::Value &root) .....
3、匹配stl容器/其他第三方類(lèi)庫(kù)的Unmarshal模板
//只能匹配 vector<T> map<string,T> map<long,T> map<int,T> 的一組模板 //vector template <typename T> bool Unmarshal(vector<T>& obj,const Json::Value& root){ if(!root.isArray()) return false; obj.clear(); bool ret = true; for(int i=0;i<root.size();++i){ T tmp; //類(lèi)型T要含有T()構(gòu)造函數(shù) if(!Unmarshal(tmp,root[i])) //遞歸調(diào)用Unmarshal函數(shù) ret = false; obj.push_back(tmp); } return ret; } //map key:string template <typename T> bool Unmarshal(map<string,T>& obj,const Json::Value& root){ ... } //map key:long template <typename T> bool Unmarshal(map<long,T>& obj,const Json::Value& root){ ... } //map key:int template <typename T> bool Unmarshal(map<int,T>& obj,const Json::Value& root){ ... }
4、匹配自定義struct/class的Unmarshal模板
實(shí)現(xiàn)一組只能匹配自己定義的struct/class
就需要我們定義的對(duì)象有一些特殊的標(biāo)志,才能被模板函數(shù)識(shí)別。在這里選擇給我們自己定義的類(lèi)都實(shí)現(xiàn)public的unmarshal
方法(實(shí)現(xiàn)方式后面講),這樣當(dāng)編譯時(shí)發(fā)現(xiàn)一個(gè)對(duì)象含有 public
的 unmarshal
方法時(shí),就知道使是我們自己定義的類(lèi),然后就調(diào)用特定的模板函數(shù),這里用到到了一個(gè)C++的語(yǔ)法 SFINAE(Substitution Failure Is Not An Error) 和std庫(kù)中enable_if
我們先來(lái)看一下C++ std庫(kù)中 enable_if 的簡(jiǎn)要實(shí)現(xiàn):
// 版本1 一個(gè)空的enable_if 結(jié)構(gòu)體 template <bool, class _Tp = void> struct enable_if {}; // 版本2 是版本1第一個(gè)參數(shù)為true的特例化實(shí)現(xiàn),這個(gè)版本的enable_if含有 type類(lèi)型 template <class _Tp> struct enable_if<true, _Tp> {typedef _Tp type;}; int main(){ enable_if<true,int>::type a = 3; //匹配版本2,相當(dāng)于 int a = 3 enable_if<false,int>::type b = 3; //匹配版本1,enable_if{}沒(méi)有type類(lèi)型,觸發(fā)編譯錯(cuò)誤 }
SFINAE 準(zhǔn)則就是匹配失敗并不是錯(cuò)誤,如果編譯器在匹配一個(gè)模板時(shí)引發(fā)了錯(cuò)誤,這時(shí)候編譯器應(yīng)當(dāng)嘗試下一個(gè)匹配,而不應(yīng)該報(bào)錯(cuò)中止。利用這條規(guī)則和enable_if,解析我們自己struct/class Umarshal
模板設(shè)計(jì)如下:
// 檢測(cè)一個(gè)類(lèi) 是否含有非靜態(tài)非重載的unmarshal方法 template<typename T> struct TestUnmarshalFunc { //版本1 template<typename TT> static char func(decltype(&TT::unmarshal)); //版本2 template<typename TT> static int func(...); /* * 如果類(lèi)型T沒(méi)有unmarshal方法,func<T>(NULL)匹配版本1時(shí)會(huì)產(chǎn)生錯(cuò)誤,由于SFINAE準(zhǔn)則,只能匹配版本2 * 的func,此時(shí)返回值4個(gè)字節(jié),has變量為false.反之 has變量為true */ const static bool has = (sizeof(func<T>(NULL)) == sizeof(char)); }; //如果對(duì)象自身含有 unmarshal 方法,則調(diào)用對(duì)象的unmarshal.否則會(huì)因SFINAE準(zhǔn)則跳過(guò)這個(gè)版本的Unamrshal template <typename T,typename enable_if<TestUnmarshalFunc<T>::has,int>::type = 0> inline bool Unmarshal(T& obj,const Json::Value &root){ return obj.unmarshal(root); }
好了,至此我們對(duì)三種基本類(lèi)型的Umarshal
函數(shù)設(shè)計(jì)好了,這時(shí)候任意一個(gè)T類(lèi)型 在調(diào)用Unmarshal
時(shí),最終會(huì)與上面三種其中一個(gè)匹配。json 為string的可以利用上面的Unmarshal
再封裝一個(gè)版本:
template <typename T> bool Unmarshal(T& obj,const string &s){ Json::Reader reader; Json::Value root; if(!reader.parse(s,root)) return false; return Unmarshal(obj,root); }
接下來(lái)我們看如何在自定義的類(lèi)中實(shí)現(xiàn)unmarshal
函數(shù):
//假設(shè)有一個(gè)People對(duì)象,有3個(gè)field需要反序列化,根據(jù)上面的要求,可能需要我們自己編寫(xiě)unmarshal如下 struct People{ bool sex; int age; string name; //盡力解析每個(gè)field,只有全部正確解析才返回true bool unmarshal(const Json::Value &root){ bool ret = true; if(!Json::Unmarshal(sex,root["sex"])){ ret = false; } if(!Json::Unmarshal(age,root["age"])){ ret = false; } if(!Json::Unmarshal(name,root["name"])){ ret = false; } return ret; } };
顯然如果field數(shù)量很多,就很麻煩,而且解析每個(gè)field
時(shí),代碼格式非常相似,是否存在一個(gè)宏可以自動(dòng)生成呢?答案是肯定的。talk is cheap
,show me the code
,上代碼!
struct People{ bool sex; int age; string name; //用JSON_HELP宏把需要序列化的field傳進(jìn)去,就自動(dòng)在類(lèi)里生成unmarshal、marshal函數(shù) JSON_HELP(sex,age,name) }; // JSON_HELP 是一個(gè)變參宏 #define JSON_HELP(...) \ UNMARSHAL_OBJ(__VA_ARGS__) \ //這里生成unmarshal函數(shù) MARSHAL_OBJ(__VA_ARGS__) /* * UNMARSHAL_OBJ中FOR_EACH宏第一個(gè)參數(shù)傳入一個(gè)函數(shù),第二個(gè)參數(shù)傳入一個(gè)list * 作用是對(duì)list中每個(gè)元素調(diào)用傳入的函數(shù),這里有點(diǎn)像python里高階函數(shù)map()的味道 * 這樣就批量生成了下面的代碼結(jié)構(gòu): * if(!Json::Unmarshal(field_1,root["field_1"])){ * ret = false; * } * if(!Json::Unmarshal(field_2,root["field_2"])){ * ret = false; * } * ... .. */ #define UNMARSHAL_OBJ(...) \ bool unmarshal(const Json::Value& root){ \ bool ret = true; \ FOR_EACH(__unmarshal_obj_each_field__,__VA_ARGS__) \ return ret; \ } #define __unmarshal_obj_each_field__(field) \ if(!Json::Unmarshal(field,root[#field])){ \ ret = false; \ } //###### FOR_EACH 實(shí)現(xiàn)####### //作用:傳入一個(gè)函數(shù)func和一個(gè)list,把func用在list的每個(gè)元素上 #define FOR_EACH(func,...) \ MACRO_CAT(__func_,COUNT(__VA_ARGS__))(func,__VA_ARGS__) /* * FOR_EACH在實(shí)現(xiàn)中 COUNT宏用于統(tǒng)計(jì)參數(shù)個(gè)數(shù),返回一個(gè)數(shù)字,MACRO_CAT宏用于把兩個(gè)token連接起來(lái), * 如果__VA_ARGS__有三個(gè)變量為a,b,c 那么這一步宏展開(kāi)后為: * __func_3(func,a,b,c), 而__func_3 __func_2 __func_1 ... 定義如下 * / // 宏展開(kāi)實(shí)現(xiàn)偽循環(huán) /* * __func_3(func,a,b,c) 具體展開(kāi)過(guò)程: * 第一次: __func_1(func,a) __func_2(func,b,c) * 第二次: func(a) __func_1(func,b) __func_1(func,c) * 第三次: func(a) func(b) func(c) * 最終在a,b,c上都調(diào)用了一次傳入的func函數(shù) */ #define __func_1(func,member) func(member); #define __func_2(func,member,...) __func_1(func,member) __func_1(func,__VA_ARGS__) #define __func_3(func,member,...) __func_1(func,member) __func_2(func,__VA_ARGS__) #define __func_4(func,member,...) __func_1(func,member) __func_3(func,__VA_ARGS__) #define __func_5(func,member,...) __func_1(func,member) __func_4(func,__VA_ARGS__) ... ... //###### COUNT 宏實(shí)現(xiàn)####### //作用: 返回傳入?yún)?shù)個(gè)數(shù). eg: COUNT(a,b,c)返回3 #define COUNT(...) __count__(0, ##__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) #define __count__(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N ###### MACRO_CAT 宏實(shí)現(xiàn)####### //作用: 將兩個(gè)token(可以是宏),連接在一起 #define MACRO_CAT(a,b) __macro_cat__(a,b) #define __macro_cat__(a,b) a##b
5、測(cè)試
我們的Umarshal
和 Marshal
函數(shù)編寫(xiě)好了,現(xiàn)在測(cè)試一下吧:
場(chǎng)景:有一個(gè)map
對(duì)象存著教師的信息,每個(gè)教師又保存著ta教學(xué)生信息,數(shù)據(jù)結(jié)構(gòu)定義如下:
struct Student { long id; bool sex; double score; string name; JSON_HELP(id,sex,score,name) }; struct Teacher { string name; int subject; vector<Student> stus; JSON_HELP(name,subject,stus) }; map<string,Teacher> tchs; //需要序列化和反序列化的對(duì)象
測(cè)試代碼:
// 對(duì)應(yīng)于結(jié)構(gòu) map<string,Teacher> 的json string ori = R"( { "Tea_1": { "name": "Tea_1", "subject": 3, "stus": [ { "id": 201721020126, "sex": false, "score": 80, "name": "Stu.a" }, { "id": 201101101537, "sex": true, "score": 0, "name": "Stu.b" } ] }, "Tea_2": { "name": "Tea_2", "subject": 1, "stus": [ { "id": 201521020128, "sex": true, "score": 59, "name": "Stu.c" } ] } } )"; int main() { map<string,Teacher> tchs; // 從json字符串反序列化對(duì)象 bool ret = Json::Unmarshal(tchs,ori); if(!ret){ cout<<"反序列失敗"<<endl; return 0; }else{ cout<<"反序列成功"<<endl; } // 序列化對(duì)象到 json字符串 cout<<"輸出對(duì)象序列化的json:"<<endl; string obj2json; Json::Marshal(tchs,obj2json); cout<<obj2json; } //##### 輸出結(jié)果##### 反序列成功 輸出對(duì)象序列化的json: {"Tea_1":{"name":"Tea_1","stus":[{"id":201721020126,"name":"Stu.a","score":80.0,"sex":false},{
到此這篇關(guān)于C++ 輕量級(jí)對(duì)象JSON序列化實(shí)現(xiàn)詳情的文章就介紹到這了,更多相關(guān)C++ 輕量級(jí)對(duì)象JSON序列化實(shí)現(xiàn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
QT中QByteArray與char、int、float之間的互相轉(zhuǎn)化
本文主要介紹了QT中QByteArray與char、int、float之間的互相轉(zhuǎn)化,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05OpenCV實(shí)現(xiàn)繞圖片中任意角度旋轉(zhuǎn)任意角度
這篇文章主要為大家詳細(xì)介紹了在圖片不被裁剪時(shí),opencv如何實(shí)現(xiàn)繞圖片中任意點(diǎn)旋轉(zhuǎn)任意角度,文中的示例代碼講解詳細(xì),需要的可以參考一下2022-09-09C++實(shí)戰(zhàn)之二進(jìn)制數(shù)據(jù)處理與封裝
在電腦上一切數(shù)據(jù)都是通過(guò)二進(jìn)制(0或1)進(jìn)行存儲(chǔ)的,通過(guò)多位二進(jìn)制數(shù)據(jù)可以進(jìn)而表示整形、浮點(diǎn)型、字符、字符串等各種基礎(chǔ)類(lèi)型數(shù)據(jù)或者一些更復(fù)雜的數(shù)據(jù)格式。本文將為大家詳細(xì)講講二進(jìn)制數(shù)據(jù)處理與封裝,需要的可以參考一下2022-08-08C++非遞歸隊(duì)列實(shí)現(xiàn)二叉樹(shù)的廣度優(yōu)先遍歷
這篇文章主要介紹了C++非遞歸隊(duì)列實(shí)現(xiàn)二叉樹(shù)的廣度優(yōu)先遍歷,實(shí)例分析了遍歷二叉樹(shù)相關(guān)算法技巧,并附帶了兩個(gè)相關(guān)算法實(shí)例,需要的朋友可以參考下2015-07-07解析C++編程中異常相關(guān)的堆棧展開(kāi)和throw()異常規(guī)范
這篇文章主要介紹了C++編程中異常相關(guān)的堆棧展開(kāi)和throw()異常規(guī)范,throw()規(guī)范部分文中結(jié)合了C++11標(biāo)準(zhǔn)的新特性來(lái)講,需要的朋友可以參考下2016-01-01