【C++ JSON 開源庫】nlohmann入門使用總結


一、前言

以前更多使用 Qt5 專門的 QJsonDocument 及其相關類來讀寫 JSON 文檔,但用久了發現比較麻煩,不夠簡潔美觀,所以更換使用 nlohmann。

nlohmann 是一個用於解析 JSON 的開源 C++ 庫,口碑一流,使用非常方便直觀,是很多 C++ 程序員的首選。

nlohmann - Readme處有詳細說明用法,但篇幅過長,不便於迅速閱讀抓重點。而且,所舉例的某些用法實踐上其實比較少用到,而某些實踐上常用到的一些用法,官網卻缺例子。所以這里簡要總結了一下它的主要用法,並加上幾個示例,希望能幫助剛接觸的同學快速用上。


二、工程引用

雖然提供了 CMakeLists.txt,但不需要 CMake編譯。只要將 https://github.com/nlohmann/json/tree/develop/include 下的 nlohmann 目錄拷貝到新建工程的 include 目錄下,並添加路徑到 VS 工程中,后面只需要引用一個頭文件即可:

#include "nlohmann/json.hpp"

using json = nlohmann::json;

三、主要用法

3.1 構建JSON對象

如果你想要創建一個如下這樣形式的 JSON 對象:

{
  "pi": 3.141,
  "happy": true,
  "name": "Niels",
  "nothing": null,
  "answer": {
    	"everything": 42
  },
  "list": [1, 0, 2],
  "object": {
	    "currency": "USD",
 	    "value": 42.99
  }
}

使用 nlohmann 庫可以非常方便的完成:

json j; // 首先創建一個空的json對象
j["pi"] = 3.141;
j["happy"] = true;
j["name"] = "Niels";
j["nothing"] = nullptr;
j["answer"]["everything"] = 42; // 初始化answer對象
j["list"] = { 1, 0, 2 }; // 使用列表初始化的方法對"list"數組初始化
j["object"] = { {"currency", "USD"}, {"value", 42.99} }; // 初始化object對象

注意: 在進行如上方式構造 JSON 對象時,你並不需要告訴編譯器你要使用哪種類型,nlohmann 會自動進行隱式轉換。


3.2 獲取並打印JSON元素值

用上面創建的 JSON 對象舉例:

float pi = j.at("pi");
std::string name = j.at("name");
int everything = j.at("answer").at("everything");
std::cout << pi << std::endl; // 輸出: 3.141
std::cout << name << std::endl; // 輸出: Niels
std::cout << everything << std::endl; // 輸出: 42
// 打印"list"數組
for(int i=0; i<3; i++)
std::cout << j.at("list").at(i) << std::endl;
// 打印"object"對象中的元素
std::cout << j.at("object").at("currency") << std::endl; // 輸出: USD
std::cout << j.at("object").at("value") << std::endl; // 輸出: 42.99

3.3 寫JSON對象到.json文件

還是用上面創建的 JSON 對象舉例:

std::ofstream file("pretty.json");
file << j << std::endl;

3.4 string序列化和反序列化

反序列化:從字節序列恢復 JSON 對象。

json j = "{\"happy\":true,\"pi\":3.141}"_json;
auto j2 = R"({"happy":true,"pi":3.141})"_json;

// 或者
std::string s = "{\"happy\":true,\"pi\":3.141}";
auto j = json::parse(s.toStdString().c_str());
std::cout << j.at("pi") << std::endl; // 輸出:3.141

序列化:從 JSON 對象轉化為字節序列。

std::string s = j.dump();    // 輸出:{\"happy\":true,\"pi\":3.141}

dump()返回 JSON 對象中存儲的原始 string 值。


3.5 stream的序列化和反序列化

標准輸出(std::cout)和標准輸入(std::cin)

json j;
std::cin >> j; // 從標准輸入中反序列化json對象
std::cout << j; // 將json對象序列化到標准輸出中

將 json 對象序列化到本地文件,或者從存儲在本地的文件中反序列化出 json 對象,是非常常用的一個操作。nlohmann 對於這個操作也很簡單。

// 讀取一個json文件,nlohmann會自動解析其中數據
std::ifstream i("file.json");
json j;
i >> j;

// 以易於查看的方式將json對象寫入到本地文件
std::ofstream o("pretty.json");
o << std::setw(4) << j << std::endl;

3.6 任意類型轉換

下面總結一下對於任意類型的轉換方法。

對於某一種任意數據類型,可以使用如下方式轉換:

namespace ns {
    // 首先定義一個結構體
    struct person {
        std::string name;
        std::string address;
        int age;
    };
}

ns::person p = {"Ned Flanders", "744 Evergreen Terrace", 60}; // 定義初始化p

// 從結構體轉換到json對象
json j;
j["name"] = p.name;
j["address"] = p.address;
j["age"] = p.age;

// 從json對象轉換到結構體
ns::person p {
    j["name"].get<std::string>(),
    j["address"].get<std::string>(),
    j["age"].get<int>()
};

但是這樣的方式在經常需要轉換的場景下就不方便了,nlohmann 提供了更為方便的方式:

using nlohmann::json;

namespace ns {
    void to_json(json& j, const person& p) {
        j = json{{"name", p.name}, {"address", p.address}, {"age", p.age}};
    }

    void from_json(const json& j, person& p) {
        j.at("name").get_to(p.name);
        j.at("address").get_to(p.address);
        j.at("age").get_to(p.age);
    }
} // namespace ns

ns::person p {"Ned Flanders", "744 Evergreen Terrace", 60};
json j = p;
std::cout << j << std::endl;
// {"address":"744 Evergreen Terrace","age":60,"name":"Ned Flanders"}

// conversion: json -> person
auto p2 = j.get<ns::person>();

// that's it
assert(p == p2);

只需要在 person 結構體所在的命名空間下,定義函數to_json()from_json()就可以輕松的完成任意類型到 json 對象的轉換,以及 json 轉換為任意對象。

注意:

  • 函數to_json()from_json()和你定義的數據類型在同一個命名空間中;
  • 當你要在另外的文件中使用這兩個函數時,要正確的包含它所在的頭文件;
  • from_json中要使用at(),因為當你讀取不存在的名稱時,它會拋出錯誤。

當可以將任意類型數據方便的轉換到 json,就可以將這個 json 方便的序列化到本地保存,也可以從本地反序列化到內存中,比自己去寫每一個字節的操作方便多了。


3.7 建議使用顯式類型轉換

當從 json 對象中獲取數據時,強烈建議使用顯式類型轉換。例如:

std::string s1 = "Hello, world!";
json js = s1;
auto s2 = js.get<std::string>();
// 不建議
std::string s3 = js;
std::string s4;
s4 = js;

// Booleans
bool b1 = true;
json jb = b1;
auto b2 = jb.get<bool>();
// 不建議
bool b3 = jb;
bool b4;
b4 = jb;

// numbers
int i = 42;
json jn = i;
auto f = jn.get<double>();
// 不建議
double f2 = jb;
double f3;
f3 = jb;
// etc.

3.8 轉換JSON到二進制格式

盡管 JSON 格式非常常用,但是它的缺點也很明顯,它並不是一種緊湊的格式,不適合通過網絡傳輸,或者寫到本地,常常需要將 json 對象就行二進制轉換,nlohmann 庫支持多種二進制格式,包括 BSON,CBOR,MessagePack 和 UBJSON。

// create a JSON value
json j = R"({"compact": true, "schema": 0})"_json;

// serialize to 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, 0x00

// roundtrip
json j_from_bson = json::from_bson(v_bson);

// serialize to 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

// roundtrip
json j_from_cbor = json::from_cbor(v_cbor);

// serialize to 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

// roundtrip
json j_from_msgpack = json::from_msgpack(v_msgpack);

// serialize to 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

// roundtrip
json j_from_ubjson = json::from_ubjson(v_ubjson);

如果需要寫到本地,可以使用如下方式:

std::ofstream ofs(path, std::ios::out | std::ios::binary);
const auto msgpack = nlohmann::json::to_msgpack(json);
ofs.write(reinterpret_cast<const char*>(msgpack.data()), msgpack.size() * sizeof(uint8_t));
ofs.close();

3.9 從STL容器轉換到JSON

nlohmann 庫支持從 STL 的任意序列容器初始化獲得 json 對象(std::array, std::vector, std::deque, std::forward_list, std::list),它們的值可以被用來構造 json 的值。

std::set, std::multiset, std::unordered_set, std::unordered_multiset關聯容器也具有同樣的用法,並且也會保存其內在的順序。

另外像std::map, std::multimap, std::unordered_map, std::unordered_multimap,nlohmann 也是支持的,但是需要注意的是其中的 Key 被構造為 std::string 保存。

std::vector<int> c_vector {1, 2, 3, 4};
json j_vec(c_vector);
// [1, 2, 3, 4]

std::deque<double> 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::forward_list<int64_t> c_flist {12345678909876, 23456789098765, 34567890987654, 45678909876543};
json j_flist(c_flist);
// [12345678909876, 23456789098765, 34567890987654, 45678909876543]

std::array<unsigned long, 4> c_array {{1, 2, 3, 4}};
json j_array(c_array);
// [1, 2, 3, 4]

std::set<std::string> c_set {"one", "two", "three", "four", "one"};
json j_set(c_set); // only one entry for "one" is used
// ["four", "one", "three", "two"]

std::unordered_set<std::string> c_uset {"one", "two", "three", "four", "one"};
json j_uset(c_uset); // only one entry for "one" is used
// maybe ["two", "three", "four", "one"]

std::multiset<std::string> c_mset {"one", "two", "one", "four"};
json j_mset(c_mset); // both entries for "one" are used
// maybe ["one", "two", "one", "four"]

std::unordered_multiset<std::string> c_umset {"one", "two", "one", "four"};
json j_umset(c_umset); // both entries for "one" are used
// maybe ["one", "two", "one", "four"]

std::map<std::string, int> c_map { {"one", 1}, {"two", 2}, {"three", 3} };
json j_map(c_map);
// {"one": 1, "three": 3, "two": 2 }

std::unordered_map<const char*, double> c_umap { {"one", 1.2}, {"two", 2.3}, {"three", 3.4} };
json j_umap(c_umap);
// {"one": 1.2, "two": 2.3, "three": 3.4}

std::multimap<std::string, bool> c_mmap { {"one", true}, {"two", true}, {"three", false}, {"three", true} };
json j_mmap(c_mmap); // only one entry for key "three" is used
// maybe {"one": true, "two": true, "three": true}

std::unordered_multimap<std::string, bool> c_ummap { {"one", true}, {"two", true}, {"three", false}, {"three", true} };
json j_ummap(c_ummap); // only one entry for key "three" is used
// maybe {"one": true, "two": true, "three": true}

當你使用這些通過 STL 容器構造的 json 對象時,你只需要安排 STL 容器那樣的方式去使用它。


四、示例

示例1

如果 JSON 文件夠簡單,如下:

{
	"pi":3.1415,
	"happy":true
}

不包括任何結構體或數組,則解析代碼如下:

#include "json.hpp"
#include <fstream>
#include <iostream>
using namespace std;
using json = nlohmann::json;
int main() {
	json j;			// 創建 json 對象
	ifstream jfile("test.json");
	jfile >> j;		// 以文件流形式讀取 json 文件
	float pi = j.at("pi");
	bool happy = j.at("happy");
	return 0;
}

示例2

下面是個較復雜的 JSON 文件解析,一般來說,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
        }
      ]
    }
  ]
}

整個解析過程如下:

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

using json = nlohmann::json;

int main() {
    json j;
    std::ifstream jfile("test.json");
    jfile >> j;

    // 打印output對象【也可以用j["output"].at("width")】
    std::cout << j.at("output").at("width") << std::endl;
    std::cout << j.at("output").at("height") << std::endl;
    std::cout << j.at("output").at("frameRate") << std::endl;
    std::cout << j.at("output").at("crf") << std::endl;
    // 打印tracks數組對象
    for(int i=0; i<j["tracks"].size(); i++) {
        std::cout << j["tracks"][i].at("name") << std::endl;

        // 打印pieces子數組對象
        json j2 = j["tracks"][i].at("pieces");
        for(int k=0; k<j2.size(); k++) {
            std::cout << j2[k].at("file") << std::endl;
            std::cout << j2[k].at("startTime") << std::endl;
            std::cout << j2[k].at("endTime") << std::endl;
        }
    }

    return 0;
}

參考:

有哪些 C++ 的 JSON 庫比較好呢?

【C++】使用 nlohmann 解析 json 文件

json庫nlohmann簡單使用教程



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM