JSONPath 教程

一、背景:JSONPath 是什么

JSONPath 是面向 JSON 的路径查询语言。思路类似 XML 的 XPath:用一条声明式表达式,从 JSON 中定位、筛选并提取节点,而不必手写层层循环。

  • 查询是只读的,不修改原文档。
  • 结果通常是匹配值组成的列表(即使只有一个,很多引擎也按列表返回)。
  • 实现众多(Jayway、JSONPath-Plus 等),细节上存在方言差异。

JSON 基础见 JSON 教程;在线查询可用 JSONPath 工具

二、为什么会有 JSONPath

业务 JSON 往往很深。硬写 data.a.b[i].c 难读难维护;结构一变就要改代码。JSONPath 把「怎么走、按什么条件选」写成路径,便于在联调工具、测试脚本、配置映射里复用。

// 意图:fiction 类图书的书名
$.store.book[?(@.category == 'fiction')].title

三、应用场景

  • 接口联调:从大响应里抽出关键字段核对
  • 自动化测试:对返回体做断言
  • 日志监控:批量提取 userId、错误码、耗时等
  • 配置 / 规则引擎:字段映射与条件路由
  • 数据清洗:从嵌套报文抽扁平列表

四、示例数据(语法小节均基于此)

{
  "store": {
    "book": [
      {
        "category": "reference",
        "author": "Nigel Rees",
        "title": "Sayings of the Century",
        "price": 8.95
      },
      {
        "category": "fiction",
        "author": "Evelyn Waugh",
        "title": "Sword of Honour",
        "price": 12.99
      },
      {
        "category": "fiction",
        "author": "Herman Melville",
        "title": "Moby Dick",
        "isbn": "0-553-21311-3",
        "price": 8.99
      },
      {
        "category": "fiction",
        "author": "J. R. R. Tolkien",
        "title": "The Lord of the Rings",
        "isbn": "0-395-19395-8",
        "price": 22.99
      }
    ],
    "bicycle": {
      "color": "red",
      "price": 19.95
    }
  },
  "expensive": 10
}

五、语法说明(每条附小示例)

5.1 根节点 $

表示文档根,查询一般从这里开始。

$
→ 整个 JSON 文档

$.expensive
→ 10

5.2 子节点:点号 .key

按对象键逐级向下取。

$.store.bicycle.color
→ "red"

$.store.bicycle.price
→ 19.95

5.3 子节点:方括号 ['key']

与点号等价,更适合键名含 .-、空格的情况。

$['store']['bicycle']['color']
→ "red"

// 若键名是 user-id,应写:
// $['user-id']

5.4 数组下标 [n]

下标从 0 开始。部分实现支持 [-1] 表示最后一项。

$.store.book[0].title
→ "Sayings of the Century"

$.store.book[2].author
→ "Herman Melville"

5.5 多选 [n,m]

一次取多个下标。

$.store.book[0,2].title
→ [
  "Sayings of the Century",
  "Moby Dick"
]

5.6 切片 [start:end]

取下标区间。是否包含终点、是否支持步长,因实现而异。

$.store.book[0:2].title
→ 前两本书的 title(具体是否含终点请以所用引擎为准)

5.7 通配 *

匹配某一层的全部子节点或数组全部元素。

$.store.book[*].author
→ [
  "Nigel Rees",
  "Evelyn Waugh",
  "Herman Melville",
  "J. R. R. Tolkien"
]

$.store.*
→ store 下的 book 数组与 bicycle 对象

5.8 递归下降 ..

任意深度查找字段。范围大、可能更慢,层级明确时优先写死路径。

$..author
→ 所有 author

$..price
→ [8.95, 12.99, 8.99, 22.99, 19.95]
  (书价 + 自行车价格)

$.store..price
→ 仅从 store 子树开始找 price

5.9 当前节点 @

只用在过滤器里,表示「正在判断的那一个元素」。

// @ 就是 book 数组里当前这一本
$.store.book[?(@.price < 10)]

5.10 过滤器 [?()]

按条件筛选数组元素;后面可继续点字段做投影。

$.store.book[?(@.price < 10)]
→ 价格小于 10 的书(完整对象)

$.store.book[?(@.price < 10)].title
→ ["Sayings of the Century", "Moby Dick"]

5.11 比较运算符

运算符 含义
== 等于
!= 不等于
> 大于
>= 大于等于
< 小于
<= 小于等于
// ==
$.store.book[?(@.category == 'fiction')].title

// !=
$.store.book[?(@.category != 'fiction')].title
→ ["Sayings of the Century"]

// > / >=
$.store.book[?(@.price > 10)].title
$.store.book[?(@.price >= 20)].title
→ ["The Lord of the Rings"]

// < / <=
$.store.book[?(@.price < 10)].title
$.store.book[?(@.price <= 8.99)].title

比较时类型要一致:JSON 里是数字,就不要写成 @.price == '8.95'

5.12 与根路径比较

过滤器里除了 @,还可以用 $ 引用文档其他位置。

$.store.book[?(@.price > $.expensive)].title
→ [
  "Sword of Honour",
  "The Lord of the Rings"
]

5.13 存在性判断

字段存在即可命中(具体写法因实现略有差异)。

$.store.book[?(@.isbn)].title
→ [
  "Moby Dick",
  "The Lord of the Rings"
]

$.store.book[?(@.isbn)].isbn
→ [
  "0-553-21311-3",
  "0-395-19395-8"
]

5.14 逻辑组合(方言)

&& 并且、|| 或者;部分库还支持正则 =~。上线前请在目标引擎验证。

$.store.book[?(@.price < 10 && @.category == 'fiction')].title
→ ["Moby Dick"]

$.store.book[?(@.price < 10 || @.category == 'reference')].title
→ ["Sayings of the Century", "Moby Dick"]

5.15 路径操作符速查

符号 含义
$ 根节点
@ 过滤器中的当前节点
.key / ['key'] 子属性
.. 递归下降
* 通配
[n] / [n,m] / [start:end] 下标 / 多选 / 切片
[?()] 过滤
== != > >= < <= 比较(过滤器内)

六、综合示例

下面把多种语法组合到真实一点的报文里。

6.1 接口联调:订单列表

{
  "code": 0,
  "message": "ok",
  "data": {
    "page": 1,
    "total": 2,
    "list": [
      {
        "orderId": "O1001",
        "status": "PAID",
        "amount": 99.5,
        "buyer": { "name": "Alice" }
      },
      {
        "orderId": "O1002",
        "status": "CREATED",
        "amount": 12,
        "buyer": { "name": "Bob" }
      }
    ]
  }
}

$.code
→ 0

$.data.list[*].orderId
→ ["O1001", "O1002"]

$.data.list[?(@.status == 'PAID')].amount
→ [99.5]

$.data.list[?(@.amount >= 50)].buyer.name
→ ["Alice"]

$.data.list[*].buyer.name
→ ["Alice", "Bob"]

$..orderId
→ ["O1001", "O1002"]

6.2 日志排查

{
  "ts": "2026-07-19T08:00:00Z",
  "level": "ERROR",
  "service": "pay-api",
  "traceId": "t-9f3a",
  "req": { "path": "/v1/pay", "userId": 10086 },
  "err": { "code": "TIMEOUT", "detail": "upstream timeout" }
}

$.traceId      → "t-9f3a"
$.req.userId    → 10086
$.err.code      → "TIMEOUT"
$.level         → "ERROR"

6.3 权限角色

{
  "user": {
    "id": 7,
    "name": "gary",
    "roles": [
      { "code": "admin", "enabled": true },
      { "code": "editor", "enabled": false },
      { "code": "viewer", "enabled": true }
    ]
  }
}

$.user.roles[?(@.enabled == true)].code
→ ["admin", "viewer"]

$.user.roles[?(@.code == 'admin')].enabled
→ [true]

$.user.roles[?(@.enabled != true)].code
→ ["editor"]

6.4 树形评论

{
  "postId": 1,
  "comments": [
    {
      "id": 11,
      "text": "顶",
      "replies": [
        { "id": 111, "text": "同意" },
        { "id": 112, "text": "补充一点" }
      ]
    },
    {
      "id": 12,
      "text": "求更新",
      "replies": []
    }
  ]
}

$.comments[*].text
→ ["顶", "求更新"]

$.comments[*].replies[*].text
→ ["同意", "补充一点"]

$..id
→ [11, 111, 112, 12]

$.comments[0].replies[1].text
→ "补充一点"

6.5 配置开关

{
  "app": {
    "name": "jsonme",
    "features": {
      "editor": { "enabled": true, "maxSizeMb": 50 },
      "compare": { "enabled": true, "maxSizeMb": 20 },
      "legacy": { "enabled": false, "maxSizeMb": 5 }
    }
  }
}

$.app.name
→ "jsonme"

$.app.features.editor.maxSizeMb
→ 50

$.app.features.*.enabled
→ [true, true, false]

七、实践建议与常见坑

  • 先窄后宽:优先明确路径;不稳再用 $..
  • 特殊键名用方括号$['user-id']$['a.b']
  • 结果按列表处理:断言时更稳。
  • 比较类型一致:数字别和字符串硬比。
  • 空列表 ≠ 语法错:可能是条件未命中。
  • 方言要实测:不同库细节不同。

八、延伸阅读