nlohmann/json的介绍和使用详解

9,045次阅读
没有评论

共计 30878 个字符,预计需要花费 78 分钟才能阅读完成。

nlohmann/json 的介绍和使用详解

  • 设计目标
  • 例子
    • 从文件中读取 JSON
    • 从 JSON 文字创建 json 对象
    • JSON 作为一流的数据类型
    • 序列化 / 反序列化
    • 类 STL 访问
    • 从 STL 容器转换
    • JSON 指针和 JSON 补丁
    • JSON 合并补丁
    • 隐式转换
    • 与任意类型的转换
    • 专门枚举转换
    • 二进制格式(BSON、CBOR、MessagePack、UBJSON 和 BJData)
  • 支持的编译器
  • 整合
    • CMake
    • 包管理器
    • pkg 配置
  • 使用 JSON 进行现代 C ++ 的项目
  • 笔记
  • 执行单元测试

设计目标

有无数的 JSON 库,每个库甚至都有其存在的理由。我们的类有以下设计目标:

  • 直观的语法。在像 Python 这样的语言中,JSON 感觉就像一种一等数据类型。我们使用了现代 C ++ 的所有操作符魔法来在你的代码中实现同样的感觉。查看下面的示例,你会明白我的意思。

  • 简单的集成。我们的整个代码由一个头文件 json.hpp 组成。就这样。没有库,没有子项目,没有依赖,没有复杂的构建系统。这个类是用纯 C ++11 编写的。总的来说,一切都不需要你调整编译器标志或项目设置。

  • 严格的测试。我们的代码经过了严格的单元测试,涵盖了 100% 的代码,包括所有异常行为。此外,我们还用 Valgrind 和 Clang Sanitizers 检查过,确保没有内存泄漏。Google OSS-Fuzz 还对所有解析器进行了 24/ 7 的模糊测试,到目前为止已经执行了数十亿次测试。为了保持高质量,该项目遵循了核心基础设施倡议(CII)最佳实践。

其他方面对我们来说并不那么重要:

  • 内存效率。每个 JSON 对象都有额外的一个指针(最大的联合体大小)和一个枚举元素(1 字节)。默认的泛化使用以下 C ++ 数据类型:std::string 用于字符串,int64_t、uint64_t 或 double 用于数字,std::map 用于对象,std::vector 用于数组,以及 bool 用于布尔值。然而,你可以根据需要将泛化的类 basic_json 模板化。

  • 速度。当然,市面上还有其他更快的 JSON 库。然而,如果你的目标是通过添加单个头文件来加快你的开发进度并添加 JSON 支持,那么这个库就是正确的选择。如果你知道如何使用 std::vector 或 std::map,那么你就已经准备好了。

    有关更多信息,请查看贡献指南。

有关更多信息,请参阅贡献指南。

例子

以下是一些示例,以帮助您了解如何使用该类。

除了下面的示例外,您可能还希望:

→检查留档
→浏览独立示例文件

每个 API 函数(记录在 API 文档中)都有一个相应的独立示例文件。例如,emplace()函数有一个匹配的 emplace. cpp 示例文件。

从文件中读取 JSON

json 类提供了一个用于操作 JSON 值的 API。要通过读取一个 JSON 文件来创建一个 json 对象,可以这样做:

#include 
#include 
using json = nlohmann::json;

// ...

std::ifstream f("example.json");
json data = json::parse(f);

从 JSON 文字创建 json 对象

假设您想在文件中硬编码这个字面量的 JSON 值,作为一个 json 对象:

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

有多种选择:

// Using (raw) string literals and json::parse
json ex1 = json::parse(R"(
  {
    "pi": 3.141,
    "happy": true
  }
)");

// Using user-defined (raw) string literals
using namespace nlohmann::literals;
json ex2 = R"(
  {
    "pi": 3.141,
    "happy": true
  }
)"_json;

// Using initializer lists
json ex3 = {{"happy", true},
  {"pi", 3.141},
};

JSON 作为一流的数据类型

以下是一些示例,以帮助您了解如何使用该类。

假设您想创建一个 JSON 对象

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

使用这个库,您可以编写:

// create an empty structure (null)
json j;

// add a number that is stored as double (note the implicit conversion of j to an object)
j["pi"] = 3.141;

// add a Boolean that is stored as bool
j["happy"] = true;

// add a string that is stored as std::string
j["name"] = "Niels";

// add another null object by passing nullptr
j["nothing"] = nullptr;

// add an object inside the object
j["answer"]["everything"] = 42;

// add an array that is stored as std::vector (using an initializer list)
j["list"] = {1, 0, 2};

// add another object (using an initializer list of pairs)
j["object"] = {{"currency", "USD"}, {"value", 42.99} };

// instead, you could also write (which looks very similar to the JSON above)
json j2 = {{"pi", 3.141},
  {"happy", true},
  {"name", "Niels"},
  {"nothing", nullptr},
  {"answer", {{"everything", 42}
  }},
  {"list", {1, 0, 2}},
  {"object", {{"currency", "USD"},
    {"value", 42.99}
  }}
};

注意,在所有这些情况下,您都不需要“告诉”编译器您想使用哪种 JSON 值类型。如果您想明确或表达一些边缘情况,函数 json::array()和 json::object()会有所帮助:

// a way to express the empty array []
json empty_array_explicit = json::array();

// ways to express the empty object {}
json empty_object_implicit = json({});
json empty_object_explicit = json::object();

// a way to express an _array_ of key/value pairs [["currency", "USD"], ["value", 42.99]]
json array_not_object = json::array({{"currency", "USD"}, {"value", 42.99} });

序列化 / 反序列化

To/from strings

您可以通过在字符串字面量后加上_json 来创建一个 JSON 值(反序列化):

// create object from string literal
json j = "{"happy": true,"pi": 3.141}"_json;

// or even nicer with a raw string literal
auto j2 = R"(
  {
    "happy": true,
    "pi": 3.141
  }
)"_json;

请注意,在不附加 _json 后缀的情况下,传递的字符串文字不会被解析,而只是用作 JSON 字符串 值。也就是说,json j = "{"happy": true,"pi": 3.141}"将只存储字符串 "{"happy": true,"pi": 3.141}"而不是解析实际对象。

为了引入字符串字面量,需要使用命名空间 nlohmann::literals;(参见 json::parse())。

上述示例也可以明确地使用 json::parse() 来表示:

// parse explicitly
auto j3 = json::parse(R"({"happy": true,"pi": 3.141})");

您还可以获得 JSON 值的字符串表示形式(序列化):

// explicit conversion to string
std::string s = j.dump();    // {"happy":true,"pi":3.141}

// serialization with pretty printing
// pass in the amount of spaces to indent
std::cout  

请注意序列化和赋值之间的区别:

// store a string in a JSON value
json j_string = "this is a string";

// retrieve the string value
auto cpp_string = j_string.template get<:string>();
// retrieve the string value (alternative when a variable already exists)
std::string cpp_string2;
j_string.get_to(cpp_string2);

// retrieve the serialized value (explicit JSON serialization)
std::string serialized_string = j_string.dump();

// output of original string
std::cout ()  

.dump() 返回最初存储的字符串值。

注意,该库仅支持 UTF- 8 编码。当您在库中存储不同编码的字符串时,除非使用 json::error_handler_t::replace 或 json::error_handler_t::ignore 作为错误处理器,否则调用 dump() 可能会抛出异常。

To/from streams(例如文件、字符串流)

您还可以使用流来序列化和反序列化:

// deserialize from standard input
json j;
std::cin >> j;

// serialize to standard output
std::cout  

这些运算符适用于 std::istream 或 std::ostream 的任何子类。

// read a JSON file
std::ifstream i("file.json");
json j;
i >> j;

// write prettified JSON to another file
std::ofstream o("pretty.json");
o  

请注意,为 failbit 设置异常位在此用例中是不合适的。由于使用的 noexcept 规范,这将导致程序终止。

从迭代器范围读取

您还可以从迭代器范围内解析 JSON;也就是说,从任何通过迭代器访问的容器中读取,其值类型是 1、2 或 4 字节的整数类型,这将分别被解释为 UTF-8、UTF-16 和 UTF-32。例如,std::vector<:uint8_t>,或std::list<:uint16_t>

std::vector<:uint8_t> v = {'t', 'r', 'u', 'e'};
json j = json::parse(v.begin(), v.end());

您可以将迭代器留给范围[开始,结束):

std::vector<:uint8_t> v = {'t', 'r', 'u', 'e'};
json j = json::parse(v);
自定义数据源

由于 parse 函数接受任意迭代器范围,您可以通过实现 LegacyInputIterator 概念来提供自己的数据源。

struct MyContainer {void advance();
  const char& get_current();};

struct MyIterator {
    using difference_type = std::ptrdiff_t;
    using value_type = char;
    using pointer = const char*;
    using reference = const char&;
    using iterator_category = std::input_iterator_tag;

    MyIterator& operator++() {target->advance();
        return *this;
    }

    bool operator!=(const MyIterator& rhs) const {return rhs.target != target;}

    reference operator*() const {return target->get_current();}

    MyContainer* target = nullptr;
};

MyIterator begin(MyContainer& tgt) {return MyIterator{&tgt};
}

MyIterator end(const MyContainer&) {return {};
}

void foo() {
    MyContainer c;
    json j = json::parse(c);
}
SAX 接口

该库使用具有以下功能的类似 SAX 的接口:

// called when null is parsed
bool null();

// called when a boolean is parsed; value is passed
bool boolean(bool val);

// called when a signed or unsigned integer number is parsed; value is passed
bool number_integer(number_integer_t val);
bool number_unsigned(number_unsigned_t val);

// called when a floating-point number is parsed; value and original string is passed
bool number_float(number_float_t val, const string_t& s);

// called when a string is parsed; value is passed and can be safely moved away
bool string(string_t& val);
// called when a binary value is parsed; value is passed and can be safely moved away
bool binary(binary_t& val);

// called when an object or array begins or ends, resp. The number of elements is passed (or -1 if not known)
bool start_object(std::size_t elements);
bool end_object();
bool start_array(std::size_t elements);
bool end_array();
// called when an object key is parsed; value is passed and can be safely moved away
bool key(string_t& val);

// called when a parse error occurs; byte position, the last token, and an exception is passed
bool parse_error(std::size_t position, const std::string& last_token, const detail::exception& ex);

每个函数的返回值决定了解析是否应该继续进行。

要实现您自己的 SAX 处理器,请按照以下步骤操作:

  1. 在类中实现 SAX 接口。您可以使用 nlohmann::json_sax 作为基类,但也可以使用任何实现了上述描述的函数并且是公共类的类。
  2. 创建一个您的 SAX 接口类的实例,例如 my_sax。
  3. 调用 bool json::sax_parse(input, &my_sax);其中第一个参数可以是任何输入,如字符串或输入流,第二个参数是指向您的 SAX 接口的指针。

注意,sax_parse 函数只返回一个布尔值,表示最后一个执行的 SAX 事件的结果。它不会返回一个 json 值 - 由您决定如何处理 SAX 事件。此外,在解析错误的情况下不会抛出异常 - 由您决定如何处理传递给您的 parse_error 实现的异常对象。内部地,SAX 接口被用于 DOM 解析器(类 json_sax_dom_parser)以及接受器(json_sax_acceptor),参见文件 json_sax.hpp。

类 STL 访问

我们设计了 JSON 类以行为像 STL 容器。实际上,它满足了 ReversibleContainer 的要求。

// create an array using push_back
json j;
j.push_back("foo");
j.push_back(1);
j.push_back(true);

// also use emplace_back
j.emplace_back(1.78);

// iterate the array
for (json::iterator it = j.begin(); it != j.end(); ++it) {std::cout ();
j[1] = 42;
bool foo = j.at(2);

// comparison
j == R"(["foo", 1, true, 1.78])"_json;  // true

// other stuff
j.size();     // 4 entries
j.empty();    // false
j.type();     // json::value_t::array
j.clear();    // the array is empty again

// convenience type checkers
j.is_null();
j.is_boolean();
j.is_number();
j.is_object();
j.is_array();
j.is_string();

// create an object
json o;
o["foo"] = 23;
o["bar"] = false;
o["baz"] = 3.141;

// also use emplace
o.emplace("weather", "sunny");

// special iterator member functions for objects
for (json::iterator it = o.begin(); it != o.end(); ++it) {
  std::cout  

从 STL 容器转换

任何序列容器(std::array、std::vector、std::deque、std::forward_list、std::list),其值可以用来构建 JSON 值(例如,整数、浮点数、布尔值、字符串类型或再次在本节中描述的 STL 容器)都可以用于创建 JSON 数组。类似地,对于关联容器(std::set、std::multiset、std::unordered_set、std::unordered_multiset),情况也是如此,但这些情况下数组元素的顺序取决于相应 STL 容器中元素的排序方式。

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

std::deque 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 c_list {true, true, false, true};
json j_list(c_list);
// [true, true, false, true]

std::forward_list c_flist {12345678909876, 23456789098765, 34567890987654, 45678909876543};
json j_flist(c_flist);
// [12345678909876, 23456789098765, 34567890987654, 45678909876543]

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

std::set<: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<: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<: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<: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::multimap、std::unordered_map、std::unordered_multimap),其键可以构造一个 std::string,且其值可以用来构建 JSON 值(见上文示例),都可以用于创建 JSON 对象。请注意,在多映射的情况下,JSON 对象中只使用了一个键,而值取决于 STL 容器的内部排序。

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

std::unordered_map 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<: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<: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}

JSON 指针和 JSON 补丁

该库支持JSON 指针(RFC 6901)作为处理结构化值的替代方法。除此之外,JSON 补丁(RFC 6902)允许描述两个 JSON 值之间的差异 - 有效地允许进行从 Unix 中已知的补丁和差异操作。

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

// access members with a JSON pointer (RFC 6901)
j_original["/baz/1"_json_pointer];
// "two"

// a JSON patch (RFC 6902)
json j_patch = R"([{ "op": "replace", "path": "/baz", "value": "boo"},
  {"op": "add", "path": "/hello", "value": ["world"] },
  {"op": "remove", "path": "/foo"}
])"_json;

// apply the patch
json j_result = j_original.patch(j_patch);
// {
//    "baz": "boo",
//    "hello": ["world"]
// }

// calculate a JSON patch from two JSON values
json::diff(j_result, j_original);
// [//   { "op":"replace", "path": "/baz", "value": ["one", "two", "three"] },
//   {"op": "remove","path": "/hello"},
//   {"op": "add", "path": "/foo", "value": "bar"}
// ]

JSON 合并补丁

该库支持JSON Merge Patch(RFC 7386)作为补丁格式。与其使用 JSON 指针(见上文)来指定要操作的值,它使用一种与被修改文档非常相似的语法来描述更改。

// a JSON value
json j_document = R"({"a":"b","c": {"d":"e","f":"g"}
})"_json;

// a patch
json j_patch = R"({"a":"z","c": {"f": null}
})"_json;

// apply the patch
j_document.merge_patch(j_patch);
// {
//  "a": "z",
//  "c": {
//    "d": "e"
//  }
// }

隐式转换

支持的类型可以隐式转换为 JSON 值。

建议不要从 JSON 值使用隐式转换。您可以在这里找到有关此建议的更多详细信息。通过在包含 json.hpp 头文件之前定义 JSON_USE_IMPLICIT_CONVERSIONS 为 0,可以关闭隐式转换。在使用 CMake 时,还可以通过将选项 JSON_ImplicitConversions 设置为 OFF 来实现这一点。

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

// Booleans
bool b1 = true;
json jb = b1;
auto b2 = jb.template get();
// NOT RECOMMENDED
bool b3 = jb;
bool b4;
b4 = jb;

// numbers
int i = 42;
json jn = i;
auto f = jn.template get();
// NOT RECOMMENDED
double f2 = jb;
double f3;
f3 = jb;

// etc.

请注意,char类型不会自动转换为 JSON 字符串,而是转换为整数。必须明确指定到字符串的转换:

char ch = 'A';                       // ASCII value 65
json j_default = ch;                 // stores integer number 65
json j_string = std::string(1, ch);  // stores string "A"

任意类型转换

每种类型都可以在 JSON 中序列化,不仅仅是 STL 容器和标量类型。通常,你会这样做:

namespace ns {
    // a simple struct to model a person
    struct person {
        std::string name;
        std::string address;
        int age;
    };
}

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

// convert to JSON: copy each value into the JSON object
json j;
j["name"] = p.name;
j["address"] = p.address;
j["age"] = p.age;

// ...

// convert from JSON: copy each value from the JSON object
ns::person p {j["name"].template get<:string>(),
    j["address"].template get<:string>(),
    j["age"].template get()};

这样做是可行的,但那确实是相当多的样板代码... 幸运的是,有更好的方法:

// create a person
ns::person p {"Ned Flanders", "744 Evergreen Terrace", 60};

// conversion: person -> json
json j = p;

std::cout  person
auto p2 = j.template get<:person>();

// that's it
assert(p == p2);
基本用法

要使其适用于您的一种类型,您只需要提供两个功能:

using json = 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

就这些!当你用你的类型调用 json 构造函数时,你的自定义 to_json 方法将自动被调用。同样,当调用模板 get()或 get_to(your_type&)时,from_json 方法将被调用。

一些重要的事情:

  • 这些方法必须在你的类型的命名空间(可以是全局命名空间)中,否则库将无法找到它们(在这个例子中,它们在 ns 命名空间中定义)。
  • 这些方法必须在你使用这些转换的任何地方都是可用的(例如,必须包含正确的头文件)。查看问题 1108 以了解可能发生的错误。
  • 当使用 template get() 时,your_type 必须 是 DefaultConstructible 的。
  • 在 from_json 函数中,使用 at()函数来访问对象值而不是 operator[]。如果一个键不存在,at 会抛出一个异常,你可以处理它,而 operator[]表现出未定义的行为。
  • 你不需要为 STL 类型如 std::vector 添加序列化器或反序列化器:库已经实现了这些。
使用宏简化您的生活

如果你只是想序列化 / 反序列化一些结构体,to_json/from_json 函数可能会有很多样板代码。

有两个宏可以让你的生活更轻松,只要你(1)想要使用 JSON 对象作为序列化和(2)想要在该对象中使用成员变量名称作为对象键:

  • NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(name, member1, member2, ...) 是在类 / 结构的命名空间内部定义的,用于创建代码。
  • NLOHMANN_DEFINE_TYPE_INTRUSIVE(name, member1, member2, ...) 是在类 / 结构内部定义的,用于创建代码。这个宏也可以访问私有成员。

在这两个宏中,第一个参数是类 / 结构的名称,所有剩余的参数都命名了成员。

例子

上面 person 结构体的 to_json/from_json 函数可以用以下方式创建:

namespace ns {NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(person, name, address, age)
}

下面是需要 NLOHMANN_DEFINE_TYPE_INTRUSIVE 的私有成员示例:

namespace ns {
    class address {
      private:
        std::string street;
        int housenumber;
        int postcode;

      public:
        NLOHMANN_DEFINE_TYPE_INTRUSIVE(address, street, housenumber, postcode)
    };
}
如何转换第三方类型?

这需要一些更高级的技术。但首先,让我们看看这个转换机制是如何工作的:

库使用 JSON 序列化器将类型转换为 json。nlohmann::json 的默认序列化器是 nlohmann::adl_serializer(ADL 表示依赖参数的查找)。

它是这样实现的(简化版):

template 
struct adl_serializer {static void to_json(json& j, const T& value) {// calls the "to_json" method in T's namespace}

    static void from_json(const json& j, T& value) {// same thing, but with the "from_json" method}
};

这个序列化器在你有控制类型命名空间时工作得很好。然而,对于 boost::optional 或 std::filesystem::path(C++17)怎么办?劫持 boost 命名空间是相当糟糕的,而且向 std 添加除了模板特化之外的任何东西都是非法的...

为了解决这个问题,你需要为 nlohmann 命名空间添加 adl_serializer 的一个特化版本,这里有一个示例:

// partial specialization (full specialization works too)
namespace nlohmann {template 
    struct adl_serializer<:optional>> {static void to_json(json& j, const boost::optional& opt) {if (opt == boost::none) {j = nullptr;} else {j = *opt; // this will call adl_serializer::to_json which will
                        // find the free function to_json in T's namespace!
            }
        }

        static void from_json(const json& j, boost::optional& opt) {if (j.is_null()) {opt = boost::none;} else {opt = j.template get(); // same as above, but with
                                           // adl_serializer::from_json
            }
        }
    };
}
对于不可默认构造 / 不可复制的类型,我如何使用 get()?

如果你的类型是可移动构造的,有一种方法。你也需要特化 adl_serializer,但是需要使用一个特殊的 from_json 重载函数:

struct move_only_type {move_only_type() = delete;
    move_only_type(int ii): i(ii) {}
    move_only_type(const move_only_type&) = delete;
    move_only_type(move_only_type&&) = default;

    int i;
};

namespace nlohmann {
    template 
    struct adl_serializer {
        // note: the return type is no longer 'void', and the method only takes
        // one argument
        static move_only_type from_json(const json& j) {return {j.template get()};
        }

        // Here's the catch! You must provide a to_json method! Otherwise, you
        // will not be able to convert move_only_type to json, since you fully
        // specialized adl_serializer on that type
        static void to_json(json& j, move_only_type t) {j = t.i;}
    };
}
我能否编写自己的序列化器?(高级使用)

是的。您可能想看看测试套件中的 unit-udt.cpp,以查看一些示例。

如果你编写自己的序列化器,你需要做几件事:

  • 使用与 nlohmann::json 不同的 basic_json 别名(basic_json 的最后一个模板参数是 JSONSerializer)
  • 在所有你的 to_json/from_json 方法中使用你的 basic_json 别名(或模板参数)
  • 在需要 ADL 时使用 nlohmann::to_json 和 nlohmann::from_json

这里有一个示例,没有简化,只接受大小≤32 的类型,并使用 ADL。

// You should use void as a second template argument
// if you don't need compile-time checks on T
template::type>
struct less_than_32_serializer {template 
    static void to_json(BasicJsonType& j, T value) {
        // we want to use ADL, and call the correct to_json overload
        using nlohmann::to_json; // this method is called by adl_serializer,
                                 // this is where the magic happens
        to_json(j, value);
    }

    template 
    static void from_json(const BasicJsonType& j, T& value) {
        // same thing here
        using nlohmann::from_json;
        from_json(j, value);
    }
};

重新实现序列化程序时要非常小心,如果不注意,可能会堆栈溢出:

template 
struct bad_serializer
{template 
    static void to_json(BasicJsonType& j, const T& value) {// this calls BasicJsonType::json_serializer::to_json(j, value);
      // if BasicJsonType::json_serializer == bad_serializer ... oops!
      j = value;
    }

    template 
    static void to_json(const BasicJsonType& j, T& value) {// this calls BasicJsonType::json_serializer::from_json(j, value);
      // if BasicJsonType::json_serializer == bad_serializer ... oops!
      value = j.template get(); // oops!}
};

特化枚举转换

默认情况下,枚举值被序列化为 JSON 作为整数。在某些情况下,这可能导致不希望的行为。如果在数据被序列化为 JSON 之后修改或重新排序枚举,那么后续反序列化的 JSON 数据可能未定义或与原始意图不同的枚举值。

可以通过以下方式更精确地指定给定枚举如何映射到和从 JSON:

// example enum type declaration
enum TaskState {
    TS_STOPPED,
    TS_RUNNING,
    TS_COMPLETED,
    TS_INVALID=-1,
};

// map TaskState values to JSON as strings
NLOHMANN_JSON_SERIALIZE_ENUM( TaskState, {{TS_INVALID, nullptr},
    {TS_STOPPED, "stopped"},
    {TS_RUNNING, "running"},
    {TS_COMPLETED, "completed"},
})

NLOHMANN_JSON_SERIALIZE_ENUM()宏声明了一个用于 TaskState 类型的 to_json() / from_json()函数集,同时避免了重复和样板序列化代码。

用法:

// enum to JSON as string
json j = TS_STOPPED;
assert(j == "stopped");

// json string to enum
json j3 = "running";
assert(j3.template get() == TS_RUNNING);

// undefined json value to enum (where the first map entry above is the default)
json jPi = 3.14;
assert(jPi.template get() == TS_INVALID);

就像上面的任意类型转换一样,

  • NLOHMANN_JSON_SERIALIZE_ENUM() 必须在你的枚举类型的命名空间(可以是全局命名空间)中声明,否则库将无法找到它,它将默认为整数序列化。
  • 它必须在你使用这些转换的任何地方都是可用的(例如,正确的头文件必须被包含)。

其他要点:

  • 当使用模板 get()时,未定义的 JSON 值将默认为你映射中的第一对指定值。仔细选择这个默认值。
  • 如果在你的映射中指定了一个枚举或 JSON 值多次,那么在转换为 JSON 或从 JSON 转换为数据时,将返回映射顶部第一个匹配的出现。

二进制格式(BSON、CBOR、MessagePack、UBJSON 和 BJData)

尽管 JSON 是一种无处不在的数据格式,但它并不是一种适合数据交换的非常紧凑的格式,例如通过网络。因此,该库支持 BSON(二进制 JSON)、CBOR(简明二进制对象表示)、MessagePack、UBJSON(通用二进制 JSON 规范)和 BJData(二进制 JData),以高效地将 JSON 值编码为字节向量并解码这样的向量。

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

// serialize to BSON
std::vector<: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<: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<: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<: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);

库还支持来自 BSON 的二进制类型、CBOR(字节字符串)和 MessagePack(bin, ext, fixext)。它们默认存储为 std::vector<:uint8_t> 以在库外处理。

// CBOR byte string with payload 0xCAFE
std::vector<:uint8_t> v = {0x42, 0xCA, 0xFE};

// read value
json j = json::from_cbor(v);

// the JSON value has type binary
j.is_binary(); // true

// get reference to stored binary value
auto& binary = j.get_binary();

// the binary value has no subtype (CBOR has no binary subtypes)
binary.has_subtype(); // false

// access std::vector<:uint8_t> member functions
binary.size(); // 2
binary[0]; // 0xCA
binary[1]; // 0xFE

// set subtype to 0x10
binary.set_subtype(0x10);

// serialize to MessagePack
auto cbor = json::to_msgpack(j); // 0xD5 (fixext2), 0x10, 0xCA, 0xFE

支持的编译器

尽管已经是 2023 年了,但 C ++11 的支持仍然有些稀少。目前已知以下编译器可以工作:

  • GCC 4.8-12.0(可能更高版本)
  • Clang 3.4-15.0(可能更高版本)
  • Apple Clang 9.1-13.1(可能更高版本)
  • 英特尔 C ++ 编译器 17.0.2(可能更高版本)
  • Nvidia CUDA 编译器 11.0.221(可能更高版本)
  • Microsoft VisualC++2015/Build Tools14.0.25123.0(可能更高版本)
  • Microsoft VisualC++2017/Build Tools15.5.180.51428(可能更高版本)
  • Microsoft VisualC++2019/ 构建工具 16.3.1+1def00d3d(可能更高版本)
  • Microsoft VisualC++2022/ 构建工具 19.30.30709.0(可能更高版本)

我很乐意了解其他编译器 / 版本。

请注意:

  • GCC 4.8 存在一个 bug (57824):多行原始字符串不能作为宏的参数。不要在这个编译器中使用多行原始字符串直接在宏中。

  • Android 默认使用非常旧的编译器和 C ++ 库。为了修复这个问题,请在您的 Application.mk 文件中添加以下内容。这将切换到 LLVM C++ 库、Clang 编译器,并启用默认禁用的 C ++11 和其他特性。

    APP_STL := c++_shared
    NDK_TOOLCHAIN_VERSION := clang3.6
    APP_CPPFLAGS += -frtti -fexceptions
    

    该代码编译成功与安卓 NDK,修订版 9 -11(可能更高)和 CrystaX 的安卓 NDK 版本 10。

  • 对于运行在 MinGW 或 Android SDK 上的 GCC,可能会出现错误 'to_string' 不是 std 的成员(或者类似地,对于 strtod 或 strtof),注意这不是代码的问题,而是编译器本身的问题。在 Android 上,参考上面的信息以使用更新的环境进行构建。对于 MinGW,请参考这个网站和这个讨论来获取如何修复这个 bug 的信息。对于使用 APP_STL := gnustl_static 的 Android NDK,请参考这个讨论。

  • 不支持的 GCC 和 Clang 版本会被 #error 指令拒绝。这可以通过定义 JSON_SKIP_UNSUPPORTED_COMPILER_CHECK 来关闭。请注意,在这种情况下你可能会期望没有支持。

以下编译器目前在 AppVeyor、Cirrus CI 和 GitHub Actions 的持续集成中使用:

Compiler Operating System CI Provider
Apple Clang 11.0.3 (clang-1103.0.32.62); Xcode 11.7 macOS 11.7.1 GitHub Actions
Apple Clang 12.0.0 (clang-1200.0.32.29); Xcode 12.4 macOS 11.7.1 GitHub Actions
Apple Clang 12.0.5 (clang-1205.0.22.11); Xcode 12.5.1 macOS 11.7.1 GitHub Actions
Apple Clang 13.0.0 (clang-1300.0.29.3); Xcode 13.0 macOS 11.7.1 GitHub Actions
Apple Clang 13.0.0 (clang-1300.0.29.3); Xcode 13.1 macOS 12.6.1 GitHub Actions
Apple Clang 13.0.0 (clang-1300.0.29.30); Xcode 13.2.1 macOS 12.6.1 GitHub Actions
Apple Clang 13.1.6 (clang-1316.0.21.2.3); Xcode 13.3.1 macOS 12.6.1 GitHub Actions
Apple Clang 13.1.6 (clang-1316.0.21.2.5); Xcode 13.4.1 macOS 12.6.1 GitHub Actions
Apple Clang 14.0.0 (clang-1400.0.29.102); Xcode 14.0 macOS 12.6.1 GitHub Actions
Apple Clang 14.0.0 (clang-1400.0.29.102); Xcode 14.0.1 macOS 12.6.1 GitHub Actions
Apple Clang 14.0.0 (clang-1400.0.29.202); Xcode 14.1 macOS 12.6.1 GitHub Actions
Clang 3.5.2 Ubuntu 20.04.3 LTS GitHub Actions
Clang 3.6.2 Ubuntu 20.04.3 LTS GitHub Actions
Clang 3.7.1 Ubuntu 20.04.3 LTS GitHub Actions
Clang 3.8.1 Ubuntu 20.04.3 LTS GitHub Actions
Clang 3.9.1 Ubuntu 20.04.3 LTS GitHub Actions
Clang 4.0.1 Ubuntu 20.04.3 LTS GitHub Actions
Clang 5.0.2 Ubuntu 20.04.3 LTS GitHub Actions
Clang 6.0.1 Ubuntu 20.04.3 LTS GitHub Actions
Clang 7.0.1 Ubuntu 20.04.3 LTS GitHub Actions
Clang 8.0.0 Ubuntu 20.04.3 LTS GitHub Actions
Clang 9.0.0 Ubuntu 20.04.3 LTS GitHub Actions
Clang 10.0.0 Ubuntu 20.04.3 LTS GitHub Actions
Clang 10.0.0 with GNU-like command-line Windows-10.0.17763 GitHub Actions
Clang 11.0.0 with GNU-like command-line Windows-10.0.17763 GitHub Actions
Clang 11.0.0 with MSVC-like command-line Windows-10.0.17763 GitHub Actions
Clang 11.0.0 Ubuntu 20.04.3 LTS GitHub Actions
Clang 12.0.0 Ubuntu 20.04.3 LTS GitHub Actions
Clang 12.0.0 with GNU-like command-line Windows-10.0.17763 GitHub Actions
Clang 13.0.0 Ubuntu 20.04.3 LTS GitHub Actions
Clang 13.0.0 with GNU-like command-line Windows-10.0.17763 GitHub Actions
Clang 14.0.0 Ubuntu 20.04.3 LTS GitHub Actions
Clang 14.0.0 with GNU-like command-line Windows-10.0.17763 GitHub Actions
Clang 15.0.0 with GNU-like command-line Windows-10.0.17763 GitHub Actions
Clang 15.0.4 Ubuntu 20.04.3 LTS GitHub Actions
Clang 16.0.0 (16.0.0-++20221031071727+500876226c60-1exp120221031071831.439) Ubuntu 20.04.3 LTS GitHub Actions
GCC 4.8.5 (Ubuntu 4.8.5-4ubuntu2) Ubuntu 20.04.3 LTS GitHub Actions
GCC 4.9.4 Ubuntu 20.04.3 LTS GitHub Actions
GCC 5.5.0 Ubuntu 20.04.3 LTS GitHub Actions
GCC 6.5.0 Ubuntu 20.04.3 LTS GitHub Actions
GCC 7.5.0 Ubuntu 20.04.3 LTS GitHub Actions
GCC 8.1.0 (i686-posix-dwarf-rev0, Built by MinGW-W64 project) Windows-10.0.17763 GitHub Actions
GCC 8.1.0 (x86_64-posix-seh-rev0, Built by MinGW-W64 project) Windows-10.0.17763 GitHub Actions
GCC 8.5.0 Ubuntu 20.04.3 LTS GitHub Actions
GCC 9.5.0 Ubuntu 20.04.3 LTS GitHub Actions
GCC 10.4.0 Ubuntu 20.04.3 LTS GitHub Actions
GCC 11.1.0 Ubuntu (aarch64) Cirrus CI
GCC 11.3.0 Ubuntu 20.04.3 LTS GitHub Actions
GCC 12.2.0 Ubuntu 20.04.3 LTS GitHub Actions
GCC 13.0.0 20220605 (experimental) Ubuntu 20.04.3 LTS GitHub Actions
Intel C++ Compiler 2021.5.0.20211109 Ubuntu 20.04.3 LTS GitHub Actions
NVCC 11.0.221 Ubuntu 20.04.3 LTS GitHub Actions
Visual Studio 14 2015 MSVC 19.0.24241.7 (Build Engine version 14.0.25420.1) Windows-6.3.9600 AppVeyor
Visual Studio 15 2017 MSVC 19.16.27035.0 (Build Engine version 15.9.21+g9802d43bc3 for .NET Framework) Windows-10.0.14393 AppVeyor
Visual Studio 16 2019 MSVC 19.28.29912.0 (Build Engine version 16.9.0+57a23d249 for .NET Framework) Windows-10.0.17763 GitHub Actions
Visual Studio 16 2019 MSVC 19.28.29912.0 (Build Engine version 16.9.0+57a23d249 for .NET Framework) Windows-10.0.17763 AppVeyor
Visual Studio 17 2022 MSVC 19.30.30709.0 (Build Engine version 17.0.31804.368 for .NET Framework) Windows-10.0.20348 GitHub Actions

集成

json.hpp 是 single_include/nlohmann 或在这里发布的单个必需文件。

#include 

// for convenience
using json = nlohmann::json;

您需要将以下代码添加到您希望处理 JSON 的文件,并设置必要的开关以启用 C++11(例如,对于 GCC 和 Clang 使用 -std=c++11)。

您可以进一步使用文件 include/nlohmann/json_fwd.hpp 进行前向声明。通过设置 -DJSON_MultipleHeaders=ON,可以实现 json_fwd.hpp 的安装(作为 cmake 安装步骤的一部分)。

CMake

您还可以在 CMake 中使用 nlohmann_json::nlohmann_json 接口目标。此目标为 INTERFACE_INCLUDE_DIRECTORIES 指向适当的包含目录和 INTERFACE_COMPILE_FEATURES 提供必要的 C++11 标志填充适当的使用要求。

外部

要从 CMake 项目中使用此库,可以直接用 find_package() 查找它,并使用生成的包配置中导入的目标:

# CMakeLists.txt
find_package(nlohmann_json 3.2.0 REQUIRED)
...
add_library(foo ...)
...
target_link_libraries(foo PRIVATE nlohmann_json::nlohmann_json)

包配置文件 nlohmann_jsonConfig.cmake 可以从安装树或直接从构建树中使用。

嵌入式

要将库直接嵌入到现有的 CMake 项目中,请将整个源代码树放在一个子目录中,并在您的 CMakeLists.txt 文件中调用 add_subdirectory()

# Typically you don't care so much for a third party library's tests to be
# run from your own project's code.
set(JSON_BuildTests OFF CACHE INTERNAL "")

# If you only include this third party in PRIVATE source files, you do not
# need to install it when your main project gets installed.
# set(JSON_Install OFF CACHE INTERNAL "")

# Don't use include(nlohmann_json/CMakeLists.txt) since that carries with it
# unintended consequences that will break the build.  It's generally
# discouraged (although not necessarily well documented as such) to use
# include(...) for pulling in other CMake projects anyways.
add_subdirectory(nlohmann_json)
...
add_library(foo ...)
...
target_link_libraries(foo PRIVATE nlohmann_json::nlohmann_json)
嵌入式(FetchContent)

自 CMake v3.11 以来,获取内容可以 用于在配置时自动下载版本作为依赖项。

示例:

include(FetchContent)

FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz)
FetchContent_MakeAvailable(json)

target_link_libraries(foo PRIVATE nlohmann_json::nlohmann_json)

注意:建议使用上述描述的 URL 方法,该方法自 3.10.0 版本起得到支持。见 CMake - JSON for Modern C++ 获取更多信息。

同时支持

为了让您的项目能够支持外部提供的 JSON 库或者内置的 JSON 库,您可以使用类似于以下模式的方法:

# Top level CMakeLists.txt
project(FOO)
...
option(FOO_USE_EXTERNAL_JSON "Use an external JSON library" OFF)
...
add_subdirectory(thirdparty)
...
add_library(foo ...)
...
# Note that the namespaced target will always be available regardless of the
# import method
target_link_libraries(foo PRIVATE nlohmann_json::nlohmann_json)
# thirdparty/CMakeLists.txt
...
if(FOO_USE_EXTERNAL_JSON)
  find_package(nlohmann_json 3.2.0 REQUIRED)
else()
  set(JSON_BuildTests OFF CACHE INTERNAL "")
  add_subdirectory(nlohmann_json)
endif()
...

thirdparty/nlohmann_json是此源树的完整副本。

包管理器

🍺如果您使用的是 OS X 和 Homebrew,只需输入 brew install nlohmann-json 即可。如果您希望使用非最新版本,而是使用最新的发布版本,可以使用 brew install nlohmann-json --HEAD。有关详细信息,请参阅 nlohmann-json。

如果您使用的是 Meson Build System,请将此源代码树添加为 meson 子项目。您也可以使用本项目 Releases 中发布的 include.zip 来减小供应商源代码树的大小。或者,您可以从 Meson WrapDB 下载 wrap 文件,或者简单地使用 meson wrap install nlohmann_json。有关包装的任何问题,请参阅 meson 项目。

提供的 meson.build 也可以用作为 CMake 安装 nlohmann_json 系统范围内的替代方案,在这种情况下会安装一个 pkg-config 文件。要使用它,只需让您的构建系统需要依赖 nlohmann_json pkg-config 依赖项。在 Meson 中,建议使用带有子项目回退的 dependency() 对象,而不是直接使用子项目。

如果您使用的是 Bazel,可以直接引用此存储库并依赖 @nlohmann_json//:json。

如果您使用 Conan 来管理您的依赖项,只需将 nlohmann_json/x.y.z 添加到您的 conanfile 要求中,其中 x.y.z 是您要使用的发布版本。如果您遇到软件包问题,请在此提交问题。

如果您使用 Spack 来管理您的依赖项,您可以使用 nlohmann-json 包。有关包装的任何问题,请参阅 spack 项目。

如果您的项目正在使用 hunter 处理外部依赖项,那么您可以使用 nlohmann_json 包。有关包装的任何问题,请参阅 hunter 项目。

如果您的项目正在使用 Buckaroo,可以通过 buckaroo add github.com/buckaroo-pm/nlohmann-json 安装此库的模块。如果遇到问题,请在这里报告。这里有一个例子仓库。

如果您的项目正在使用 vcpkg 处理外部依赖项,那么可以通过执行命令 vcpkg install nlohmann-json 安装 nlohmann-json 包,然后按照显示的描述进行操作。有关包装的任何问题,请参阅 vcpkg 项目。

如果您正在使用 cget,可以通过 cget install nlohmann/json 安装最新开发版本。特定版本可以通过 cget install nlohmann/json@v3.1.0 安装。此外,多 header 版本可以通过添加 -DJSON_MultipleHeaders=ON flag(即 cget install nlohmann/json -DJSON_MultipleHeaders=ON)来安装。

如果您使用 CocoaPods,

可以通过在 podfile(参见示例)中添加 pod "nlohmann_json", '~>3.1.2' 来使用该库。如果遇到问题,请在这里报告。

如果您正在使用 Swift Package Manager,可以通过向此存储库添加包依赖项来使用该库,并将目标依赖项标记为 .product(name: "nlohmann-json", package: "json")

如果您正在使用 NuGet,可以使用名为 nlohmann.json 的包。有关如何使用该包的详细说明,请查看这里。如果遇到问题,请在这里报告。

如果您正在使用 conda,可以通过执行命令 conda install -c conda-forge nlohmann_json 来使用来自 conda-forge 的 nlohmann_json 包。如果遇到问题,请在这里报告。

如果您正在使用 MSYS2,可以通过输入命令 pacman -S mingw-w64-i686-nlohmann-json 或 pacman -S mingw-w64-x86_64-nlohmann-json 来安装 nlohmann-json package。如果遇到问题,请在这里报告。

如果您正在使用 MacPorts,通过执行命令 sudo port install nlohmann-json 可以安装

nlohmann-json 包。

如果您使用的是 build2,您可以从公共存储库 https://cppget.org 或直接从包的源存储库使用 nlohmann-json 包。在项目的清单文件中,只需添加 depends: nlohmann-json(可能还包含一些版本约束)。如果您不熟悉在 build2 中使用依赖项的方法,请阅读这篇介绍性文章。如果遇到问题,请在此处提交问题。

如果您使用的是 wsjcpp,您可以使用命令 wsjcpp install "https://github.com/nlohmann/json:develop" 来获取最新版本。请注意,您可以将分支 ":Development" 更改为现有标记或其他分支。

如果您使用的是 CPM.cmake,您可以检查此 example。将 CPM 脚本添加到项目后,将以下代码片段实现到 CMake 中:

CPMAddPackage(
    NAME nlohmann_json
    GITHUB_REPOSITORY nlohmann/json
    VERSION 3.9.1)

pkg-config

如果您使用的是 Makefile,您可以使用 pkg-config 生成指向库安装位置的包含标志:

pkg-config nlohmann_json --cflags

Meson 构建系统的用户也可以使用系统范围内的库,该库将由 pkg-config 找到:

json = dependency('nlohmann_json', required: true)

JSON 库在现代 C ++ 中的项目使用 

该库目前被用于苹果 macOS Sierra-Monterey 和 iOS 10-15。我不确定他们使用该库的目的是什么,但我很高兴它能在这么多设备上运行。

注释

字符编码

该库支持以下 Unicode 输入:

  • 只支持 UTF- 8 编码的输入,这是根据 RFC 8259 标准的默认 JSON 编码。
  • std::u16string 和 std::u32string 可以被解析,分别假设为 UTF-16 和 UTF-32 编码。这些编码在从文件或其他输入容器读取时是不被支持的。
  • 其他编码如 Latin- 1 或 ISO 8859- 1 等不支持,将导致解析或序列化错误。
  • 库不会用非字符替换 Unicode 字符。
  • 无效代理对(例如,不完整的代理对如 uDEAD)将导致解析错误。
  • 存储在库中的字符串是 UTF- 8 编码的。当使用默认字符串类型(std::string)时,请注意其长度 / 大小函数返回的是存储字节的数量,而不是字符数量或字形的数量。
  • 当你在库中存储不同编码的字符串时,调用 dump()可能会抛出异常,除非使用 json::error_handler_t::replace 或 json::error_handler_t::ignore 作为错误处理器。
  • 要存储宽字符串(例如,std::wstring),你需要在之前将其转换为 UTF- 8 编码的 std::string,参见示例。

JSON 中的注释

该库默认不支持注释。原因如下:

  1. 注释不是 JSON 规范的一部分。您可能会争辩说 JavaScript 允许 ///* */,但 JSON 不是 JavaScript。

  2. 这不是疏忽:Douglas Crockford 在 2012 年 5 月写道:

    我从 JSON 中删除了注释,因为我看到人们使用它们来持有解析指令,这种做法会破坏互操作性。我知道缺乏注释让一些人感到难过,但这不应该如此。

    如果你正在使用 JSON 来保存配置文件,并且你想要添加注释。那就尽情插入所有你喜欢的注释吧。然后通过 JSMin 处理后再交给你的 JSON 解析器。

  3. 如果某些库添加了注释支持而其他库没有,这对互操作性是危险的。请查看健壮性原则的有害后果。

然而,你可以在解析函数中传递参数 ignore_comments 设置为 true 来忽略 // 或 /* */ 注释。此时,注释将被当作空白处理。

对象键的顺序

默认情况下,该库不保留对象的插入顺序。这是符合标准的,因为 JSON 标准定义对象为“一个无序的名称 / 值对集合”。

如果您想保留插入顺序,您可以尝试 nlohmann::ordered_json 类型。或者,您可以使用更复杂的有序映射,如 tsl::ordered_map(集成)或 nlohmann::fifo_mapfifo_map 集成)。

内存释放

我们与 Valgrind 和 Address Sanitizer (ASAN)一起检查过,没有内存泄漏。

如果你发现使用这个库的解析程序没有释放内存,请考虑以下情况,可能与这个库无关。

你的程序是用 glibc 编译的。存在一个可调整的阈值,glibc 用来决定是否真正将内存归还给系统或缓存它以供后续重用。如果你的程序中有很多小分配,且这些小分配不是一个连续的块并且假定低于阈值,那么它们不会被归还给操作系统。这里有相关问题 #1924。

进一步说明

  • 代码包含大量调试断言,可以通过定义预处理器宏 NDEBUG 来关闭,参见 assert.h 的文档。特别是要注意 operator[]实现对 const 对象的未检查访问:如果给定的键不存在,行为是未定义的(想象一下空指针解引用)并在断言打开时引发断言失败。如果你不确定对象中的元素是否存在,请使用 at()函数进行受检访问。此外,你可以定义 JSON_ASSERT(x)来替换调用 assert(x)。
  • 由于 JSON 规范中没有明确定义确切的数字类型,此库尝试自动选择最适合的 C ++ 数字类型。因此,double 类型可能被用来存储数字,这在某些罕见情况下可能会导致浮点异常(如果调用代码中的浮点异常已被解除屏蔽)。这些异常不是由库引起的,需要在调用库函数之前修复调用代码中的异常问题,例如重新屏蔽异常。
  • 作为 C ++ 运行时类型识别特性可以不进行编译;也就是说,你可以使用 -fno-rtti 编译器标志进行编译。
  • 库广泛使用了异常处理机制。然而,可以使用编译器标志 -fno-exceptions 或定义符号 JSON_NOEXCEPTION 来禁用异常处理机制。在这种情况下,异常会被 abort()调用所替代。你可以通过定义 JSON_THROW_USER(覆盖 throw)、JSON_TRY_USER(覆盖 try)和 JSON_CATCH_USER(覆盖 catch)来进一步控制这种行为。需要注意的是 JSON_THROW_USER 应该在之后离开当前作用域(例如通过抛出或 abort),因为继续执行可能会产生未定义的行为。需要注意如果在 MSVC 下禁用了异常处理机制的话,异常的错误消息是不提供的,参见 #2824。

执行单元测试

要编译并运行测试,您需要执行以下命令:

$ mkdir build
$ cd build
$ cmake .. -DJSON_BuildTests=On
$ cmake --build .
$ ctest --output-on-failure

请注意,在 ctest 阶段,从外部存储库下载了几个 JSON 测试文件。如果策略禁止在测试期间下载工件,您可以自己下载这些文件并将包含测试文件的目录通过 -DJSON_TestDataDirectory=path 传递给 CMake。这样就不需要互联网连接了。有关更多信息,请参见问题 #2189。

如果未找到测试套件,则会出现多个测试套件失败的情况,如下所示:

===============================================================================
json/tests/src/make_test_data_available.hpp:21:
TEST CASE:  check test suite is downloaded

json/tests/src/make_test_data_available.hpp:23: FATAL ERROR: REQUIRE(utils::check_testsuite_downloaded() ) is NOT correct!
  values: REQUIRE(false)
  logged: Test data not found in 'json/cmake-build-debug/json_test_data'.
          Please execute target 'download_test_data' before running this test suite.
          See  for more information.

===============================================================================

如果您下载了库而不是通过 Git 克隆代码,test cmake_fetch_content_configure 将失败。请执行 ctest -LE git_required 以跳过这些测试。有关更多信息,请参阅问题 #2189。

某些测试更改安装的文件,因此整个过程无法重现。请执行 ctest -LE not_reproducible 以跳过这些测试。有关更多信息,请参阅问题 #2324。

注意,您需要调用 cmake -LE "not_reproducible|git_required" 来排除这两个标签。有关更多信息,请参阅问题 #2596。

由于 Intel 编译器默认使用不安全的浮点数优化,单元测试可能会失败。然后使用标志 /fp:precise。

原文地址: nlohmann/json 的介绍和使用详解

    正文完
     0
    Yojack
    版权声明:本篇文章由 Yojack 于2024-11-04发表,共计30878字。
    转载说明:
    1 本网站名称:优杰开发笔记
    2 本站永久网址:https://yojack.cn
    3 本网站的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系站长进行删除处理。
    4 本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
    5 本站所有内容均可转载及分享, 但请注明出处
    6 我们始终尊重原创作者的版权,所有文章在发布时,均尽可能注明出处与作者。
    7 站长邮箱:laylwenl@gmail.com
    评论(没有评论)