json:JSON for Modern C++

JSON for Modern C++

分支31Tags57
当前项目代码仓暂无内容

JSON for Modern C++

构建状态 Ubuntu macOS Windows 覆盖率状态 Coverity 扫描构建状态 Codacy 评级 Cirrus CI 模糊测试状态 在线试用 文档 GitHub 许可证 GitHub 版本发布 打包状态 GitHub 下载量 GitHub 问题 问题平均解决时间 CII 最佳实践 OpenSSF 评分卡 备份状态 GitHub 赞助者 REUSE 状态 Discord

设计目标

市面上有无数种 JSON 库,每一种都有其存在的理由。我们的类在设计时有以下目标:

  • 直观的语法。在 Python 等语言中,JSON 就像一种一等公民的数据类型。我们利用了现代 C++ 的所有运算符魔法,让您的代码也能获得同样的体验。看看下面的示例,您就会明白我的意思。

  • 轻松集成。我们的全部代码只包含一个头文件 json.hpp。仅此而已。没有库文件,没有子项目,没有依赖,没有复杂的构建系统。该类使用纯 C++11 编写。总而言之,一切都不需要您调整编译器选项或项目设置。该库也包含在所有主流的包管理器中。

  • 严谨的测试。我们的代码经过了大量的单元测试,覆盖率达到 100%,包括所有异常行为。此外,我们还使用 ValgrindClang Sanitizers 进行了检查,确保没有内存泄漏。Google OSS-Fuzz 还会对所有解析器进行全天候的模糊测试,迄今为止已有效执行了数十亿次测试。为了保持高质量,该项目遵循 Core Infrastructure Initiative (CII) 最佳实践。请参阅质量保证概述文档。

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

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

  • 速度。市面上当然有更快的 JSON 库。但是,如果您的目标是通过添加一个头文件来加速开发并获得 JSON 支持,那么这个库就是最佳选择。如果您已经熟悉 std::vectorstd::map 的用法,那么您已经可以上手了。

更多信息请参阅贡献指南

赞助商

您可以通过 GitHub Sponsors 赞助本库。

🙋 优先赞助商

🏷️ 署名赞助商

其他支持

本库的开发还得到了 JetBrains 的支持,他们免费提供了其 IDE 工具的使用权限。

JetBrains logo.

感谢每一位支持者!

支持

❓ 如果您有疑问,请先查看 FAQ问答 板块中是否已有解答。如果没有,欢迎在 此处提出新问题

📚 如果您想深入学习如何使用本库,可以查阅 README 的其余部分,查看 代码示例,或浏览 帮助页面

🚧 如果您想更好地理解 API,请查阅 API 参考 或下方的 快速参考

🐛 如果您发现了缺陷,请先查看 FAQ 确认这是否是已知问题或设计决策的结果。在 创建新 issue 之前,也请浏览一下 问题列表。请尽可能提供详细的信息,以帮助我们理解和复现您遇到的问题。

此外,文档浏览器 DashVelocityZeal 还有对应的 docset,其中包含完整的 文档,可作为离线资源使用。

快速参考

完整 API 文档

示例

以下是一些示例,帮助您了解如何使用该类。

除了下面的示例外,您还可以:

→ 查阅文档
→ 浏览独立示例文件
→ 阅读完整的API 文档,其中包含每个函数的自包含示例

从文件读取 JSON

json 类提供了操作 JSON 值的 API。要通过读取 JSON 文件来创建 json 对象:

#include <fstream>
#include <nlohmann/json.hpp>
using json = nlohmann::json;

// ...

std::ifstream f("example.json");
json data = json::parse(f);

如果使用模块(通过 NLOHMANN_JSON_BUILD_MODULES 启用),该示例将变为:

import std;
import nlohmann.json;

using json = nlohmann::json;

// ...

std::ifstream f("example.json");
json data = json::parse(f);

从 JSON 字面量创建 json 对象

假设你想将这个 JSON 字面量值硬编码为一个 json 对象:

{
  "pi": 3.141,
  "happy": true
}

有以下几种可选方案:

// Using (raw) string literals and json::parse
json ex1 = json::parse(R"(
  {
    "pi": 3.141,
    "happy": true
  }
)");

// Using user-defined (raw) string literals
using namespace nlohmann::literals;
json ex2 = R"(
  {
    "pi": 3.141,
    "happy": true
  }
)"_json;

// Using initializer lists
json ex3 = {
  {"happy", true},
  {"pi", 3.141},
};

JSON 作为一等数据类型

以下示例将帮助您了解如何使用该类。

假设您想创建如下 JSON 对象:

{
  "pi": 3.141,
  "happy": true,
  "name": "Niels",
  "nothing": null,
  "answer": {
    "everything": 42
  },
  "list": [1, 0, 2],
  "object": {
    "currency": "USD",
    "value": 42.99
  }
}

有了这个库,你可以这样写:

// create an empty structure (null)
json j;

// add a number stored as double (note the implicit conversion of j to an object)
j["pi"] = 3.141;

// add a Boolean stored as bool
j["happy"] = true;

// add a string 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 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 }",而不会解析出实际的对象。

该字符串字面量需要通过 using namespace nlohmann::literals; 引入作用域 (参见 json::parse())。

上述示例也可以使用 json::parse() 显式地表达:

// parse explicitly
auto j3 = json::parse(R"({"happy": true, "pi": 3.141})");

您也可以获取JSON值的字符串表示形式(即序列化):

// explicit conversion to string
std::string s = j.dump();    // {"happy":true,"pi":3.141}

// serialization with pretty printing
// pass in the amount of spaces to indent
std::cout << 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 a variable already exists)
std::string cpp_string2;
j_string.get_to(cpp_string2);

// retrieve the serialized value (explicit JSON serialization)
std::string serialized_string = j_string.dump();

// output of original string
std::cout << cpp_string << " == " << cpp_string2 << " == " << j_string.get<std::string>() << '\n';
// output of serialized value
std::cout << j_string << " == " << serialized_string << std::endl;

.dump() 返回最初存储的字符串值。

请注意,该库仅支持 UTF-8 编码。当你在库中存储使用其他编码的字符串时,调用 dump() 可能会抛出异常,除非使用 json::error_handler_t::replacejson::error_handler_t::ignore 作为错误处理程序。

流传输(例如,文件、字符串流)

你也可以使用流来进行序列化和反序列化:

// 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;也就是说,可以从任何通过迭代器访问的容器中解析,只要其 value_type 是 1、2 或 4 字节的整型类型,这些类型将分别被解释为 UTF-8、UTF-16 和 UTF-32。例如,std::vector<std::uint8_t>std::list<std::uint16_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);

自定义数据源

由于解析函数接受任意迭代器范围,您可以通过实现LegacyInputIterator概念来提供自定义的数据源。

struct MyContainer {
  void advance();
  const char& get_current();
};

struct MyIterator {
    using difference_type = std::ptrdiff_t;
    using value_type = char;
    using pointer = const char*;
    using reference = const char&;
    using iterator_category = std::input_iterator_tag;

    explicit MyIterator(MyContainer* tgt = nullptr) : target(tgt) {}

    MyIterator& operator++() {
        target->advance();
        return *this;
    }

    bool operator!=(const MyIterator& rhs) const {
        return rhs.target != target;
    }

    reference operator*() const {
        return target->get_current();
    }

    MyContainer* target = nullptr;
};

MyIterator begin(MyContainer& tgt) {
    return MyIterator{&tgt};
}

MyIterator end(const MyContainer&) {
    return MyIterator{};
}

void foo() {
    MyContainer c;
    json j = json::parse(begin(c), end(c));
}

SAX 接口

该库采用类似 SAX 的接口,包含以下函数:

// called when null is parsed
bool null();

// called when a boolean is parsed; value is passed
bool boolean(bool val);

// called when a signed or unsigned integer number is parsed; value is passed
bool number_integer(number_integer_t val);
bool number_unsigned(number_unsigned_t val);

// called when a floating-point number is parsed; value and original string is passed
bool number_float(number_float_t val, const string_t& s);

// called when a string is parsed; value is passed and can be safely moved away
bool string(string_t& val);
// called when a binary value is parsed; value is passed and can be safely moved away
bool binary(binary_t& val);

// called when an object or array begins or ends, resp. The number of elements is passed (or -1 if not known)
bool start_object(std::size_t elements);
bool end_object();
bool start_array(std::size_t elements);
bool end_array();
// called when an object key is parsed; value is passed and can be safely moved away
bool key(string_t& val);

// called when a parse error occurs; byte position, the last token, and an exception is passed
bool parse_error(std::size_t position, const std::string& last_token, const detail::exception& ex);

每个函数的返回值决定了解析是否应继续。

要实现自定义的 SAX 处理器,请按以下步骤操作:

  1. 在类中实现 SAX 接口。您可以使用 nlohmann::json_sax<json> 类作为基类,也可以使用任何实现了上述函数并将其设为公开的类。
  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 容器。事实上,它满足 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 == R"(["foo", 1, true, 1.78])"_json;  // true

// other stuff
j.size();     // 4 entries
j.empty();    // false
j.type();     // json::value_t::array
j.clear();    // the array is empty again

// convenience type checkers
j.is_null();
j.is_boolean();
j.is_number();
j.is_object();
j.is_array();
j.is_string();

// create an object
json o;
o["foo"] = 23;
o["bar"] = false;
o["baz"] = 3.141;

// also use emplace
o.emplace("weather", "sunny");

// special iterator member functions for objects
for (json::iterator it = o.begin(); it != o.end(); ++it) {
  std::cout << it.key() << " : " << it.value() << "\n";
}

// the same code as range for
for (auto& el : o.items()) {
  std::cout << el.key() << " : " << el.value() << "\n";
}

// even easier with structured bindings (C++17)
for (auto& [key, value] : o.items()) {
  std::cout << key << " : " << value << "\n";
}

// find an entry
if (o.contains("foo")) {
  // there is an entry with key "foo"
}

// or via find and an iterator
if (o.find("foo") != o.end()) {
  // there is an entry with key "foo"
}

// 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 对象。需要注意的是,对于多重映射(multimap),JSON 对象中仅使用一个键,具体值取决于 STL 容器的内部顺序。

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

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

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

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

JSON Pointer 与 JSON Patch

本库支持 JSON PointerRFC 6901)作为定位结构化值的另一种方式。在此基础上,JSON PatchRFC 6902)可用于描述两个 JSON 值之间的差异——实际上实现了 Unix 中常见的补丁与差异操作。

// a JSON value
json j_original = R"({
  "baz": ["one", "two", "three"],
  "foo": "bar"
})"_json;

// access members with a JSON pointer (RFC 6901)
j_original["/baz/1"_json_pointer];
// "two"

// a JSON patch (RFC 6902)
json j_patch = R"([
  { "op": "replace", "path": "/baz", "value": "boo" },
  { "op": "add", "path": "/hello", "value": ["world"] },
  { "op": "remove", "path": "/foo"}
])"_json;

// apply the patch
json j_result = j_original.patch(j_patch);
// {
//    "baz": "boo",
//    "hello": ["world"]
// }

// calculate a JSON patch from two JSON values
json::diff(j_result, j_original);
// [
//   { "op":" replace", "path": "/baz", "value": ["one", "two", "three"] },
//   { "op": "remove","path": "/hello" },
//   { "op": "add", "path": "/foo", "value": "bar" }
// ]

JSON Merge Patch

本库支持 JSON Merge PatchRFC 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 值进行的 隐式转换。 关于此建议的更多细节,请参见 此处。 您可以在包含 json.hpp 头文件之前,将 JSON_USE_IMPLICIT_CONVERSIONS 定义为 0,从而关闭隐式转换。使用 CMake 时,也可以通过将选项 JSON_ImplicitConversions 设置为 OFF 来实现这一点。

// strings
std::string s1 = "Hello, world!";
json js = s1;
auto s2 = js.get<std::string>();
// NOT RECOMMENDED
std::string s3 = js;
std::string s4;
s4 = js;

// Booleans
bool b1 = true;
json jb = b1;
auto b2 = jb.get<bool>();
// NOT RECOMMENDED
bool b3 = jb;
bool b4;
b4 = jb;

// numbers
int i = 42;
json jn = i;
auto f = jn.get<double>();
// NOT RECOMMENDED
double f2 = jn;
double f3;
f3 = jn;

// 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 json = nlohmann::json;

namespace ns {
    void to_json(json& j, const person& p) {
        j = json{{"name", p.name}, {"address", p.address}, {"age", p.age}};
    }

    void from_json(const json& j, person& p) {
        j.at("name").get_to(p.name);
        j.at("address").get_to(p.address);
        j.at("age").get_to(p.age);
    }
} // namespace ns

就这样!当你用自定义类型调用 json 构造函数时,你自定义的 to_json 方法会被自动调用。同样地,调用 get<your_type>()get_to(your_type&) 时,from_json 方法也会被调用。

有几个重要事项:

  • 这些方法必须定义在你的类型所在的命名空间中(可以是全局命名空间),否则库将无法找到它们(在本例中,它们位于 ns 命名空间中,person 也定义在那里)。
  • 这些方法在使用这些转换的任何地方必须可用(例如,必须包含正确的头文件)。请参见 issue 1108 了解否则可能出现的错误。
  • 使用 get<your_type>() 时,your_type 必须DefaultConstructible 的。(后面会有一种绕过此要求的方法。)
  • from_json 函数中,使用 at() 函数来访问对象的值,而不是 operator[]。如果键不存在,at 会抛出异常以便处理,而 operator[] 则会表现为未定义行为。
  • 你不需要为 STL 类型(如 std::vector)添加序列化或反序列化器:库已经实现了这些功能。

用宏简化你的工作

如果你只想序列化/反序列化一些结构体,编写 to_json/from_json 函数可能会产生大量样板代码。只要你打算使用 JSON 对象进行序列化,就有 好几个宏 可以让你的工作更轻松。

选择哪个宏取决于以下因素:是否需要访问私有成员变量、是否需要进行反序列化、缺失的值是应产生错误还是用默认值替代,以及是否使用了派生类。请参阅 这个概述来选择适合你用例的宏

宏的使用示例

上面 person 结构体的 to_json/from_json 函数可以用 NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE 来创建。在所有宏中,第一个参数是类/结构体的名称,其余参数是成员名。

namespace ns {
    NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(person, name, address, age)
}

如果你想要继承 person 结构体并为其添加一个字段,可以通过以下方式实现:

namespace ns {
    struct person_derived : person {
        std::string email;
    };
    
    NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(person_derived, person, email)
}

下面是另一个包含私有成员的示例,此时需要使用 NLOHMANN_DEFINE_TYPE_INTRUSIVE

namespace ns {
    class address {
      private:
        std::string street;
        int housenumber;
        int postcode;
  
      public:
        NLOHMANN_DEFINE_TYPE_INTRUSIVE(address, street, housenumber, postcode)
    };
}

或者,如果您使用了某种不希望暴露给JSON的命名约定:

namespace ns {
    class address {
      private:
        std::string m_street;
        int m_housenumber;
        int m_postcode;

      public:
        NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_NAMES(address, "street", m_street,
                                                           "housenumber", m_housenumber,
                                                           "postcode", m_postcode)
    };
}

如何转换第三方类型?

这需要更进阶的技巧。不过首先,让我们了解一下这个转换机制的工作原理:

该库使用 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::optionalstd::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.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 值将默认映射为您在映射表中指定的第一对键值。请谨慎选择该默认对。如果您希望在这种情况下抛出异常,请使用 NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(),其行为与前者完全一致,唯一区别在于遇到无法识别的值时会抛出异常。
  • 如果枚举值或 JSON 值在映射表中出现多次,则在 JSON 与枚举互转时,将返回映射表中自上而下的首个匹配项。

二进制格式(BSON、CBOR、MessagePack、UBJSON 与 BJData)

尽管 JSON 是一种无所不在的数据格式,但它并非一种非常紧凑的数据交换格式,尤其是在网络传输等场景下。为此,该库支持 BSON(二进制 JSON)、CBOR(简明二进制对象表示)、MessagePackUBJSON(通用二进制 JSON 规范)以及 BJData(二进制 JData),以便高效地将 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);

该库还支持来自BSON、CBOR(字节字符串)和MessagePack(bin、ext、fixext)的二进制类型。默认情况下,这些类型以std::vector<std::uint8_t>的形式存储,以便在库外部进行处理。

// CBOR byte string with payload 0xCAFE
std::vector<std::uint8_t> v = {0x42, 0xCA, 0xFE};

// read value
json j = json::from_cbor(v);

// the JSON value has type binary
j.is_binary(); // true

// get reference to stored binary value
auto& binary = j.get_binary();

// the binary value has no subtype (CBOR has no binary subtypes)
binary.has_subtype(); // false

// access std::vector<std::uint8_t> member functions
binary.size(); // 2
binary[0]; // 0xCA
binary[1]; // 0xFE

// set subtype to 0x10
binary.set_subtype(0x10);

// serialize to MessagePack
auto cbor = json::to_msgpack(j); // 0xD5 (fixext2), 0x10, 0xCA, 0xFE

用户

该库已被广泛应用于多个项目、应用程序、操作系统等。以下列表并非详尽无遗,仅为互联网搜索所得。若您知晓该库的其他用户,请告知我,联系方式见联系

使用该库的用户标识图

生态系统

除了直接使用该库的项目外,还有一些第三方项目在其基础上构建,例如 schema 验证器、语言绑定、格式转换器等。请参阅精心整理的生态系统页面。

支持的编译器

尽管已是 2026 年,对 C++11 的支持仍略显不足。目前,以下编译器已知可以正常工作:

  • GCC 4.8 - 14.2(以及可能的更高版本)
  • Clang 3.4 - 21.0(以及可能的更高版本)
  • Apple Clang 9.1 - 16.0(以及可能的更高版本)
  • Intel C++ Compiler 17.0.2(以及可能的更高版本)
  • Nvidia CUDA Compiler 11.0.221(以及可能的更高版本)
  • 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(以及可能的更高版本)
  • Microsoft Visual C++ 2022 / Build Tools 19.30.30709.0(以及可能的更高版本)

如您了解其他可用的编译器/版本,欢迎告知。

请注意:

  • 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' is not a member of 'std'(或类似的 strtodstrtof 错误)。请注意,这并非代码问题,而是编译器本身的问题。在 Android 上,请参阅上文以使用更新的环境进行构建。对于 MinGW,请参阅此网站此讨论了解如何修复此缺陷。对于使用 APP_STL := gnustl_static 的 Android NDK,请参阅此讨论

  • 不受支持的 GCC 和 Clang 版本会通过 #error 指令被拒绝。可以通过定义 JSON_SKIP_UNSUPPORTED_COMPILER_CHECK 来关闭此检查。请注意,在这种情况下,您将无法获得任何支持。

关于 CI 中用于检查该库的编译器信息,请参阅质量保证页面。

集成方法

json.hpp 是位于 single_include/nlohmann 目录下(或从发布页面获取)的唯一必需文件。你只需添加

#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.12.0 REQUIRED)
...
add_library(foo ...)
...
target_link_libraries(foo PRIVATE nlohmann_json::nlohmann_json)

包配置文件 nlohmann_jsonConfig.cmake 既可用于安装目录,也可直接用于构建目录。

嵌入式集成

若要将该库直接嵌入现有 CMake 项目,可将整个源码树放入子目录,并在 CMakeLists.txt 文件中调用 add_subdirectory()

# Typically you don't care so much for a third party library's tests to be
# run from your own project's code.
set(JSON_BuildTests OFF CACHE INTERNAL "")

# If you only include this third party in PRIVATE source files, you do not
# need to install it when your main project gets installed.
# set(JSON_Install OFF CACHE INTERNAL "")

# Don't use include(nlohmann_json/CMakeLists.txt) since that carries with it
# unintended consequences that will break the build.  It's generally
# discouraged (although not necessarily well documented as such) to use
# include(...) for pulling in other CMake projects anyways.
add_subdirectory(nlohmann_json)
...
add_library(foo ...)
...
target_link_libraries(foo PRIVATE nlohmann_json::nlohmann_json)
嵌入式(FetchContent)

自 CMake v3.11 起,可使用 FetchContent 在配置阶段自动下载指定版本作为依赖项。

示例:

include(FetchContent)

FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.12.0/json.tar.xz)
FetchContent_MakeAvailable(json)

target_link_libraries(foo PRIVATE nlohmann_json::nlohmann_json)

注意:建议采用上述URL方式,该方式自3.10.0版本起得到支持。更多信息请参阅 https://json.nlohmann.me/integration/cmake/#fetchcontent

同时支持两种方式

为便于您的项目既能支持外部提供的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.12.0 REQUIRED)
else()
  set(JSON_BuildTests OFF CACHE INTERNAL "")
  add_subdirectory(nlohmann_json)
endif()
...

thirdparty/nlohmann_json 目录即为此源代码树的完整副本。

包管理器

使用您偏好的 包管理器 来使用该库。

该库已被众多包管理器收录。有关详细说明和示例,请参阅 文档

Pkg-config

如果你使用的是裸 Makefiles,可以通过 pkg-config 来生成指向库安装位置的 include 标志:

pkg-config nlohmann_json --cflags

许可证

OSI approved license

该类库基于 MIT 许可证 授权:

版权所有 © 2013-2026 Niels Lohmann

特此免费授予任何获得本软件及相关文档文件(以下简称“软件”)副本的人无偿使用本软件的权利,包括但不限于使用、复制、修改、合并、发布、分发、再许可和/或出售软件副本的权利,并允许向提供本软件的人授予上述权利,但须满足以下条件:

上述版权声明和本许可声明应包含在本软件的所有副本或实质部分中。

本软件按“现状”提供,不做任何形式的明示或暗示的担保,包括但不限于适销性、特定用途适用性和非侵权性的担保。在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任承担责任,无论该责任是基于合同、侵权或其他方式,亦无论是因本软件或与本软件的使用或其他交易有关而产生的。


REUSE Software

该类库符合 REUSE 规范 3.3 版本的要求:

  • 每个源文件均包含 SPDX 版权声明头。
  • 仓库中使用的所有许可证的全文可在 LICENSES 文件夹中找到。
  • 文件 .reuse/dep5 包含所有文件的版权与许可证概览。
  • 运行 pipx run reuse lint 以验证项目的 REUSE 合规性,运行 pipx run reuse spdx 以生成 SPDX SBOM。

联系方式

如您对本库有任何疑问,欢迎在 GitHub 上提交 issue。请尽可能详细地描述您的请求、问题或疑问,并注明您所使用的库版本、编译器版本以及操作系统版本。在 GitHub 上提交 issue 可以让其他用户和贡献者共同参与协作。例如,我对 MSVC 的经验有限,而许多相关问题都是由不断壮大的社区解决的。如果您浏览一下已关闭的 issue,您会发现我们在多数情况下响应相当及时。

仅当您的请求涉及机密信息时,请发送邮件给我。如需加密邮件,请使用此密钥

安全

Niels Lohmann 的提交发布版本均使用此 PGP 密钥签名。

致谢

我由衷感谢以下人士的帮助。

贡献者的 GitHub 头像

  1. Teemperor 实现了 CMake 支持和 lcov 集成,完成了字符串解析器中的转义和 Unicode 处理,并修复了 JSON 序列化。
  2. elliotgoodrich 修复了迭代器类中双重删除的问题。
  3. kirkshoop 使该类的迭代器可与其他库组合使用。
  4. wancw 修复了一个阻碍该类在 Clang 下编译的 bug。
  5. Tomas Åblad 发现了迭代器实现中的一个 bug。
  6. Joshua C. Randall 修复了浮点序列化中的一个 bug。
  7. Aaron Burghardt 实现了流式增量解析代码。此外,他通过允许定义过滤函数来在解析时丢弃不需要的元素,极大地改进了解析器类。
  8. Daniel Kopeček 修复了 GCC 5.0 编译时的一个 bug。
  9. Florian Weber 修复了比较运算符中的一个 bug 并提升了其性能。
  10. Eric Cornelius 指出了 NaN 和无穷值处理中的一个 bug,并提升了字符串转义的性能。
  11. 易思龙 实现了匿名枚举的转换。
  12. kepkin 耐心地推动了 Microsoft Visual Studio 的支持工作。
  13. gregmarr 简化了反向迭代器的实现,并提供了大量提示和改进。尤其是他大力推动了用户自定义类型的实现。
  14. Caio Luppi 修复了 Unicode 处理中的一个 bug。
  15. dariomt 修正了示例中的一些拼写错误。
  16. Daniel Frey 清理了一些指针并实现了异常安全的內存分配。
  17. Colin Hirsch 处理了一个小的命名空间问题。
  18. Huu Nguyen 更正了文档中的一个变量名。
  19. Silverweed 重载了 parse() 以接受右值引用。
  20. dariomt 修复了 MSVC 类型支持中的一个细节问题,并实现了 get_ref() 函数以获取存储值的引用。
  21. ZahlGraf 添加了一个允许使用 Android NDK 编译的变通方案。
  22. whackashoe 替换了一个被 Visual Studio 标记为不安全的函数。
  23. 406345 修复了两个小警告。
  24. Glen Fernandes 指出了 has_mapped_type 函数中一个潜在的移植性问题。
  25. Corbin Hughes 修正了贡献指南中的一些拼写错误。
  26. twelsby 修复了数组下标运算符——一个导致 MSVC 构建失败的问题,以及浮点解析/导出问题。他还增加了对无符号整数的支持,并实现了对已解析数字更好的往返支持。
  27. Volker Diels-Grabsch 修复了 README 文件中的一个链接。
  28. msm- 增加了对美国模糊测试器(American Fuzzy Lop)的支持。
  29. Annihil 修复了 README 文件中的一个示例。
  30. Themercee 指出 README 文件中的一个错误 URL。
  31. Lv Zheng 修复了 int64_tuint64_t 的命名空间问题。
  32. abc100m 分析了 GCC 4.8 的问题并提出了部分解决方案
  33. zewt 在 README 文件中添加了关于 Android 的有用说明。
  34. Róbert Márki 添加了使用移动迭代器的修复,并改进了 CMake 集成。
  35. Chris Kitching 清理了 CMake 文件。
  36. Tom Needham 修复了 MSVC 2015 的一个细微 bug,Michael K. 也提出了同样的问题。
  37. Mário Feroldi 修正了一个小拼写错误。
  38. duncanwerner 发现了 2.0.0 版本中一个令人尴尬的性能回退。
  39. Damien 修复了最后一个转换警告之一。
  40. Thomas Braun 修复了一个测试用例中的警告,并调整了 CI 中的 MSVC 调用。
  41. Théo DELRIEU 耐心且富有建设性地监督了实现迭代器范围解析的漫长过程。他还实现了用户自定义类型序列化/反序列化背后的魔法,并将单一头文件拆分成了更小的块。
  42. Stefan 修复了文档中的一个小问题。
  43. Vasil Dimov 修复了关于 std::multiset 转换的文档。
  44. ChristophJud 重写了 CMake 文件以简化项目包含。
  45. Vladimir Petrigo 使一个 SFINAE 技巧更易读,并将 Visual Studio 17 添加到了构建矩阵中。
  46. Denis Andrejew 修复了 README 文件中的一处语法问题。
  47. Pierre-Antoine Lacazedump() 函数中发现了一个细微的 bug。
  48. TurpentineDistillery 指出了使用 std::locale::classic() 以避免过多的 locale 切换,在解析器中找到了一些不错的性能改进,改进了基准测试代码,并实现了与 locale 无关的数字解析和打印。
  49. cgzones 提出了修复 Coverity 扫描的思路。
  50. Jared Grubb 消除了一个烦人的文档警告。
  51. Yixin Zhang 修复了一个整数溢出检查。
  52. Bosswestfalen 将两个迭代器类合并为一个更精简的类。
  53. Daniel599 帮助 Travis 使用 Clang 的 sanitizers 执行测试。
  54. Jonathan Lee 修复了 README 文件中的一个示例。
  55. gnzlbg 支持了用户自定义类型的实现。
  56. Alexej Harm 帮助使自定义类型在 Visual Studio 下正常工作。
  57. Jared Grubb 支持了用户自定义类型的实现。
  58. EnricoBilla 指出一个示例中的拼写错误。
  59. Martin Hořeňovský 找到了一种将测试套件编译时间提升 2 倍的方法。
  60. ukhegg 对示例部分提出了改进建议。
  61. rswanson-ihi 指出 README 中的一处拼写错误。
  62. Mihai Stan 修复了与 nullptr 比较时的一个 bug。
  63. Tushar Maheshwari 添加了 cotire 支持以加速编译。
  64. TedLyngmo 指出 README 中的一处拼写错误,移除了不必要的位运算,并修复了一些 -Weffc++ 警告。
  65. Krzysztof Woś 使异常更加可见。
  66. ftillier 修复了一个编译器警告。
  67. tinloaf 确保所有压入的警告都被正确地弹出。
  68. Fytch 在文档中发现了一个 bug。
  69. Jay Sistar 实现了 Meson 构建描述。
  70. Henry Lee 修复了 ICC 中的一个警告并改进了迭代器实现。
  71. Vincent Thiery 维护着 Conan 包管理器的一个包。
  72. Steffen 修复了 MSVC 与 std::min 的一个潜在问题。
  73. Mike Tzou 修正了一些拼写错误。
  74. amrcode 指出关于浮点数比较的文档具有误导性。
  75. Oleg Endo 通过将 <iostream> 替换为 <iosfwd> 减少了内存消耗。
  76. dan-42 清理了 CMake 文件以简化库的包含/复用。
  77. Nikita Ofitserov 允许从初始化列表中移动值。
  78. Greg Hurrell 修正了一个拼写错误。
  79. Dmitry Kukovinets 修正了一个拼写错误。
  80. kbthomp1 修复了一个与 Intel OSX 编译器相关的问题。
  81. Markus Werle 修正了一个拼写错误。
  82. WebProdPP 修复了一个前置条件检查中的细微错误。
  83. Alex 指出一个代码示例中的错误。
  84. Tom de Geus 报告了 ICC 的一些警告并帮助修复了它们。
  85. Perry Kundert 简化了从输入流读取的操作。
  86. Sonu Lohani 修复了一个小的编译错误。
  87. Jamie Seward 修复了所有 MSVC 警告。
  88. Nate Vargas 添加了一个 Doxygen 标签文件。
  89. pvleuven 帮助修复了 ICC 中的一个警告。
  90. Pavel 帮助修复了 MSVC 中的一些警告。
  91. Jamie Seward 避免了 find()count() 中不必要的字符串拷贝。
  92. Mitja 修正了一些拼写错误。
  93. Jorrit Wronski 更新了 Hunter 软件包链接。
  94. Matthias Möller 为 MSVC 调试视图添加了 .natvis
  95. bogemic 修复了一些 C++17 弃用警告。
  96. Eren Okka 修复了一些 MSVC 警告。
  97. abolz 集成了 Grisu2 算法以实现正确的浮点格式化,使更多往返检查得以成功。
  98. Vadim Evard 修复了 README 中的一处 Markdown 问题。
  99. zerodefect 修复了一个编译器警告。
  100. Kert 允许在序列化中对字符串类型进行模板化,并增加了覆盖异常行为的能力。
  101. mark-99 帮助修复了一个 ICC 错误。
  102. Patrik Huber 修复了 README 文件中的链接。
  103. johnfb 在 CBOR 不定长度字符串的实现中发现了一个 bug。
  104. Paul Fultz II 添加了关于 cget 包管理器的说明。
  105. Wilson Lin 使 README 的集成部分更加简洁。
  106. RalfBielig 检测并修复了解析器回调中的内存泄漏。
  107. agrianius 允许将 JSON 导出到另一种字符串类型。
  108. Kevin Tonon 重写了 CMake 中的 C++11 编译器检查。
  109. Axel Huebl 简化了一个 CMake 检查,并增加了对 Spack 包管理器的支持。
  110. Carlos O'Ryan 修正了一个拼写错误。
  111. James Upjohn 修正了编译器部分中的一个版本号。
  112. Chuck Atkins 按照 CMake 打包指南调整了 CMake 文件,并为 CMake 集成提供了文档。
  113. Jan Schöppach 修正了一个拼写错误。
  114. martin-mfg 修正了一个拼写错误。
  115. Matthias Möller 移除了对 std::stringstream 的依赖。
  116. agrianius 添加了使用替代字符串实现的代码。
  117. Daniel599 允许 items() 函数使用更多算法。
  118. Julius Rakow 修复了 Meson 包含目录,并修复了指向 cppreference.com 的链接。
  119. Sonu Lohani 修复了 MSVC 2015 调试模式下的编译问题。
  120. grembo 修复了测试套件并重新启用了多个测试用例。
  121. Hyeon Kim 引入了宏 JSON_INTERNAL_CATCH 来控制库内部的异常处理。
  122. thyu 修复了一个编译器警告。
  123. David Guthrie 修复了 Clang 3.4.2 下的一个细微编译错误。
  124. Dennis Fischer 允许在不安装库的情况下调用 find_package
  125. Hyeon Kim 修复了双重宏定义的问题。
  126. Ben Berman 使一些错误信息更易于理解。
  127. zakalibit 修复了 Intel C++ 编译器的一个编译问题。
  128. mandreyel 修复了一个编译问题。
  129. Kostiantyn Ponomarenko 为 Meson 构建文件添加了版本和许可证信息。
  130. Henry Schreiner 添加了对 GCC 4.8 的支持。
  131. knilch 确保测试套件在错误目录下运行时不会停滞。
  132. Antonio Borondo 修复了一个 MSVC 2017 警告。
  133. Dan Gendreau 实现了 NLOHMANN_JSON_SERIALIZE_ENUM 宏,用于快速定义枚举/JSON 映射。
  134. efp 为解析错误添加了行和列信息。
  135. julian-becker 添加了 BSON 支持。
  136. Pratik Chowdhury 添加了对结构化绑定的支持。
  137. David Avedissian 添加了对 Clang 5.0.1(PS4 版本)的支持。
  138. Jonathan Dumaresq 实现了一个从 FILE* 读取的输入适配器。
  139. kjpus 修复了文档中的一个链接。
  140. Manvendra Singh 修正了文档中的一处拼写错误。
  141. ziggurat29 修复了一个 MSVC 警告。
  142. Sylvain Corlay 添加了避免 MSVC 问题的代码。
  143. mefyl 修复了从输入流解析 JSON 时的一个 bug。
  144. Millian Poquet 允许通过 Meson 安装该库。
  145. Michael Behrns-Miller 发现了缺少命名空间的问题。
  146. Nasztanovics Ferenc 修复了 libc 2.12 下的编译问题。
  147. Andreas Schwab 修复了字节序转换。
  148. Mark-Dunning 修复了 MSVC 中的一个警告。
  149. Gareth Sylvester-Bradley 为 JSON Pointer 添加了 operator/
  150. John-Mark 指出了缺少头文件的问题。
  151. Vitaly Zaitsev 修复了 GCC 9.0 下的编译问题。
  152. Laurent Stacul 修复了 GCC 9.0 下的编译问题。
  153. Ivor Wanders 帮助将 CMake 要求降低到 3.1 版本。
  154. njlr 更新了 Buckaroo 指令。
  155. Lion 修复了 CentOS 上 GCC 7 的编译问题。
  156. Isaac Nickaein 提升了整数序列化性能并实现了 contains() 函数。
  157. past-due 抑制了一个无法修复的警告。
  158. Elvis Oric 改进了 Meson 支持。
  159. Matěj Plch 修复了 README 中的一个示例。
  160. Mark Beckwith 修正了一个拼写错误。
  161. scinart 修复了序列化器中的一个 bug。
  162. Patrick Boettcher 为 JSON Pointer 实现了 push_back()pop_back()
  163. Bruno Oliveira 添加了对 Conda 的支持。
  164. Michele Caini 修复了 README 中的链接。
  165. Hani 记录了如何使用 NuGet 安装该库。
  166. Mark Beckwith 修正了一个拼写错误。
  167. yann-morin-1998 帮助将 CMake 要求降低到 3.1 版本。
  168. Konstantin Podsvirov 维护着 MSYS2 软件发行版的一个包。
  169. remyabel 在 CMake 文件中添加了 GNUInstallDirs。
  170. Taylor Howard 修复了一个单元测试。
  171. Gabe Ron 实现了 to_string 方法。
  172. Watal M. Iwasaki 修复了一个 Clang 警告。
  173. Viktor Kirilov 将单元测试从 Catch 切换到了 doctest
  174. Juncheng E 修正了一个拼写错误。
  175. tete17 修复了 contains 函数中的一个 bug。
  176. Xav83 修复了一些 cppcheck 警告。
  177. 0xflotus 修正了一些拼写错误。
  178. Christian Deneke 添加了 json_pointer::back 的 const 版本。
  179. Julien Hamaide 使 items() 函数适用于自定义字符串类型。
  180. Evan Nemerson 更新修复了 Hedley 中的一个 bug,并相应地更新了本库。
  181. Florian Pigorsch 修正了大量拼写错误。
  182. Camille Bégué 修复了从 std::pairstd::tuple 转换为 json 时的一个问题。
  183. Anthony VH 修复了枚举反序列化中的一个编译错误。
  184. Yuriy Vountesmery 发现了预处理器检查中的一个细微错误。
  185. Chen 修复了库中的众多问题。
  186. Antony Kellermann 为 GCC 10.1 添加了 CI 步骤。
  187. Alex 修复了一个 MSVC 警告。
  188. Rainer 针对 CBOR 中的浮点数序列化提出了改进建议。
  189. Francois Chabot 对输入适配器进行了性能优化。
  190. Arthur Sonzogni 记录了如何通过 FetchContent 引入该库。
  191. Rimas Misevičius 修复了一个错误信息。
  192. Alexander Myasnikov 修复了 README 中的一些示例和链接。
  193. Hubert Chathi 使 CMake 的版本配置文件与架构无关。
  194. OmnipotentEntity 为 CBOR、MessagePack、BSON 和 UBJSON 实现了二进制值的支持。
  195. ArtemSarmini 修复了 GCC 10 下的编译问题,并修复了一个内存泄漏。
  196. Evgenii Sopov 将该库集成到了 wsjcpp 包管理器中。
  197. Sergey Linev 修复了一个编译器警告。
  198. Miguel Magalhães 修正了版权信息中的年份。
  199. Gareth Sylvester-Bradley 修复了一个 MSVC 编译问题。
  200. Alexander "weej" Jones 修复了 README 中的一个示例。
  201. Antoine Cœur 修正了文档中的一些拼写错误。
  202. jothepro 更新了 Hunter 包的链接。
  203. Dave Lee 修复了 README 中的一个链接。
  204. Joël Lamotte 添加了使用 Build2 包管理器的说明。
  205. Paul Jurczak 修复了 README 中的一个示例。
  206. Sonu Lohani 修复了一个警告。
  207. Carlos Gomes Martinho 更新了 Conan 包的来源。
  208. Konstantin Podsvirov 修复了 MSYS2 包的文档。
  209. Tridacnid 改进了 CMake 测试。
  210. Michael 修复了 MSVC 的警告。
  211. Quentin Barbarat 修复了文档中的一个示例。
  212. XyFreak 修复了一个编译器警告。
  213. TotalCaesar659 修复了 README 中的链接。
  214. Tanuj Garg 提高了 UBSAN 输入的模糊测试覆盖率。
  215. AODQ 修复了一个编译器警告。
  216. jwittbrodtNLOHMANN_DEFINE_TYPE_NON_INTRUSIVE 宏设为内联。
  217. pfeatherstone 提高了 NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE/NLOHMANN_DEFINE_TYPE_INTRUSIVE 宏的参数上限。
  218. Jan Procházka 修复了 CBOR 解析器中二进制值和字符串值的 bug。
  219. T0b1-iOS 修复了新哈希实现中的一个 bug。
  220. Matthew Bauer 调整了 CBOR 写入器,使其能为二进制子类型创建标签。
  221. gatopeichnlohmann::ordered_json 实现了有序映射容器。
  222. Érico Nogueira Rolim 添加了对 pkg-config 的支持。
  223. KonanMNLOHMANN_DEFINE_TYPE_NON_INTRUSIVE/NLOHMANN_DEFINE_TYPE_INTRUSIVE 宏提出了一个实现方案。
  224. Guillaume Racicot 实现了 string_view 支持,并允许 C++20 支持。
  225. Alex Reinking 改进了 CMake 对 FetchContent 的支持。
  226. Hannes Domani 提供了一个 GDB 美化打印器。
  227. Lars Wirzenius 审阅了 README 文件。
  228. Jun Jie 修复了 CMake 脚本中的编译器路径。
  229. Ronak Buch 修正了文档中的拼写错误。
  230. Alexander Karzhenkov 修复了移动构造函数和 Travis 构建。
  231. Leonardo Lima 添加了 CPM.Cmake 支持。
  232. Joseph Blackman 修复了一个警告。
  233. Yaroslav 更新了 doctest 并实现了单元测试。
  234. Martin Stump 修复了 CMake 文件中的一个 bug。
  235. Jaakko Moisio 修复了输入适配器中的一个 bug。
  236. bl-ue 修复了 README 文件中的一些 Markdown 问题。
  237. William A. Wieselquist 修复了 README 中的一个示例。
  238. abbaswasim 修复了 README 中的一个示例。
  239. Remy Jette 修复了一个警告。
  240. Fraser 修复了文档。
  241. Ben Beasley 更新了 doctest。
  242. Doron Behar 修复了 pkg-config.pc 文件。
  243. raduteo 修复了一个警告。
  244. David Pfahler 增加了在不支持 I/O 的情况下编译该库的可能性。
  245. Morten Fyhn Amundsen 修正了一个拼写错误。
  246. jpl-mac 允许在 CMake 中将该库视为系统头文件。
  247. Jason Dsouza 修复了 CMake 文件的缩进。
  248. offa 在文档中添加了指向 Conan Center 的链接。
  249. TotalCaesar659 将文档中的链接更新为使用 HTTPS。
  250. Rafail Giavrimis 修复了 Google Benchmark 的默认分支。
  251. Louis Dionne 修复了一个转换运算符。
  252. justanotheranonymoususer 使 README 中的示例更加一致。
  253. Finkman 抑制了一些 -Wfloat-equal 警告。
  254. Ferry Huberts 修复了 -Wswitch-enum 警告。
  255. Arseniy Terekhin 使 GDB 美化打印器在变量名未设置时也能健壮运行。
  256. Amir Masoud Abdol 更新了 Homebrew 命令,因为 nlohmann/json 现已收录于 homebrew-core。
  257. Hallot 修复了一些 -Wextra-semi-stmt 警告。
  258. Giovanni Cerretani 修复了 JSON_DIAGNOSTICS 下的 -Wunused 警告。
  259. Bogdan Popescu 托管了用于离线文档阅读器的 docset
  260. Carl Smedstad 修复了使用 JSON_DIAGNOSTICS 时的一个断言错误。
  261. miikka75 提供了一个重要的修复,使得使用 Clang 9 编译 C++17 代码成为可能。
  262. Maarten Becker 修复了一个关于变量遮蔽的警告。
  263. Cristi Vîjdea 修正了 operator[] 文档中的拼写错误。
  264. Alex Beregszaszi 修正了注释中的拼写错误。
  265. Dirk Stolle 修正了文档中的拼写错误。
  266. Daniel Albuschat 更正了 parse 文档中的参数名。
  267. Prince Mendiratta 修复了一个指向 FAQ 的链接。
  268. Florian Albrechtskirchinger 实现了对对象键的 std::string_view 支持,并进行了数十项其他改进。
  269. Qianqian Fang 实现了二进制 JData (BJData) 格式。
  270. pketelsen 添加了宏 NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULTNLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT
  271. DarkZeros 调整了代码以避免与 Arduino 的宏定义冲突。
  272. flagarde 修复了 meta() 在 MSVC 下的输出问题。
  273. Giovanni Cerretani 修复了一个关于 std::filesystem 的检查。
  274. Dimitris Apostolou 修正了一个拼写错误。
  275. Ferry Huberts 修正了一个拼写错误。
  276. Michael Nosthoff 修正了一个拼写错误。
  277. JungHoon Lee 修正了一个拼写错误。
  278. Faruk D. 修复了 CITATION.CFF 文件。
  279. Andrea Cocito 在文档中添加了关于宏使用的说明。
  280. Krzysiek Karbowiak 重构了测试,使用 CHECK_THROWS_WITH_AS
  281. Chaoqi Zhang 修正了一个拼写错误。
  282. ivanovmp 修正了一个空白字符错误。
  283. KsaNL 修复了包含 <windows.h> 时的构建错误。
  284. Andrea Pappacoda.pc.cmake 文件移动到了 share 目录。
  285. Wolf Vollprecht 添加了 patch_inplace 函数。
  286. Jake Zimmerman 在 README 文件中强调了常见的使用模式。
  287. NN 将 Visual Studio 输出目录添加到了 .gitignore 中。
  288. Romain Reignier 提升了向量输出适配器的性能。
  289. Mike 修复了 std::iterator_traits
  290. Richard Hozák 添加了宏 JSON_NO_ENUM 来禁用默认的枚举转换。
  291. vakokako 修复了使用 C++20 编译时的测试。
  292. Alexander "weej" Jones 修复了 README 中的一个示例。
  293. Eli Schwartzinclude.zip 存档中添加了更多文件。
  294. Kevin Lu 修复了存在特定名称的 typedef 时的编译问题。
  295. Trevor Hickey 改进了一个示例的描述。
  296. Jef LeCompte 更新了 README 文件中的年份。
  297. Alexandre Hamez 修复了一个警告。
  298. Maninderpal Badhan 修正了一个拼写错误。
  299. kevin-- 在 README 文件的一个示例中添加了注释。
  300. I 修正了一个拼写错误。
  301. Gregorio Litenstein 修复了 Clang 检测。
  302. Andreas Smas 添加了 Doozer 徽章。
  303. WanCW 修复了使用 Clang 时的字符串转换问题。
  304. zhaohuaxishi 修复了一个 Doxygen 错误。
  305. emvivre 从 CMake 中移除了一个无效参数。
  306. Tobias Hermann 修复了 README 文件中的一个链接。
  307. Michael 修复了一个警告。
  308. Ryan Mulderdump 函数添加了 ensure_ascii 参数。
  309. Muri Nicanor 修复了 Makefile 中 sed 的发现机制。
  310. David Avedissian 实现了对 SFINAE 友好的 iterator_traits
  311. AQNOUCH Mohammed 修正了 README 中的一个拼写错误。
  312. Gareth Sylvester-Bradley 添加了 operator/=operator/ 来构造 JSON 指针。
  313. Michael Macnair 添加了对 afl-fuzz 测试的支持。
  314. Berkus Decker 修正了 README 中的一个拼写错误。
  315. Illia Polishchuk 改进了 CMake 测试。
  316. Ikko Ashimine 修正了一个拼写错误。
  317. Raphael Grimm 增加了定义自定义基类的可能性。
  318. tocic 修正了文档中的拼写错误。
  319. Vertexwahn 添加了 Bazel 构建支持。
  320. Dirk Stolle 修正了文档中的拼写错误。
  321. DavidKorczynski 添加了 CIFuzz CI GitHub Action。
  322. Finkman 修复了调试美化打印器。
  323. Florian Segginger 更新了 README 中的年份。
  324. haadfida 清理了所用服务的徽章。
  325. Arsen Arsenović 修复了一个构建错误。
  326. theevilone45 修正了 CMake 文件中的一个拼写错误。
  327. Sergei Trofimovich 修复了自定义分配器支持。
  328. Joyce 修复了 GitHub 工作流中的一些安全问题。
  329. Nicolas Jakob 添加了 vcpkg 版本徽章。
  330. Tomerkm 添加了测试。
  331. No. 修复了 get<> 调用的使用问题。
  332. taro 修正了 CODEOWNERS 文件中的一个拼写错误。
  333. Ikko Eltociear Ashimine 修正了一个拼写错误。
  334. Felix Yan 修正了 README 中的一个拼写错误。
  335. HO-COOH 修复了文档中的一个括号问题。
  336. Ivor Wanders 修复了示例,使其通过 const& 捕获异常。
  337. miny1233 修复了文档中的一个括号问题。
  338. tomalakgeretkal 修复了一个编译错误。
  339. alferov 修复了一个编译错误。
  340. Craig Scott 修复了 CMake 中的一个弃用警告。
  341. Vyacheslav Zhdanovskiy 为仅用于序列化的类型添加了宏。
  342. Mathieu Westphal 修正了拼写错误。
  343. scribam 修复了 MinGW 工作流。
  344. Aleksei Sapitskii 添加了对 Apple 的 Swift Package Manager 的支持。
  345. Benjamin Buch 修复了 CMake 中的安装路径。
  346. Colby Haskell 阐明了文件无法打开时的解析错误信息。
  347. Juan Carlos Arevalo Baeza 修复了枚举转换。
  348. alferov 修复了文档中的一个版本号。
  349. ss 修复了合并调用。
  350. AniketDhemare 修复了文档中的一个版本号。
  351. Philip Müller 修复了一个示例。
  352. Leila Shcheglova 修复了测试中的一个警告。
  353. Alex Prabhat Bara 修复了文档中的一个函数名。
  354. laterlaugh 修正了一些拼写错误。
  355. Yuanhao Jia 修复了 GDB 美化打印器。
  356. Fallen_Breath 修复了一个 JSON Pointer 的示例。
  357. Nikhil Idiculla 修正了一些拼写错误。
  358. Griffin Myers 更新了 Natvis 文件。
  359. thetimr 修正了文档中的一个拼写错误。
  360. Balazs Erseki 修复了贡献指南中的一个 URL。
  361. Niccolò Iardella 添加了 NLOHMANN_DEFINE_DERIVED_TYPE_* 宏。
  362. Borislav Stanimirov 允许覆盖 CMake 目标名称。
  363. Captain Crutches 使 iterator_proxy_value 成为 std::forward_iterator
  364. Fredrik Sandheistd::optional 添加了类型转换支持。
  365. jh96 在向 parse 传递 nullptr 时添加了异常抛出。
  366. Stuart Gorman 修复了 errno 中设置了 EINTR 时的数字解析问题。
  367. Dylan Baker 生成了一个符合 pkg-config 约定的 pkg-config 文件。
  368. Tianyi Chen 优化了二进制 get_number 实现。
  369. peng-wang-cn 为多维数组添加了类型转换支持。
  370. Einars Netlis-GalejsNLOHMANN_DEFINE_DERIVED_TYPE_* 宏添加了 ONLY_SERIALIZE 选项。
  371. Marcel 移除了 Bazel 的 alwayslink=True 标志。
  372. Harinath Nampally 为异常添加了诊断位置信息。
  373. Nissim Armand Ben Danan 修复了 NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT 在 JSON 实例为空时的问题。
  374. Michael Valladolid 添加了对 BSON uint64 序列化/反序列化的支持。
  375. Nikhil 更新了文档。
  376. Nebojša Cvetković 添加了对 BJDATA 优化二进制数组类型的支持。
  377. Sushrut Shringarputale 添加了对诊断位置的支持。
  378. kimci86NLOHMANN_DEFINE_TYPE 宏模板化,使其也支持 ordered_json
  379. Richard Topchii 在 Swift Package Manager 中添加了对 VisionOS 的支持。
  380. Robert Chisholm 修正了一个拼写错误。
  381. zjyhjqs 添加了 CPack 支持。
  382. bitFiedler 使 GDB 美化打印器兼容 Python 3.8。
  383. Gianfranco Costamagna 修复了一个编译器警告。
  384. risa2000std::filesystem::path 与 UTF-8 编码字符串之间的转换变得明确。

非常感谢各位的帮助!如果遗漏了谁,请告诉我

使用的第三方工具

本库本身仅由一个采用 MIT 许可证授权的头文件组成。然而,其构建、测试、文档编写等环节均依赖于众多第三方工具与服务。在此致以诚挚感谢!

说明

标准符合性

本库致力于严格遵循 RFC 8259 标准。原始版 JSONTestSuite 及其修订版均在持续集成(CI)中运行测试;其测试数据在配置阶段从 nlohmann/json_test_data 下载,而非直接提交到本仓库(参见 tests/src/unit-testsuites.cpp):

  • 修订版通过严格的 parse() 入口运行所有必须接受(y_)和必须拒绝(n_)的用例;原始版则通过 parse() 运行其 n_ 用例,并通过 operator>> 运行其 y_ 用例。
  • 对于实现定义(i_)的用例,依据 RFC 8259 的规定,接受拒绝均可,因此“全部通过 i_ 用例”并非有意义的合规性衡量标准。本库在这些用例上做出了审慎且有文档记录的选择:不人为限制嵌套深度;对开头的 UTF-8 字节序标记静默忽略;Unicode 非字符 原样透传;无效的 UTF-8 及孤立/未配对的 UTF-16 代理项将被拒绝(比标准要求更为严格);无法在不产生 NaN/INF 的情况下存储的数字会抛出 out_of_range.406 异常。

有一个行为细节值得特别说明,因为浅显的测试往往会将其误判为不合规:parse() 是严格的,会拒绝一个值之后的尾随数据;而 operator>> 遵循宽松的 iostream 语义——它解析单个值,并将流位置保留在该值之后。将“有效文档后跟尾随字节”的输入通过 operator>> 处理会报告成功;同样的输入通过 parse() 处理则会被拒绝。这是一种有文档记录的双 API 设计,并非合规性缺陷。详见 解析

字符编码

本库支持Unicode 输入,具体如下:

  • 仅支持 UTF-8 编码的输入,这是 RFC 8259 规定的 JSON 默认编码。
  • 可以解析 std::u16stringstd::u32string,分别假定为 UTF-16 和 UTF-32 编码。从文件或其他输入容器读取时,不支持这些编码。
  • 其他编码(如 Latin-1 或 ISO 8859-1)受支持,会产生解析或序列化错误。
  • Unicode 非字符 不会被库替换。
  • 无效的代理项(例如不完整的配对,如 \uDEAD)会产生解析错误。
  • 库中存储的字符串是 UTF-8 编码的。使用默认字符串类型(std::string)时,请注意其 length/size 函数返回的是存储的字节数,而不是字符数或字形数。
  • 当你在库中存储不同编码的字符串时,调用 dump() 可能会抛出异常,除非使用 json::error_handler_t::replacejson::error_handler_t::ignore 作为错误处理程序。
  • 要存储宽字符串(例如 std::wstring),需要先将其转换为 UTF-8 编码的 std::string,参见示例

JSON 中的注释

本库默认不支持注释,原因有三:

  1. 注释不属于 JSON 规范 的一部分。你可能会说 ///* */ 在 JavaScript 中是允许的,但 JSON 不是 JavaScript。

  2. 这并非疏忽:Douglas Crockford 在 2012 年 5 月就此写过

    我从 JSON 中移除了注释,因为我看到人们用它们来存放解析指令,这种做法会破坏互操作性。我知道缺少注释会让一些人感到遗憾,但这不应该。

    假设你使用 JSON 来保存配置文件,并希望添加注释。尽管插入你喜欢的注释吧,然后在交给 JSON 解析器之前,先用 JSMin 处理一下。

  3. 如果一些库添加注释支持而另一些不支持,会对互操作性造成危害。请参阅稳健性原则的有害后果

不过,你可以将 parse 函数中的参数 ignore_comments 设置为 true,以忽略 ///* */ 注释。注释将被视为空白字符。

尾随逗号

JSON 规范不允许在数组和对象中使用尾随逗号,因此本库默认将其视为解析错误。

与注释类似,您可以在 parse 函数中将参数 ignore_trailing_commas 设置为 true,以忽略数组和对象中的尾随逗号。请注意,数组或对象中仅包含单个逗号([,]{,})是不允许的,多个尾随逗号(如 [1,,])同样不允许。

本库在序列化 JSON 数据时不会添加尾随逗号。

更多信息,请参阅 JSON With Commas and Comments (JWCC)

对象键的顺序

默认情况下,本库不保留对象元素的插入顺序。这符合标准要求,因为 JSON 标准 将对象定义为“零个或多个名称/值对的无序集合”。

如果您确实希望保留插入顺序,可以尝试使用 nlohmann::ordered_json 类型。或者,您也可以使用更复杂的有序映射,例如 tsl::ordered_map集成方法)或 nlohmann::fifo_map集成方法)。

更多信息请参阅 关于对象顺序的文档

内存释放

我们使用 Valgrind 和 Address Sanitizer (ASAN) 进行了检查,确认不存在内存泄漏。

如果您发现使用本库的解析程序未释放内存,请考虑以下情况,这可能与本库无关。

您的程序是使用 glibc 编译的。 glibc 使用一个可调阈值来决定是将内存实际归还给系统,还是将其缓存以供后续重用。如果您的程序中进行了大量小规模内存分配,并且这些分配不是连续的内存块,且可能低于该阈值,那么它们将不会被归还给操作系统。 相关 issue 请参见 #1924

补充说明

  • 代码中包含大量调试断言,可通过定义预处理宏 NDEBUG 将其关闭,参见 assert 的文档。特别需要注意的是,operator[] 对 const 对象实现的是未检查访问:如果给定的键不存在,其行为是未定义的(可以想象为解引用空指针),并且在断言开启时会触发断言失败。如果您不确定对象中的某个元素是否存在,请使用带有检查的访问方式,即 at() 函数。此外,您还可以定义 JSON_ASSERT(x) 来替代对 assert(x) 的调用。更多信息请参阅运行时断言的文档
  • 由于 JSON 规范中未定义具体的数字类型,本库会尝试自动选择最合适的 C++ 数字类型。因此,在某些罕见情况下,double 类型可能被用来存储数字,如果调用代码中已取消屏蔽浮点异常,则可能触发浮点异常。这些异常并非由本库引起,需要在调用代码中修复,例如在调用库函数之前重新屏蔽这些异常。
  • 代码可以在没有 C++ 运行时类型识别功能的情况下编译;也就是说,您可以使用 -fno-rtti 编译器标志。
  • 库中广泛使用了异常。不过,您可以通过使用编译器标志 -fno-exceptions 或定义符号 JSON_NOEXCEPTION 来关闭异常功能。在这种情况下,异常将被替换为 abort() 调用。您还可以通过定义 JSON_THROW_USER(覆盖 throw)、JSON_TRY_USER(覆盖 try)和 JSON_CATCH_USER(覆盖 catch)来进一步控制该行为。请注意,JSON_THROW_USER 应离开当前作用域(例如通过抛出异常或调用 abort()),因为在其之后继续执行可能导致未定义行为。另外需要注意的是,如果禁用了异常,MSVC 编译器将无法提供异常的说明性 what() 字符串,参见 #2824。更多信息请参阅异常的文档

执行单元测试

若要编译并运行测试,你需要执行以下命令:

mkdir build
cd build
cmake .. -DJSON_BuildTests=On
cmake --build .
ctest --output-on-failure

请注意,在 ctest 阶段,会有多个 JSON 测试文件从外部仓库下载。如果测试期间的政策不允许下载工件,您可以自行下载这些文件,并通过 -DJSON_TestDataDirectory=path 参数将包含测试文件的目录传递给 CMake。这样便无需网络连接。更多信息请参见 issue #2189

如果找不到测试数据,多个测试套件将会失败,如下所示:

===============================================================================
json/tests/src/make_test_data_available.hpp:21:
TEST CASE:  check test suite is downloaded

json/tests/src/make_test_data_available.hpp:23: FATAL ERROR: REQUIRE( utils::check_testsuite_downloaded() ) is NOT correct!
  values: REQUIRE( false )
  logged: Test data not found in 'json/cmake-build-debug/json_test_data'.
          Please execute target 'download_test_data' before running this test suite.
          See <https://github.com/nlohmann/json#execute-unit-tests> for more information.

===============================================================================

若您是通过下载库文件而非通过 Git 检出代码,则 cmake_fetch_content_configure 测试将会失败。请执行 ctest -LE git_required 以跳过这些测试。更多信息请参见 issue #2189

部分测试需要联网才能正常执行,这些测试被标记为 git_required。请执行 ctest -LE git_required 以跳过这些测试。更多信息请参见 issue #4851

部分测试会修改已安装的文件,从而导致整个构建过程无法复现。请执行 ctest -LE not_reproducible 以跳过这些测试。更多信息请参见 issue #2324。此外,必须关闭断言以确保构建的可复现性(参见 discussion 4494)。

请注意,您需要调用 cmake -LE "not_reproducible|git_required" 来同时排除这两类标签。更多信息请参见 issue #2596

由于 Intel 编译器默认启用不安全的浮点优化,单元测试可能会失败。此时请使用 /fp:precise 标志。