首页
/ nlohmann/json:用 json_pointer::push_back 动态构建 JSON Pointer 路径

nlohmann/json:用 json_pointer::push_back 动态构建 JSON Pointer 路径

2026-09-07 17:46:41作者:何举烈Damon

本文围绕 JSON for Modern C++(nlohmann/json)的 API 文档 json_pointer::push_back 展开,讲清这个重载函数在库中的准确签名与语义:向一个已存在的 JSON Pointer 末尾追加一个未转义的引用 token。读懂本文后,你能掌握在运行时逐段拼装 JSON Pointer(例如先取根节点、再按字段名和数组下标逐级下钻)的方法,理解它与 operator/=to_string 转义规则之间的协作关系,并知道其实现位于哪个源码文件、有哪些测试用例为其兜底。

接口签名与语义

官方文档给出的接口为两个重载:

void push_back(const string_t& token);

void push_back(string_t&& token);
  • 第一个重载接收 token 的常量引用,适用于 token 之后还要继续使用的场景;
  • 第二个重载接收右值引用,内部通过 std::move 转移字符串,避免一次不必要的拷贝,适合拼接临时构造的 token。

文档对行为的定义只有一句话,但信息量很大:Append an unescaped token at the end of the reference pointer——追加的是一个“未转义”的 token。这意味着调用方传入的是语义层面的原始字段名(例如 /~foo),JSON Pointer 规范的 ~0/~1 转义会在最终输出字符串时由库自动完成,调用者不需要也不应该手动转义。

参数与复杂度

项目 说明
token(入参) 要追加到指针末尾的引用 token,类型为 string_t
返回值 无(void),原地修改指针对象
时间复杂度 均摊常数(Amortized constant),即 std::vector::push_back 的复杂度特征
异常 文档未声明抛出条件;见下文源码分析

完整示例:从空指针逐级拼装出 /foo/0/bar

文档自带的示例演示了从空 JSON Pointer 出发、连续三次 push_back 的结果。示例源码见 json_pointer__push_back.cpp,期望输出见 json_pointer__push_back.output

#include <iostream>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

int main()
{
    // create empty JSON Pointer
    json::json_pointer ptr;
    std::cout << "\"" << ptr << "\"\n";

    // call push_back()
    ptr.push_back("foo");
    std::cout << "\"" << ptr << "\"\n";

    ptr.push_back("0");
    std::cout << "\"" << ptr << "\"\n";

    ptr.push_back("bar");
    std::cout << "\"" << ptr << "\"\n";
}

实际输出为:

""
"/foo"
"/foo/0"
"/foo/0/bar"

三段式演进展示了该接口的核心用法:

  1. 默认构造得到指向文档根的空指针,输出为 ""
  2. push_back("foo") 追加对象键,得到 /foo
  3. push_back("0") 追加数组下标(token 本身只是字符串,库不会区分“键”和“下标”的语义,区分发生在用指针访问 JSON 值时);
  4. push_back("bar") 继续下钻,得到 /foo/0/bar

源码实现:一条对 vector 的透传

push_back 的实现在 json_pointer.hpp 中,两个重载的函数体都只有一行:

/// @brief append an unescaped token at the end of the reference pointer
void push_back(const string_t& token)
{
    reference_tokens.push_back(token);
}

void push_back(string_t&& token)
{
    reference_tokens.push_back(std::move(token));
}

从源码结构看,整个 json_pointer 类就是围绕一个私有成员组织的(json_pointer.hpp#L1049-L1052):

private:
    /// the reference tokens
    std::vector<string_t> reference_tokens;
  • push_backstd::vector::push_back 的薄封装,因此“均摊常数”的复杂度承诺直接继承自底层容器的行为;
  • 右值重载使用 std::move,与文档中两个重载的区分一一对应;
  • 由于实现只是向量追加,没有额外校验逻辑,文档也未声明异常条件——空指针上 push_back 是合法操作,正是上面示例的第一步。

operator/= 的关系:push_back 是组合算子的底座

同一个头文件里,operator/=(string_t token) 的重载(json_pointer.hpp#L106-L112)内部直接委托给 push_back

/// @brief append an unescaped reference token at the end of this JSON pointer
json_pointer& operator/=(string_t token)
{
    push_back(std::move(token));
    return *this;
}

也就是说,ptr /= "foo"ptr.push_back("foo") 效果等价(前者额外返回 *this 支持链式调用),此外 operator/= 还提供 std::size_t 数组下标与整个 json_pointer 的追加版本。写风格化代码可以用 /=,写通用逻辑时 push_back 是更直白的选择。

“未转义”的落点:转义发生在 to_string

“unescaped token”这一语义的另一半体现在输出端。to_string()json_pointer.hpp#L66-L76)在把 token 序列还原成指针字符串时,对每个 token 调用 detail::escape 再拼上 / 分隔符。反过来,构造时从字符串解析的 split() 会对 ~0/~1detail::unescapejson_pointer.hpp#L840-L847)。因此内部 reference_tokens 中保存的永远是原始 token,push_back 传入什么就存什么。

单元测试对这一闭环有明确验证,unit-json_pointer.cpp 中:

// push key which has to be encoded
ptr.push_back("object");
ptr.push_back("/");
CHECK(j[ptr] == j["object"]["/"]);
CHECK(ptr.to_string() == "/object/~1");

向指针里 push_back 一个字段名 / 后,to_string() 输出 /object/~1——转义由库完成;而用它索引 JSON 时又能正确命中 j["object"]["/"]。同一测试文件(unit-json_pointer.cpp#L546-L593)还覆盖了 push_backpop_backpop_frontparent_pointer() 的成对使用,例如逐层 push 出 answer/everything 后用 j[ptr]j["answer"]["everything"] 断言等价。

典型应用场景:运行时逐段下钻

push_back 的主要价值在于“指针内容在编译期未知”的场景。静态情况下直接写字面量更省事:

json j = {{"foo", {{42, 43}, {"bar"}}}};
// 静态已知路径,直接用字面量
auto v = j["/foo/0/bar"_json_pointer];

而路径需要运行时拼装时,可以这样写:

json::json_pointer ptr;          // 指向根
ptr.push_back("foo");            // "/foo"
for (std::size_t i = 0; i < 10; ++i) {
    ptr.push_back(std::to_string(i));   // 移动语义,右值重载
}
ptr.push_back("bar");

与同类 API 搭配时,还能利用 pop_back 逐级回退:从 json_pointer.hpp#L195-L205 可以看到,pop_back 在空指针上会抛出 out_of_range(错误码 405,"JSON pointer has no parent"),因此“push 下去、pop 回来”的遍历模式需要自行保证不成对调用;判断指针是否为空应使用 empty()json_pointer.hpp#L233-L236)。

另外注意与 basic_json::push_back 的区分:后者(向 JSON 值本身添加数组元素/对象键值对,见 single_include/nlohmann/json.hpp#L24511-L24513)修改的是 JSON 值,本文的 json_pointer::push_back 修改的只是指针的 token 序列,两者同名但作用对象完全不同。

版本历史

  • Added in version 3.6.0:该 API 首次加入;
  • Changed in version 3.11.0:参数 token 的类型调整为 string_t。这一变化使 json_pointer 与库的自定义字符串类型(StringType 模板参数,兼容 std::stringstd::wstring 等)保持一致,头文件中的 string_t 定义见 json_pointer.hpp#L56-L58。当前仓库版本为 3.12.0(见 json_pointer.hpp 文件头),上述签名即当前行为。

延伸阅读

同一 API 分组下的相邻文档可帮助补齐 JSON Pointer 的完整操作面:

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388