- 设计目标
- 例子
- 从文件中读取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 <fstream> #include <nlohmann/json.hpp> 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 << j.dump(4) << std::endl; // { // "happy": true, // "pi": 3.141 // }
请注意序列化和赋值之间的区别:
// 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<std::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 << cpp_string << " == " << cpp_string2 << " == " << j_string.template get<std::string>() << '\n'; // output of serialized value std::cout << j_string << " == " << serialized_string << std::endl;
.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 << j; // the setw manipulator was overloaded to set the indentation for pretty printing std::cout << std::setw(4) << j << std::endl;
这些运算符适用于 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 << std::setw(4) << j << std::endl;
请注意,为failbit设置异常位在此用例中是不合适的。由于使用的noexcept规范,这将导致程序终止。
从迭代器范围读取
您还可以从迭代器范围内解析JSON;也就是说,从任何通过迭代器访问的容器中读取,其值类型是1、2或4字节的整数类型,这将分别被解释为UTF-8、UTF-16和UTF-32。例如,std::vector<std::uint8_t>
,或std::list<std::uint16_t>
:
std::vector<std::uint8_t> v = {'t', 'r', 'u', 'e'}; json j = json::parse(v.begin(), v.end());
您可以将迭代器留给范围[开始,结束):
std::vector<std::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处理器,请按照以下步骤操作:
- 在类中实现SAX接口。您可以使用nlohmann::json_sax作为基类,但也可以使用任何实现了上述描述的函数并且是公共类的类。
- 创建一个您的SAX接口类的实例,例如my_sax。
- 调用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 << *it << '\n'; } // range-based for for (auto& element : j) { std::cout << element << '\n'; } // getter/setter const auto tmp = j[0].template get<std::string>(); 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 << it.key() << " : " << it.value() << "\n"; } // the same code as range for for (auto& el : o.items()) { std::cout << el.key() << " : " << el.value() << "\n"; } // even easier with structured bindings (C++17) for (auto& [key, value] : o.items()) { std::cout << key << " : " << value << "\n"; } // find an entry if (o.contains("foo")) { // there is an entry with key "foo" } // or via find and an iterator if (o.find("foo") != o.end()) { // there is an entry with key "foo" } // or simpler using count() int foo_present = o.count("foo"); // 1 int fob_present = o.count("fob"); // 0 // delete an entry o.erase("foo");
从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<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::multimap、std::unordered_map、std::unordered_multimap),其键可以构造一个std::string,且其值可以用来构建JSON值(见上文示例),都可以用于创建JSON对象。请注意,在多映射的情况下,JSON对象中只使用了一个键,而值取决于STL容器的内部排序。
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}
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<std::string>(); // NOT RECOMMENDED std::string s3 = js; std::string s4; s4 = js; // Booleans bool b1 = true; json jb = b1; auto b2 = jb.template get<bool>(); // NOT RECOMMENDED bool b3 = jb; bool b4; b4 = jb; // numbers int i = 42; json jn = i; auto f = jn.template get<double>(); // 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<std::string>(), j["address"].template get<std::string>(), j["age"].template get<int>() };
这样做是可行的,但那确实是相当多的样板代码...幸运的是,有更好的方法:
// create a person ns::person p {"Ned Flanders", "744 Evergreen Terrace", 60}; // conversion: person -> json json j = p; std::cout << j << std::endl; // {"address":"744 Evergreen Terrace","age":60,"name":"Ned Flanders"} // conversion: json -> person auto p2 = j.template get<ns::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<your_type>()或get_to(your_type&)时,from_json方法将被调用。
一些重要的事情:
- 这些方法必须在你的类型的命名空间(可以是全局命名空间)中,否则库将无法找到它们(在这个例子中,它们在ns命名空间中定义)。
- 这些方法必须在你使用这些转换的任何地方都是可用的(例如,必须包含正确的头文件)。查看问题1108以了解可能发生的错误。
- 当使用
template get<your_type>()
时,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 <typename T> 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 <typename T> struct adl_serializer<boost::optional<T>> { static void to_json(json& j, const boost::optional<T>& opt) { if (opt == boost::none) { j = nullptr; } else { j = *opt; // this will call adl_serializer<T>::to_json which will // find the free function to_json in T's namespace! } } static void from_json(const json& j, boost::optional<T>& opt) { if (j.is_null()) { opt = boost::none; } else { opt = j.template get<T>(); // same as above, but with // adl_serializer<T>::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<move_only_type> { // 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<int>()}; } // 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<typename T, typename SFINAE = typename std::enable_if<sizeof(T) <= 32>::type> struct less_than_32_serializer { template <typename BasicJsonType> 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 <typename BasicJsonType> static void from_json(const BasicJsonType& j, T& value) { // same thing here using nlohmann::from_json; from_json(j, value); } };
重新实现序列化程序时要非常小心,如果不注意,可能会堆栈溢出:
template <typename T, void> struct bad_serializer { template <typename BasicJsonType> static void to_json(BasicJsonType& j, const T& value) { // this calls BasicJsonType::json_serializer<T>::to_json(j, value); // if BasicJsonType::json_serializer == bad_serializer ... oops! j = value; } template <typename BasicJsonType> static void to_json(const BasicJsonType& j, T& value) { // this calls BasicJsonType::json_serializer<T>::from_json(j, value); // if BasicJsonType::json_serializer == bad_serializer ... oops! value = j.template get<T>(); // 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<TaskState>() == TS_RUNNING); // undefined json value to enum (where the first map entry above is the default) json jPi = 3.14; assert(jPi.template get<TaskState>() == TS_INVALID );
就像上面的任意类型转换一样,
- NLOHMANN_JSON_SERIALIZE_ENUM() 必须在你的枚举类型的命名空间(可以是全局命名空间)中声明,否则库将无法找到它,它将默认为整数序列化。
- 它必须在你使用这些转换的任何地方都是可用的(例如,正确的头文件必须被包含)。
其他要点:
- 当使用模板get<ENUM_TYPE>()时,未定义的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<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);
库还支持来自BSON的二进制类型、CBOR(字节字符串)和MessagePack(bin, ext, fixext)。它们默认存储为std::vector<std::uint8_t>
以在库外处理。
// CBOR byte string with payload 0xCAFE std::vector<std::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<std::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 <nlohmann/json.hpp> // 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中的注释
该库默认不支持注释。原因如下:
-
注释不是JSON规范的一部分。您可能会争辩说JavaScript允许
//
或/* */
,但JSON不是JavaScript。 -
这不是疏忽:Douglas Crockford在2012年5月写道:
我从JSON中删除了注释,因为我看到人们使用它们来持有解析指令,这种做法会破坏互操作性。我知道缺乏注释让一些人感到难过,但这不应该如此。
如果你正在使用JSON来保存配置文件,并且你想要添加注释。那就尽情插入所有你喜欢的注释吧。然后通过JSMin处理后再交给你的JSON解析器。
-
如果某些库添加了注释支持而其他库没有,这对互操作性是危险的。请查看健壮性原则的有害后果。
然而,你可以在解析函数中传递参数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 <https://github.com/nlohmann/json#execute-unit-tests> 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。