设计目标
-
直观的语法。在像Python这样的语言中,JSON就像是一个一流的数据类型。我们使用了现代C++的所有操作符魔法,在您的代码中实现了相同的感觉。查看以下示例你会明白我的意思。
-
琐碎的集成。我们的整个代码由一个单独的头文件组成json.hpp。就这样。没有库,没有子项目,没有依赖,没有复杂的构建系统。这个类是用普通的C++11编写的。总之,一切都不需要调整你的编译器标志或项目设置。
-
严格的测试。我们的类经过包含100%的单元测试,包括所有异常行为。此外,我们与Valgrind还有xxx确认没有内存泄漏。谷歌OSS-模糊此外,对所有解析器24/7运行模糊测试,迄今为止高效地执行了数十亿次测试。为了保持高质量,该项目遵循(CII)最佳实践。
还有一些其他方面对我们来说并不重要:
-
内存效率。每个JSON对象都有一个指针(联合的最大大小)和一个枚举元素(1字节)的开销。默认泛化使用以下C++数据类型:
std::string
对应字符串,int64_t,uint64_t
或者double
对应数字,std::map
对于对象object,std::vector
对应数组,以及bool
对应布尔值。但是,您可以模板化通用类basic_json
满足你的需求。 -
速度。肯定有更快的JSON库在外面。但是,如果您的目标是通过添加带有单个头文件的JSON支持来加快开发速度,那么这个库就是您的不二之选。
Integration
json.hpp 唯一需要的文件。 只需要添加
#include <nlohmann/json.hpp>
// for convenience
using json = nlohmann::json;
你想要处理json的文件中,并且设置支持C++即可。
您可以进一步使用文件include/nlohmann/json_fwd.hpp用于转发声明。json_fwd.hpp的安装(作为cmake安装步骤的一部分)可以通过设置-DJSON _ MultipleHeaders =ON
例子
除了下面的示例,您可能还想查看文档,其中每个函数包含一个单独的代码示例(例如,查看emplace()
)。全部示例文件可以自己编译和执行(例如,文件emplace.cpp
)。
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 }"
,而不是解析实际的对象。
上面的例子也可以用json::parse()
:
// parse explicitly
auto j3 = json::parse("{ \"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
// }
如上所示,dump添加参数,可以使得打印结果更美观。
请注意序列化和赋值之间的区别:
// store a string in a JSON value
json j_string = "this is a string";
// 在json类中存储一个字符串
// retrieve the string value
// 获取内部的字符串的值
auto cpp_string = j_string.get<std::string>();
// retrieve the string value (alternative when an variable already exists)
std::string cpp_string2;
j_string.get_to(cpp_string2);
// 将内部的字符串的值赋给cpp_string2?
// retrieve the serialized value (explicit JSON serialization)
// 显式的将json对象序列化为std::string
std::string serialized_string = j_string.dump();
// output of original string
// 输出:获取内部的字符串的值、通过json对象赋给字符串的值、直接从json获取内部字符串的值
std::cout << cpp_string << " == " << cpp_string2 << " == " << j_string.get<std::string>() << '\n';
// output of serialized value
// 重载了cout操作符,直接输出json对象的值
std::cout << j_string << " == " << serialized_string << std::endl;
第一个,是在json类中存储一个字符串
第二个,是获取内部字符串的值。
json对象重载了cout操作符。
.dump()
总是返回序列化值,并且。.get<std::string>()
返回最初存储的字符串值。
该库仅支持UTF-8。当您在库中存储不同编码的字符串时,调用.dump()
可能会引发异常,除非json::error_handler_t::replace
或者json::error_handler_t::ignore
被使用。
Note the library only supports UTF-8. When you store strings with
different encodings in the library, calling dump() may throw an
exception unless json::error_handler_t::replace or
json::error_handler_t::ignore are used as error handlers.
To/from streams (e.g. files, string streams)
也可以使用stream来序列化和反序列化:
// 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;
setw()
操作符被重载过,可以通过它来设置缩进和更nice的打印格式。
这些运算符适用于std::istream
或者std::ostream
的任何子类。以下是文件的一些示例:
// read a JSON file
// 通过文件流得到json对象
std::ifstream i("file.json");
json j;
i >> j;
// write prettified JSON to another file
// 将json对象nice的写入到文件中
std::ofstream o("pretty.json");
o << std::setw(4) << j << std::endl;
请注意,如果将 异常位 设置为failbit
,则不适合这个用例。这将导致程序终止,原因是noexcept
说明符正在使用。
Please note that setting the exception bit for failbit is inappropriate for this use case. It will result in program termination due to the noexcept specifier in use.
Read from iterator range
通过范围迭代器来解析。
也可以从迭代器范围解析JSON也就是说,来自迭代器可访问的任何容器,值类型是1、2或4字节的整数类型,将分别解释为UTF-8、UTF-16和UTF-32。例如,一个std::vector<std::uint8_t>
或者std::vector<std::uint16_t>
:
std::vector<std::uint8_t> v = {'t', 'r', 'u', 'e'};
json j = json::parse(v.begin(), v.end());
You may leave the iterators for the range [begin, end):
std::vector<std::uint8_t> v = {'t', 'r', 'u', 'e'};
json j = json::parse(v);
类似于STL的访问(STL-like access)
我们将JSON类设计成行为就像STL容器一样。事实上,它满足了可逆容器要求。
添加元素:
// 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].get<std::string>();
j[1] = 42;
bool foo = j.at(2);
比较:
// comparison
j == "[\"foo\", 42, true]"_json; // true
其他接口:
// other stuff
j.size(); // 3 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"
}
通过count()来查找:
// 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对象。请注意,在multimap的情况下,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 Pointer and JSON Patch
JSON PATCH 使用_mojianpo 莫建坡-CSDN博客 JSON PATCH 使用_mojianpo 莫建坡-CSDN博客
此库支持JSON指针(RFC 6901)作为解决结构化价值的替代手段。最重要的是,JSON补丁(patch)(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值。您可以,在包含json.hpp头文件之前,通过定义JSON_USE_IMPLICIT_CONVERSIONS
为0,来关闭隐式转换。如果使用CMake,您也可以通过设置选项JSON_ImplicitConversions
为OFF来实现。
// strings
std::string s1 = "Hello, world!";
json js = s1;
auto s2 = js.get<std::string>();
// NOT RECOMMENDED
std::string s3 = js;
std::string s4;
s4 = js;
// Booleans
bool b1 = true;
json jb = b1;
auto b2 = jb.get<bool>();
// NOT RECOMMENDED
bool b3 = jb;
bool b4;
b4 = jb;
// numbers
int i = 42;
json jn = i;
auto f = jn.get<double>();
// NOT RECOMMENDED
double f2 = jb;
double f3;
f3 = jb;
// etc.
请注意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"].get<std::string>(),
j["address"].get<std::string>(),
j["age"].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.get<ns::person>();
// that's it
assert(p == p2);
要使这与您的类型一起工作,您只需要提供两个函数:
using nlohmann::json;
namespace ns {
void to_json(json& j, const person& p) {
j = json{{"name", p.name}, {"address", p.address}, {"age", p.age}};
}
void from_json(const json& j, person& p) {
j.at("name").get_to(p.name);
j.at("address").get_to(p.address);
j.at("age").get_to(p.age);
}
} // namespace ns
就这样!当调用json构造函数与您的类型、自定义的to_json
方法将被自动调用。同样,当调用get<your_type>()
或者get_to(your_type&)
,from_json
方法将被调用。