使用C++17手擼JSON庫

zhoutk發表於2022-05-23

zjson

介紹

從node.js轉到c++,特別懷念在js中使用json那種暢快感。在c++中也使用過了些庫,但提供的介面使用方式,總不是習慣,很煩鎖,介面函式太多,不直觀。參考了很多庫,如:rapidjson, cJson, CJsonObject, drleq-cppjson, json11等,受cJson的資料結構啟發很大,決定用C++手擼一個。最後因為資料儲存需要不區分型別,又要能知道其型別,所以選擇了C++17才支援的std::variant以及std::any,最終,C++版本定格在c++17,本庫設計為單標頭檔案,且不依賴c++標準庫以外的任何庫。

專案名稱說明

本人姓名拼音第一個字母z加上josn,即得本專案名稱zjson,沒有其它任何意義。我將編寫一系列以z開頭的相關專案,命名是個很麻煩的事,因此採用了這種簡單粗暴的方式。

設計思路

簡單的介面函式、簡單的使用方法、靈活的資料結構、儘量支援鏈式操作。使用模板技術,使用給Json物件增加值的方法只有兩個,AddValueBase和AddValueJson。採用連結串列結構(向cJSON致敬)來儲存Json物件,請看我下面的資料結構設計,表頭與後面的結點,都用使用一致的結構,這使得在索引操作([])時,可以進行鏈式操作。

專案進度

專案目前完成一半,可以新建Json物件,增加資料,按key(Object型別)或索引(Array型別)提取相應的值或子物件,生成json字串。
已經做過記憶體洩漏測試,解構函式能正確執行,百萬級別生成與銷燬未見記憶體明顯增長。
任務列表:

  • [x] 建構函式、複製建構函式、解構函式
  • [x] AddValueBase(為Json物件增加值型別)、AddValueJson(為Json物件增加物件型別)
  • [x] operator=、operator[]
  • [x] toString(生成json字串)
  • [x] toInt、toDouble、toFalse 等值型別轉換
  • [x] isError、isNull、isArray 等節點型別判斷
  • [ ] parse, 從json字串生成Json物件;相應的建構函式
  • [ ] Extend Json - 擴充套件物件
  • [ ] Remove[All] key - 刪除資料, 因為Json物件允許重複的key
  • [ ] findAll - 查詢全部, 因為Json物件允許重複的key
  • [ ] std::move語義

資料結構

Json 節點型別定義

(內部使用,資料型別只在Json類內部使用)

enum Type {
    Error,                //錯誤,查詢無果,這是一個無效Json物件
    False,                //Json值型別 - false
    True,                 //Json值型別 - true
    Null,                 //Json值型別 - null
    Number,               //Json值型別 - 數字,庫中以double型別儲存
    String,               //Json值型別 - 字串
    Object,               //Json類物件型別 - 這是Object巢狀,物件型中只有child需要關注
    Array                 //Json類物件型別 - 這是Array巢狀,物件型中只有child需要關注
};

Json 節點定義

class Json {
    Json* brother;       //與cJSON中的next對應,值型別才有效,指向並列的資料,但有可能是值型別,也有可能是物件型別
    Json* child;         //孩子節點,物件型別才有效
    Type type;           //節點型別
    std::variant <int, bool, double, string> data;   //節點資料
    string name;         //節點的key
}

介面說明

公開的物件型別,json只支援Object與Array兩種物件,與內部型別對應(公開型別)。

enum class JsonType
{
    Object = 6,
    Array = 7
};

介面列表

  • Json(JsonType type = JsonType::Object) //預設建構函式,生成Object或Array型別的Json物件
  • Json(const Json& origin) //複製建構函式
  • Json& operator = (const Json& origin) //賦值操作
  • Json operator[](const int& index) //Json陣列物件元素查詢
  • Json operator[](const string& key) //Json Object 物件按key查詢
  • bool AddValueJson(Json& obj) //增加子Json類物件型別, 只面向Array
  • bool AddValueJson(string name, Json& obj) //增加子Json類物件型別,當obj為Array時,name會被忽略
  • template<typename T> bool AddValueBase(T value) //增加值物件型別,只面向Array
  • template<typename T> bool AddValueBase(string name, T value) //增加值物件型別,當this為Array時,name會被忽略
  • string toString() //Json物件序列化為字串
  • bool isError() //無效Json物件判定
  • bool isNull() //null值判定
  • bool isObject() //Object物件判定
  • bool isArray() //Array物件判定
  • bool isNumber() //number值判定,Json內使用double型別儲存number值
  • bool isTrue() //true值判定
  • bool isFalse() //false值判定
  • int toInt() //值物件轉為int
  • float toFloat() //值物件轉為float
  • double toDouble() //值物件轉為double
  • bool toBool() //值物件轉為bool

程式設計示例

簡單使用示例

    Json ajson(JsonType::Object);                   //新建Object物件,輸入引數可以省略
    std::string data = "kevin";                     
    ajson.AddValueBase("fail", false);              //增加false值物件
    ajson.AddValueBase("name", data);               //增加字串值物件
    ajson.AddValueBase("school-en", "the 85th.");   
    ajson.AddValueBase("age", 10);                  //增加number值物件,此處為整數
    ajson.AddValueBase("scores", 95.98);            //增加number值物件,此處為浮點數,還支援long,long long
    ajson.AddValueBase("nullkey", nullptr);         //增加null值物件,需要送入nullptr, NULL會被認為是整數0

    Json sub;                                       //新建Object物件
    sub.AddValueBase("math", 99);                 
    ajson.AddValueJson("subJson", sub);             //為ajson增加子Json型別物件,完成巢狀需要

    Json subArray(JsonType::Array);                 //新建Array物件,輸入引數不可省略
    subArray.AddValueBase("I'm the first one.");    //增加Array物件的字串值子物件
    subArray.AddValueBase("two", 2);                //增加Array物件的number值子物件,第一個引數會被忽略
    
    Json sub2;                            
    sub2.AddValueBase("sb2", 222);

    subArray.AddValueJson("subObj", sub2);          //為Array物件增加Object類子物件,完成巢狀需求
    
    ajson.AddValueJson("array", subArray);          //為ajson增加Array物件,且這個Array物件本身就是一個巢狀結構

    std::cout << "ajson's string is : " << ajson.toString() << std::endl;    //輸出ajson物件序列化後的字串, 結果見下方

    string name = ajson["name"].toString();         //提取key為name的字串值,結果為:kevin
    int oper = ajson["sb2"].toInt();                //提取巢狀深層結構中的key為sb2的整數值,結果為:222
    Json operArr = ajson["array"];                  //提取key為array的陣列物件
    string first = ajson["array"][0].toString();    //提取key為array的陣列物件的序號為0的值,結果為:I'm the first one.

ajson序列化後結果為:

{
    "fail": false,
    "name": "kevin",
    "school-en": "the 85th.",
    "age": 10,
    "scores": 95.98,
    "nullkey": null,
    "subJson": {
        "math": 99
    },
    "array": [
        "I'm the first one.",
        2,
        {
            "sb2": 222
        }
    ]
}

詳情請參看demo.cpp或tests目錄下的測試用例

專案地址

https://gitee.com/zhoutk/zjson
或
https://github.com/zhoutk/zjson

執行方法

該專案在vs2019, gcc7.5, clang12.0下均編譯執行正常。

git clone https://github.com/zhoutk/zjson
cd zjson
cmake -Bbuild .

---windows
cd build && cmake --build .

---linux & mac
cd build && make

run zjson or ctest

相關專案

會有一系列專案出爐,網路服務相關,敬請期待...

相關文章