C++ 輕量級物件JSON序列化實現
背景:
在專案裡經常遇到物件和json字串的相互轉換這類問題,在大多數程式裡,一般這個問題都比較有比較好的解決方法,往往一個函式搞定。但是到了c++這邊卻需要我們手擼json庫一個一個欄位初始化/序列化。如果這時候有一個函式 可以一行程式碼 unmarshal /marshal 物件豈不是很方便?本文以jsoncpp庫為基礎,設計這樣一個可以支援這種功能的函式,下面進入正題~
設計思路
以unmarshal 為例,我們最終的函式是打造兩個這樣的模板函式 :
一個從string 的josn直接反序列化物件,一個從jsoncpp庫的json物件,反序列化物件。
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是具有自遞迴結構的,所以在設計時,應該也是以遞迴的方式解決複雜的組合類,我們可以簡單的把程式中的變數分為下面幾類:
這樣我們只需要把這幾個場景的 Unmarshal實現了,整體的Unmarshal也就實現了。模板設計的類圖應該和我們的分類相對應:
在實現中要注意以下幾點:
1、每個分類的Unmarshal模板具有排他性,也就是說基本型別在編譯期間只能匹配解析基本型別的模板
2、由於1的保證和這些模板函式名稱都是Unmarshal,所以他們之間可以相互巢狀呼叫對方。
3、指標、和原生陣列會涉及到空間分配和長度檢測會使得情況變得複雜,本次設計支援的範圍不包含對指標、原生陣列的支援。
匹配基本型別的Unmarshal模板
//只能解析基本型別 int long bool float double string 的一組模板
/*
* 其實可宣告為過載函式,宣告為模板只是為了可以放在標頭檔案中
* 不能宣告為 template <typename T> bool Unmarshal(int& obj,const Json::Value &root);是因為
* 在編譯時編譯器不能推斷出T的型別,導致編譯失敗.所以將模板型別列表設定為template <int = 0> (Nontype
* Parameters) 此處int並無實際意義
*/
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)
.....
匹配stl容器/其他第三方類庫的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; //型別T要含有T()建構函式
if(!Unmarshal(tmp,root[i])) //遞迴呼叫Unmarshal函式
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){
...
}
匹配自定義struct/class的Unmarshal模板
實現一組只能匹配自己定義的struct/class 就需要我們定義的物件有一些特殊的標誌,才能被模板函式識別。在這裡選擇給我們自己定義的類都實現public的unmarshal方法(實現方式後面講),這樣當編譯時發現一個物件含有 public的 unmarshal方法時,就知道使是我們自己定義的類,然後就呼叫特定的模板函式,這裡用到到了一個C++的語法 SFINAE(Substitution Failure Is Not An Error) 和std庫中enable_if
我們先來看一下C++ std庫中 enable_if 的簡要實現:
// 版本1 一個空的enable_if 結構體
template <bool, class _Tp = void>
struct enable_if {};
// 版本2 是版本1第一個引數為true的特例化實現,這個版本的enable_if含有 type型別
template <class _Tp>
struct enable_if<true, _Tp> {typedef _Tp type;};
int main(){
enable_if<true,int>::type a = 3; //匹配版本2,相當於 int a = 3
enable_if<false,int>::type b = 3; //匹配版本1,enable_if{}沒有type型別,觸發編譯錯誤
}
SFINAE 準則就是匹配失敗並不是錯誤,如果編譯器在匹配一個模板時引發了錯誤,這時候編譯器應當嘗試下一個匹配,而不應該報錯中止。利用這條規則和enable_if,解析我們自己struct/class Umarshal模板設計如下:
// 檢測一個類 是否含有非靜態非過載的unmarshal方法
template<typename T>
struct TestUnmarshalFunc {
//版本1
template<typename TT>
static char func(decltype(&TT::unmarshal));
//版本2
template<typename TT>
static int func(...);
/*
* 如果型別T沒有unmarshal方法,func<T>(NULL)匹配版本1時會產生錯誤,由於SFINAE準則,只能匹配版本2
* 的func,此時返回值4個位元組,has變數為false.反之 has變數為true
*/
const static bool has = (sizeof(func<T>(NULL)) == sizeof(char));
};
//如果物件自身含有 unmarshal 方法,則呼叫物件的unmarshal.否則會因SFINAE準則跳過這個版本的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);
}
好了,至此我們對三種基本型別的Umarshal函式設計好了,這時候任意一個T型別 在呼叫Unmarshal時,最終會與上面三種其中一個匹配。json 為string的可以利用上面的Unmarshal再封裝一個版本:
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);
}
接下來我們看如何在自定義的類中實現unmarshal函式:
//假設有一個People物件,有3個field需要反序列化,根據上面的要求,可能需要我們自己編寫unmarshal如下
struct People{
bool sex;
int age;
string name;
//盡力解析每個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數量很多,就很麻煩,而且解析每個field時,程式碼格式非常相似,是否存在一個巨集可以自動生成呢?答案是肯定的。talk is cheap,show me the code ,上程式碼!
struct People{
bool sex;
int age;
string name;
//用JSON_HELP巨集把需要序列化的field傳進去,就自動在類裡生成unmarshal、marshal函式
JSON_HELP(sex,age,name)
};
// JSON_HELP 是一個變參巨集
#define JSON_HELP(...) \
UNMARSHAL_OBJ(__VA_ARGS__) \ //這裡生成unmarshal函式
MARSHAL_OBJ(__VA_ARGS__)
/*
* UNMARSHAL_OBJ中FOR_EACH巨集第一個引數傳入一個函式,第二個引數傳入一個list
* 作用是對list中每個元素呼叫傳入的函式,這裡有點像python裡高階函式map()的味道
* 這樣就批量生成了下面的程式碼結構:
* 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 實現#######
//作用:傳入一個函式func和一個list,把func用在list的每個元素上
#define FOR_EACH(func,...) \
MACRO_CAT(__func_,COUNT(__VA_ARGS__))(func,__VA_ARGS__)
/*
* FOR_EACH在實現中 COUNT巨集用於統計引數個數,返回一個數字,MACRO_CAT巨集用於把兩個token連線起來,
* 如果__VA_ARGS__有三個變數為a,b,c 那麼這一步巨集展開後為:
* __func_3(func,a,b,c), 而__func_3 __func_2 __func_1 ... 定義如下
* /
// 巨集展開實現偽迴圈
/*
* __func_3(func,a,b,c) 具體展開過程:
* 第一次: __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上都呼叫了一次傳入的func函式
*/
#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 巨集實現#######
//作用: 返回傳入引數個數. 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 巨集實現#######
//作用: 將兩個token(可以是巨集),連線在一起
#define MACRO_CAT(a,b) __macro_cat__(a,b)
#define __macro_cat__(a,b) a##b
測試
我們的Umarshal 和 Marshal函式編寫好了,現在測試一下吧:
場景:有一個map物件存著教師的資訊,每個教師又儲存著ta教學生資訊,資料結構定義如下:
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; //需要序列化和反序列化的物件
測試程式碼:
// 對應於結構 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字串反序列化物件
bool ret = Json::Unmarshal(tchs,ori);
if(!ret){
cout<<"反序列失敗"<<endl;
return 0;
}else{
cout<<"反序列成功"<<endl;
}
// 序列化物件到 json字串
cout<<"輸出物件序列化的json:"<<endl;
string obj2json;
Json::Marshal(tchs,obj2json);
cout<<obj2json;
}
//##### 輸出結果#####
反序列成功
輸出物件序列化的json:
{"Tea_1":{"name":"Tea_1","stus":[{"id":201721020126,"name":"Stu.a","score":80.0,"sex":false},{"id":201101101537,"name":"Stu.b","score":0.0,"sex":true}],"subject":3},"Tea_2":{"name":"Tea_2","stus":[{"id":201521020128,"name":"Stu.c","score":59.0,"sex":true}],"subject":1}}
完整例子地址: https://git.xiaojukeji.com/sunriseyangxin/easyjson.git
作者:楊昕
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31559758/viewspace-2709197/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Spring的輕量級實現Spring
- Golang web filter 輕量級實現GolangWebFilter
- C#中實現JSON功能及物件的序列化和反序列化C#JSON物件
- JavaScript物件序列化為JSONJavaScript物件JSON
- PHP實現一個輕量級容器PHP
- c++實現Json庫C++JSON
- 用C++優雅的實現物件到檔案的序列化/反序列化C++物件
- python json反序列化為物件PythonJSON物件
- 死磕Synchronized底層實現--輕量級鎖synchronized
- 死磕Synchronized底層實現–輕量級鎖synchronized
- Java物件的序列化與反序列化-Json篇Java物件JSON
- Spring Boot整合Postgres實現輕量級全文搜尋Spring Boot
- 深度解析 Lucene 輕量級全文索引實現原理索引
- Json反序列化物件通用工具類JSON物件
- 輕量級工作流引擎的設計與實現
- JAVA物件分析之偏向鎖、輕量級鎖、重量級鎖升級過程Java物件
- 低程式碼之光!輕量級 GUI 的設計與實現GUI
- 輕量級分散式鎖的設計原理分析與實現分散式
- 輕量級流量複製goreplay實踐Go
- 一個輕量級的基於RateLimiter的分散式限流實現MIT分散式
- 移出Json物件的三級屬性JSON物件
- 輕量級超級 css 工具CSS
- 數倉的兩種輕量級資料交換格式:json與jsonbJSON
- JSON字串轉換為物件直接量JSON字串物件
- Oracle輕量級實時監控工具-oratopOracle
- 輕量級 memcached快取代理 twemproxy實踐快取
- Java:對一個物件序列化和反序列化的簡單實現Java物件
- C++實現對Json資料的友好處理C++JSON
- C#實體物件序列化成Json,並讓欄位的首字母小寫C#物件JSON
- 序列化: 一個老傢伙的鹹魚翻身(Java物件、XML、JSON、反序列化)Java物件XMLJSON
- PetaPoco .net 輕量級orm簡單實用教程ORM
- JSON-B:簡化 JSON 序列化和反序列化JSON
- PAT甲級1126~1130|C++實現C++
- SpringBoot實現輕量級動態定時任務管控及元件化Spring Boot元件化
- Kotlin Json 序列化KotlinJSON
- Java物件的序列化和反序列化實踐Java物件
- 物件序列化(序列化)物件
- JS實現JSON物件與URL引數的相互轉換JSON物件