设计目标
目前有很多 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 库,如这个。然而,如果你的目标是通过添加一个单独的头文件来加速开发,那么这个库就是你的选择。如果你懂得如何使用
std::vector
或std::map
,那么你已经准备好了。
有关更多信息,请参阅贡献指南。
集成
json.hpp
是single_include/nlohmann
目录中的一个必要文件,或者可以从这里发布。您需要在想要处理 JSON 的文件中添加以下内容,并设置必要的选项来启用 C++11(例如, GCC 和 Clang 的-std=c++11
)。
#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
进行 CMake 安装步骤时可以安装 json_fwd.hpp。
CMake
您也可以在 CMake 中使用nlohmann_json::nlohmann_json
接口目标。此目标根据INTERFACE_INCLUDE_DIRECTORIES
的适当使用需求填充到适当的包含目录,并为必要的 C++11 标志填充到INTERFACE_COMPILE_FEATURES
。
外部
要从 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)
同时支持两种
为了让您的项目支持外部提供或嵌入式 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
此时为该源树的完整副本。
包管理器
brew tap nlohmann/json
和 brew install nlohmann-json
并设置好。如果您想使用最新版本而不是发行版,请使用 brew install nlohmann-json --HEAD
。
如果您使用 Meson Build System,请将此源树作为meson 子项目。您也可以使用该项目在 Releases 中发布的 include.zip
来减小vendor源树的大小。或者,您也可以从 Meson WrapDB 下载wrap文件,或者简单地使用 meson wrap install nlohmann_json
。有关包装问题,请查阅 meson 项目。
提供的 meson.build 也可以用作安装 nlohmann_json
系统级的 cmake 的替代品,这样就会安装 pkg-config 文件。要使用它,只需让您的构建系统需要 nlohmann_json
的 pkg-config 依赖项。在 Meson 中,首选方法是使用带子项目回退的 dependency()
对象,而不是直接使用子项目。
如果您使用 Conan 来管理您的依赖项,只需将 jsonformoderncpp/x.y.z@vthiery/stable
添加到您的 conanfile.py
的 requires 中,其中 x.y.z
是您要使用的发行版版本。如果您在使用软件包时遇到问题,请在 此处 提出问题。
如果您使用 Spack 来管理您的依赖项,您可以使用 nlohmann-json
软件包。有关包装问题,请参阅 spack 项目。
如果您在项目中使用 hunter 进行外部依赖项,则可以使用 nlohmann_json 软件包。有关包装问题,请参阅 hunter 项目。
如果您使用 Buckaroo,您可以使用 buckaroo add github.com/buckaroo-pm/nlohmann-json
安装此库的模块。有关问题,请 在此处 提交。还有一个示例库 在此处。
如果您在项目中使用 vcpkg 来管理外部依赖项,那么您可以使用 nlohmann-json 软件包。有关包装问题,请参阅 vcpkg 项目。
如果您正在使用 cget,可以使用命令 cget install nlohmann/json
安装最新的开发版本。要安装特定版本,请使用命令 cget install nlohmann/[email&protected]
。此外,通过添加 -DJSON_MultipleHeaders=ON
标志(即 cget install nlohmann/json -DJSON_MultipleHeaders=ON
),可以安装多头版本。
如果您使用的是 CocoaPods,您可以通过在 podfile 中添加 "nlohmann_json", '~>3.1.2'
来使用这个库(请参见 示例)。请在 这里 报告问题。
如果您使用的是 NuGet,可以使用包 nlohmann.json。请查阅这份详细的描述,了解如何使用包。请在 这里 报告问题。
如果您使用的是 conda,可以从 conda-forge 使用包 nlohmann_json,只需执行 conda install -c conda-forge nlohmann_json
即可。请在 这里 报告问题。
如果您使用的是 MSYS2,您可以安装 mingw-w64-nlohmann_json 包,只需输入 pacman -S mingw-w64-i686-nlohmann_json
或 pacman -S mingw-w64-x86_64-nlohmann_json
进行安装。如果您在包上遇到问题,请在 这里 报告问题。
示例
除了下面的示例外,您可能还想检查文档,其中每个函数都包含单独的代码示例(例如,检查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} });
序列化/反序列化
从/到字符串
您可以通过向字符串字面量添加 _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
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);
// 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
返回最初存储的字符串值。
请注意,此库仅支持 UTF-8。当你在库中存储不同编码的字符串时,除非使用 json::error_handler_t::replace
或 json::error_handler_t::ignore
作为错误处理器,否则在调用 dump()
时可能会抛出异常。
从/到流(例如文件,字符串流)
您还可以使用流进行序列化和反序列化
// 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::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接口
该库使用类似于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 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`函数仅返回一个`bool`,指示最后一个执行的SAX事件的 resulted. 它不返回一个`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].get<std::string>();
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";
}
// 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.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容器转换
任何可以使用来构建JSON值的序列容器(`std::array`、`std::vector`、`std::deque`、`std::forward_list`、`std::list`)都可以用来创建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"]
同样,任何可以构造字符串的键及其值可以构造JSON值的关联键值容器(`std::map`、`std::multimap`、`std::unordered_map`、`std::unordered_multimap`)都可以用来创建JSON对象。注意,在multimaps的情况下,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中已知的补丁和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 Merge Patch
该库支持作为补丁格式的<强>JSON Merge Patch强>(RFC 7386)。它不是使用上面提到的JSON Pointer来指定要操作的对象,而是使用类似于文档修改后的语法来描述更改。
// 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值<强>转换强>隐式转换。您可以在此处找到更多关于此建议的详细信息。
// 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
方法。
一些重要的事项
- 这些方法 必须 在您的类型的命名空间(可以是全局命名空间)中,否则库将无法定位它们(在此示例中,它们在命名空间
ns
中,其中person
被定义)。 - 这些方法 必须 在您使用这些转换的任何地方都是可用的(例如,必须包含适当的头文件)。有关可能出现的错误,请参阅问题1108。
- 在使用
get<your_type>()
时,your_type
必须 是可默认构造的。稍后会描述一个绕过此要求的方法。 - 在函数
from_json
中,使用函数at()
来访问对象值,而不是使用operator[]
。如果键不存在,则at
抛出一个异常,你可以处理此异常,而operator[]
的行为是未定义的。 - 对于STL类型,例如
std::vector
,您不需要添加序列化器或反序列化器:库已经实现了这些。
如何转换第三方类型?
这需要一点更高级的技术。但首先,让我们看看这个转换机制是如何工作的。
库使用JSON Serializers
将类型转换为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.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.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.get<TaskState>() == TS_RUNNING);
// undefined json value to enum (where the first map entry above is the default)
json jPi = 3.14;
assert(jPi.get<TaskState>() == TS_INVALID );
就像在上述任意类型转换中一样
NLOHMANN_JSON_SERIALIZE_ENUM()
一定要在您的枚举类型命名空间中声明(可以是全局命名空间),否则库将无法找到并默认为整数序列化。- 它必须在您使用转换的任何地方可用(例如,必须包含适当的头文件)。
其他重要点
- 当使用
get<ENUM_TYPE>()
时,未定义的 JSON 值将默认为在您的映射中指定的第一个对。请仔细选择此默认对。 - 如果您的映射中指定了多个枚举或 JSON 值,则转换到或从 JSON 时将返回从映射顶部开始匹配的第一个出现。
二进制格式(BSON、CBOR、MessagePack 和 UBJSON)
尽管JSON是一种普遍使用的数据格式,但它不是一个非常适合数据交换(例如在网络上进行交换)的紧凑格式。因此,该库支持BSON(二进制JSON)、CBOR(简洁二进制对象表示)、MessagePack和UBJSON(通用二进制JSON规范),以便有效地将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);
支持的编译器
尽管已经到了2019年,但对于C++11的支持仍然相对稀少。目前,以下编译器已知可以工作
- GCC 4.8 - 9.2(以及可能的后续版本)
- Clang 3.4 - 9.0(以及可能的后续版本)
- Intel C++ Compiler 17.0.2(以及可能的后续版本)
- Microsoft Visual C++ 2015 / Build Tools 14.0.25123.0(以及可能的后续版本)
- Microsoft Visual C++ 2017 / Build Tools 15.5.180.51428(以及可能的后续版本)
- Microsoft Visual C++ 2019 / Build Tools 16.3.1+1def00d3d(以及可能的后续版本)
我很乐意了解其他编译器/版本。
请注意
-
GCC 4.8存在一个错误57824):多行原始字符串不能用作宏的参数。请勿使用此编译器直接在宏中直接使用多行原始字符串。
-
Android默认使用非常旧的编译器和C++库。为了解决这个问题,请将以下内容添加到您的
Application.mk
中。这将切换到LLVM C++库、Clang编译器,并启用默认禁用的C++11和其他功能。APP_STL := c++_shared NDK_TOOLCHAIN_VERSION := clang3.6 APP_CPPFLAGS += -frtti -fexceptions
该代码可以在Android NDK版本9 - 11(以及可能的后续版本)和CrystaX的Android NDK版本10成功编译。
-
对于在MinGW或Android SDK上运行的GCC,可能会出现错误
'to_string' 不是 'std' 的成员
(或类似情况下,对于strtod
)的错误。请注意,这不是一个问题,而是编译器本身的问题。在Android上,请参阅上述内容以使用较新的环境进行编译。对于MinGW,请参阅此网站和此讨论获取有关如何修复此错误的详细信息。对于使用APP_STL := gnustl_static
的Android NDK,请参阅此讨论。 -
不支持的GCC和Clang版本被
#error
指令拒绝。可以通过定义JSON_SKIP_UNSUPPORTED_COMPILER_CHECK
来关闭此功能。请注意,在这种情况下,您可能无法获得支持。
以下编译器 currently用于Travis、AppVeyor、CircleCI和Doozer的持续集成。
编译器 | 操作系统 | 版本字符串 |
---|---|---|
GCC 4.8.5 | Ubuntu 14.04.5 LTS | g++-4.8 (Ubuntu 4.8.5-2ubuntu1~14.04.2) 4.8.5 |
GCC 4.8.5 | CentOS Release-7-6.1810.2.el7.centos.x86_64 | g++ (GCC) 4.8.5 20150623 (Red Hat 4.8.5-36) |
GCC 4.9.2 (armv7l) | Raspbian GNU/Linux 8 (jessie) | g++ (Raspbian 4.9.2-10+deb8u2) 4.9.2 |
GCC 4.9.4 | Ubuntu 14.04.1 LTS | g++-4.9 (Ubuntu 4.9.4-2ubuntu1~14.04.1) 4.9.4 |
GCC 5.3.1 (armv7l) | Ubuntu 16.04 LTS | g++ (Ubuntu/Linaro 5.3.1-14ubuntu2) 5.3.1 20160413 |
GCC 5.5.0 | Ubuntu 14.04.1 LTS | g++-5 (Ubuntu 5.5.0-12ubuntu1~14.04) 5.5.0 20171010 |
gcc 6.3.0 | Debian 9 (stretch) | g++ (Debian 6.3.0-18+deb9u1) 6.3.0 20170516 |
gcc 6.3.1 | Fedora发行版 24 (Twenty Four) | g++ (GCC) 6.3.1 20161221 (Red Hat 6.3.1-1) |
gcc 6.4.0 | Ubuntu 14.04.1 LTS | g++-6 (Ubuntu 6.4.0-17ubuntu1~14.04) 6.4.0 20180424 |
gcc 7.3.0 | Ubuntu 14.04.1 LTS | g++-7 (Ubuntu 7.3.0-21ubuntu1~14.04) 7.3.0 |
gcc 7.3.0 | Windows Server 2012 R2 (x64) | g++ (x86_64-posix-seh-rev0, 由 MinGW-W64 项目构建) 7.3.0 |
gcc 8.1.0 | Ubuntu 14.04.1 LTS | g++-8 (Ubuntu 8.1.0-5ubuntu1~14.04) 8.1.0 |
gcc 9.2.1 | Ubuntu 14.05.1 LTS | g++-9 (Ubuntu 9.2.1-16ubuntu1~14.04.1) 9.2.1 20191030 |
Clang 3.5.0 | Ubuntu 14.04.1 LTS | clang 版本 3.5.0-4ubuntu2~trusty2 (tags/RELEASE_350/final) (基于 LLVM 3.5.0) |
Clang 3.6.2 | Ubuntu 14.04.1 LTS | clang 版本 3.6.2-svn240577-1~exp1 (branches/release_36) (基于 LLVM 3.6.2) |
Clang 3.7.1 | Ubuntu 14.04.1 LTS | clang 版本 3.7.1-svn253571-1~exp1 (branches/release_37) (基于 LLVM 3.7.1) |
Clang 3.8.0 | Ubuntu 14.04.1 LTS | clang 版本 3.8.0-2ubuntu3~trusty5 (tags/RELEASE_380/final) |
Clang 3.9.1 | Ubuntu 14.04.1 LTS | clang 版本 3.9.1-4ubuntu3~14.04.3 (tags/RELEASE_391/rc2) |
Clang 4.0.1 | Ubuntu 14.04.1 LTS | clang 版本 4.0.1-svn305264-1~exp1 (branches/release_40) |
Clang 5.0.2 | Ubuntu 14.04.1 LTS | clang 版本 5.0.2-svn328729-1 |
Clang 6.0.1 | Ubuntu 14.04.1 LTS | clang 版本 6.0.1-svn334776-1 |
Clang 7.0.1 | Ubuntu 14.04.1 LTS | clang 版本 7.0.1-svn348686-1 |
Clang Xcode 8.3 | OSX 10.11.6 | Apple LLVM 版本 8.1.0 (clang-802.0.38) |
Clang Xcode 9.0 | OSX 10.12.6 | Apple LLVM 版本 9.0.0 (clang-900.0.37) |
Clang Xcode 9.1 | OSX 10.12.6 | Apple LLVM 版本 9.0.0 (clang-900.0.38) |
Clang Xcode 9.2 | OSX 10.13.3 | Apple LLVM 版本 9.1.0 (clang-902.0.39.1) |
Clang Xcode 9.3 | OSX 10.13.3 | Apple LLVM 版本 9.1.0 (clang-902.0.39.2) |
Clang Xcode 10.0 | OSX 10.13.3 | Apple LLVM 版本 10.0.0 (clang-1000.11.45.2) |
Clang Xcode 10.1 | OSX 10.13.3 | Apple LLVM 版本 10.0.0 (clang-1000.11.45.5) |
Clang Xcode 10.2 | OSX 10.14.4 | Apple LLVM 版本 10.0.1 (clang-1001.0.46.4) |
Visual Studio 14 2015 | Windows Server 2012 R2 (x64) | Microsoft (R) 生成引擎版本 14.0.25420.1, MSVC 19.0.24215.1 |
Visual Studio 15 2017 | Windows Server 2012 R2 (x64) | Microsoft (R) 生成引擎版本 15.9.21+g9802d43bc3, MSVC 19.16.27032.1 |
Visual Studio 16 2019 | Windows Server 2012 R2 (x64) | Microsoft (R) 生成引擎版本 16.3.1+1def00d3d, MSVC 19.23.28106.4 |
许可证
此类受MIT 许可证许可
版权所有 © 2013-2019 Niels Lohmann
特此许可,任何人都可以免费获得此软件及其相关文档副本(“软件”),无需限制地处理软件,包括但不限于使用、复制、修改、合并、发布、分发、特许经营和/或销售软件副本,允许多人获得软件副本后这么做,但需遵守以下条件
上述版权声明和本许可声明应包含在软件的所有副本或主要部分的副本中。
本软件按照“原样”提供,不提供任何明示或暗示的保证,包括但不限于适销性、适用于特定目的和不侵犯版权的保证。在任何情况下,作者或版权所有者都不会对任何主张、损害或其他责任承担责任,无论是基于合同、侵权或其他方式,源于、因与软件或软件的使用或其他任何原因。
该类包含Bjoern Hoehrmann开发的UTF-8解码器,许可证为MIT许可证(见上文)。版权所有 © 2008-2009 Björn Hoehrmann [email protected]
该类包含Florian Loitsch开发的Grisu2算法的略微修改版,许可证为MIT许可证(见上文)。版权所有 © 2009 Florian Loitsch
该类包含了Evan Nemerson的Hedley的副本,许可证为CC0-1.0。
联系
如果您对库有任何疑问,我邀请您在GitHub上创建一个问题。请尽可能详细地描述您的请求、问题或疑问,并提及您所使用的库版本、编译器版本和操作系统版本。在GitHub上创建问题允许其他用户和贡献者协作。例如,我几乎没有使用MSVC的经验,而与此相关的大多数问题都由不断增长的社区解决了。如果您查看已关闭的问题,您会发现我们在大多数情况下都相当及时地做出了反应。
只有在您的请求包含机密信息的情况下,请通过电子邮件与我联系。对于加密消息,请使用此密钥。
安全
Niels Lohmann的提交和发布都使用此PGP密钥签名。
感谢
我非常感谢以下人员的帮助。
- Teemperor实现了CMake支持和lcov集成,在字符串解析器中实现了转义和Unicode处理,并修复了JSON序列化。
- elliotgoodrich修复了迭代器类中的重复删除问题。
- kirkshoop使类的迭代器能够与其他库组合。
- wancw修复了一个阻止类与Clang编译的bug。
- Tomas Åblad发现迭代器实现中的bug。
- Joshua C. Randall修复了浮点数序列化中的bug。
- Aaron Burghardt 实现了代码以增量方式解析流。此外,他还通过允许在解析过程中定义过滤器函数来丢弃不需要的元素,极大地改进了解析类。
- Daniel Kopeček 修复了使用 GCC 5.0 编译时出现的一个bug。
- Florian Weber 修复了比较运算符的一个bug并提高了性能。
- Eric Cornelius 指出了解析 NaN 和无穷大值时的一个bug。他还提高了字符串转义的性能。
- 易思龙 实现了匿名枚举的转换。
- kepkin 耐心推进了对 Microsoft Visual studio 的支持。
- gregmarr 简化了逆迭代器的实现,并提供了许多提示和改进。特别是,他推进了用户定义类型的实现。
- Caio Luppi 修复了 Unicode 处理中的一个bug。
- dariomt 修复了示例中的一些错别字。
- Daniel Frey 清理了一些指针并实现了异常安全的内存分配。
- Colin Hirsch 解决了一个小命名空间问题。
- Huu Nguyen 在文档中纠正了一个变量名。
- Silverweed 重载了
parse()
函数以接受右值引用。 - dariomt 修复了 MSVC 类型支持中的一个细微之处,并实现了
get_ref()
函数以获取存储值的引用。 - ZahlGraf 添加了一个绕过方案,允许使用 Android NDK 进行编译。
- whackashoe 替换了一个被 Visual Studio 标记为不安全的函数。
- 406345 修复了两个小警告。
- Glen Fernandes 指出
has_mapped_type
函数中可能存在的可移植性问题。 - Corbin Hughes 修复了贡献指南中的几个错别字。
- twelsby 修复了数组下标运算符,该问题导致 MSVC 构建失败,并解决了浮点解析/转储问题。他还增加了对无符号整数数字的支持,并实现了更好的解析数字的往返支持。
- Volker Diels-Grabsch 修复了 README 文件中的一个链接。
- msm- 添加了对美国模糊洛普(American Fuzzy Lop)的支持。
- Annihil 修复了 README 文件中的一个示例。
- Themercee 指出 README 文件中的一个错误 URL。
- Lv Zheng 解决了与
int64_t
和uint64_t
相关的命名空间问题。 - abc100m 分析了 GCC 4.8 的问题,并提出了一个部分解决方案。
- zewt 在关于 Android 的 README 文件中添加了有用的注释。
- Róbert Márki 添加了一个修复,使用移动迭代器,并改善了通过 CMake 的集成。
- Chris Kitching 清理了 CMake 文件。
- Tom Needham 修复了 MSVC 2015 中一个被 Michael K. 提出的细微bug。
- Mário Feroldi 修复了一个小错别字。
- duncanwerner 发现了 2.0.0 版本中一个真正尴尬的性能下降。
- Damien 解决了最后一个转换警告之一。
- Thomas Braun 修复了一个测试用例中的警告。
- Théo DELRIEU 耐心地并建设性地监督了通往 迭代器范围解析 的漫长道路。他还实现了用户定义类型序列化和反序列化背后的魔法,并将单个头文件拆分成更小的块。
- Stefan修复了文档中的一项小问题。
- Vasil Dimov修复了有关从
std::multiset
转换的文档。 - ChristophJud修改了CMake文件,以简化项目的包含。
- Vladimir Petrigo使SFINAE技巧更易于阅读,并添加了Visual Studio 17到构建矩阵中。
- Denis Andrejew修复了README文件中的一个语法问题。
- Pierre-Antoine Lacaze在
dump()
函数中找到了一个微妙的问题。 - TurpentineDistillery指向
std::locale::classic()
以避免过多的区域设置跳动,在解析器中找到了一些不错的性能改进,改进了基准测试代码,并实现了区域无关的数字解析和打印。 - cgzones提出了一种修复Coverity扫描的方法。
- Jared Grubb消除了一个恶意的文档警告。
- Yixin Zhang修复了一个整数溢出检查。
- Bosswestfalen将两个迭代器类合并为一个较小的类。
- Daniel599帮助Travis使用Clang的sanitizers执行测试。
- Jonathan Lee修复了README文件中的一个示例。
- gnzlbg支持用户定义类型的实现。
- Alexej Harm帮助用户定义类型与Visual Studio兼容。
- Jared Grubb支持用户定义类型的实现。
- EnricoBilla指出示例中的一个拼写错误。
- Martin Hořeňovský找到一个方法可以实现测试套件的编译时间加速2倍。
- ukhegg提出改进示例部分的建议。
- rswanson-ihi指出README中的一个拼写错误。
- Mihai Stan修复了与
nullptr
的比较中的一个错误。 - Tushar Maheshwari添加了对 cotire 的支持,以加快编译速度。
- TedLyngmo指出README中的一个拼写错误,删除了不必要的位运算,并修复了一些
-Weffc++
警告。 - Krzysztof Woś使异常更加明显。
- ftillier修复了一个编译器警告。
- tinloaf确保所有压入的警告都得到了适当的弹出。
- Fytch在文档中找到了一个错误。
- Jay Sistar实现了Meson构建描述。
- Henry Lee修复了ICC中的一个警告,并改进了迭代器的实现。
- Vincent Thiery维护了Conan包管理器的包。
- Steffen修复了MSVC和
std::min
中的一个潜在问题。 - Mike Tzou修复了一些拼写错误。
- amrcode指出有关浮点数比较的误导性文档。
- Oleg Endo通过用
<iosfwd>
代替<iostream>
减少了内存消耗。 - dan-42清理了CMake文件,以简化库的包含/重用。
- Nikita Ofitserov允许从初始列表中移动值。
- Greg Hurrell修复了一个拼写错误。
- Dmitry Kukovinets修复了一个拼写错误。
- kbthomp1修复了与Intel OSX编译器相关的问题。
- Markus Werle修复了一个拼写错误。
- WebProdPP在一个前置条件检查中修复了一个微妙错误。
- Alex指出代码样本中的一个错误。
- Tom de Geus 报告了一些与 ICC 相关的警告,并帮助修复了这些问题。
- Perry Kundert 简化了输入流的读取。
- Sonu Lohani 修复了一个小的编译错误。
- Jamie Seward 修复了所有 MSVC 警告。
- Nate Vargas 添加了一个 Doxygen 标签文件。
- pvleuven 帮助修复了 ICC 中的一个警告。
- Pavel 帮助修复了 MSVC 中的某些警告。
- Jamie Seward 在
find()
和count()
中避免了不必要的字符串复制。 - Mitja 修复了一些错别字。
- Jorrit Wronski 更新了 Hunter 软件包链接。
- Matthias Möller 为 MSVC 调试视图添加了一个
.natvis
文件。 - bogemic 修复了一些 C++17 废弃警告。
- Eren Okka 修复了一些 MSVC 警告。
- abolz 集成了 Grisu2 算法,以实现正确的浮点数格式化,允许更多的往返检查成功。
- Vadim Evard 修复了 README 中的 Markdown 问题。
- zerodefect 修复了一个编译器警告。
- Kert 允许在序列化中对字符串类型进行模板化,并添加了覆盖异常行为的可能性。
- mark-99 帮助修复了一个 ICC 错误。
- Patrik Huber 修复了 README 文件中的链接。
- johnfb 在 CBOR 的不定长度字符串实现中发现了错误。
- Paul Fultz II 添加了有关 cget 软件包管理器的注释。
- Wilson Lin 使 README 中的集成部分更加简洁。
- RalfBielig 侦测并修复了解析器回调中的内存泄漏。
- agrianius 允许将 JSON 输出到可选的字符串类型。
- Kevin Tonon 重写了 CMake 中的 C++11 编译器检查。
- Axel Huebl 简化了一个 CMake 检查,并添加了对 Spack 软件包管理器 的支持。
- Carlos O'Ryan 修复了一个错别字。
- James Upjohn 修复了编译器部分中的一个版本号。
- Chuck Atkins 将 CMake 文件调整为 CMake 打包指南,并提供了 CMake 集成文档。
- Jan Schöppach 修复了一个错别字。
- martin-mfg 修复了一个错别字。
- Matthias Möller 移除了对
std::stringstream
的依赖。 - agrianius 添加了代码以使用可选的字符串实现。
- Daniel599 允许在使用
items()
函数时使用更多算法。 - Julius Rakow 修复了 Meson 包含目录和到 cppreference.com 的链接。
- Sonu Lohani 修复了 MSVC 2015 在调试模式下的编译问题。
- grembo 修复了测试套件并重新启用了几个测试用例。
- Hyeon Kim 导入了宏
JSON_INTERNAL_CATCH
,以控制库内部的异常处理。 - thyu 修复了一个编译器警告。
- David Guthrie 修复了与 Clang 3.4.2 相关联的微妙的编译错误。
- Dennis Fischer 允许在没有安装库的情况下调用
find_package
。 - Hyeon Kim 修复了重复宏定义的问题。
- Ben Berman 使一些错误信息更加易于理解。
- zakalibit解决了Intel C++编译器的一个编译问题。
- mandreyel解决了一个编译问题。
- Kostiantyn Ponomarenko将版本和许可信息添加到Meson构建文件中。
- Henry Schreiner增加了对GCC 4.8的支持。
- knilch确保在错误目录下运行测试套件时测试套件不会卡住。
- Antonio Borondo解决了MSVC 2017的警告。
- Dan Gendreau实现了宏
NLOHMANN_JSON_SERIALIZE_ENUM
,以便快速定义枚举/JSON映射。 - efp将行和列信息添加到解析错误中。
- julian-becker增加了对BSON的支持。
- Pratik Chowdhury增加了对结构化绑定的支持。
- David Avedissian增加了对Clang 5.0.1(PS4版本)的支持。
- Jonathan Dumaresq实现了一个输入适配器,用于从
FILE*
读取。 - kjpus修复了文档中一个链接的问题。
- Manvendra Singh修复了文档中的一个拼写错误。
- ziggurat29修复了MSVC警告。
- Sylvain Corlay添加了避免MSVC问题问题的代码。
- mefyl解决了从输入流解析JSON时的一个问题。
- Millian Poquet允许通过Meson安装库。
- Michael Behrns-Miller发现一个缺少命名空间的问题。
- Nasztanovics Ferenc修复了与libc 2.12相关的编译问题。
- Andreas Schwab修复了端序转换问题。
- Mark-Dunning修复了MSVC中的警告。
- Gareth Sylvester-Bradley为JSON指针增加了
operator/
运算符。 - John-Mark注意到缺失了一个头文件。
- Vitaly Zaitsev解决了GCC 9.0的编译问题。
- Laurent Stacul解决了GCC 9.0的编译问题。
- Ivor Wanders帮助将CMake的要求降低到版本3.1。
- njlr更新了Buckaroo说明。
- Lion解决了GCC 7在CentOS上的编译问题。
- Isaac Nickaein提高了整数序列化性能,并实现了函数
contains()
。 - past-due压制了一个不可修复的警告。
- Elvis Oric改进了Meson支持。
- Matěj Plch修复了README中的一个示例。
- Mark Beckwith修复了一个拼写错误。
- scinart修复了序列器中的错误。
- Patrick Boettcher为JSON指针实现了
push_back()
和pop_back()
。 - Bruno Oliveira增加了对Conda的支持。
- Michele Caini修复了README中的链接。
- Hani记录了如何使用NuGet安装库。
- Mark Beckwith修复了一个拼写错误。
- yann-morin-1998帮助将CMake的要求降低到版本3.1。
- Konstantin Podsvirov维护了MSYS2软件发行版的包。
- remyabel将GNUInstallDirs添加到CMake文件中。
- Taylor Howard修复了一个单元测试。
- Gabe Ron实现了方法
to_string
。 - Watal M. Iwasaki修复了一个Clang警告。
- Viktor Kirilov将单元测试从Catch切换到doctest
- Juncheng E修复了一个拼写错误。
- tete17修复了函数
contains
中的一个错误。 - Xav83 修复了部分cppcheck警告。
- 0xflotus 修复了一些错误。
- Christian Deneke 添加了
json_pointer::back
的 const 版本。 - Julien Hamaide 让
items()
函数能使用自定义字符串类型。 - Evan Nemerson 修复了Hedley中的一个bug,并相应地更新了本库。
- Florian Pigorsch 修复了大量的拼写错误。
- Camille Bégué 修复了从
std::pair
和std::tuple
转换到json
的问题。 - Anthony VH 修复了一个枚举反序列化的编译错误。
非常感谢大家的帮助!请告知我如果遗漏了某人。
使用第三方工具
本库本身只有一个MIT授权的头文件。然而,它使用了大量第三方工具和服务进行构建、测试、文档编写等。非常感谢!
- amalgamate.py - 合并C源文件和头文件 创建单个头文件
- American fuzzy lop 用于模糊测试
- AppVeyor 在Windows上用于 持续集成
- Artistic Style 用于自动代码缩进
- CircleCI 用于 持续集成
- Clang 用于启用代码清理器的编译
- CMake 用于构建自动化
- Codacity 进行额外代码分析
- Coveralls 用于衡量代码覆盖率
- Coverity Scan 用于静态分析
- cppcheck 用于静态分析
- doctest 用于单元测试
- Doozer 用于Linux(CentOS, Raspbian, Fedora)上的持续集成
- Doxygen 用于生成文档
- fastcov 用于处理覆盖率信息
- git-update-ghpages 用于将文档上传到 gh-pages
- GitHub Changelog Generator 用于生成变更日志
- Google Benchmark 用于实现基准测试
- Hedley 以避免重新发明多个编译器无关的特性宏
- lcov 用于处理覆盖率信息并创建HTML视图
- libFuzzer 用于实现OSS-Fuzz的模糊测试
- OSS-Fuzz 对库进行持续的模糊测试(项目仓库)
- Probot 用于自动化维护者任务,如关闭陈旧问题、请求缺失信息或检测有害评论。
- send_to_wandbox 用于将代码示例发送到 Wandbox
- Travis 为 Linux 和 macOS 上的 持续集成
- Valgrind 用于检查正确的内存管理
- Wandbox 用于 在线示例
使用 JSON 的现代 C++ 项目
该库目前用于 Apple macOS Sierra 和 iOS 10。我不确定他们到底使用该库做什么,但我很高兴它能在这么多设备上运行。
注意
字符编码
该库支持以下 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
作为错误处理器。
JSON 中的注释
该库不支持注释。这是由于三个原因
-
评注不是JSON规范的一部分。JSON规范可能允许使用
//
或/* */
,但JSON不是JavaScript。 -
这不是疏忽:Douglas Crockford在2012年5月提到了这一点。
我将评注从JSON中删除,因为我看到人们正在使用它们来保存解析指令,这种做法会破坏互操作性。我知道缺少评注会让某些人感到悲伤,但这不应如此。
假设您正在使用JSON来保存配置文件,您想对它们进行注释。请插入所有您喜欢的评注,然后再使用JSMin将其传递给您的JSON解析器。
-
如果某些库添加评注支持而其他库没有,这将危害互操作性。请参阅《鲁棒性原则的有害后果》。
这个库将不会在未来支持评注。如果您希望使用评注,我看到了三个选择
- 在使用此库之前去除评注。
- 使用具有评注支持的不同的JSON库。
- 使用原生支持评注的格式(例如,YAML或JSON5)。
对象键的顺序
默认情况下,库不会保留对象元素的插入顺序。这是符合标准的,因为JSON标准将对象定义为“一个包含零个或多个名称/值对的未排序集合”。如果您想要保留插入顺序,您可以使用tsl::ordered_map
(集成)或nlohmann::fifo_map
(集成)这样的容器来特别化对象类型。
其他说明
- 代码中包含多个调试断言,可以通过定义预处理器宏
NDEBUG
来关闭,请参阅assert
的文档。特别是请注意,operator[]
实现了对const对象的未检查访问:如果给定的键不存在,则行为未定义(考虑为空指针的间接引用)并在启用断言的情况下导致断言失败。如果您不确定对象中是否存在元素,请使用具有at()
函数的已检查访问。 - 由于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
应该离开当前作用域(例如,通过抛出或中止),因为在其后继续可能会产生未定义的行为。
执行单元测试
要编译和运行测试,您需要执行
$ mkdir build
$ cd build
$ cmake ..
$ cmake --build .
$ ctest --output-on-failure
有关更多信息,请参阅文件.travis.yml。