共计 30878 个字符,预计需要花费 78 分钟才能阅读完成。
- 设计目标
- 例子
- 从文件中读取 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 处理器,请按照以下步骤操作:
- 在类中实现 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 ();
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
一些重要的事情:
- 这些方法必须在你的类型的命名空间(可以是全局命名空间)中,否则库将无法找到它们(在这个例子中,它们在 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 中的注释
该库默认不支持注释。原因如下:
-
注释不是 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 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 的介绍和使用详解