zoukankan      html  css  js  c++  java
  • 【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简单使用教程


  • 相关阅读:
    Openfire 调试信息
    ejabberd分析(三)启动流程
    Openfire 离线消息的处理机制
    OpenFire SSLSocketFactory 编译报错解决
    用消息队列和消息应用状态表来消除分布式事务
    XMPP文件传输过程
    ejabberd分析(一)
    erlang(1)
    Openfire 好友状态的发送(用户登录)
    ejabberd分析(二) 用户注册
  • 原文地址:https://www.cnblogs.com/linuxAndMcu/p/14503341.html
Copyright © 2011-2022 走看看