首页 前端知识 nlohmann/json的介绍和使用详解

nlohmann/json的介绍和使用详解

2024-11-02 11:11:09 前端知识 前端哥 967 157 我要收藏

  • 设计目标
  • 例子
    • 从文件中读取JSON
    • 从JSON文字创建json对象
    • JSON作为一流的数据类型
    • 序列化/反序列化
    • 类STL访问
    • 从STL容器转换
    • JSON指针和JSON补丁
    • JSON合并补丁
    • 隐式转换
    • 与任意类型的转换
    • 专门枚举转换
    • 二进制格式(BSON、CBOR、MessagePack、UBJSON和BJData)
  • 支持的编译器
  • 整合
    • CMake
    • 包管理器
    • pkg配置
  • 使用JSON进行现代C++的项目
  • 笔记
  • 执行单元测试

设计目标

有无数的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库。然而,如果你的目标是通过添加单个头文件来加快你的开发进度并添加JSON支持,那么这个库就是正确的选择。如果你知道如何使用std::vector或std::map,那么你就已经准备好了。

    有关更多信息,请查看贡献指南。

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

例子

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

除了下面的示例外,您可能还希望:

→检查留档
→浏览独立示例文件

每个API函数(记录在API文档中)都有一个相应的独立示例文件。例如,emplace()函数有一个匹配的emplace. cpp示例文件。

从文件中读取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);

从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 that is stored as double (note the implicit conversion of j to an object)
j["pi"] = 3.141;

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

// add a string that is stored as std::string
j["name"] = "Niels";

// add another null object by passing nullptr
j["nothing"] = nullptr;

// add an object inside the object
j["answer"]["everything"] = 42;

// add an array that is stored as std::vector (using an initializer list)
j["list"] = { 1, 0, 2 };

// add another object (using an initializer list of pairs)
j["object"] = { {"currency", "USD"}, {"value", 42.99} };

// instead, you could also write (which looks very similar to the JSON above)
json j2 = {
  {"pi", 3.141},
  {"happy", true},
  {"name", "Niels"},
  {"nothing", nullptr},
  {"answer", {
    {"everything", 42}
  }},
  {"list", {1, 0, 2}},
  {"object", {
    {"currency", "USD"},
    {"value", 42.99}
  }}
};

注意,在所有这些情况下,您都不需要“告诉”编译器您想使用哪种JSON值类型。如果您想明确或表达一些边缘情况,函数json::array()和json::object()会有所帮助:

// a way to express the empty array []
json empty_array_explicit = json::array();

// ways to express the empty object {}
json empty_object_implicit = json({});
json empty_object_explicit = json::object();

// a way to express an _array_ of key/value pairs [["currency", "USD"], ["value", 42.99]]
json array_not_object = json::array({ {"currency", "USD"}, {"value", 42.99} });

序列化/反序列化

To/from strings

您可以通过在字符串字面量后加上_json来创建一个JSON值(反序列化):

// create object from string literal
json j = "{ \"happy\": true, \"pi\": 3.141 }"_json;

// or even nicer with a raw string literal
auto j2 = R"(
  {
    "happy": true,
    "pi": 3.141
  }
)"_json;

请注意,在不附加_json后缀的情况下,传递的字符串文字不会被解析,而只是用作JSON字符串 值。也就是说,json j = "{ \"happy\": true, \"pi\": 3.141 }"将只存储字符串 "{ "happy": true, "pi": 3.141 }"而不是解析实际对象。

为了引入字符串字面量,需要使用命名空间 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.template 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.template get<std::string>() << '\n';
// output of serialized value
std::cout << j_string << " == " << serialized_string << std::endl;

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

注意,该库仅支持UTF-8编码。当您在库中存储不同编码的字符串时,除非使用 json::error_handler_t::replace 或 json::error_handler_t::ignore 作为错误处理器,否则调用 dump() 可能会抛出异常。

To/from streams(例如文件、字符串流)

您还可以使用流来序列化和反序列化:

// deserialize from standard input
json j;
std::cin >> j;

// serialize to standard output
std::cout << j;

// the setw manipulator was overloaded to set the indentation for pretty printing
std::cout << std::setw(4) << j << std::endl;

这些运算符适用于 std::istream 或 std::ostream 的任何子类。

// read a JSON file
std::ifstream i("file.json");
json j;
i >> j;

// write prettified JSON to another file
std::ofstream o("pretty.json");
o << std::setw(4) << j << std::endl;

请注意,为failbit设置异常位在此用例中是不合适的。由于使用的noexcept规范,这将导致程序终止。

从迭代器范围读取

您还可以从迭代器范围内解析JSON;也就是说,从任何通过迭代器访问的容器中读取,其值类型是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());

您可以将迭代器留给范围[开始,结束):

std::vector<std::uint8_t> v = {'t', 'r', 'u', 'e'};
json j = json::parse(v);
自定义数据源

由于 parse 函数接受任意迭代器范围,您可以通过实现 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;

    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 {};
}

void foo() {
    MyContainer c;
    json j = json::parse(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作为基类,但也可以使用任何实现了上述描述的函数并且是公共类的类。
  2. 创建一个您的SAX接口类的实例,例如my_sax。
  3. 调用bool json::sax_parse(input, &my_sax);其中第一个参数可以是任何输入,如字符串或输入流,第二个参数是指向您的SAX接口的指针。

注意,sax_parse函数只返回一个布尔值,表示最后一个执行的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].template 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::array、std::vector、std::deque、std::forward_list、std::list),其值可以用来构建JSON值(例如,整数、浮点数、布尔值、字符串类型或再次在本节中描述的STL容器)都可以用于创建JSON数组。类似地,对于关联容器(std::set、std::multiset、std::unordered_set、std::unordered_multiset),情况也是如此,但这些情况下数组元素的顺序取决于相应STL容器中元素的排序方式。

std::vector<int> c_vector {1, 2, 3, 4};
json j_vec(c_vector);
// [1, 2, 3, 4]

std::deque<double> c_deque {1.2, 2.3, 3.4, 5.6};
json j_deque(c_deque);
// [1.2, 2.3, 3.4, 5.6]

std::list<bool> c_list {true, true, false, true};
json j_list(c_list);
// [true, true, false, true]

std::forward_list<int64_t> c_flist {12345678909876, 23456789098765, 34567890987654, 45678909876543};
json j_flist(c_flist);
// [12345678909876, 23456789098765, 34567890987654, 45678909876543]

std::array<unsigned long, 4> c_array {{1, 2, 3, 4}};
json j_array(c_array);
// [1, 2, 3, 4]

std::set<std::string> c_set {"one", "two", "three", "four", "one"};
json j_set(c_set); // only one entry for "one" is used
// ["four", "one", "three", "two"]

std::unordered_set<std::string> c_uset {"one", "two", "three", "four", "one"};
json j_uset(c_uset); // only one entry for "one" is used
// maybe ["two", "three", "four", "one"]

std::multiset<std::string> c_mset {"one", "two", "one", "four"};
json j_mset(c_mset); // both entries for "one" are used
// maybe ["one", "two", "one", "four"]

std::unordered_multiset<std::string> c_umset {"one", "two", "one", "four"};
json j_umset(c_umset); // both entries for "one" are used
// maybe ["one", "two", "one", "four"]

同样地,任何关联键值容器(std::map、std::multimap、std::unordered_map、std::unordered_multimap),其键可以构造一个std::string,且其值可以用来构建JSON值(见上文示例),都可以用于创建JSON对象。请注意,在多映射的情况下,JSON对象中只使用了一个键,而值取决于STL容器的内部排序。

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

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

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

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

JSON指针和JSON补丁

该库支持JSON指针(RFC 6901)作为处理结构化值的替代方法。除此之外,JSON补丁(RFC 6902)允许描述两个JSON值之间的差异 - 有效地允许进行从Unix中已知的补丁和差异操作。

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

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

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

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

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

JSON合并补丁

该库支持JSON Merge Patch(RFC 7386)作为补丁格式。与其使用JSON指针(见上文)来指定要操作的值,它使用一种与被修改文档非常相似的语法来描述更改。

// a JSON value
json j_document = R"({
  "a": "b",
  "c": {
    "d": "e",
    "f": "g"
  }
})"_json;

// a patch
json j_patch = R"({
  "a":"z",
  "c": {
    "f": null
  }
})"_json;

// apply the patch
j_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.template get<std::string>();
// NOT RECOMMENDED
std::string s3 = js;
std::string s4;
s4 = js;

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

// numbers
int i = 42;
json jn = i;
auto f = jn.template 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"].template get<std::string>(),
    j["address"].template get<std::string>(),
    j["age"].template 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.template 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命名空间中定义)。
  • 这些方法必须在你使用这些转换的任何地方都是可用的(例如,必须包含正确的头文件)。查看问题1108以了解可能发生的错误。
  • 当使用template get<your_type>()时,your_type 必须是DefaultConstructible的。
  • 在from_json函数中,使用at()函数来访问对象值而不是operator[]。如果一个键不存在,at会抛出一个异常,你可以处理它,而operator[]表现出未定义的行为。
  • 你不需要为STL类型如std::vector添加序列化器或反序列化器:库已经实现了这些。
使用宏简化您的生活

如果你只是想序列化/反序列化一些结构体,to_json/from_json函数可能会有很多样板代码。

有两个宏可以让你的生活更轻松,只要你(1)想要使用JSON对象作为序列化和(2)想要在该对象中使用成员变量名称作为对象键:

  • NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(name, member1, member2, ...) 是在类/结构的命名空间内部定义的,用于创建代码。
  • NLOHMANN_DEFINE_TYPE_INTRUSIVE(name, member1, member2, ...) 是在类/结构内部定义的,用于创建代码。这个宏也可以访问私有成员。

在这两个宏中,第一个参数是类/结构的名称,所有剩余的参数都命名了成员。

例子

上面person结构体的to_json/from_json函数可以用以下方式创建:

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

下面是需要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序列化器将类型转换为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.template 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.template 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.template get<TaskState>() == TS_RUNNING);

// undefined json value to enum (where the first map entry above is the default)
json jPi = 3.14;
assert(jPi.template get<TaskState>() == TS_INVALID );

就像上面的任意类型转换一样,

  • NLOHMANN_JSON_SERIALIZE_ENUM() 必须在你的枚举类型的命名空间(可以是全局命名空间)中声明,否则库将无法找到它,它将默认为整数序列化。
  • 它必须在你使用这些转换的任何地方都是可用的(例如,正确的头文件必须被包含)。

其他要点:

  • 当使用模板get<ENUM_TYPE>()时,未定义的JSON值将默认为你映射中的第一对指定值。仔细选择这个默认值。
  • 如果在你的映射中指定了一个枚举或JSON值多次,那么在转换为JSON或从JSON转换为数据时,将返回映射顶部第一个匹配的出现。

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

尽管JSON是一种无处不在的数据格式,但它并不是一种适合数据交换的非常紧凑的格式,例如通过网络。因此,该库支持BSON(二进制JSON)、CBOR(简明二进制对象表示)、MessagePack、UBJSON(通用二进制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

支持的编译器

尽管已经是2023年了,但C++11的支持仍然有些稀少。目前已知以下编译器可以工作:

  • GCC 4.8-12.0(可能更高版本)
  • Clang 3.4-15.0(可能更高版本)
  • Apple Clang 9.1-13.1(可能更高版本)
  • 英特尔C++编译器17.0.2(可能更高版本)
  • Nvidia CUDA编译器11.0.221(可能更高版本)
  • Microsoft VisualC++2015/Build Tools14.0.25123.0(可能更高版本)
  • Microsoft VisualC++2017/Build Tools15.5.180.51428(可能更高版本)
  • Microsoft VisualC++2019/构建工具16.3.1+1def00d3d(可能更高版本)
  • Microsoft VisualC++2022/构建工具19.30.30709.0(可能更高版本)

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

请注意:

  • 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
    

    该代码编译成功与安卓NDK,修订版9-11(可能更高)和CrystaX的安卓NDK版本10。

  • 对于运行在MinGW或Android SDK上的GCC,可能会出现错误'to_string'不是std的成员(或者类似地,对于strtod或strtof),注意这不是代码的问题,而是编译器本身的问题。在Android上,参考上面的信息以使用更新的环境进行构建。对于MinGW,请参考这个网站和这个讨论来获取如何修复这个bug的信息。对于使用APP_STL := gnustl_static的Android NDK,请参考这个讨论。

  • 不支持的GCC和Clang版本会被#error指令拒绝。这可以通过定义JSON_SKIP_UNSUPPORTED_COMPILER_CHECK来关闭。请注意,在这种情况下你可能会期望没有支持。

以下编译器目前在AppVeyor、Cirrus CI和GitHub Actions的持续集成中使用:

CompilerOperating SystemCI Provider
Apple Clang 11.0.3 (clang-1103.0.32.62); Xcode 11.7macOS 11.7.1GitHub Actions
Apple Clang 12.0.0 (clang-1200.0.32.29); Xcode 12.4macOS 11.7.1GitHub Actions
Apple Clang 12.0.5 (clang-1205.0.22.11); Xcode 12.5.1macOS 11.7.1GitHub Actions
Apple Clang 13.0.0 (clang-1300.0.29.3); Xcode 13.0macOS 11.7.1GitHub Actions
Apple Clang 13.0.0 (clang-1300.0.29.3); Xcode 13.1macOS 12.6.1GitHub Actions
Apple Clang 13.0.0 (clang-1300.0.29.30); Xcode 13.2.1macOS 12.6.1GitHub Actions
Apple Clang 13.1.6 (clang-1316.0.21.2.3); Xcode 13.3.1macOS 12.6.1GitHub Actions
Apple Clang 13.1.6 (clang-1316.0.21.2.5); Xcode 13.4.1macOS 12.6.1GitHub Actions
Apple Clang 14.0.0 (clang-1400.0.29.102); Xcode 14.0macOS 12.6.1GitHub Actions
Apple Clang 14.0.0 (clang-1400.0.29.102); Xcode 14.0.1macOS 12.6.1GitHub Actions
Apple Clang 14.0.0 (clang-1400.0.29.202); Xcode 14.1macOS 12.6.1GitHub Actions
Clang 3.5.2Ubuntu 20.04.3 LTSGitHub Actions
Clang 3.6.2Ubuntu 20.04.3 LTSGitHub Actions
Clang 3.7.1Ubuntu 20.04.3 LTSGitHub Actions
Clang 3.8.1Ubuntu 20.04.3 LTSGitHub Actions
Clang 3.9.1Ubuntu 20.04.3 LTSGitHub Actions
Clang 4.0.1Ubuntu 20.04.3 LTSGitHub Actions
Clang 5.0.2Ubuntu 20.04.3 LTSGitHub Actions
Clang 6.0.1Ubuntu 20.04.3 LTSGitHub Actions
Clang 7.0.1Ubuntu 20.04.3 LTSGitHub Actions
Clang 8.0.0Ubuntu 20.04.3 LTSGitHub Actions
Clang 9.0.0Ubuntu 20.04.3 LTSGitHub Actions
Clang 10.0.0Ubuntu 20.04.3 LTSGitHub Actions
Clang 10.0.0 with GNU-like command-lineWindows-10.0.17763GitHub Actions
Clang 11.0.0 with GNU-like command-lineWindows-10.0.17763GitHub Actions
Clang 11.0.0 with MSVC-like command-lineWindows-10.0.17763GitHub Actions
Clang 11.0.0Ubuntu 20.04.3 LTSGitHub Actions
Clang 12.0.0Ubuntu 20.04.3 LTSGitHub Actions
Clang 12.0.0 with GNU-like command-lineWindows-10.0.17763GitHub Actions
Clang 13.0.0Ubuntu 20.04.3 LTSGitHub Actions
Clang 13.0.0 with GNU-like command-lineWindows-10.0.17763GitHub Actions
Clang 14.0.0Ubuntu 20.04.3 LTSGitHub Actions
Clang 14.0.0 with GNU-like command-lineWindows-10.0.17763GitHub Actions
Clang 15.0.0 with GNU-like command-lineWindows-10.0.17763GitHub Actions
Clang 15.0.4Ubuntu 20.04.3 LTSGitHub Actions
Clang 16.0.0 (16.0.0-++20221031071727+500876226c60-1exp120221031071831.439)Ubuntu 20.04.3 LTSGitHub Actions
GCC 4.8.5 (Ubuntu 4.8.5-4ubuntu2)Ubuntu 20.04.3 LTSGitHub Actions
GCC 4.9.4Ubuntu 20.04.3 LTSGitHub Actions
GCC 5.5.0Ubuntu 20.04.3 LTSGitHub Actions
GCC 6.5.0Ubuntu 20.04.3 LTSGitHub Actions
GCC 7.5.0Ubuntu 20.04.3 LTSGitHub Actions
GCC 8.1.0 (i686-posix-dwarf-rev0, Built by MinGW-W64 project)Windows-10.0.17763GitHub Actions
GCC 8.1.0 (x86_64-posix-seh-rev0, Built by MinGW-W64 project)Windows-10.0.17763GitHub Actions
GCC 8.5.0Ubuntu 20.04.3 LTSGitHub Actions
GCC 9.5.0Ubuntu 20.04.3 LTSGitHub Actions
GCC 10.4.0Ubuntu 20.04.3 LTSGitHub Actions
GCC 11.1.0Ubuntu (aarch64)Cirrus CI
GCC 11.3.0Ubuntu 20.04.3 LTSGitHub Actions
GCC 12.2.0Ubuntu 20.04.3 LTSGitHub Actions
GCC 13.0.0 20220605 (experimental)Ubuntu 20.04.3 LTSGitHub Actions
Intel C++ Compiler 2021.5.0.20211109Ubuntu 20.04.3 LTSGitHub Actions
NVCC 11.0.221Ubuntu 20.04.3 LTSGitHub Actions
Visual Studio 14 2015 MSVC 19.0.24241.7 (Build Engine version 14.0.25420.1)Windows-6.3.9600AppVeyor
Visual Studio 15 2017 MSVC 19.16.27035.0 (Build Engine version 15.9.21+g9802d43bc3 for .NET Framework)Windows-10.0.14393AppVeyor
Visual Studio 16 2019 MSVC 19.28.29912.0 (Build Engine version 16.9.0+57a23d249 for .NET Framework)Windows-10.0.17763GitHub Actions
Visual Studio 16 2019 MSVC 19.28.29912.0 (Build Engine version 16.9.0+57a23d249 for .NET Framework)Windows-10.0.17763AppVeyor
Visual Studio 17 2022 MSVC 19.30.30709.0 (Build Engine version 17.0.31804.368 for .NET Framework)Windows-10.0.20348GitHub Actions

集成

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,可以实现 json_fwd.hpp 的安装(作为 cmake 安装步骤的一部分)。

CMake

您还可以在 CMake 中使用 nlohmann_json::nlohmann_json 接口目标。此目标为 INTERFACE_INCLUDE_DIRECTORIES 指向适当的包含目录和 INTERFACE_COMPILE_FEATURES 提供必要的 C++11 标志填充适当的使用要求。

外部

要从 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)
嵌入式(FetchContent)

自CMake v3.11以来, 获取内容可以 用于在配置时自动下载版本作为依赖项。

示例:

include(FetchContent)

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

target_link_libraries(foo PRIVATE nlohmann_json::nlohmann_json)

注意:建议使用上述描述的URL方法,该方法自3.10.0版本起得到支持。见 CMake - JSON for Modern C++获取更多信息。

同时支持

为了让您的项目能够支持外部提供的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 install nlohmann-json 即可。如果您希望使用非最新版本,而是使用最新的发布版本,可以使用 brew install nlohmann-json --HEAD。有关详细信息,请参阅nlohmann-json。

如果您使用的是Meson Build System,请将此源代码树添加为meson子项目。您也可以使用本项目 Releases 中发布的 include.zip 来减小供应商源代码树的大小。或者,您可以从 Meson WrapDB 下载 wrap 文件,或者简单地使用 meson wrap install nlohmann_json。有关包装的任何问题,请参阅 meson 项目。

提供的 meson.build 也可以用作为 CMake 安装 nlohmann_json 系统范围内的替代方案,在这种情况下会安装一个 pkg-config 文件。要使用它,只需让您的构建系统需要依赖 nlohmann_json pkg-config 依赖项。在 Meson 中,建议使用带有子项目回退的 dependency() 对象,而不是直接使用子项目。

如果您使用的是 Bazel,可以直接引用此存储库并依赖 @nlohmann_json//:json。

如果您使用Conan来管理您的依赖项,只需将nlohmann_json/x.y.z添加到您的conanfile要求中,其中x.y.z是您要使用的发布版本。如果您遇到软件包问题,请在此提交问题。

如果您使用Spack来管理您的依赖项,您可以使用nlohmann-json包。有关包装的任何问题,请参阅spack项目。

如果您的项目正在使用 hunter 处理外部依赖项,那么您可以使用 nlohmann_json 包。有关包装的任何问题,请参阅 hunter 项目。

如果您的项目正在使用 Buckaroo,可以通过buckaroo add github.com/buckaroo-pm/nlohmann-json 安装此库的模块。如果遇到问题,请在这里报告。这里有一个例子仓库。

如果您的项目正在使用 vcpkg 处理外部依赖项,那么可以通过执行命令 vcpkg install nlohmann-json 安装 nlohmann-json 包,然后按照显示的描述进行操作。有关包装的任何问题,请参阅 vcpkg 项目。

如果您正在使用 cget,可以通过 cget install nlohmann/json 安装最新开发版本。特定版本可以通过 cget install nlohmann/json@v3.1.0 安装。此外,多header版本可以通过添加 -DJSON_MultipleHeaders=ON flag(即 cget install nlohmann/json -DJSON_MultipleHeaders=ON)来安装。

如果您使用CocoaPods,

可以通过在 podfile(参见示例)中添加 pod "nlohmann_json", '~>3.1.2' 来使用该库。如果遇到问题,请在这里报告。

如果您正在使用 Swift Package Manager,可以通过向此存储库添加包依赖项来使用该库,并将目标依赖项标记为 .product(name: "nlohmann-json", package: "json")

如果您正在使用 NuGet,可以使用名为 nlohmann.json 的包。有关如何使用该包的详细说明,请查看这里。如果遇到问题,请在这里报告。

如果您正在使用conda,可以通过执行命令 conda install -c conda-forge nlohmann_json 来使用来自conda-forge的nlohmann_json包。如果遇到问题,请在这里报告。

如果您正在使用 MSYS2,可以通过输入命令 pacman -S mingw-w64-i686-nlohmann-json 或 pacman -S mingw-w64-x86_64-nlohmann-json 来安装 nlohmann-json package。如果遇到问题,请在这里报告。

如果您正在使用 MacPorts,通过执行命令 sudo port install nlohmann-json 可以安装

nlohmann-json包。

如果您使用的是build2,您可以从公共存储库https://cppget.org或直接从包的源存储库使用nlohmann-json包。在项目的清单文件中,只需添加 depends: nlohmann-json(可能还包含一些版本约束)。如果您不熟悉在 build2中使用依赖项的方法,请阅读这篇介绍性文章。如果遇到问题,请在此处提交问题。

如果您使用的是wsjcpp,您可以使用命令wsjcpp install "https://github.com/nlohmann/json:develop"来获取最新版本。请注意,您可以将分支":Development"更改为现有标记或其他分支。

如果您使用的是CPM.cmake,您可以检查此example。将CPM脚本添加到项目后,将以下代码片段实现到CMake中:

CPMAddPackage(
    NAME nlohmann_json
    GITHUB_REPOSITORY nlohmann/json
    VERSION 3.9.1)

pkg-config

如果您使用的是Makefile,您可以使用pkg-config生成指向库安装位置的包含标志:

pkg-config nlohmann_json --cflags

Meson构建系统的用户也可以使用系统范围内的库,该库将由pkg-config找到:

json = dependency('nlohmann_json', required: true)

JSON库在现代C++中的项目使用 

该库目前被用于苹果macOS Sierra-Monterey和iOS 10-15。我不确定他们使用该库的目的是什么,但我很高兴它能在这么多设备上运行。

注释

字符编码

该库支持以下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作为错误处理器。
  • 要存储宽字符串(例如,std::wstring),你需要在之前将其转换为UTF-8编码的std::string,参见示例。

JSON中的注释

该库默认不支持注释。原因如下:

  1. 注释不是JSON规范的一部分。您可能会争辩说JavaScript允许///* */,但JSON不是JavaScript。

  2. 这不是疏忽:Douglas Crockford在2012年5月写道:

    我从JSON中删除了注释,因为我看到人们使用它们来持有解析指令,这种做法会破坏互操作性。我知道缺乏注释让一些人感到难过,但这不应该如此。

    如果你正在使用JSON来保存配置文件,并且你想要添加注释。那就尽情插入所有你喜欢的注释吧。然后通过JSMin处理后再交给你的JSON解析器。

  3. 如果某些库添加了注释支持而其他库没有,这对互操作性是危险的。请查看健壮性原则的有害后果。

然而,你可以在解析函数中传递参数ignore_comments设置为true来忽略//或/* */注释。此时,注释将被当作空白处理。

对象键的顺序

默认情况下,该库不保留对象的插入顺序。这是符合标准的,因为JSON标准定义对象为“一个无序的名称/值对集合”。

如果您想保留插入顺序,您可以尝试nlohmann::ordered_json类型。或者,您可以使用更复杂的有序映射,如tsl::ordered_map(集成)或nlohmann::fifo_mapfifo_map集成)。

内存释放

我们与Valgrind和Address Sanitizer (ASAN)一起检查过,没有内存泄漏。

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

你的程序是用glibc编译的。存在一个可调整的阈值,glibc用来决定是否真正将内存归还给系统或缓存它以供后续重用。如果你的程序中有很多小分配,且这些小分配不是一个连续的块并且假定低于阈值,那么它们不会被归还给操作系统。这里有相关问题 #1924。

进一步说明

  • 代码包含大量调试断言,可以通过定义预处理器宏NDEBUG来关闭,参见assert.h的文档。特别是要注意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下禁用了异常处理机制的话,异常的错误消息是不提供的,参见#2824。

执行单元测试

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

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

请注意,在ctest阶段,从外部存储库下载了几个JSON测试文件。如果策略禁止在测试期间下载工件,您可以自己下载这些文件并将包含测试文件的目录通过-DJSON_TestDataDirectory=path传递给CMake。这样就不需要互联网连接了。有关更多信息,请参见问题#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克隆代码,test cmake_fetch_content_configure将失败。请执行ctest -LE git_required以跳过这些测试。有关更多信息,请参阅问题#2189。

某些测试更改安装的文件,因此整个过程无法重现。请执行ctest -LE not_reproducible以跳过这些测试。有关更多信息,请参阅问题#2324。

注意,您需要调用cmake -LE "not_reproducible|git_required"来排除这两个标签。有关更多信息,请参阅问题#2596。

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

转载请注明出处或者链接地址:https://www.qianduange.cn//article/19837.html
标签
评论
发布的文章
大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!