news 2026/9/30 6:46:30

DB Browser for SQLite 内置的 nlohmann/json:单头文件现代 C++ JSON 库完整实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
DB Browser for SQLite 内置的 nlohmann/json:单头文件现代 C++ JSON 库完整实战指南
  • 数据库
  • 数据库客户端
  • 桌面应用

【免费下载链接】sqlitebrowser

Official home of the DB Browser for SQLite (DB4S) project. Previously known as "SQLite Database Browser" and "Database Browser for SQLite". Website at:

项目地址:https://gitcode.com/gh_mirrors/sq/sqlitebrowser
点击查看免费下载

导读

本指南以 DB Browser for SQLite(DB4S)仓库内随附的 nlohmann/json 官方文档(JSON for Modern C++)为核心,全面讲解这一以"现代 C++ 一等数据类型"体验著称的单头文件 JSON 库:从设计目标、构造与序列化/反序列化、STL 式容器访问,到 JSON Pointer/Patch、任意自定义类型转换、枚举映射以及 BSON/CBOR/MessagePack/UBJSON 二进制格式。文末结合 DB4S 源码,展示它在查询结果 JSON 导出、单元格 JSON 数据校验与格式化中的真实落地用法,帮助你既能直接上手该库,也能读懂 DB4S 内部的数据交换实现。

关联文档:libs/json/README.md;仓库内置头文件:libs/json/json.hpp(版本 3.10.4,单头文件约 2.6 万行)。

设计目标:为什么选它

nlohmann/json 的设计目标决定了它非常适合被嵌入到像 DB4S 这样的 C++ 桌面应用中:

  • 直观的语法。借助现代 C++ 的运算符重载,JSON 在代码中拥有接近 Python 般的"一等数据类型"体验——直接j["key"] = value即可构造嵌套结构,无需手写 DOM 构建器。
  • 极简集成。整个库只有一个头文件json.hpp(即仓库中的 libs/json/json.hpp),不依赖任何第三方库、不需要子项目、不需要复杂的构建系统,纯 C++11 编写,几乎无需调整编译器标志或工程设置。
  • 严肃的测试。官方宣称对全部代码(含所有异常路径)做了单元测试覆盖,并通过 Valgrind、Clang Sanitizers 检查内存泄漏,另有 OSS-Fuzz 对各解析器进行持续模糊测试;项目遵循 Core Infrastructure Initiative(CII)最佳实践。

同时,文档也坦诚列出了非首要目标,这有助于你选型时建立正确预期:

  • 内存效率并非极致。每个 JSON 对象开销约为一个指针(联合体的最大尺寸)加一个枚举元素(1 字节)。默认泛化使用std::string存字符串、int64_t/uint64_t/double存数字、std::map存对象、std::vector存数组、bool存布尔值。若需要定制,可对泛化类basic_json传入自定义模板参数。
  • 速度并非最快。存在更快的解析器,但如果你的目标是"用一个头文件快速给工程加上 JSON 支持",那么会用std::vector和std::map就能上手。

仓库内的实际集成方式

在 DB4S 工程中,该库以"随附第三方源码"的形式落地:整个库位于 libs/json 目录,包含 json.hpp、CMakeLists.txt、ChangeLog.md、README.md 与 LICENSE.MIT。

根 CMakeLists.txt 通过一条target_include_directories将其作为系统私有包含目录加入编译:

target_include_directories(${PROJECT_NAME} SYSTEM PRIVATE ${CMAKE_CURRENT_LIST_DIR}/libs/json)

随后源码中直接以尖括号包含头文件并取别名即可使用:

#include <json.hpp> // 方便起见 using json = nlohmann::json; using ordered_json = nlohmann::ordered_json;

上述用法分别出现在 src/ExportDataDialog.cpp 与 src/EditDialog.cpp 中。注意官方 README 的示例使用#include <nlohmann/json.hpp>,而 DB4S 将头文件平铺在libs/json目录下,因此实际包含形式为<json.hpp>——这是集成方式上的一个细节差异,二选一取决于头文件在包含路径中的摆放层级。

核心用法一:JSON 作为一等数据类型

假设要构造如下 JSON 对象:

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

两种写法等价,且全程无需告知编译器目标 JSON 类型:

// 写法一:逐键赋值(隐式把 j 升级为 object) json j; j["pi"] = 3.141; // 存储为 double j["happy"] = true; // 存储为 bool j["name"] = "Niels"; // 存储为 std::string j["nothing"] = nullptr; // 存储为 null j["answer"]["everything"] = 42; // 对象内嵌对象 j["list"] = { 1, 0, 2 }; // 存储为 std::vector(初始化列表) j["object"] = { {"currency", "USD"}, {"value", 42.99} }; // 键值对初始化列表 // 写法二:整体初始化列表构造 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 empty_array_explicit = json::array(); // [] json empty_object_implicit = json({}); // {} json empty_object_explicit = json::object(); // {} json array_not_object = json::array({ {"currency", "USD"}, {"value", 42.99} }); // 注意:上例是"键值对组成的数组",即 [["currency","USD"],["value",42.99]]

核心用法二:序列化 / 反序列化

字符串与字面量后缀

给字符串字面量追加_json后缀即可完成反序列化(解析):

json j = "{ \"happy\": true, \"pi\": 3.141 }"_json; // 用原始字符串字面量更易读 auto j2 = R"( { "happy": true, "pi": 3.141 } )"_json; // 显式解析 auto j3 = json::parse(R"({"happy": true, "pi": 3.141})");

关键陷阱:不加_json后缀时,字符串不会被解析,而只是作为 JSON 字符串值存储。即json j = "{ \"happy\": true, \"pi\": 3.141 }"存储的是字符串本体而非对象。

序列化使用dump(),传入缩进空格数即可美化输出:

std::string s = j.dump(); // {"happy":true,"pi":3.141} std::cout << j.dump(4) << std::endl; // { // "happy": true, // "pi": 3.141 // }

注意区分"取字符串值"与"序列化":

json j_string = "this is a string"; auto cpp_string = j_string.get<std::string>(); // 取回字符串值 std::string cpp_string2; j_string.get_to(cpp_string2); // 取回(写入既有变量) std::string serialized_string = j_string.dump(); // 显式 JSON 序列化

库仅支持 UTF-8;若以其他编码存入字符串,调用dump()可能抛异常,除非使用json::error_handler_t::replace或json::error_handler_t::ignore错误处理器。

流(文件 / 字符串流)

流操作符适用于任何std::istream/std::ostream子类:

json j; std::cin >> j; // 从标准输入解析 std::cout << j; // 序列化到标准输出 std::cout << std::setw(4) << j << std::endl; // setw 被重载用于设置缩进 // 文件读写 std::ifstream i("file.json"); json j; i >> j; std::ofstream o("pretty.json"); o << std::setw(4) << j << std::endl;

文档特别提醒:为此类使用场景设置failbit异常位并不合适,会因为库内的noexcept说明符导致程序终止。

迭代器区间与自定义数据源

可以从任意"value_type 为 1/2/4 字节整型"的迭代器容器解析(分别按 UTF-8/UTF-16/UTF-32 解释):

std::vector<std::uint8_t> v = {'t', 'r', 'u', 'e'}; json j = json::parse(v.begin(), v.end()); // 也可省略区间 json j2 = json::parse(v);

由于parse接受任意迭代器区间,你可以实现LegacyInputIterator概念提供自定义数据源(如从自定义容器逐字符读取),这是网络流、加密流等场景的扩展点。

SAX 接口:事件驱动解析

除构建 DOM 外,库还提供 SAX 风格解析接口,回调函数包括:

bool null(); bool boolean(bool val); bool number_integer(number_integer_t val); bool number_unsigned(number_unsigned_t val); bool number_float(number_float_t val, const string_t& s); bool string(string_t& val); bool binary(binary_t& val); bool start_object(std::size_t elements); bool end_object(); bool start_array(std::size_t elements); bool end_array(); bool key(string_t& val); bool parse_error(std::size_t position, const std::string& last_token, const detail::exception& ex);

每个回调的返回值决定解析是否继续。使用方法分三步:实现 SAX 接口(可继承nlohmann::json_sax<json>)→ 创建处理对象 → 调用bool json::sax_parse(input, &my_sax)。

注意:sax_parse只返回最后一个 SAX 事件的结果,不返回json值;解析出错时不抛异常,由你的parse_error实现决定如何处理。内部 DOM 解析器(json_sax_dom_parser)与合法性检查器(json_sax_acceptor)都构建在这套 SAX 接口之上。

核心用法三:STL 式访问

json类满足ReversibleContainer要求,行为与 STL 容器一致:

// 数组:push_back / emplace_back / 迭代 / 比较 json j; j.push_back("foo"); j.push_back(1); j.push_back(true); j.emplace_back(1.78); for (json::iterator it = j.begin(); it != j.end(); ++it) std::cout << *it << '\n'; for (auto& element : j) // 基于范围的 for std::cout << element << '\n'; j == R"(["foo", 1, true, 1.78])"_json; // true j.size(); // 4 j.empty(); // false j.type(); // json::value_t::array j.clear(); // 便捷类型检查 j.is_null(); j.is_boolean(); j.is_number(); j.is_object(); j.is_array(); j.is_string(); // 对象:下标 / emplace / 键值迭代 json o; o["foo"] = 23; o["bar"] = false; o["baz"] = 3.141; o.emplace("weather", "sunny"); for (auto& el : o.items()) std::cout << el.key() << " : " << el.value() << "\n"; // C++17 结构化绑定 for (auto& [key, value] : o.items()) std::cout << key << " : " << value << "\n"; // 查找与删除 if (o.contains("foo")) { /* 存在 */ } if (o.find("foo") != o.end()) { /* 存在 */ } int foo_present = o.count("foo"); // 1 o.erase("foo");

核心用法四:STL 容器转换

所有值类型可构造成 JSON 的序列容器(std::array、std::vector、std::deque、std::forward_list、std::list)可直接构造 JSON 数组;std::set等关联容器同样适用,但数组元素顺序取决于容器内部排序:

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::set<std::string> c_set {"one", "two", "three", "four", "one"}; json j_set(c_set); // 仅一个 "one",["four","one","three","two"]

键值容器(std::map、std::multimap、std::unordered_map、std::unordered_multimap)构造 JSON 对象:

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}

注意 multimap 系列在 JSON 对象中只保留一个键,值取决于容器内部顺序。

核心用法五:JSON Pointer、JSON Patch 与 Merge Patch

JSON Pointer(RFC 6901)用于寻址结构化值;JSON Patch(RFC 6902)用于描述两个 JSON 值之间的差异(类似 Unix 的 patch/diff):

json j_original = R"({ "baz": ["one", "two", "three"], "foo": "bar" })"_json; j_original["/baz/1"_json_pointer]; // "two" —— JSON Pointer 下标访问 json j_patch = R"([ { "op": "replace", "path": "/baz", "value": "boo" }, { "op": "add", "path": "/hello", "value": ["world"] }, { "op": "remove", "path": "/foo"} ])"_json; json j_result = j_original.patch(j_patch); // { "baz": "boo", "hello": ["world"] } // 计算两个 JSON 值的差异 json::diff(j_result, j_original);

JSON Merge Patch(RFC 7386)以"贴近被修改文档的语法"描述变更,null值表示删除键:

json j_document = R"({ "a": "b", "c": { "d": "e", "f": "g" } })"_json; json j_patch = R"({ "a":"z", "c": { "f": null } })"_json; j_document.merge_patch(j_patch); // { "a": "z", "c": { "d": "e" } } —— f 被 null 删除,d 保留

隐式转换与建议

支持的标量类型可隐式转换到JSON 值;但文档强烈建议不要从 JSON 值隐式转换回原生类型,而是使用get<T>()/get_to():

// 推荐 auto s2 = js.get<std::string>(); auto b2 = jb.get<bool>(); auto f = jn.get<double>(); // 不推荐:从 JSON 隐式转换 std::string s3 = js; double f2 = jb;

可通过在包含json.hpp前定义JSON_USE_IMPLICIT_CONVERSIONS为0关闭隐式转换;使用 CMake 时则设置选项JSON_ImplicitConversions为OFF。

另外注意char不会被自动转换为 JSON 字符串,而是整数(ASCII 码值);要存字符串需显式构造:

char ch = 'A'; // ASCII 65 json j_default = ch; // 存整数 65 json j_string = std::string(1, ch); // 存字符串 "A"

任意自定义类型转换

手动 to_json / from_json

假设要序列化结构体ns::person,最朴素的写法是逐字段拷贝,但更优雅的方式是提供两个函数(必须在类型的命名空间中,可以是全局命名空间):

using json = nlohmann::json; namespace ns { struct person { std::string name; std::string address; int age; }; 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); } } ns::person p {"Ned Flanders", "744 Evergreen Terrace", 60}; json j = p; // 自动调用 to_json auto p2 = j.get<ns::person>(); // 自动调用 from_json

要点与坑:

  • to_json/from_json必须位于类型所在命名空间,否则库无法通过 ADL 找到;
  • 这些函数在每次使用转换处都必须可见(含对应头文件);
  • get<your_type>()要求类型DefaultConstructible(后续有绕过方法);
  • from_json内请用at()而非operator[]——键不存在时at()抛可捕获的异常,而operator[]是未定义行为;
  • STL 类型(如std::vector)的序列化/反序列化已内置,无需额外编写。

宏简化:NON_INTRUSIVE / INTRUSIVE

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(name, member1, member2, ...)放在类/结构体所在命名空间中;NLOHMANN_DEFINE_TYPE_INTRUSIVE(...)放在类内部(可访问私有成员)。两者都要求 JSON 对象做序列化载体、成员名做对象键:

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

第三方类型:特化 adl_serializer

默认序列化器是nlohmann::adl_serializer(ADL = 实参依赖查找)。处理boost::optional、std::filesystem::path等无法侵入其命名空间的类型时,可在nlohmann命名空间内特化adl_serializer:

namespace nlohmann { template <typename T> struct adl_serializer<boost::optional<T>> { static void to_json(json& j, const boost::optional<T>& opt) { j = (opt == boost::none) ? nullptr : static_cast<json>(*opt); } static void from_json(const json& j, boost::optional<T>& opt) { opt = j.is_null() ? boost::none : j.get<T>(); } }; }

非默认构造/不可拷贝类型

若类型满足 MoveConstructible,可特化adl_serializer并提供单参from_json返回类型:

namespace nlohmann { template <> struct adl_serializer<move_only_type> { static move_only_type from_json(const json& j) { return {j.get<int>()}; } // 注意:必须同时提供 to_json,否则该类型无法转回 JSON static void to_json(json& j, move_only_type t) { j = t.i; } }; }

自写序列化器(进阶)

可自定义序列化器(示例:只接受sizeof(T) <= 32的类型)。关键要求:使用不同于nlohmann::json的basic_json别名(最后一个模板参数是JSONSerializer)、在全部to_json/from_json中使用该别名、需要 ADL 时显式using nlohmann::to_json;/using nlohmann::from_json;。文档警告:重新实现序列化器时若内部再调用j = value会形成自递归,可能栈溢出,务必小心。

枚举转换

默认枚举值按整数序列化。但若枚举在数据序列化后发生重排,反序列化会得到未定义/非预期值,因此可用NLOHMANN_JSON_SERIALIZE_ENUM宏把枚举映射为字符串:

enum TaskState { TS_STOPPED, TS_RUNNING, TS_COMPLETED, TS_INVALID=-1, }; // 映射为字符串;宏必须在枚举的命名空间(含全局)内声明 NLOHMANN_JSON_SERIALIZE_ENUM( TaskState, { {TS_INVALID, nullptr}, {TS_STOPPED, "stopped"}, {TS_RUNNING, "running"}, {TS_COMPLETED, "completed"}, }) json j = TS_STOPPED; assert(j == "stopped"); json j3 = "running"; assert(j3.get<TaskState>() == TS_RUNNING); json jPi = 3.14; assert(jPi.get<TaskState>() == TS_INVALID); // 未定义值回退到映射表首项

两个要点:未定义 JSON 值回退到映射表第一个条目,请谨慎选择默认项;枚举或 JSON 值在映射表中重复出现时,取自上而下第一个匹配项。

二进制格式:BSON、CBOR、MessagePack 与 UBJSON

JSON 不紧凑,不适合网络传输。库内置四种二进制格式的编解码,均以std::vector<std::uint8_t>为载体并支持 roundtrip:

json j = R"({"compact": true, "schema": 0})"_json; std::vector<std::uint8_t> v_bson = json::to_bson(j); json j_from_bson = json::from_bson(v_bson); std::vector<std::uint8_t> v_cbor = json::to_cbor(j); json j_from_cbor = json::from_cbor(v_cbor); std::vector<std::uint8_t> v_msgpack = json::to_msgpack(j); json j_from_msgpack = json::from_msgpack(v_msgpack); std::vector<std::uint8_t> v_ubjson = json::to_ubjson(j); json j_from_ubjson = json::from_ubjson(v_ubjson);

BSON、CBOR(字节串)、MessagePack(bin/ext/fixext)中的二进制类型默认存为std::vector<std::uint8_t>:

// CBOR 字节串,载荷 0xCAFE std::vector<std::uint8_t> v = {0x42, 0xCA, 0xFE}; json j = json::from_cbor(v); j.is_binary(); // true auto& binary = j.get_binary(); binary.has_subtype(); // false(CBOR 无二进制子类型) binary.size(); // 2 binary[0]; // 0xCA binary.set_subtype(0x10); // 设置子类型 auto cbor = json::to_msgpack(j); // 0xD5 (fixext2), 0x10, 0xCA, 0xFE

在 DB Browser for SQLite 中的真实应用

nlohmann/json 并不是 DB4S 的附属装饰,而是查询结果导出与单元格数据编辑两条功能线的核心依赖。

查询结果导出 JSON(ExportDataDialog)

src/ExportDataDialog.cpp 的exportQueryJson()演示了"SQLite 行 → JSON"的完整流水线:先sqlite3_prepare_v2准备 SQL 语句,逐行sqlite3_step取结果,再按列类型写入json_row:

  • SQLITE_INTEGER→json_row[column_name] = content;(写入qint64)
  • SQLITE_FLOAT→ 写入double
  • SQLITE_NULL→json_row[column_name] = nullptr;(对应 JSONnull)
  • SQLITE_TEXT→ 经 UTF-8 转换后写入std::string
  • SQLITE_BLOB→ Base64 编码为字符串后写入

每行用json_table.push_back(json_row)聚合为数组,最后通过json_table.dump(ui->checkPrettyPrint->isChecked() ? 4 : -1)输出——勾选"美化打印"时缩进 4 个空格,否则用-1输出紧凑单行。这正是官方 README 中dump(indent)参数语义的直接落地。相关 UI 状态还会写入设置项exportjson/prettyprint(见 src/ExportDataDialog.cpp)。

单元格 JSON 校验与格式化(EditDialog)

src/EditDialog.cpp 在 JSON 编辑器模式下用json::parse解析单元格文本,并捕获json::parse_error处理非法输入;为保留键的插入顺序,它还使用ordered_json类型(见 src/EditDialog.cpp)。此外 src/EditDialog.cpp 通过json::parse(cellData, nullptr, false)判断数据是否为合法 JSON 且非数字,用于数据类型探测;在 src/EditDialog.cpp 中,JSON 数据经jsonDoc.dump(4)缩进后重新写入单元格,实现"格式化 JSON"功能。

这两处源码可以当作该库在生产级桌面应用中的参考范本:解析用异常捕获保障健壮性、序列化用dump(indent)控制输出形态、ordered_json应对键序敏感场景。

编译器支持

文档列出的已知可用编译器范围(以文档撰写时期为准):GCC 4.8–11.0、Clang 3.4–13.0、Apple Clang 9.1–12.4、Intel C++ Compiler 17.0.2+、MSVC 2015/2017/2019(Build Tools 14.0.25123.0 起)。注意事项:

  • GCC 4.8 存在 [57824] 号 bug:多行原始字符串不能作为宏参数,请勿在该编译器下于宏中直接使用多行原始字符串;
  • Android 默认使用过旧编译器/库时,可在Application.mk中设置APP_STL := c++_shared、NDK_TOOLCHAIN_VERSION := clang3.6、APP_CPPFLAGS += -frtti -fexceptions;
  • MinGW 或 Android SDK 下若报'to_string' is not a member of 'std',属编译器自身问题而非库问题;
  • 不受支持的 GCC/Clang 版本会被#error指令拒绝编译,可通过定义JSON_SKIP_UNSUPPORTED_COMPILER_CHECK关闭该检查(但此情形下官方不提供支持)。

工程集成方式

直接使用头文件

json.hpp是唯一必需文件,使用前开启 C++11(如 GCC/Clang 加-std=c++11)。也可使用json_fwd.hpp做前置声明(安装它需在 CMake 中设置-DJSON_MultipleHeaders=ON)。

CMake

nlohmann_json::nlohmann_json接口目标会自动填充INTERFACE_INCLUDE_DIRECTORIES与 C++11 所需的INTERFACE_COMPILE_FEATURES。

外部依赖(find_package):

find_package(nlohmann_json 3.2.0 REQUIRED) add_library(foo ...) target_link_libraries(foo PRIVATE nlohmann_json::nlohmann_json)

内嵌子目录:

set(JSON_BuildTests OFF CACHE INTERNAL "") add_subdirectory(nlohmann_json) add_library(foo ...) target_link_libraries(foo PRIVATE nlohmann_json::nlohmann_json)

FetchContent(CMake ≥ 3.11):

include(FetchContent) FetchContent_Declare(json GIT_REPOSITORY https://github.com/nlohmann/json.git GIT_TAG v3.7.3) FetchContent_GetProperties(json) if(NOT json_POPULATED) FetchContent_Populate(json) add_subdirectory(${json_SOURCE_DIR} ${json_BINARY_DIR} EXCLUDE_FROM_ALL) endif() target_link_libraries(foo PRIVATE nlohmann_json::nlohmann_json)

文档提示:完整源码仓库体积庞大(含基准测试数据集),可用更精简的镜像仓库或随 release 发布的include.zip作为依赖源。

同时支持外部与内嵌:可在顶层用option(FOO_USE_EXTERNAL_JSON ...)开关,配合thirdparty/CMakeLists.txt中if(FOO_USE_EXTERNAL_JSON) find_package(...) else() add_subdirectory(...) endif()的模式,而链接目标始终统一为nlohmann_json::nlohmann_json。

包管理器与 pkg-config

文档覆盖了主流包管理器:Homebrew(brew install nlohmann-json,尝鲜用--HEAD)、Meson(subproject 或meson wrap install nlohmann_json)、Conan(nlohmann_json/x.y.z)、Spack、Hunter、Buckaroo、vcpkg(vcpkg install nlohmann-json)、cget(cget install nlohmann/json)、CocoaPods、NuGet、conda-forge(conda install -c conda-forge nlohmann_json)、MSYS2(pacman -S mingw-w64-x86_64-nlohmann-json)、MacPorts(sudo port install nlohmann-json)、build2(depends: nlohmann-json)、wsjcpp、CPM.cmake 等。

裸 Makefile 可借助 pkg-config:

pkg-config nlohmann_json --cflags

Meson 中则用dependency('nlohmann_json', required: true)获取系统级依赖。

JSON 语义细节与陷阱

字符编码

  • 仅支持UTF-8输入(这也是 RFC 8259 规定的 JSON 默认编码);std::u16string/std::u32string可按 UTF-16/UTF-32 解析,但不支持从文件等输入容器读取;
  • Latin-1、ISO 8859-1 等其他编码不支持,会产生解析/序列化错误;
  • Unicode 非字符不会被替换;无效代理对(如孤立的\uDEAD)会触发解析错误;
  • 存储的字符串为 UTF-8,默认std::string的size()返回字节数而非字符数;
  • 宽字符串(std::wstring)需先转成 UTF-8std::string再存储。

注释支持

库默认不支持JSON 注释。文档援引了三点理由:注释不属于 JSON 规范(JSON 不是 JavaScript);Douglas Crockford 曾明确移除注释以免其被用于承载解析指令、破坏互操作性;部分实现加注释而部分不加会危及互操作。如需处理带注释的配置文件,可先经 JSMin 类工具预处理,或调用json::parse(input, nullptr, true /* ignore_comments */)将//与/* */注释当空白忽略。

对象键顺序

默认不保留对象元素的插入顺序(JSON 规范将对象定义为无序集合)。需要保留顺序时可使用nlohmann::ordered_json——这正是 DB4S 的 src/EditDialog.cpp 所采用的做法;也可接入tsl::ordered_map或nlohmann::fifo_map等有序容器。

内存释放

文档声明经 Valgrind 与 ASAN 检查无内存泄漏。若解析程序未立即归还内存,多与 glibc 的分配阈值缓存策略有关:大量低于阈值的小分配不会立即归还操作系统,这属于运行时库行为而非本库泄漏。

其他关键编译期行为

  • 代码内含大量调试断言,可定义NDEBUG关闭。尤其operator[]对 const 对象是非检查访问:键不存在时行为未定义(类似解引用空指针),断言开启时会触发断言失败;不确定键是否存在时请使用at()。也可定义JSON_ASSERT(x)替换assert(x);
  • JSON 规范未规定数字的精确 C++ 类型,库会自动选择最合适类型,double可能被选中,若调用方解除了浮点异常掩码,某些罕见情况下会触发浮点异常(属调用方问题);
  • 可用-fno-rtti编译(无需运行时类型识别);
  • 异常可被关闭:使用-fno-exceptions或定义JSON_NOEXCEPTION(异常将替换为abort()),并可分别用JSON_THROW_USER/JSON_TRY_USER/JSON_CATCH_USER覆盖 throw/try/catch。注意JSON_THROW_USER应离开当前作用域(抛出或 abort),否则继续执行可能产生未定义行为。

运行单元测试

从源码构建并运行测试:

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

注意事项(原文照录并加以说明):

  • ctest阶段会从外部仓库下载多个 JSON 测试数据文件;若策略禁止下载,可自行下载后通过-DJSON_TestDataDirectory=path传入,从而无需联网;
  • 以压缩包方式获取的源码没有 Git 元数据,cmake_fetch_content_configure测试会失败,可执行ctest -LE git_required跳过;
  • 部分测试会改动已安装文件导致过程不可复现,可执行ctest -LE not_reproducible跳过;同时排除两组标签需用cmake -LE "not_reproducible|git_required";
  • Intel 编译器默认使用不安全的浮点优化可能致测试失败,请加/fp:precise。

许可证与第三方成分

本库采用 MIT License(版权 2013–2021,Niels Lohmann),详见 libs/json/LICENSE.MIT。库内还包含以下第三方成分:Bjoern Hoehrmann 的 UTF-8 解码器(MIT)、Florian Loitsch 的 Grisu2 算法修改版(MIT)、Evan Nemerson 的 Hedley(CC0-1.0)、Google Abseil 的部分代码(Apache 2.0)。构建与质量保障依赖大量第三方工具(amalgamate、AFL/libFuzzer、Valgrind、Clang Sanitizers、doctest、Doxygen、OSS-Fuzz、Coverity 等)。

小结

nlohmann/json 以"单头文件 + 纯 C++11 + STL 化体验"三个特征,成为 C++ 项目中接入 JSON 的低摩擦方案。通过本文你可以:用_json字面量、parse/dump完成双向转换;用 STL 式 API 增删改查;用to_json/from_json、NLOHMANN_DEFINE_TYPE_*宏和adl_serializer特化打通自定义类型;用四种二进制格式提升网络传输效率;并在 DB4S 的 ExportDataDialog 与 EditDialog 源码中看到它在真实产品中的调用范式。若需查阅更完整的能力清单,仓库内还保留了 libs/json/ChangeLog.md 供追溯各版本演进。

  • 数据库
  • 数据库客户端
  • 桌面应用

【免费下载链接】sqlitebrowser

Official home of the DB Browser for SQLite (DB4S) project. Previously known as "SQLite Database Browser" and "Database Browser for SQLite". Website at:

项目地址:https://gitcode.com/gh_mirrors/sq/sqlitebrowser
点击查看免费下载
上一篇:5步掌握超星学习通自动化签到:从零到精通的完整实践指南
下一篇:企业微信开发终极指南:从零构建组织架构与消息推送系统

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!