C++json library nlohmann simple use tutorial

For relevant concepts of Jason, see Simple use of C++ Json Library.

1. Define JSON value type

If you want to create a JSON object in the following form:

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

It is very convenient to use nlohmann library.

json j; //First, create an empty json object
j["pi"] = 3.141; //Then initialize by name / value pair. At this time, the value corresponding to the name "pi" is 3.141
j["happy"] = true;//Assign the name "happy" to true
j["name"] = "Niels";//Store the string "Niels" to "name"
j["nothing"] = nullptr;//"nothing" corresponds to a null pointer
j["answer"]["everything"] = 42;//Initializes the objects in the object
j["list"] = { 1, 0, 2 };//Use the list initialization method to initialize the "list" array
j["object"] = { {"currency", "USD"}, {"value", 42.99} };//Initialize object

Note: when constructing JSON objects in the above way, you do not need to tell the compiler which type you want to use. nlohmann will automatically perform implicit conversion.

If you want to explicitly define or express some situations, you can consider the following methods:

json empty_array_explicit = json::array();//Initializes an empty array in JSON format
json empty_object_implicit = json({});//Implicitly define an empty object
json empty_object_explicit = json::object();//Explicitly define an empty object
json array_not_object = json::array({ {"currency", "USD"}, {"value", 42.99} });//Explicitly define and initialize a JSON array

2. Convert from STL container to json

The nlohmann library supports obtaining json objects (std::array, std::vector, std::deque, std::forward_list, std::list) from any sequence container initialization of STL. Their values can be used to construct json values.

std::set, std::multiset, std::unordered_ set, std::unordered_ The multiset Association container has the same usage and also saves its internal order.

In addition, std::map, std::multimap, std::unordered_map, std::unordered_multimap and nlohmann are also supported, but it should be noted that the Key is constructed as std::string to save.

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}

When you use these json objects constructed through the STL container, you only need to arrange the STL container to use it in the same way.

3.string serialization and deserialization

Deserialization: recover JSON objects from byte sequences.

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

Serialization: convert from JSON object to byte sequence.

std::string s = j.dump();    // {"happy":true,"pi":3.141}
std::cout << j.dump(4) << std::endl;//Output as follows
// {
//     "happy": true,
//     "pi": 3.141
// }

dump() returns the original string value stored in the JSON object.

4. Serialization and deserialization of stream

Standard output (std::cout) and standard input (std::cin)

json j;
std::cin >> j;//Deserialize json objects from standard input

std::cout << j;//Serialize json objects into standard output

Serializing json objects to local files or deserializing json objects from files stored locally is a very common operation. nlohmann is also very simple for this operation.

//Read a json file, and nlohmann will automatically parse the data in it
std::ifstream i("file.json");
json j;
i >> j;

//Write json objects to local files in an easy to view manner
std::ofstream o("pretty.json");
o << std::setw(4) << j << std::endl;

5. Any type conversion

Let me summarize the conversion methods for any type.

For any data type, you can convert it as follows:

namespace ns {
    //First define a structure
    struct person {
        std::string name;
        std::string address;
        int age;
    };
}

ns::person p = {"Ned Flanders", "744 Evergreen Terrace", 60};//Define initialization p

//Convert from structure to json object
json j;
j["name"] = p.name;
j["address"] = p.address;
j["age"] = p.age;

//Convert from json object to structure
ns::person p {
    j["name"].get<std::string>(),
    j["address"].get<std::string>(),
    j["age"].get<int>()
};

However, such a method is inconvenient in scenes that often need to be converted. nlohmann provides a more convenient method:

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);

You only need to define the function to under the namespace where the person structure is located_ json () and from_json() can easily convert any type to json object and convert json to any object.

be careful:

  1. Function to_json() and from_json() is in the same namespace as the data type you defined;
  2. When you want to use these two functions in another file, you should correctly include the header file where it is located;
  3. From_ Use at() in JSON because it throws an error when you read a name that doesn't exist.

When you can easily convert any type of data to json, you can easily serialize the json to local storage or deserialize it to memory, which is much more convenient than writing each byte yourself.

6. Explicit type conversion is recommended

Explicit type conversion is strongly recommended when retrieving data from json objects. For example:

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

// Booleans
bool b1 = true;
json jb = b1;
auto b2 = jb.get<bool>();
//Not recommended
bool b3 = jb;
bool b4;
b4 = jb;

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

7. Convert JSON to binary format

Although JSON format is very commonly used, its disadvantages are also obvious. It is not a compact format and is not suitable for network transmission or local writing. JSON objects often need to be binary converted. nlohmann library supports a variety of binary formats, including BSON, CBOR, MessagePack and 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);

If you need to write locally, you can use the following methods:

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();

8. Examples

This example shows how to save the member variables in the object locally and read the binary deserialized object locally.

#include <iostream>
#include <nlohmann/json.hpp>
#include <string>
#include <map>
#include <unordered_map>
#include <fstream>

using nlohmann::json;
using namespace std;

class TEST{
public:
    TEST(){}

    void SetValue(){
        a = 1;
        b = 2;
        c = 3;
        d = 4;
        e = 5;
        f = "I love you!";

        for (int i = 0; i < 10; ++i) {
            g.emplace_back(i);
        }

        for (int i = 0; i < 10; ++i) {
            h[to_string(i)] = static_cast<double>(i);
        }

        for (int i = 0; i < 10; ++i) {
            json json_temp(i);
            j[to_string(i)] = json_temp;
        }
    }

    json to_json(){
        json json_temp;
        json_temp["a"] = a;
        json_temp["b"] = b;
        json_temp["c"] = c;
        json_temp["d"] = d;
        json_temp["e"] = e;
        json_temp["f"] = f;
        json_temp["g"] = g;
        json_temp["h"] = h;
        json_temp["j"] = j;

        return json_temp;
    }

    void from_json(const json& json_temp){
        a = json_temp.at("a").get<int>();
        b = json_temp.at("b").get<float>();
        c = json_temp.at("c").get<double>();
        d = json_temp.at("d").get<long>();
        e = json_temp.at("e").get<long long>();
        f = json_temp.at("f").get<string>();
        g = json_temp.at("g").get<vector<int>>();
        h = json_temp.at("h").get<map<string, double>>();
        j = json_temp.at("j").get<unordered_map<string, json>>();
    }

public:
    int a;
    float b;
    double c;
    long d;
    long long e;
    string f;
    vector<int> g;
    map<string, double> h;
    unordered_map<string, json> j;
};

int main(){
    TEST test;
    test.SetValue();
    json js = test.to_json();

    const vector<uint8_t> message_pack = nlohmann::json::to_msgpack(js);

    ofstream ofs("./binary", std::ios::out | std::ios::binary | std::ios::trunc);

    ofs.write(reinterpret_cast<const char*>(message_pack.data()), message_pack.size()*sizeof(uint8_t));
    ofs.close();

    ifstream ifs("./binary", ios::binary | ios::in);

    std::vector<uint8_t> message_pack_1;
    while (true){
        uint8_t buffer;
        ifs.read(reinterpret_cast<char*>(&buffer), sizeof(uint8_t));

        if (ifs.eof()){
            break;
        }

        message_pack_1.emplace_back(buffer);
    }
    ifs.close();

    json js1 = nlohmann::json::from_msgpack(message_pack_1);

    TEST test1;
    test1.from_json(js1);

    cout << test1.a << endl;
    cout << test1.b << endl;
    cout << test1.c << endl;
    cout << test1.d << endl;
    cout << test1.e << endl;
    cout << test1.f << endl;

    return 0;
}

Warm tip: although it is convenient to convert data into json objects, and then save and serialize them, when the data class is huge, reaching the level of millions or tens of millions, using this method again will consume a lot of time in constructing json objects. I tried a data of about 300M, which takes about tens of seconds to convert into json objects. It only takes 0.1 seconds for 300M megabytes of data to be written locally. So at this time, it's better to directly operate the original data and write it locally. Don't convert it into json objects. Although the algorithm will be a little troublesome, the speed is very fast.

9. Download JSON source code

Download address

Keywords: C++ JSON

Added by jonasr on Sat, 06 Nov 2021 11:26:47 +0200