1. 程式人生 > >Boost.JSON Boost的JSON解析庫(1.75首發)

Boost.JSON Boost的JSON解析庫(1.75首發)

## 目錄 - [目錄](#目錄) - [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