Boost.JSON Boost的JSON解析庫(1.75首發)
阿新 • • 發佈:2020-12-20
## 目錄
- [目錄](#目錄)
- [Boost的1.75版本新庫](#boost的175版本新庫)
- [JSON庫簡介](#json庫簡介)
- [JSON的簡單使用](#json的簡單使用)
- [編碼](#編碼)
- [最通用的方法](#最通用的方法)
- [使用`std::initializer_list`](#使用stdinitializer_list)
- [json物件的輸出](#json物件的輸出)
- [兩種對比](#兩種對比)
- [解碼](#解碼)
- [簡單的解碼](#簡單的解碼)
- [增加錯誤處理](#增加錯誤處理)
- [非嚴格模式](#非嚴格模式)
- [流輸入](#流輸入)
- [進階應用](#進階應用)
- [物件序列化](#物件序列化)
- [反序列化](#反序列化)
- [`Boost.JSON`的型別](#boostjson的型別)
- [array](#array)
- [object](#object)
- [string](#string)
- [value](#value)
- [總結](#總結)
- [引用](#引用)
## Boost的1.75版本新庫
12月11日,Boost社群釋出了1.75版本,相比較於原定的12月9日,推遲了兩天。這次更新帶來了三個新庫:`JSON`,`LEAF`,`PFR`。
其中`JSON`自然是json格式的解析庫,來自`Vinnie Falco`和`Krystian Stasiowski`。
`LEAF`是一個輕量的異常處理庫,來自`Emil Dotchevski`。
`PFR`是一個基礎的反射庫,不需要使用者使用巨集和樣版程式碼(由於還未仔細閱讀此庫,可能翻譯有一些不準確),來自`Antony Polukhin`。
## JSON庫簡介
其實在之前,`Boost`就已經有能夠解析JSON的庫了,名字叫做`Boost.PropertyTree`。`Boost.PropertyTree`不僅僅能夠解析`JSON`,還能解析`XML`,`INI`和`INFO`格式的檔案。但是由於成文較早及需要相容其他的資料格式,相比較於其他的`C++`解析庫,其顯得比較笨重,使用的時候有很多的不方便。
`Boost.JSON`相對於`Boost.PropertyTree`來所,其只能支援`JSON`格式的解析,但是其使用方法更為簡便,直接。華麗胡哨的東西也更多了。
## JSON的簡單使用
有兩種方法使用`Boost.JSON`,一種是動態連結庫,此時引入標頭檔案`boost/json.hpp`,同時連結對應的動態庫;第二種是使用header only模式,此時只需要引入標頭檔案`boost/json/src.hpp`即可。兩種方法各有優缺點,酌情使用。
### 編碼
#### 最通用的方法
我們要構造的json如下,包含了各種型別。
```json
{
"a_string" : "test_string",
"a_number" : 123,
"a_null" : null,
"a_array" : [1, "2", {"123" : "123"}],
"a_object" : {
"a_name": "a_data"
},
"a_bool" : true
}
```
構造的方法也很簡單:
```C++
boost::json::object val;
val["a_string"] = "test_string";
val["a_number"] = 123;
val["a_null"] = nullptr;
val["a_array"] = {
1, "2", boost::json::object({{"123", "123"}})
};
val["a_object"].emplace_object()["a_name"] = "a_data";
val["a_bool"] = true;
```
首先定義一個`object`,然後往裡面塞東西就好。其中有一個`emplace_object`這個比較重要,後面會提到。
結果:
![](https://img2020.cnblogs.com/blog/2105008/202012/2105008-20201219210900343-91590508.png)
#### 使用`std::initializer_list`
`Boost.JSON`支援使用`std::initializer_list`來構造自己的物件。所以也可以這樣使用:
```C++
boost::json::value val2 = {
{"a_string", "test_string"},
{"a_number", 123},
{"a_null", nullptr},
{"a_array", {1, "2", {{"123", "123"}}}},
{"a_object", {{"a_name", "a_data"}}},
{"a_bool", true}
};
```
結果如下:
![](https://img2020.cnblogs.com/blog/2105008/202012/2105008-20201219210935628-186020789.png)
#### json物件的輸出
生成了`json`物件以後,就可以使用`serialize`對物件進行序列化了。
```C++
std::cout << boost::json::serialize(val2) << std::endl;
```
結果如前兩圖。
除了直接把整個物件直接輸出,`Boost.JSON`還支援分部分進行流輸出,這種方法在資料量較大時,可以有效降低記憶體佔用。
```C++
boost::json::serializer ser;
ser.reset(&val);
char temp_buff[6];
while (!ser.done()) {
std::memset(temp_buff, 0, sizeof(char) * 6);
ser.read(temp_buff, 5);
std::cout << temp_buff << std::endl;
}
```
結果:
![](https://img2020.cnblogs.com/blog/2105008/202012/2105008-20201219210958851-1229465610.png)
如果快取變數是陣列,還可以直接使用`ser.read(temp_buff)`。
需要注意的是,`ser.read`並不會預設在字串末尾加`\0`,所以如果需要直接輸出,在輸入時對快取置0,同時為`\0`空餘一個字元。
也可以直接使用輸出的`boost::string_view`。
#### 兩種對比
這兩種方法對比的話,各有各的優點。前一種方法比較時候邊執行邊生成,後者適合一開始就需要直接生成的情形,而且相對來說,後者顯得比較的直觀。
但是第二種方法有一個容易出現問題的地方。比如以下兩個`json`物件:
```json
// json1
[["data", "value"]]
//json2
{"data": "value"}
```
如果使用第二種方法進行構建,如果一不小心的話,就有可能寫出一樣的程式碼:
```C++
boost::json::value confused_json1 = {{"data", "value"}};
boost::json::value confused_json2 = {{"data", "value"}};
std::cout << "confused_json1: " << boost::json::serialize(confused_json1) << std::endl;
std::cout << "confused_json2: " << boost::json::serialize(confused_json2) << std::endl;
```
而得到的結果,自然也是一樣的:
![](https://img2020.cnblogs.com/blog/2105008/202012/2105008-20201219211010192-115924265.png)
如果需要消除這一歧義,可以直接使用`Boost.JSON`提供的物件構建有可能產生歧義的地方:
```C++
boost::json::value no_confused_json1 = {boost::json::array({"data", "value"})};
boost::json::value no_confused_json2 = boost::json::object({{"data", "value"}});
```
結果為:
![](https://img2020.cnblogs.com/blog/2105008/202012/2105008-20201219211037544-1028703067.png)
### 解碼
`JSON`的解碼也比較簡單。
#### 簡單的解碼
```C++
auto decode_val = boost::json::parse("{\"123\": [1, 2, 3]}");
```
直接使用`boost::json::parse`,輸入相應的字串就行了。
#### 增加錯誤處理
```C++
boost::json::error_code ec;
boost::json::parse("{\"123\": [1, 2, 3]}", ec);
std::cout << ec.message() << std::endl;
boost::json::parse("{\"123\": [1, 2, 3}", ec);
std::cout << ec.message() << std::endl;
```
結果:
![](https://img2020.cnblogs.com/blog/2105008/202012/2105008-20201219211053398-1718286523.png)
#### 非嚴格模式
在這個模式下,`Boost.JSON`可以選擇性的對一些不那麼嚴重的錯誤進行忽略。
```C++
unsigned char buf[4096];
boost::json::static_resource mr(buf);
boost::json::parse_options opt;
opt.allow_comments = true; // 允許註釋
opt.allow_trailing_commas = true; // 允許最後的逗號
boost::json::parse("[1, 2, 3, ] // comment test", ec, &mr, opt);
std::cout << ec.message() << std::endl;
boost::json::parse("[1, 2, 3, ] // comment test", ec, &mr);
std::cout << ec.message() << std::endl;
```
結果如下:
![](https://img2020.cnblogs.com/blog/2105008/202012/2105008-20201219211115269-224341345.png)
可以看到,增加了選項的直譯器成功的解析了結果。
#### 流輸入
和輸出一樣,輸入也有流模式。
```C++
boost::json::stream_parser p;
p.reset();
p.write("[1, 2,");
p.write("3]");
p.finish();
std::cout << boost::json::serialize(p.release()) << std::endl;
```
結果:
![](https://img2020.cnblogs.com/blog/2105008/202012/2105008-20201219211128349-1433092488.png)
## 進階應用
### 物件序列化
有時候我們需要將物件轉換為`JSON`,對物件進行序列化然後儲存。`Boost.JSON`提供了一個非常簡單的方法,能夠使我們非常簡單的將一個我們自己定義的物件轉化為`JSON`物件。
我們只需要在需要序列化的類的名稱空間中,定義一個過載函式`tag_invoke`。注意,是類所在的名稱空間,而不是在類裡面定義。
使用示例:
```C++
namespace MyNameSpace {
class MyClass {
public:
int a;
int b;
MyClass (int a = 0, int b = 1):
a(a), b(b) {}
};
void tag_invoke(boost::json::value_from_tag, boost::json::value &jv, MyClass const &c) {
auto & jo = jv.emplace_object();
jo["a"] = c.a;
jo["b"] = c.b;
}
}
```
其中,`boost::json::value_from_tag`是作為標籤存在的,方便`Boost.JSON`分辨序列化函式的。`jv`是輸出的`JSON`物件,`c`是輸入的物件。
```C++
boost::json::value_from(MyObj)
```
使用的話,直接呼叫`value_from`函式即可。
結果:
![](https://img2020.cnblogs.com/blog/2105008/202012/2105008-20201219211142199-634862451.png)
序列化還有一個好處就是,可以在使用`std::initializer_list`初始化`JSON`物件時,直接使用自定義物件。譬如:
```C++
boost::json::value val = {MyObj};
```
注意,這裡的`val`是一個數組,裡面包含了一個物件`MyObj`。
### 反序列化
有序列化,自然就會有反序列化。操作和序列化的方法差不多,也是定義一個`tag_invoke`函式,不過其引數並不一致。
```C++
MyClass tag_invoke(boost::json::value_to_tag, boost::json::value const &jv) {
auto &jo = jv.as_object();
return MyClass(jo.at("a").as_int64(), jo.at("b").as_int64());
}
```
需要注意的是,由於傳入的`jv`是被`const`修飾的,所以不能類似於`jv["a"]`使用。
使用也和上面的類似,提供了一個`value_to<>`模板函式。
```C++
auto MyObj = boost::json::value_to(vj);
```
無論是序列化還是反序列化,對於標準庫中的容器,`Boost.JSON`都可以直接使用。
### `Boost.JSON`的型別
#### array
陣列型別,用於儲存`JSON`中的陣列。實際使用的時候類似於`std::vector`,差異極小。
#### object
物件型別,用於儲存`JSON`中的物件。實際使用時類似於`std::map`,但是相對來說,它們之間的差異較大。
#### string
字串型別,用於儲存`JSON`中的字串。實際使用時和`std::basic_string`類似,不過其只支援`UTF-8`編碼,如果需要支援其他編碼,在解碼時候需要修改option中相應的選項。
#### value
可以儲存任意型別,也可以變換為各種型別。其中有一些特色的函式比如`as_object`,`get_array`,`emplace_int64`之類的。它們的工作都類似,將`boost::json::value`物件轉化為對應的型別。但是他們之間也有一定的區別。
- `as_xxx` 返回一個引用,如果型別不符合,會丟擲異常
- `get_xxx` 返回一個引用,不檢查型別,如果型別不符合,可能導致未定義行為
- `is_xxx` 判斷是否為`xxx`型別
- `if_xxx` 返回指標,如果型別不匹配則返回`nullptr`
- `emplace_xxx` 返回一個引用,可以直接改變其型別和內容。
## 總結
大致的使用方法就這些了。如果還要更進一步的話,就是涉及到其記憶體管理了。
縱觀整個庫的話,感覺其對於模板的使用相當剋制,能不使用就不使用,這在一定程度上也提高了編譯的速度。
## 引用
1. https://www.boost.org/doc/libs/1_75_0/libs/json/doc/html/index.html
部落格原文:https://www.cnblogs.com/ink19/p/Boost_JS