Json文件解析(下)
代碼地址:https://github.com/nlohmann/json
從STL容器轉換
任何序列容器(std::array,std::vector,std::deque,std::forward_list,std::list),其值可以被用於構建JSON值(例如,整數,浮點數,布爾值,字符串類型,或者再次在本節中描述STL容器)可被用於創建JSON陣列。這同樣適用於類似的關聯容器(std::set,std::multiset,std::unordered_set,std::unordered_multiset),但是在這些情況下,數組中的元素的順序取決於元素是如何在各個STL容器排序。
std ::矢量< INT > c_vector { 1,2,3,4 };
json j_vec(c_vector);
// [1、2、3、4]
std ::雙端隊列< 雙 > c_deque { 1.2,2.3,3.4,5.6 };
json j_deque(c_deque);
// [1.2、2.3、3.4、5.6]
std :: list < bool > c_list { true,true,false,true };
json j_list(c_list);
// [true,true,false,true]
std ::修飾符Modifiers < 的int64_t > c_flist { 12345678909876,23456789098765,34567890987654,45678909876543 };
json j_flist(c_flist);
// [12345678909876、23456789098765、34567890987654、45678909876543]
std ::陣列< 無符號 長,4 > c_array {{ 1,2,3,4 }};
json j_array(c_array);
// [1、2、3、4]
std :: set <std :: string> c_set { “一個”,“兩個”,“三個”,“四個”,“一個” };
json j_set(c_set); //僅使用“一個”的一個條目
// // [“四個”,“一個”,“三個”,“兩個”]
std :: unordered_set <std :: string> c_uset { “一”,“二”,“三”,“四”,“一” };
json j_uset(c_uset); //僅使用“一個”的一個條目
// //可能是[“兩個”,“三個”,“四個”,“一個”]
std :: multiset <std :: string> c_mset { “一個”,“兩個”,“一個”,“四個” };
json j_mset(c_mset); //使用兩個用於“一個”的條目
//也許[[一個”,“兩個”,“一個”,“四個”]
std :: unordered_multiset <std :: string> c_umset { “一”,“二”,“一”,“四” };
json j_umset(c_umset); //使用兩個用於“一個”的條目
//也許[[一個”,“兩個”,“一個”,“四個”]
同樣,任何締鍵值容器(std::map,std::multimap,std::unordered_map,std::unordered_multimap),其鍵可以構造一個std::string,並且其值可以被用於構建JSON值(見上文實施例)可用於創建一個JSON對象。請注意,在使用多圖的情況下,JSON對象中僅使用一個鍵,並且該值取決於STL容器的內部順序。
std :: map <std :: string,int > c_map {{ “一個”,1 },{ “兩個”,2 },{ “三個”,3 }};
json j_map(c_map);
// {“一個”:1,“三個”:3,“兩個”:2}
std :: unordered_map < const char *,double > c_umap {{ “一個”,1.2 },{ “兩個”,2.3 },{ “三個”,3.4 }};
json j_umap(c_umap);
// {“一個”:1.2,“兩個”:2.3,“三個”:3.4}
std :: multimap <std :: string,bool > c_mmap {{ “一個”,true },{ “兩個”,true },{ “三個”,false },{ “三個”,true }};
json j_mmap(c_mmap); //僅使用鍵“三”的一個條目
// //也許{“一個”:true,“兩個”:true,“三個”:true}
std :: unordered_multimap <std :: string,bool > c_ummap {{ “一”,true },{ “二”,true },{ “三”,false },{ “三”,true }};
json j_ummap(c_ummap); //僅使用鍵“三”的一個條目
// //也許{“一個”:true,“兩個”:true,“三個”:true}
JSON指針和JSON補丁
該庫支持JSON指針(RFC 6901)作為解決結構化值的替代方法。除此之外,JSON Patch(RFC 6902)允許描述兩個JSON值之間的差異-有效地允許Unix已知的patch和diff操作。
// JSON值
json j_original = R“( {{
” baz“:[” one“,” two“,” three“],
” foo“:” bar“
} )” _json;
//使用JSON指針(RFC 6901)訪問成員
j_original [ “ / baz / 1 ” _json_pointer];
// “兩個”
//一個JSON修補程序(RFC 6902)
json j_patch = R“( [[
{” op“:” replace“,” path“:” / baz“,” value“:” boo“},
{” op“:” add “,” path“:” / hello“,” value“:[” world“]},
{” op“:” remove“,” path“:” / foo“}
] )” _json;
//應用補丁
json j_result = j_original.patch(j_patch);
// {
// “ baz”:“ boo”,
// “ hello”:[“ world”]
// }
//根據兩個JSON值計算JSON補丁
json :: diff(j_result,j_original);
// [
// {“ op”:“ replace”,“ path”:“ / baz”,“ value”:[“ one”,“ Two”,“ three”]},
// {“ op”:“ remove“,” path“:” / hello“},
// {” op“:” add“,” path“:” / foo“,” value“:” bar“}
// ]
JSON合並補丁
該庫支持JSON合並補丁(RFC 7386)作為補丁格式。沒有使用JSON指針(請參見上文)來指定要操作的值,而是使用一種語法來描述更改,該語法與擬修改的文檔非常相似。
// JSON值
json j_document = R“( {
” a“:” b“,
” c“:{
” d“:” e“,
” f“:” g“
}
}} )” _json;
//補丁
json j_patch = R“( {
” a“:” z“,
” c“:{
” f“:null
}
} )” _json;
//應用補丁
j_document.merge_patch(j_patch);
// {
// “ a”:“ z”,
// “ c”:{
// “ d”:“ e”
// }
// }
隱式轉換
支持的類型可以隱式轉換為JSON值。
建議不要使用隱式轉換從一個JSON值。可以在此處找到有關此建議的更多詳細信息。
//字符串
std :: string s1 = “世界好!” ;
json js = s1;
自動 s2 = js.get <std :: string>();
//不推薦
std :: string s3 = js;
std :: string s4;
s4 = js;
//布爾
值bool b1 = true ;
json jb = b1;
自動 b2 = jb.get < bool >();
//不推薦
bool b3 = jb;
布爾 b4;
b4 = jb;
//數字
int i = 42 ;
json jn = i;
自動 f = jn.get < double >();
//不推薦使用
double f2 = jb;
雙 f3;
f3 = jb;
//等
請注意,char類型不會自動轉換為JSON字符串,而是自動轉換為整數。必須明確指定轉換為字符串:
char ch = ' A ' ; // ASCII值65
json j_default = ch; //存儲整數65
json j_string = std :: string(1,ch); //存儲字符串“ A”
任意類型轉換
每種類型都可以用JSON序列化,而不僅僅是STL容器和標量類型。通常,會按照以下方式進行操作:
名稱空間 ns {
// //一個簡單的結構,用於對人員進行建模
struct person {
std :: string名稱;
std :: string地址;
INT年齡;
};
}
ns :: person p = { “ Ned Flanders ”,“ 744 Evergreen Terrace ”,60 };
//轉換為JSON:將每個值復制到JSON對象中
json j;
j [ “ name ” ] = p.name;
j [ “ address ” ] = p.address;
j [ “ age ” ] = p.age;
// ...
//從JSON轉換:復制JSON對象中的每個值
ns :: person p {
j [ “ name ” ]。得到 <std :: string>(),
j [ “ address ” ]。得到 <std :: string>(),
j [ “年齡” ]。獲取 < int >()
};
可以工作,但是有很多樣板...幸運的是,有更好的方法:
//創建一個人
ns :: person p { “ Ned Flanders ”, “ 744 Evergreen Terrace ”, 60 };
//轉換:person-> json
json j = p;
std :: cout << j << std :: endl;
// {“地址”:“ 744 Evergreen Terrace”,“年齡”:60,“名稱”:“ Ned Flanders”}
//轉換:
json- > person auto p2 = j.get <ns :: person>();
//就是
斷言(p == p2);
基本用法
要使與一種類型一起使用,只需提供兩個功能:
使用 nlohmann :: json;
命名空間 ns {
void to_json(json&j,const person&p){
j = json {{ “ name ”,p。名稱 },{ “地址”,第 地址 },{ “年齡”,第 年齡 }};
}
無效 from_json(const json&j,person&p){
j。在(“名稱”)。get_to(第名);
j。在(“地址”)。get_to(第地址);
j。在(“年齡”)。get_to(第年齡);
}
} //名稱空間ns
就這樣!當json使用類型調用構造函數時,自定義to_json方法將被自動調用。同樣,在調用get<your_type>()或時get_to(your_type&),from_json將調用該方法。
一些重要的事情:
- 這些方法必須位於類型的名稱空間(可以是全局名稱空間)中,否則庫將無法找到(在本示例中,位於定義的名稱空間ns中person)。
- 在使用這些轉換的任何地方,這些方法都必須可用(例如,必須包含適當的標頭)。請查看問題1108,了解否則可能發生的錯誤。
- 使用時get<your_type>(),your_type 必須為DefaultConstructible。(有一種繞過此要求的方法,將在后面介紹。)
- 在函數中from_json,使用函數at()訪問對象值而不是operator[]。萬一鍵不存在,at將引發可以處理的異常,而operator[]表現出未定義的行為。
- 無需為STL類型添加序列化器或反序列化器,例如std::vector:該庫已經實現了這些。
使用宏簡化生活
如果只想對一些結構進行序列化/反序列化,則to_json/ from_json函數可能會很多。
只要(1)想要將JSON對象用作序列化和(2)要將成員變量名稱用作該對象中的對象鍵,就有兩個宏可以使生活更輕松:
- NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(name, member1, member2, ...) 將在要為其創建代碼的類/結構的名稱空間內定義。
- NLOHMANN_DEFINE_TYPE_INTRUSIVE(name, member1, member2, ...)在要為其創建代碼的類/結構中定義。該宏還可以訪問私有成員。
在兩個宏中,第一個參數是類/結構的名稱,其余所有參數都為成員命名。
例子
上面結構的to_json/ from_json函數person可以通過以下方式創建:
命名空間 ns {
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(人,姓名,地址,年齡)
}
這是一個NLOHMANN_DEFINE_TYPE_INTRUSIVE需要私人成員的示例:
命名空間 ns {
類 地址 {
私有:
std :: string street;
INT housenumber;
INT郵編;
公開:
NLOHMANN_DEFINE_TYPE_INTRUSIVE(地址,街道,門牌號,郵政編碼)
};
}
如何轉換第三方類型?
這需要更高級的技術。但首先,讓看看這種轉換機制是如何工作的:
該庫使用JSON序列化器將類型轉換為json。默認的序列化器nlohmann::json為nlohmann::adl_serializer(ADL表示參數依賴查找)。
這樣實現(簡化):
template < typename T>
struct adl_serializer {
static void to_json(json&j,const T&value){
//在T的命名空間中調用“ to_json”方法
}
static void from_json(const json&j,T&value){
//相同,但使用“ from_json”方法
}
};
當可以控制類型的名稱空間時,此序列化程序可以正常工作。但是,關於boost::optional或std::filesystem::path(C ++ 17)呢?劫持boost名稱空間是非常糟糕的,並且向模板添加模板專業化以外的內容是非法的std。
為了解決這個問題,需要adl_serializer在nlohmann名稱空間中添加的特殊化,這是一個示例:
//部分專業化(也可以完全專業化)
名稱空間 nlohmann {
模板 <類型名 T>
struct adl_serializer <boost :: optional <T >> {
靜態 void to_json(json&j, const boost :: optional <T>&opt){
如果(opt == boost :: none){
j = nullptr ;
} 其 {
j = * opt; //這將調用adl_serializer <T> :: to_json將
//發現T的命名空間中的自由函數to_json!
}
}
靜態 無效 from_json(const json&j,boost :: optional <T>&opt){
if(j。is_null()){
opt = boost :: none;
} 其 {
選擇= j。得到 <T>(); //與上述相同,但
//使用 adl_serializer <T> :: from_json
}
}
};
}
如何get()用於非默認的可構造/不可復制類型?
如果類型為MoveConstructible,則有一種方法。還需要專門化adl_serializer,但要有一個特殊的from_json重載:
struct move_only_type {
move_only_type()= 刪除 ;
move_only_type(int ii):i(ii){}
move_only_type(const move_only_type&)= delete ;
move_only_type(move_only_type &&)= 默認值;
詮釋 I;
};
名稱空間 nlohmann {
template <>
struct adl_serializer <move_only_type> {
//注意:返回類型不再是'void',該方法僅
//使用一個參數
static move_only_type from_json(const json&j){
return {j。get < int >()};
}
//這就是陷阱!必須提供to_json方法!否則
//
將//無法將move_only_type轉換為json,因為完全//對該類型的adl_serializer進行了專門化//
static void to_json(json&j,move_only_type t){
j = t ;
}
};
}
可以編寫自己的序列化器嗎?(高級使用)
是。可能需要看一下unit-udt.cpp測試套件,以查看一些示例。
如果編寫自己的序列化器,則需要做一些事情:
- 使用不同的basic_json別名nlohmann::json(的最后一個模板參數basic_json是JSONSerializer)
- basic_json在所有to_json/ from_json方法中使用別名(或模板參數)
- 使用nlohmann::to_json以及nlohmann::from_json何時需要ADL
這是一個示例,沒有簡化,僅接受大小<= 32的類型,並使用ADL。
// //應該使用void作為第二個模板參數
// //如果不需要對T
template < typename T, typename SFINAE = typename std :: enable_if < sizeof(T)<= 32 > :: type進行編譯時檢查 >
struct less_than_32_serializer {
模板 <類型名稱BasicJsonType>
靜態 void to_json(BasicJsonType&j,T值){
//要使用ADL,並
使用 nlohmann :: to_json調用正確的to_json重載;//這個方法由adl_serializer調用,
//這就是
to_json(j,value)發生魔術的地方;
}
template < typename BasicJsonType>
靜態 無效 from_json(const BasicJsonType&j,T&value){
//
使用 nlohmann :: from_json進行相同操作;
from_json(j,值);
}
};
重新實現序列化器時要非常小心,如果不注意,可能會導致堆棧溢出:
模板 < 類型名 T,無效 >
結構 bad_serializer
{
template < typename BasicJsonType>
static void to_json(BasicJsonType&j,const T&value){
//這將調用BasicJsonType :: json_serializer <T> :: to_json(j,value);
//如果BasicJsonType :: json_serializer == bad_serializer ...糟糕!
j =值;
}
template < typename BasicJsonType>
static void to_json(const BasicJsonType&j,T&value){
//這將調用BasicJsonType :: json_serializer <T> :: from_json(j,value);
//如果BasicJsonType :: json_serializer == bad_serializer ...糟糕!
值= j。模板 獲取 <T>(); //哎呀!
}
};
專門進行枚舉轉換
默認情況下,枚舉值以整數形式序列化為JSON。在某些情況下,這可能會導致不良行為。如果在將數據序列化為JSON之后對枚舉進行了修改或重新排序,則較晚的反序列化JSON數據可能是未定義的或與原始預期值不同的枚舉值。
可以更精確地指定給定枚舉如何映射到JSON和從JSON映射,如下所示:
//示例枚舉類型聲明
枚舉 TaskState {
TS_STOPPED,
TS_RUNNING,
TS_COMPLETED,
TS_INVALID = -1,
};
//將TaskState值作為字符串
NLOHMANN_JSON_SERIALIZE_ENUM 映射到JSON(TaskState,{
{TS_INVALID,nullptr },
{TS_STOPPED,“已停止” },
{TS_RUNNING,“正在運行” },
{TS_COMPLETED,“已完成”),
})
的NLOHMANN_JSON_SERIALIZE_ENUM()宏聲明一組to_json()/ from_json()功能型TaskState,同時避免重復和樣板序列化代碼。
用法:
//以字符串形式枚舉JSON
json j = TS_STOPPED;
斷言(j == “ stopped ”);
//枚舉的json字符串
json j3 = “正在運行”;
斷言(j3.get <TaskState>()== TS_RUNNING);
//要枚舉的未定義json值(其中,上面的第一個地圖項是默認值)
json jPi = 3.14 ;
斷言(jPi.get <TaskState>()== TS_INVALID);
就像上面的任意類型轉換一樣,
- NLOHMANN_JSON_SERIALIZE_ENUM() 必須在枚舉類型的名稱空間(可以是全局名稱空間)中聲明,否則庫將無法找到,並且將默認為整數序列化。
- 使用轉換的任何地方都必須可用(例如,必須包含適當的標題)。
其要點:
- 使用時get<ENUM_TYPE>(),未定義的JSON值將默認為地圖中指定的第一對。仔細選擇該默認對。
- 如果在地圖中多次指定枚舉或JSON值,則在與JSON進行相互轉換時,將從地圖頂部的第一個匹配項返回。
二進制格式(BSON,CBOR,MessagePack和UBJSON)
盡管JSON是一種無處不在的數據格式,但不是一種非常緊湊的格式,適合通過網絡進行數據交換。因此,該庫支持 BSON(二進制JSON),CBOR(簡明二進制對象表示形式),MessagePack和UBJSON(通用二進制JSON規范),以將JSON值有效地編碼為字節向量並對該向量進行解碼。
//創建一個JSON值
json j = R“( {” compact“:true,” schema“:0} )” _json;
//序列化為BSON
std :: vector <std :: uint8_t > v_bson = json :: to_bson(j);
// 0x1B,0x00、0x00、0x00、0x08、0x63、0x6F,0x6D,0x70、0x61、0x63、0x74、0x00、0x01、0x10、0x73、0x63、0x68、0x65、0x6D,0x61、0x00、0x00 0x00、0x00、0x00
//往返
json j_from_bson = json :: from_bson(v_bson);
//序列化為CBOR
std :: vector <std :: uint8_t > v_cbor = json :: to_cbor(j);
// 0xA2、0x67、0x63、0x6F,0x6D,0x70、0x61、0x63、0x74、0xF5、0x66、0x73、0x63、0x68、0x65、0x6D,0x61、0x00
//往返
json j_from_cbor = json :: from_cbor(v_cbor);
//序列化為MessagePack
std :: vector <std :: uint8_t > v_msgpack = json :: to_msgpack(j);
// 0x82、0xA7、0x63、0x6F,0x6D,0x70、0x61、0x63、0x74、0xC3、0xA6、0x73、0x63、0x68、0x65、0x6D,0x61、0x00
//往返
json j_from_msgpack = json :: from_msgpack(v_msgpack);
//序列化為UBJSON
std :: vector <std :: uint8_t > v_ubjson = json :: to_ubjson(j);
// 0x7B,0x69、0x07、0x63、0x6F,0x6D,0x70、0x61、0x63、0x74、0x54、0x69、0x06、0x73、0x63、0x68、0x65、0x6D,0x61、0x69、0x00、0x7D
//往返
json j_from_ubjson = json :: from_ubjson(v_ubjson);
該庫還支持BSON,CBOR(字節字符串)和MessagePack(bin,ext,fixext)的二進制類型。默認存儲std::vector<std::uint8_t>為在庫外部進行處理。
// CBOR字節串與有效載荷為0xCAFE
的std ::矢量<性病:: uint8_t > V = {的0x42, 0xCA, 0xFE的 };
//讀取值
json j = json :: from_cbor(v);
// JSON值的類型為binary
j.is_binary(); //正確
//獲取對存儲的二進制值的引用
auto&binary = j.get_binary();
//二進制值沒有子類型(CBOR沒有二進制子類型)
binary.has_subtype(); //錯誤
//訪問std :: vector <std :: uint8_t>成員函數
binary.size(); // 2
binary [ 0 ]; // 0xCA
binary [ 1 ]; // 0xFE
//將子類型設置為0x10
二進制。set_subtype( 0x10);
//序列化為MessagePack
auto cbor = json :: to_msgpack(j); // 0xD5(fixext2),0x10、0xCA,0xFE
支持的編譯器
盡管已經是2020年,但對C ++ 11的支持仍然很少。當前,已知以下編譯器可以工作:
- GCC 4.8-10.1(可能以后)
- 鐺3.4-10.0(可能以后)
- Apple Clang 9.1-12.0(可能更高)
- 英特爾C ++編譯器17.0.2(可能更高)
- Microsoft Visual C ++ 2015 /構建工具14.0.25123.0(以及更高版本)
- Microsoft Visual C ++ 2017 /生成工具15.5.180.51428(以及更高版本)
- Microsoft Visual C ++ 2019 /生成工具16.3.1 + 1def00d3d(以及更高版本)
很樂意了解其編譯器/版本。
請注意:
- GCC 4.8有一個錯誤57824):多行原始字符串不能作為宏的參數。不要使用此編譯器直接在宏中使用多行原始字符串。
- Android默認使用非常老的編譯器和C ++庫。要解決此問題,請將以下內容添加到中Application.mk。這將切換到LLVM C ++庫,Clang編譯器,並啟用C ++ 11和其默認禁用的功能。
- APP_STL := c++_shared
- NDK_TOOLCHAIN_VERSION := clang3.6
- APP_CPPFLAGS += -frtti -fexceptions
該代碼可使用Android NDK,修訂版9-11(以及更高版本)和CrystaX的Android NDK版本10 成功編譯。
- 對於在MinGW或Android SDK上運行的GCC 'to_string' is not a member of 'std',可能會發生錯誤(或類似的for strtod或strtof)。注意,這不是代碼問題,而是編譯器本身的問題。在Android上,請參見上文以使用較新的環境進行構建。對於MinGW,請參考此站點和此討論以獲取有關如何修復此錯誤的信息。對於Android NDK的使用APP_STL := gnustl_static,請參考此討論。
- #error指令拒絕不支持的GCC和Clang版本。可以通過定義關閉JSON_SKIP_UNSUPPORTED_COMPILER_CHECK。請注意,在這種情況下,不希望獲得任何支持。
Travis,AppVeyor,GitHub Actions和CircleCI當前在持續集成中使用編譯器