转到github
nlohmann::json在项目中的实际使用详见C++静态代码检测工具:cppdetector
设计目标
已经有无数的JSON库,每个库都有其存在的理由。我们有这些设计目标:
直观的语法。在像Python这样的语言中,JSON感觉就像是一级数据类型。我们使用现代C++的操作魔法来实现类似普通代码般的感觉。看看下面的例子,你就会明白我的意思。
整合。我们的整个代码只有一个头文件
json.hpp
。没有库,没有子项目,没有依赖项,没有复杂的构建系统,仅仅使用标准C ++ 11编写。总而言之,不需要调整任何编译器标志或项目设置。压力测试。我们的类经过严格的单元测试,涵盖了100%的代码,包括所有特殊行为。此外,我们使用Valgrind和Clang Sanitizers做了检测,没有内存泄漏。还使用谷歌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
,你就已经准备好了。
有关详细信息,请参阅贡献指南。
#include <nlohmann/json.hpp>
// for convenience
using json = nlohmann::json;
到您要处理JSON的文件时,设置必要的开关以启用C ++ 11(例如,-std=c++11
对于GCC和Clang)。
您可以进一步使用文件include/nlohmann/json_fwd.hpp
进行前向声明。安装json_fwd.hpp(作为cmake安装步骤的一部分),可以通过设置-DJSON_MultipleHeaders=ON
来实现。
包管理器
🍺如果您使用的是OS X和Homebrew,只需键入brew tap nlohmann/json
并brew install nlohmann_json
设置即可。如果您想要最新版本而不是最新版本,请使用brew install nlohmann_json --HEAD
。
如果您正在使用Meson Build System,那么您可以将此存储库包装为子项目。
如果您使用Conan来管理您的依赖项,只需添加jsonformoderncpp/x.y.z@vthiery/stable
您conanfile.py
的要求,x.y.z
您要使用的发行版本在哪里。如果您遇到包装问题,请在此处提出问题。
如果您使用Spack来管理依赖项,则可以使用该nlohmann_json
程序包。有关包装的任何问题,请参阅spack项目。
如果你在项目中使用猎人来获取外部依赖关系,那么你可以使用nlohmann_json包。有关包装的任何问题,请参阅猎人项目。
如果您使用的是Buckaroo,则可以安装此库的模块buckaroo install nlohmann/json
。请在这里提出问题。
如果您在项目中使用vcpkg来获取外部依赖项,那么您可以使用nlohmann-json包。有关包装的任何问题,请参阅vcpkg项目。
如果您使用的是cget,则可以安装最新的开发版本cget install nlohmann/json
。可以安装特定版本cget install nlohmann/json@v3.1.0
。此外,可以通过添加-DJSON_MultipleHeaders=ON
标志(即,cget install nlohmann/json -DJSON_MultipleHeaders=ON
)来安装多标头版本。
如果您使用的是CocoaPods,则可以通过将pod添加"nlohmann_json", '~>3.1.2'
到podfile 来使用该库(请参阅示例)。请在这里提出问题。
例子
除了下面的示例,您可能需要查看每个函数(包含单独代码示例)的文档(例如,查看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
// }
注意序列化和赋值之间的区别:
// store a string in a JSON value
json j_string = "this is a string";
// retrieve the string value (implicit JSON to std::string conversion)
std::string cpp_string = j_string;
// retrieve the string value (explicit JSON to std::string conversion)
auto cpp_string2 = j_string.get<std::string>();
// 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.get<std::string>() << '\n';
// output of serialized value
std::cout << j_string << " == " << serialized_string << std::endl;
.dump()
始终返回序列化值,而.get<std::string>()
返回最初存储的字符串值。
请注意,该库仅支持UTF-8。当您在库中存储具有不同编码的字符串时,调用dump()
可能会抛出异常。
To/from streams (e.g. files, string 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; 也就是说,其存储内容为连续的字节序列,可从迭代器访问的任何容器,例如std::vector<std::uint8_t>:
std::vector<std::uint8_t> v = {'t', 'r', 'u', 'e'};
json j = json::parse(v.begin(), v.end());
您也可以去掉范围[begin, end)操作符:
std::vector<std::uint8_t> v = {'t', 'r', 'u', 'e'};
json j = json::parse(v);
SAX interface(待补充)
STL-like access
我们定义的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 std::string tmp = j[0];
j[1] = 42;
bool foo = j.at(2);
// comparison
j == "[\"foo\", 1, 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";
}
// find an entry
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 Pointer和JSON Patch
该库支持JSON Pointer(RFC 6901)作为处理结构化值的替代方法。最重要的是,JSON Patch(RFC 6902)允许描述两个JSON值之间的差异 - 有效地允许Unix中已知的patch和diff操作。
// 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合并Patch
该库支持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_original.merge_patch(j_patch);
// {
// "a": "z",
// "c": {
// "d": "e"
// }
// }
隐式转换
JSON对象的类型由要存储的表达式自动确定。同样,隐式转换存储的值。
// strings
std::string s1 = "Hello, world!";
json js = s1;
std::string s2 = js;
// Booleans
bool b1 = true;
json jb = b1;
bool b2 = jb;
// numbers
int i = 42;
json jn = i;
double f = jn;
// etc.
您也可以显式请求值:
std::string vs = js.get<std::string>();
bool vb = jb.get<bool>();
int vi = jn.get<int>();
// 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
ns::person p2 = j;
// 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) {
p.name = j.at("name").get<std::string>();
p.address = j.at("address").get<std::string>();
p.age = j.at("age").get<int>();
}
} // namespace ns
就这么简单!json
使用您的类型调用构造函数时,您的自定义方法to_json
将被自动调用。同样,在调用get<your_type>()
时,from_json
将被自动调用。
一些重要的点:
- 那些方法必须在你的类型的命名空间(可以是全局命名空间)中,否则库将无法找到它们(在这个例子中,
person
在命名空间中ns
中定义)。 - 在您使用隐式转换的地方,那些方法必须是有效的(例如:正确的头文件必须被包含)查看问题1108,了解可能发生的错误。
- 使用
get<your_type>()
时,your_type
必须是DefaultConstructible。(后面会描述一种可以绕过这个要求的方法。) - 在函数
from_json
中,使用函数at()
来访问对象值而不是operator[]
。如果某个键不存在,则at
抛出一个可以处理的异常,然而operator[]
显示未定义的行为。 - 如果您的类型包含多个
operator=
定义,则代码your_variable = your_json;
可能无法编译。您需要改写成your_variable = your_json.get<decltype your_variable>();
。 - 您不需要为STL类型添加序列化或反序列化程序,例如
std::vector
:库已经实现了这些。 - 注意函数
from_json
/to_json
的定义顺序:如果一个类型B
有类型的成员A
,你必须在定义to_json(B)
之前,定义to_json(A)
。请查看问题561以获取更多详细信息。
怎么转换第三方库的类型?
这需要更高级的技术。首先,让我们来看看这种转换机制是如何工作的:
该库使用JSON Serializers将类型转换成json。nlohmann::json
的默认序列化程序是nlohmann::adl_serializer
(ADL means Argument-Dependent Lookup)。
它是这样实现的(简化):
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
命名空间是非常糟糕的,并且向stl中添加模板特化以外的东西是非法......
为了解决这个问题,您需要向命名空间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.get<T>(); // same as above, but with
// adl_serializer<T>::from_json
}
}
};
}
如何在没有默认构造函数或不可拷贝的类型使用get()
?
有个办法,如果您的类型是MoveConstructible的。您也将需要特化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.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
查看一些示例。
如果您编写自己的序列化程序,则需要执行以下操作:
- 对
basic_json
,使用与nlohmann::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!
}
};
二进制格式(CBOR, MessagePack, and UBJSON)
虽然JSON是一种普遍存在的数据格式,但它并不是一种非常紧凑的格式,适用于数据交换,例如通过网络。因为,该库支持CBOR (简明二进制对象表示),MessagePack和UBJSON (通用二进制规范)以有效地将JSON值编码为字节向量和解码此类向量。
// create a JSON value
json j = R"({"compact": true, "schema": 0})"_json;
// 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);