JSON數(shù)據(jù)自動生成C++結構體
生成的c++結構體基于nlohmann/json進行解析斗塘,實現(xiàn)了類似JavaBean和C#中
JsonConvert.SerializeObject
的效果,將c++結構體與Json數(shù)據(jù)結構進行了映射宣赔,使得json解析成c++對象這一過程對上層屏蔽,可以實現(xiàn)快速開發(fā)。
背景
在編寫服務端程序時睛琳,除了和系統(tǒng)交互、業(yè)務邏輯的內(nèi)部實現(xiàn)踏烙,最主要的一部分就是和客戶端打交道。現(xiàn)在web服務器開發(fā)历等,最流行的數(shù)據(jù)傳輸格式基本是Json讨惩、Xml、Protobuf寒屯,其中Json格式由于其和javascript語言對象模型的兼容性最好荐捻,成為b/s模型下最常用的數(shù)據(jù)傳輸格式。
在高級語言如Java寡夹、C#中处面,有一些內(nèi)置的庫實現(xiàn)了語言對象模型和Json數(shù)據(jù)間的自動轉(zhuǎn)換,這一點著實讓cpper羨慕不已菩掏。雖然c++也有一些成熟的開源解析庫如nlohmann/json
魂角、RapidJson
、Jsoncpp
等智绸,讓解析Json已經(jīng)變得相對簡單高效野揪,但讓程序員手動根據(jù)字段進行逐一解析仍然是一件比較浪費時間的事情。在性能要求沒那么高的場景下(絕大多數(shù)情況)瞧栗,如果能實現(xiàn)c++對象和Json數(shù)據(jù)的自動轉(zhuǎn)換斯稳,無疑能大幅提高開發(fā)效率,并減少因程序員手誤導致的解析錯誤迹恐。
因此挣惰,考慮基于nlohmann/json解析庫,實現(xiàn)c++和Json數(shù)據(jù)的對象映射自動代碼生成。
nlohmann/json基礎
nlohmann/json是基于c++11特性實現(xiàn)的一個開源Json解析庫憎茂,其在github上的start數(shù)達到了13.4k珍语,開源協(xié)議為MIT license, 因此可以作為商用項目使用。整個解析庫只有一個json.hpp
唇辨,可以非常方便的移植到項目程序中廊酣。且解析庫提供的接口非常人性化,上手容易赏枚,學習成本較低亡驰。
而對象映射這一功能,nolhmann庫其實已經(jīng)替我們實現(xiàn)了饿幅,舉官方的一個例子說明:
namespace ns {
// a simple struct to model a person
struct person {
std::string name;
std::string address;
int age;
};
}
// create a person
ns::person p {"Ned Flanders", "744 Evergreen Terrace", 60};
// conversion: person -> json
json j = p;
std::cout << j << std::endl;
// {"address":"744 Evergreen Terrace","age":60,"name":"Ned Flanders"}
// conversion: json -> person
auto p2 = j.get<ns::person>();
// that's it
assert(p == p2);
};
可以看到凡辱,程序當中,我們只需要定義好一個Person
結構體栗恩,再定義一個json
對象透乾,兩者即可用=
進行隱式轉(zhuǎn)換。當然磕秤,實現(xiàn)隱式轉(zhuǎn)換的前提是定義相應的to_json
和from_json
函數(shù)乳乌,該例中:
using nlohmann::json;
namespace ns {
void to_json(json& j, const person& p) {
j = json{{"name", p.name}, {"address", p.address}, {"age", p.age}};
}
void from_json(const json& j, person& p) {
j.at("name").get_to(p.name);
j.at("address").get_to(p.address);
j.at("age").get_to(p.age);
}
} // namespace ns
如上,我們得出基于nlohmann/json實現(xiàn)對象映射的核心步驟:
- 定義一個c++結構體
- 編寫該c++結構體轉(zhuǎn)換為json對象的
to_json
函數(shù) - 編寫json對象轉(zhuǎn)換為該c++結構體的
from_json
函數(shù)
Python自動生成C++代碼
如上介紹市咆,對于一個現(xiàn)有的JSON數(shù)據(jù)汉操,我們還是需要編寫上述機械化的代碼,這些完全可以找出格式上的規(guī)則使用Python進行自動化代碼生成蒙兰。在github上搜索了相關項目后磷瘤,最終參考了一個項目的實現(xiàn)思路,使生成的代碼采用nlohmman/json進行解析搜变。
注: 原項目生成的代碼使用的JSON庫是
<cppRest/json.h>
采缚,項目鏈接kcris/json2cpp。
具體的生成代碼不做詳述挠他,后面會將源碼Po上扳抽,有興趣的可以看一下≈城郑基本思想就是根據(jù)Json字段名進行類型區(qū)分摔蓝,對于對象類型進行遞歸生成。最終的生成結果采用一個Object對象類型對應一個.h頭文件和.cpp文件的形式愉耙。
C++解析贮尉、組裝函數(shù)封裝(可選)
nlohmann庫的隱式解析會拋出異常,我們需要捕獲異常并進行相應處理朴沿。因此猜谚,在cpp中考慮對這部分進行了二次封裝,使外層調(diào)用者不需要關心異常處理昌犹。此外斜姥,通信層傳輸?shù)腏SON格式有些是不帶外層節(jié)點的沧竟,有些是帶外層節(jié)點的悟泵,我們也需要對這兩種格式做適配糕非。
這部分有需要可以自己寫一下,沒有太多工作量禁筏。
快速開始
生成cpp文件
為了方便使用融师,基于tkinter做了一個界面蚁吝,打包成了一個EXE工具舀射。目前該工具只支持包含外層節(jié)點的JSON數(shù)據(jù)格式脆烟。
- 打開Json2cppTool.exe
- 填入JSON數(shù)據(jù)或者選擇JSON數(shù)據(jù)文件
- 選擇輸出路徑
- 點擊生成
JSON數(shù)據(jù)如下:
{
"UserInfoDetail": {
"mode": "",
"EmployeeNoList": [
{
"employeeNo": ""
}
]
}
}
文件導入工程
生成文件如下:
UserInfoDetail.h
UserInfoDetail.cpp
EmployeeNoList.h
EmployeeNoList.cpp
C++程序中使用
序列化
UserInfoDetail user_info_detail ;
user_info_detail.m_mode = "all";
string str_json = JsonSerialize(user_info_detail);
反序列化
ResponseStatus response_status;
if (!JsonDeserialize(str_raw, response_status))
{
return false;
}
That's it!
對于列表
std::list<T>
類型的節(jié)點驼抹,我們也無需做特殊處理框冀,nlohmann
已經(jīng)將列表和JSON Array
間的轉(zhuǎn)換實現(xiàn)掉了敏簿。
進階用法
序列化時控制是否輸出外層節(jié)點
默認會輸出外層節(jié)點,但是可以通過JsonSerialize(Obj,false)來指定不生成外層節(jié)點绣硝。
示例:
string str_json = JsonSerialize(user_info_detail, false);
輸出的JSON:
{
"mode": "",
"EmployeeNoList": [
{
"employeeNo": ""
}
]
}
指定組裝的節(jié)點
在默認情況下鹉胖,自動映射會將c++結構體中的所有成員均映射到JSON中的節(jié)點甫菠。
但有的場景王带,我們希望發(fā)送給客戶端或服務端的JSON數(shù)據(jù)中渗鬼,只包含部分必填字段带迟。
自動生成的c++結構體中包含了一個std::set<std::string> m_visibleSet;
成員仓犬,通過該成員控制需要輸出的節(jié)點舍肠。
示例:
UserInfoDetail user_info_detail ;
user_info_detail.m_mode = "all";
user_info_detail.m_visibleSet = {
"mode",
};
string str_json = JsonSerialize(clearCfg);
輸出的JSON:
{
"UserInfoDetail": {
"mode": "all"
}
}
指定需要忽略的節(jié)點
在默認情況下翠语,自動映射會將c++結構體中的所有成員均映射到JSON中的節(jié)點。
但有的場景点骑,我們希望發(fā)送給客戶端或服務端的JSON數(shù)據(jù)中黑滴,能忽略某些節(jié)點紧索。
自動生成的c++結構體中包含了一個std::set<std::string> m_hiddenSet;
成員珠漂,通過該成員控制需要忽略的節(jié)點葛菇。
示例:
UserInfoDetail user_info_detail ;
user_info_detail.m_mode = "all";
user_info_detail.m_hiddenSet = {
"EmployeeNoList",
};
string str_json = JsonSerialize(clearCfg);
輸出的JSON:
{
"UserInfoDetail": {
"mode": "all"
}
}
附錄
源碼見json2cppTool