JSON for Modern C++ 庫的介紹與使用示例程式碼

墨1024發表於2020-10-22

JSON for Modern C++ 介紹

相關連結

GitHub:https://github.com/nlohmann/json

api文件:https://nlohmann.github.io/json/api/basic_json/

標頭檔案新增

//  json.hpp 唯一需要引入的檔案
#include <nlohmann/json.hpp>
// 使用如下語句方便使用
using json = nlohmann::json;

CMakeLists 新增

find_package(nlohmann_json 3.2.0 REQUIRED) # 找包

target_link_libraries(${PROJECT_NAME}
        PRIVATE
        nlohmann_json::nlohmann_json) # link 庫

示例程式碼

示例說明
簡單使用json物件的建立
序列化與反序列化1、字串的序列化與反序列化
2、流的序列化與反序列化(輸入輸出流+檔案流)
像STL一樣操作json物件json物件形如STL操作的方法使用
Json Pointer 與 Patch1、_json_pointer 的使用
2、patch與merge_patch的補丁操作
解析簡單json檔案讀取簡單json檔案,並將資料存放到不容變數中
解析json檔案為結構體以及將結構存放為json檔案1、讀取json檔案多級資料並存放為結構體,int 、string、陣列
2、將結構體資料迴圈處理後存放入json檔案

簡單使用

demo: SimpleUseTest

程式碼: GitHub SimpleUseTest

#include <iostream>
//  json.hpp 唯一需要引入的檔案
#include <nlohmann/json.hpp>
// 使用如下語句方便使用
using json = nlohmann::json;

int main() {

    /// 簡單使用
    //建立一個空的結構
    json j1;
    // 數字 儲存為 double
    j1["pi"] = 3.141;
    // Boolean 儲存為 bool
    j1["happy"] = true;
    // 字串 儲存為 std::string
    j1["name"] = "Niels";
    // 空值 傳入 nullptr
    j1["nothing"] = nullptr;
    // 直接加入其他物件
    j1["answer"]["everything"] = 42;
    // 加入一個  array 儲存為 std::vector (使用初始化列表)
    j1["list"] = { 1, 0, 2 };
    // 新增其他物件 (使用鍵值對初始化列表)
    j1["object"] = { {"currency", "USD"}, {"value", 42.99} };
    // 直接形如json格式進行初始化
    json j2 = {
            {"pi", 3.141},
            {"happy", true},
            {"name", "Niels"},
            {"nothing", nullptr},
            {"answer", {
                           {"everything", 42}
                   }},
            {"list", {1, 0, 2}},
            {"object", {
                           {"currency", "USD"},
                         {"value", 42.99}
                   }}
    };
    std::cout << "json j1 = " << j1 << std::endl;
    std::cout << "json j2 = " << j2 << std::endl;

    ///可使用函式 json::array() 和 json::object() 等 顯示錶示
    // 空陣列
    json empty_array_explicit = json::array();
    // 空 {} 物件
    json empty_object_implicit = json({});
    json empty_object_explicit = json::object();
    // 通過 key/value 鍵值對建立陣列 [["currency", "USD"], ["value", 42.99]]
    json array_not_object = json::array({ {"currency", "USD"}, {"value", 42.99} });
    std::cout << "json empty_array_explicit = " << empty_array_explicit << std::endl;
    std::cout << "json empty_object_implicit = " << empty_object_implicit << std::endl;
    std::cout << "json empty_object_explicit = " << empty_object_explicit << std::endl;
    std::cout << "json array_not_object = " << array_not_object << std::endl;
    std::cout << "json array_not_object[?][?] = " << array_not_object[1] << std::endl;
    return 0;
}

輸出:

json j1 = {"answer":{"everything":42},"happy":true,"list":[1,0,2],"name":"Niels","nothing":null,"object":{"currency":"USD","value":42.99},"pi":3.141}
json j2 = {"answer":{"everything":42},"happy":true,"list":[1,0,2],"name":"Niels","nothing":null,"object":{"currency":"USD","value":42.99},"pi":3.141}
json empty_array_explicit = []
json empty_object_implicit = {}
json empty_object_explicit = {}
json array_not_object = [["currency","USD"],["value",42.99]]
json array_not_object[?][?] = ["value",42.99]

Process finished with exit code 0

序列化與反序列化

demo:SerializationAndDeserialization

程式碼: GitHub SerializationAndDeserialization

#include <iostream>
#include <nlohmann/json.hpp>
#include <iomanip>
#include <fstream>

using json = nlohmann::json;

int main() {

    /// 字串 序列化與反序列化
    // 通過新增 _json 字尾將字串反序列化為json變數
    json j1 = "{ \"happy\": true, \"pi\": 3.141 }"_json;
    // 最好使用原始字串
    auto j2 = R"({"happy": true,"pi": 3.141})"_json;
    // 使用 json::parse() 顯示使用
    auto j3 = json::parse(R"({ "happy": true, "pi": 3.141 })");
    std::cout << "json j1" << j1 << std::endl;
    std::cout << "json j2" << j2 << std::endl;
    std::cout << "json j3" << j3 << std::endl;
    // 將json變數序列化為 字串  .dump()返回最初儲存的字串值。 dump()  dump(4)  4 顯示格式便於檢視
    std::string s = j3.dump();    // {"happy":true,"pi":3.141}
    std::cout << "string s j3.dump()" << s << std::endl;
    std::cout << "string s j3.dump(4)" << j3.dump(4) << std::endl;
    /// 序列化和賦值之間的區別
    /// 庫只支援UTF-8 儲存具有不同編碼的字串時,呼叫dump()可能會引發異常
    // 將字串儲存在JSON值中
    json j_string = "this is a string";
    std::cout << "json j_string = " << j_string << std::endl;
    // 獲取字串值 j_string.get<std::string>()
    auto cpp_string = j_string.get<std::string>();
    std::cout << "j_string.get<std::string>() cpp_string = " << cpp_string << std::endl;
    // 獲取字串值 並存到 變數
    std::string cpp_string2;
    j_string.get_to(cpp_string2);

    // 獲取序列化的值(顯式JSON序列化)
    std::string serialized_string = j_string.dump();
    // 輸出原始字串
    std::cout << "original string: " << cpp_string << " == " << cpp_string2 << " == " << j_string.get<std::string>() << '\n';
    // 出書序列化值
    std::cout << "serialized value: " << j_string << " == " << serialized_string << std::endl;

    /// 從標準輸入輸出流中 序列化與反序列化
    // 從標準輸入反序列化
    json j4;
    std::cout << "please input:" << std::endl;
    std::cin >> j4;
    // 序列化到標準輸出
    std::cout << j4 << std::endl;
    // 格式化輸出
    std::cout << std::setw(4) << j4 << std::endl;

    /// 從檔案流中 序列化與反序列化
    // 讀取一個json檔案
    std::ifstream i("file.json");
    json j5;
    i >> j5;
    std::cout << "json j5 =  " <<  j5 << std::endl;
    // 將美化的JSON寫入另一個檔案
    std::ofstream o("pretty.json");
    o << std::setw(4) << j5 << std::endl;   // std::setw(4)  設定格式
    return 0;
}

輸出:

json j1{"happy":true,"pi":3.141}
json j2{"happy":true,"pi":3.141}
json j3{"happy":true,"pi":3.141}
string s j3.dump(){"happy":true,"pi":3.141}
string s j3.dump(4){
    "happy": true,
    "pi": 3.141
}
json j_string = "this is a string"
j_string.get<std::string>() cpp_string = this is a string
original string: this is a string == this is a string == this is a string
serialized value: "this is a string" == "this is a string"
please input:
{"happy":true,"pi":3.141}
{"happy":true,"pi":3.141}
{
    "happy": true,
    "pi": 3.141
}
json j5 =  {"output":{"crf":31,"frameRate":20,"height":1080,"width":1000},"tracks":[{"name":"t1","pieces":[{"endTime":6,"file":"x.mp4","startTime":2},{"endTime":13,"file":"y.mp4","startTime":9}]},{"name":"t2","pieces":[{"endTime":10,"file":"z.mp4","startTime":0}]}]}

Process finished with exit code 0

在這裡插入圖片描述

像STL一樣操作json物件

demo:Access_STL_like

程式碼:GitHub Access_STL_like

#include <iostream>
//  json.hpp 唯一需要引入的檔案
#include <nlohmann/json.hpp>
// 使用如下語句方便使用
using json = nlohmann::json;

// 函式可參看 api : https://nlohmann.github.io/json/api/basic_json/

int main() {
    
    // 使用 push_back 建立陣列, 將給定元素Val附加到JSON陣列的末尾
    json j;
    j.push_back("foo");
    j.push_back(1);
    j.push_back(true);
    std::cout << "json j = " << j << std::endl;
    // emplace_back 在j尾部 加入 根據傳遞的引數arg建立的JSON值
    j.emplace_back(1.78);
    std::cout << "json j = " << j << std::endl;

    // 迭代器遍歷
    for (json::iterator it = j.begin(); it != j.end(); ++it) {
        std::cout << *it << '\n';
    }
    std::cout << "=============="  << std::endl;

    // 遍歷 j
    for (auto& element : j) {
        std::cout << element << '\n';
    }
    std::cout << "=============="  << std::endl;

    // 取值 設定值
    const auto tmp = j[0].get<std::string>();
    std::cout << "json j = " << j << std::endl;
    j[1] = 42;
    std::cout << "json j = " << j << std::endl;
    bool foo = j.at(2);
    std::cout << " j.at(2) = " << foo << std::endl;

    std::cout << "=============="  << std::endl;

    // 是否相等
    std::cout <<"j == \"[\\\"foo\\\", 42, true]\"_json : " << (j == "[\"foo\", 42, true]"_json) << std::endl;
    // size
    std::cout << "j.size() : " << j.size()  <<  std::endl;
    // 是否為空
    std::cout << "j.empty() : " << j.empty()  <<  std::endl;
    // 型別
    j.type(); //  返回型別
    auto name = j.type_name();  // 將型別名稱作為字串返回
    std::cout << "j.type() : " << name <<  std::endl;
    // 清空
    j.clear();
    std::cout << "json j = " << j <<  std::endl;

    std::cout << "=============="  << std::endl;

    // 方便的型別檢查
    j.is_null();
    j.is_boolean();
    j.is_number();
    j.is_object();
    j.is_array();
    j.is_string();

    // 建立一個物件, 字典
    json o;
    o["foo"] = 23;
    o["bar"] = false;
    o["baz"] = 3.141;

    // 也可以通過 emplace 新增新元素
    o.emplace("weather", "sunny");

    std::cout << "json o = " << o <<  std::endl;

    // 迭代器遍歷
    for (json::iterator it = o.begin(); it != o.end(); ++it) {
        std::cout << it.key() << " : " << it.value() << "\n";
    }
    std::cout << "=============="  << std::endl;
    for (auto& el : o.items()) {
        std::cout << el.key() << " : " << el.value() << "\n";
    }
    std::cout << "=============="  << std::endl;
    // 結構化繫結  (C++17)
    for (auto& [key, value] : o.items()) {
        std::cout << key << " : " << value << "\n";
    }
    std::cout << "=============="  << std::endl;
    // contains 查詢是否包含 key 值
    if (o.contains("foo")) {
        std::cout << R"(o.contains("foo"))"  << std::endl;
    }
    // 通過  find
    if (o.find("foo") != o.end()) {
        std::cout << R"(o.find("foo"))"  << std::endl;
    }
    // 通過 count  返回 key 的個數
    int foo_present = o.count("foo"); // 1
    int fob_present = o.count("fob"); // 0
    // 刪除 某個鍵值對
    o.erase("foo");
    std::cout << "json o = " << o <<  std::endl;

    return 0;
}

輸出:

json j = ["foo",1,true]
json j = ["foo",1,true,1.78]
"foo"
1
true
1.78
==============
"foo"
1
true
1.78
==============
json j = ["foo",1,true,1.78]
json j = ["foo",42,true,1.78]
 j.at(2) = 1
==============
j == "[\"foo\", 42, true]"_json : 0
j.size() : 4
j.empty() : 0
j.type() : array
json j = []
==============
json o = {"bar":false,"baz":3.141,"foo":23,"weather":"sunny"}
bar : false
baz : 3.141
foo : 23
weather : "sunny"
==============
bar : false
baz : 3.141
foo : 23
weather : "sunny"
==============
bar : false
baz : 3.141
foo : 23
weather : "sunny"
==============
o.contains("foo")
o.find("foo")
json o = {"bar":false,"baz":3.141,"weather":"sunny"}

Process finished with exit code 0

Json Pointer 與 Patch

demo: JsonPointerAndPatch

程式碼:GitHub JsonPointerAndPatch

#include <iostream>
//  json.hpp 唯一需要引入的檔案
#include <nlohmann/json.hpp>
// 使用如下語句方便使用
using json = nlohmann::json;

int main() {

    /// _json_pointer
    json j_original = R"({
      "baz": ["one", "two", "three"],
      "foo": "bar"
    })"_json;

    std::cout << "json j_original = " << j_original << std::endl;

    // 通過json指標訪問成員
    // 如果沒有 會新增元素其值為null
    // eg: j_original["/baz/5"_json_pointer]  會在 增加元素 到 6 個
    auto var1 = j_original["/baz/1"_json_pointer]; // "two"
    auto var2 = j_original["/baz/2"_json_pointer]; // "three"
    auto var5 = j_original["/baz/5"_json_pointer]; // null
    std::cout << "var1 = " << var1 << std::endl;
    std::cout << "var2 = " << var2 << std::endl;
    std::cout << "var3= " << j_original["/baz/3"_json_pointer] << std::endl;
    std::cout << "var4 = " << j_original["/baz/4"_json_pointer] << std::endl;
    std::cout << "var5 = " << var5 << std::endl;
    std::cout << "=============" << std::endl;
    /// patch
    // 補丁操作
    //  "op": 指定操作   "path":指定 key值  "value":指定值
    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);
    std::cout << "json j_original = " << j_original << std::endl;
    std::cout << "json j_result = " << j_result << std::endl;
    // {
    //    "baz": "boo",
    //    "hello": ["world"]
    // }

    // 建立一個JSON Patch,以便通過呼叫修補程式函式將 源值(j_original) 更改為 目標值(j_result)
    // 故 source.patch(diff(source, target)) == target; 總是為 true
    auto op_patch = json::diff(j_result, j_original);
    std::cout << "op_patch = " << op_patch << std::endl;
    // [
    //   { "op":" replace", "path": "/baz", "value": ["one", "two", "three"] },
    //   { "op": "remove","path": "/hello" },
    //   { "op": "add", "path": "/foo", "value": "bar" }
    // ]
    std::cout << "=============" << std::endl;
    /// Merge Patch
    // 可以不是用_json_pointer而是使用 merge_patch 來修改元素
    // 根據 j_patch 的值,操作 原資料
    json j_document = R"({
      "a": "b",
      "c": {
        "d": "e",
        "f": "g"
      }
    })"_json;
    std::cout << "json j_document = " << j_document << std::endl;
    // a patch
    json j_patch2 = R"({
      "a":"z",
      "c": {
        "f": null
      }
    })"_json;
    std::cout << "json j_patch2 = " << j_patch2 << std::endl;
    j_document.merge_patch(j_patch);
    std::cout << "json j_document = " << j_document << std::endl;
    // {
    //  "a": "z",
    //  "c": {
    //    "d": "e"
    //  }
    // }
    std::cout << "=============" << std::endl;
    return 0;
}

輸出:

json j_original = {"baz":["one","two","three"],"foo":"bar"}
var1 = "two"
var2 = "three"
var3= null
var4 = null
var5 = null
=============
json j_original = {"baz":["one","two","three",null,null,null],"foo":"bar"}
json j_result = {"baz":"boo","hello":["world"]}
op_patch = [{"op":"replace","path":"/baz","value":["one","two","three",null,null,null]},{"op":"remove","path":"/hello"},{"op":"add","path":"/foo","value":"bar"}]
=============
json j_document = {"a":"b","c":{"d":"e","f":"g"}}
json j_patch2 = {"a":"z","c":{"f":null}}
json j_document = [{"op":"replace","path":"/baz","value":"boo"},{"op":"add","path":"/hello","value":["world"]},{"op":"remove","path":"/foo"}]
=============

Process finished with exit code 0

解析簡單json檔案

demo: ParseJsonFileSimple

程式碼:GitHub ParseJsonFileSimple

#include <iostream>
#include <fstream>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

/**
 simple.json 檔案
{
  "ok":true,
  "height": 20.123,
  "width": 1000,
  "name": "test"
}
 */

int main() {

    json j; // json 物件
    std::ifstream jfile("simple.json"); // 流讀取
    jfile >> j; // 檔案流形式讀取 json 檔案, 並存為 j
    jfile.close();

    std::cout << "json j = " << j << std::endl;

    bool ok = j.at("ok");
    float height = j["height"];
    int width = j["width"];
    std::string name = j.at("name");

    std::cout << "ok = " << ok << std::endl;
    std::cout << "height = " << height << std::endl;
    std::cout << "width = " << width << std::endl;
    std::cout << "name = " << name << std::endl;

    return 0;
}

輸出:

json j = {"height":20.123,"name":"test","ok":true,"width":1000}
ok = 1
height = 20.123
width = 1000
name = test

Process finished with exit code 0

解析json檔案為結構體以及將結構存放為json檔案

demo:ParseJsonFileComplex

程式碼:GitHub ParseJsonFileComplex

#include <iostream>
//  json.hpp 唯一需要引入的檔案
#include <nlohmann/json.hpp>
#include <fstream>
#include <iomanip>
// 使用如下語句方便使用
using json = nlohmann::json;
/*
 Complex.json 檔案
 {
  "output": {
    "width": 720,
    "height": 1080,
    "frameRate": 20,
    "crf": 31
  },
  "tracks": [
    {
      "name": "t1",
      "pieces": [
        {
          "file": "x.mp4",
          "startTime": 2,
          "endTime": 6
        },
        {
          "file": "y.mp4",
          "startTime": 9,
          "endTime": 13
        }
      ]
    },
    {
      "name": "t2",
      "pieces": [
        {
          "file": "z.mp4",
          "startTime": 0,
          "endTime": 10
        }
      ]
    }
  ]
}
 */
///從這個 json 檔案,可以看到,它包括兩大部分 "output" 和 "tracks"。
/// "tracks" 為陣列有兩個元素,包括 string 的 name,和另一個結構體 "pieces" 的陣列。
/// 可以定義 3個結構體 outputinfo  pieceinfo trackinfo

namespace jsonns {

    struct outputinfo {
        int width;
        int height;
        int frameRate;
        int crf;
    };

    struct pieceinfo {
        std::string  pathname;
        int     startTime{};
        int     endTime{};
    };

    struct trackinfo {
        std::string      name;
        pieceinfo   pieces[5];
        int         size{};
    };

    /// j: json檔案中 output 部分 , v: 結構體 outputinfo
    void from_json(const json& j, outputinfo& v) {
        j.at("width").get_to(v.width);
        j.at("height").get_to(v.height);
        j.at("frameRate").get_to(v.frameRate);
        j.at("crf").get_to(v.crf);
    }
    /// j: json檔案中 pieces 部分 , v: 結構體 pieceinfo
    void from_json(const json&j, pieceinfo &p) {
        j.at("file").get_to(p.pathname);
        j.at("startTime").get_to(p.startTime);
        j.at("endTime").get_to(p.endTime);
    }
    /// j: json檔案中 tracks 部分 , v: 結構體 trackinfo
    void from_json(const json&j, trackinfo &t) {
        j.at("name").get_to(t.name);
        /// 遍歷 tracks 中 多個 pieces
        int len = j["pieces"].size();
        for(int i = 0; i < len; i++) {
            t.pieces[i] = j["pieces"][i];
        }
        t.size = j["pieces"].size();
    }
}

/**
 * 1、從 json檔案讀取資料並存為結構體
 * 2、將結構體按json格式存到檔案
 * @return
 */
int main() {
    std::cout << "Hello, World!" << std::endl;

    /****** 從json檔案中讀取資料 並 存放為結構體******/
    json j;
    std::ifstream jfile("Complex.json");
    jfile >> j;
    jfile.close();
    std::cout << "json j = " << j << std::endl;

    jsonns::outputinfo vi = j.at("output");
    /// tilength json檔案中key = "tracks"值的大小
    int tilength = j["tracks"].size();
    /// 建立 trackinfo 陣列
    jsonns::trackinfo ti[tilength];
    /// 填充資料
    for (int i = 0; i < tilength; i++) {
        ti[i] = j["tracks"][i];
    }
    /****** 從json檔案中讀取資料 並 存放為結構體 end******/


    /******* 將結構體資料存成json物件 並 寫入檔案 ******/
    json j2;
    // "output" 資料
    j2["output"]["width"] = vi.width;
    j2["output"]["height"] = vi.height;
    j2["output"]["frameRate"] = vi.frameRate;
    j2["output"]["crf"] = vi.crf;
    // "tracks" 資料, 是個陣列
    json tracks;
    for (int i = 0; i < tilength; i++) {
        json ttmp;
        ttmp["name"] = ti[i].name;
        int len = ti[i].size; // pieces 多少個
        // "pieces" 資料 是個陣列
        json pieces;
        for (int k = 0; k < len; ++k) {
            json ptmp;
            ptmp["file"] = ti[i].pieces[k].pathname;
            ptmp["startTime"] =  ti[i].pieces[k].startTime;
            ptmp["endTime"] = ti[i].pieces[k].endTime;
            pieces.push_back(ptmp); // 是 陣列 通過 push_back 新增
        }
        ttmp["pieces"] = pieces;
        tracks.push_back(ttmp); // 是 陣列 通過 push_back 新增
    }

    j2["tracks"] = tracks;
    std::cout << "json j2 = " << j2 << std::endl;
    std::ofstream o("pretty.json");
    o << std::setw(4) << j2 << std::endl;   // std::setw(4)  設定格式
    o.close();
    /******* 將結構體資料存成json物件 並 寫入檔案 end******/

    return 0;

}

輸出:

Hello, World!
json j = {"output":{"crf":31,"frameRate":20,"height":1080,"width":720},"tracks":[{"name":"t1","pieces":[{"endTime":6,"file":"x.mp4","startTime":2},{"endTime":13,"file":"y.mp4","startTime":9}]},{"name":"t2","pieces":[{"endTime":10,"file":"z.mp4","startTime":0}]}]}
json j2 = {"output":{"crf":31,"frameRate":20,"height":1080,"width":720},"tracks":[{"name":"t1","pieces":[{"endTime":6,"file":"x.mp4","startTime":2},{"endTime":13,"file":"y.mp4","startTime":9}]},{"name":"t2","pieces":[{"endTime":10,"file":"z.mp4","startTime":0}]}]}

Process finished with exit code 0

在這裡插入圖片描述

相關文章