nlohmann_json_cocoa 3.7.3

nlohmann_json_cocoa 3.7.3

Nghia Tran 维护。




  • Niels Lohmann

JSON for Modern C++

Build Status Build Status Build Status Coverage Status Coverity Scan Build Status Codacy Badge Language grade: C/C++ Fuzzing Status Try online Documentation GitHub license GitHub Releases GitHub Issues Average time to resolve an issue CII Best Practices

设计目标

现在有很多 JSON 库,每个都有自己的存在理由。我们的类有以下设计目标

  • 直观的语法。在像 Python 这样的语言中,JSON 感觉像一等数据类型。我们使用了现代 C++ 中的所有操作符魔力,以在代码中获得同样的感觉。请查看下面的示例,你就会明白我的意思。

  • 简单集成。我们所有的代码都由单个头文件组成 json.hpp。就这样。没有库,没有子项目,没有依赖项,没有复杂的构建系统。类是用纯 C++11 编写的。总之,所有这一切都不需要调整您的编译器标志或项目设置。

  • 严格的测试。我们的类进行了大量的 单元测试 并涵盖了 100% 的代码,包括所有异常行为。此外,我们使用 ValgrindClang Sanitizers 进行了检查,以确保没有内存泄漏。 Google OSS-Fuzz 还对所有解析器进行了模糊测试,每天24小时,到目前为止已经执行了数十亿次测试。为了维持高质量,该项目遵循核心基础设施倡议(CII)的最佳实践

其他方面对我们来说并不是那么重要

  • 内存效率。每个 JSON 对象有一个额外指针(联合的最大大小)和一个枚举元素(1字节)的开销。默认泛化使用了以下 C++ 数据类型:字符串使用 std::string,数字使用 int64_tuint64_tdouble,对象使用 std::map,数组使用 std::vector,布尔值使用 bool。然而,您可以模板化泛化类 basic_json 以满足您的需求。

  • 速度。当然有更快的 JSON 库[链接]。但是,如果你的目标是通过添加单个头文件来加快开发速度,那么这个库是对的。如果你知道如何使用std::vectorstd::map,你就已经准备好了。

有关更多信息,请参阅贡献指南

集成

json.hpp是位于single_include/nlohmann此处发布的必要文件。您需要将其添加到您想要处理 JSON 的文件中,并设置必要的选项以启用 C++11(例如,GCC 和 Clang 的-std=c++11)。

#include <nlohmann/json.hpp>

// for convenience
using json = nlohmann::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接口目标。此目标将填充适当的用法要求,以指向适当的包含目录,并为必要的 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 然后就是此源树的完整副本。

包管理器

🍺如果你使用的是 OS X 和 Homebrew,只需输入 brew tap nlohmann/jsonbrew install nlohmann-json 即可设置。如果你想使用最新版本而不是最新释放版本,请使用 brew install nlohmann-json --HEAD

如果你使用的是 Meson 构建系统,请将此源树作为 meson 子项目 添加。你也可以使用此项目 版本发布 中发布的 include.zip 来减少 vendored 源树的体积。或者,你可以通过从 Meson WrapDB 下载它来获取一个封装文件,或者简单地使用 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,您可以通过将pod "nlohmann_json", '~>3.1.2'添加到您的Podfile中(请参阅示例)来使用该库。请在此处提交问题。

如果您使用的是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_jsonpacman -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<std::string>() 返回最初存储的字符串值。

请注意,此库只支持 UTF-8。当您在库中存储不同编码的字符串时,如果未使用 json::error_handler_t::replacejson::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::istreamstd::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处理器,请按以下方式进行

  1. 在类中实现SAX接口。您可以使用类nlohmann::json_sax作为基类,但也可以使用任何实现上述函数并公有的类。
  2. 创建您的SAX接口类的一个对象,例如my_sax
  3. 调用bool json::sax_parse(input, &my_sax);其中第一个参数可以是字符串或输入流等任何输入,第二个参数是您的SAX接口的指针。

请注意,sax_parse函数仅返回一个bool,表示最后执行的下SAX事件的结果。它不返回一个json值 - 如何处理SAX事件取决于您。此外,在解析错误的情况下不会抛出异常 - 如何处理传递给您的parse_error实现的异常对象取决于您。内部,SAX接口用于DOM解析器(类json_sax_dom_parser)以及接受者(json_sax_acceptor),请参阅文件json_sax.hpp

类似于STL的访问

我们设计的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\", 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容器转换

任何序列容器(std::arraystd::vectorstd::dequestd::forward_liststd::list),其值可以用于构造JSON值(例如,整数、浮点数、布尔值、字符串类型,或者再次为本文节中描述的STL容器),都可以用来创建JSON数组。对于类似的关联容器(std::setstd::multisetstd::unordered_setstd::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::mapstd::multimapstd::unordered_mapstd::unordered_multimap),其键可以构造出std::string,而其值可以用作构造JSON值(见上面的示例),都可以用来创建JSON对象。请注意,在多地图的情况下,只使用一个键在JSON对象中,值取决于STL容器内部顺序。

std::map<std::string, int> c_map { {"one", 1}, {"two", 2}, {"three", 3} };
json j_map(c_map);
// {"one": 1, "three": 3, "two": 2 }

std::unordered_map<const char*, double> c_umap { {"one", 1.2}, {"two", 2.3}, {"three", 3.4} };
json j_umap(c_umap);
// {"one": 1.2, "two": 2.3, "three": 3.4}

std::multimap<std::string, bool> c_mmap { {"one", true}, {"two", true}, {"three", false}, {"three", true} };
json j_mmap(c_mmap); // only one entry for key "three" is used
// maybe {"one": true, "two": true, "three": true}

std::unordered_multimap<std::string, bool> c_ummap { {"one", true}, {"two", true}, {"three", false}, {"three", true} };
json j_ummap(c_ummap); // only one entry for key "three" is used
// maybe {"one": true, "two": true, "three": true}

JSON指针和JSON补丁

该库支持作为选择的方法来引用结构化值JSON Pointer(《rfc6901《》)。在此基础上,JSON Patch(《rfc6902《》》)允许描述两个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(《rfc7386《》)。与上述使用JSON指针不同的方式指定要操作的值,它使用 similes 表明修改的文档的语法来描述更改。

// 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 序列化器 将类型转换为 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::jsonbasic_json别称(basic_json的最后一个模板参数是JSONSerializer
  • 在所有你的to_json/from_json方法中使用你的basic_json别称(或模板参数)
  • 当你需要ADL时,使用nlohmann::to_jsonnlohmann::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()宏声明了一组针对类型TaskStateto_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的支持仍然有点稀疏。目前,以下已知的编译器可以正常工作:

  • GNU编译器GCC 4.8 - 9.2(以及可能更新的版本)
  • Clang编译器 3.4 - 9.0(以及可能更新的版本)
  • Intel编译器C++ 17.0.2(以及可能更新的版本)
  • Microsoft Visual C++ 2015 / 构建工具 14.0.25123.0(以及可能更新的版本)
  • Microsoft Visual C++ 2017 / 构建工具 15.5.180.51428(以及可能更新的版本)
  • Microsoft Visual C++ 2019 / 构建工具 16.3.1+1def00d3d(以及可能更新的版本)

我很乐意了解其他编译器/版本的更新。

请注意

  • GNU编译器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
    

    代码可以成功地与Android NDK版本9 - 11(以及可能更新的版本)和CrystaX的Android NDK版本10进行编译。

  • 对于在MinGW或Android SDK上运行的GCC,可能会出现错误'to_string'不是'ld'的成员(或者类似情况,对于strtod)。请注意,这不是代码的问题,而是编译器本身的问题。在Android上,请参阅前面的内容以使用更新的环境进行构建。对于MinGW,请参考本站点此讨论以获取修复此问题的信息。对于使用APP_STL := gnustl_static的Android NDK,请参考此讨论

  • 不支持的GCC和Clang版本将因错误指令#error而被拒绝。可以定义JSON_SKIP_UNSUPPORTED_COMPILER_CHECK来关闭此功能。请注意,在这种情况下,您可能不会获得任何支持。

以下编译器目前被用于TravisAppVeyorCircleCIDoozer的持续集成。

编译器 操作系统 版本字符串
GNU编译器GCC 4.8.5 Ubuntu 14.04.5 LTS g++-4.8 (Ubuntu 4.8.5-2ubuntu1~14.04.2) 4.8.5
GNU编译器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)
GNU编译器GCC 4.9.2(armv7l) Raspbian GNU/Linux 8 (jessie) g++ (Raspbian 4.9.2-10+deb8u2) 4.9.2
GNU编译器GCC 4.9.4 Ubuntu 14.04.1 LTS g++-4.9 (Ubuntu 4.9.4-2ubuntu1~14.04.1) 4.9.4
GNU编译器GCC 5.3.1(armv7l)  Ubuntu 16.04 LTS g++ (Ubuntu/Linaro 5.3.1-14ubuntu2) 5.3.1 20160413
GNU编译器GCC 5.5.0 Ubuntu 14.04.1 LTS g++-5 (Ubuntu 5.5.0-12ubuntu1~14.04) 5.5.0 20171010
GNU编译器GCC 6.3.0 Debian 9 (stretch) g++ (Debian 6.3.0-18+deb9u1) 6.3.0 20170516
GCC 6.3.1 Fedora版本24(二十四) 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-1exp120180509123505.100 (branches/release_50)
Clang 6.0.1 Ubuntu 14.04.1 LTS clang版本6.0.1-svn334776-1exp120180726133705.85 (branches/release_60)
Clang 7.0.1 Ubuntu 14.04.1 LTS clang版本7.0.1-svn348686-1exp120181213084532.54 (branches/release_70)
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) Build Engine版本14.0.25420.1, MSVC 19.0.24215.1
Visual Studio 15 2017 Windows Server 2012 R2 (x64) Microsoft (R) Build Engine版本15.9.21+g9802d43bc3, MSVC 19.16.27032.1
Visual Studio 16 2019 Windows Server 2012 R2 (x64) Microsoft (R) Build Engine版本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 的副本,Hedley 采用 CC0-1.0 协议授权。

联系

如果您对库有任何问题,请邀请您在 GitHub 上提交问题。请尽可能详细地描述您的请求、问题或疑问,并提及您使用的库版本以及编译器和操作系统版本。在GitHub上提交问题可以让其他用户和为此库做出贡献的人协作。例如,我和MSVC的经验很少,大多数相关问题都是由日益增长的用户社区解决的问题。如果您查看 已关闭的问题,您将看到我们大多数情况下都反应得很及时。

只有在您的请求包含机密信息的情况下,请[email protected]。对于加密消息,请使用此密钥

安全

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 编译的一个错误。
  • Florian Weber 修复了比较操作符中的错误并提高了性能。
  • Eric Cornelius 指出了处理 NaN 和无穷大值的错误。他还改善了字符串转义的性能。
  • 易思龙 实现了匿名枚举的转换。
  • kepkin 耐心地推进了 Microsoft Visual Studio 的支持。
  • gregmarr 简化了反向迭代器的实现,并提供了许多提示和改进。特别是在推动用户定义类型的实现方面。
  • Caio Luppi 修复了 Unicode 处理中的错误。
  • dariomt 修复了示例中的某些打字错误。
  • David 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_tuint64_t 相关的命名空间问题。
  • abc100m 分析了 GCC 4.8 的问题,并提出了一个部分解决方案的 pull request
  • zewt 在 README 文件中添加了有关 Android 的有用说明。
  • Róbert Márki 添加了一个修正以使用移动迭代器,并改善了通过 CMake 的集成。
  • Chris Kitching 清理了 CMake 文件。
  • Tom Needham 修复了 MSVC 2015 中一个微妙的错误,这个错误也被 Michael K. 提出。
  • Mário Feroldi 修复了一个小的打字错误。
  • duncanwerner 发现了 2.0.0 版本中一个令人尴尬的性能下降。
  • Walter 消除了最后的转换警告之一。
  • Thomas Braun 修复了一个测试用例中的警告。
  • Théo DELRIEU 以耐心和建设性的态度监督了通往 迭代器范围解析 的漫长历程。他还实现了用户定义类型序列化/反序列化背后的魔法,并将单一的头文件拆分成更小的部分。
  • Stefan 在文档中修复了一个小问题。
  • Vasil Dimov 修复了关于从 std::multiset 转换的文档。
  • ChristophJud 重构了 CMake 文件,以简化项目包含。
  • Vladimir Petrigo 使 SFINAE 拼接更具可读性,并将 Visual Studio 17 添加到构建矩阵中。
  • Denis Andrejew 修复了 README 文件中的语法问题。
  • Pierre-Antoine Lacazedump() 函数中找到了一个细微的错误。
  • 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řenovský 找到一种方法将测试套件的编译时间提高 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 通过将 <iostream> 替换为 <iosfwd> 以减少内存消耗。
  • 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 Sewardfind()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的错误并相应地更新了此库。
  • Florian Pigorsch 修复了许多拼写错误。
  • Camille Bégué 修复了将 std::pairstd::tuple 转换为 json 的问题。
  • Anthony VH 修复了一个枚举反序列化中的编译错误。

非常感谢您的帮助!请让我知道如果我漏掉了某个人。

使用的第三方工具

该库本身由一个MIT许可证许可的单一头文件组成。但是,它使用许多第三方工具和服务进行构建、测试、文档化等。非常感谢!

使用 JSON 的 Modern C++ 项目

库当前用于 Apple macOS Sierra 和 iOS 10。我不知道他们为什么使用库,但我很高兴它在这么多设备上运行。

注意事项

字符编码

该库支持以下格式的 Unicode 输入

  • 只支持 UTF-8 编码的输入,这是根据 RFC 8259 JSON 的默认编码。
  • 可以解析 std::u16stringstd::u32string,分别假设为 UTF-16 和 UTF-32 编码。当从文件或其他输入容器读取时,这些编码不受支持。
  • 不支持诸如 Latin-1 或 ISO 8859-1 的其他编码,将会产生解析或序列化错误。
  • 库不会替换 Unicode 非字符
  • 非法代理(例如,不完整的对如 \uDEAD)将产生解析错误。
  • 库中存储的字符串是以 UTF-8 编码的。当使用默认字符串类型(std::string)时,请注意,其长度/大小函数返回的是存储的字节数,而不是字符数或位图数。
  • 当在库中存储具有不同编码的字符串时,如果使用默认的错误处理器,调用 dump() 可能会抛出异常,除非使用 json::error_handler_t::replacejson::error_handler_t::ignore 作为错误处理器。

JSON中的注释

此库不支持注释,原因有三:

  1. 注释不是JSON规范的一部分。你可以争辩说在JavaScript中允许///* */,但JSON不是JavaScript。

  2. 这并非疏忽:Douglas Crockford于2012年5月写道了这件事。

    我移除JSON中的注释,因为我看到人们使用它们来保存解析指令,这种做法会破坏互操作性。我知道缺少注释让一些人感到难过,但不应如此。

    假设你正在使用JSON来保存配置文件,并希望对其进行注释。请大胆地插入所有你喜欢的注释。然后将它通过JSMin处理后交给你的JSON解析器。

  3. 在某些库添加注释支持而其他库不添加注释会对互操作性造成危险。请查阅关于鲁棒性原则有害后果的研究

将来此库将不支持注释。如果你希望使用注释,我看到了三个选项:

  1. 在使用此库之前删除注释。
  2. 使用支持注释的不同JSON库。
  3. 使用原生支持注释的格式(例如,YAML或JSON5)。

对象键的顺序

默认情况下,此库不会保留对象的元素插入顺序。这是符合标准的,因为这个JSON标准将对象定义为“一个零个或多个键值对的零散集合”。如果您确实希望保留插入顺序,您可以使用容器(如tsl::ordered_map (集成) 或nlohmann::fifo_map (集成))来特别指定对象类型。

进一步说明

  • 代码中包含许多调试点断言,可以通过定义预处理器宏NDEBUG来关闭它们,请参阅assert的文档。特别是要注意,operator[]为const对象实现了未检查的访问:如果给定键不存在,行为是未定义的(类似于解引用空指针),并且如果启用了断言,将导致断言失败。如果您不确定对象中是否存在某个元素,请使用带有at()函数的checked访问。
  • 由于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