- 后端
- Web框架
【免费下载链接】symfony
The Symfony PHP framework
导读
Symfony 的 JsonPath 组件(symfony/json-path)为 PHP 开发者提供了一套标准化的 JSON 导航方案:它按照 RFC 9535 定义的 JSONPath 语法,通过JsonCrawler在 JSON 文档上执行路径查询、过滤与函数运算。本文以该组件在 Symfony 仓库(JsonPath 组件目录)中的实现为依托,完整讲解从安装、基础查询到高级过滤器、编程式路径构建、资源流优化的全部用法,并深入源码剖析其词法解析、表达式求值与合规测试机制。读完本文,你将能够直接用 JSONPath 语法在 PHP 中定位任意 JSON 节点,并理解该组件与 RFC 9535 标准的对齐程度。
组件是什么:JSON 的"XPath"
JsonPath 是一种从 JSON 文档中定位节点的查询语言,思路与 XML 世界的 XPath 类似,但专门为 JSON 的数组与对象结构设计。Symfony 的 JsonPath 组件将这一语法落地为 PHP 库,其核心承诺是:以 RFC 9535 描述的 JSONPath 语法简化 JSON 导航(见 README 的组件定位描述)。
组件的核心入口是一个名为JsonCrawler的爬虫类:给它一段 JSON(字符串或资源流),再给它一条 JSONPath 查询语句,它就返回匹配到的节点列表。从 JsonCrawler.php 的类注释可以看到,它"使用 RFC 9535 描述的 JSONPath 爬取 JSON 文档";而从 composer.json 可以看到该包要求 PHP >= 8.4.1,依赖symfony/polyfill-ctype与symfony/polyfill-mbstring,开发环境还需要symfony/json-streamer(用于资源流优化,下文详述)。
快速开始:安装与第一个查询
安装组件
composer require symfony/json-path这是官方 README 给出的标准安装方式。如果后续想使用"资源流输入 + 局部反序列化"的优化能力(见"流式处理"一节),还需要额外安装流式解析依赖:
composer require symfony/json-streamer三个开箱即用的示例
README 用一个经典的"书店(store/book)"JSON 展示了三种典型查询。这里原样继承并逐步拆解:
use Symfony\Component\JsonPath\JsonCrawler; $json = <<<'JSON' {"store": {"book": [ {"category": "reference", "author": "Nigel Rees", "title": "Sayings", "price": 8.95}, {"category": "fiction", "author": "Evelyn Waugh", "title": "Sword", "price": 12.99} ]}} JSON; $crawler = new JsonCrawler($json); $result = $crawler->find('$.store.book[0].title'); $result = $crawler->find('$.store.book[?match(@.author, "[A-Z].*el.+")]'); $result = $crawler->find("$.store.book[?(@.category == 'fiction')].title");三条语句分别演示了三类能力:
| 查询语句 | 能力类别 | 返回内容 |
|---|---|---|
$.store.book[0].title | 基本路径 + 数组索引 | ['Sayings'] |
$.store.book[?match(@.author, "[A-Z].*el.+")] | 过滤器 + 正则函数 | author 匹配该正则的整本书对象 |
$.store.book[?(@.category == 'fiction')].title | 过滤器比较 + 后续导航 | ['Sword'] |
find()的返回类型是数组(list<array|string|float|int|bool|null>),即使只匹配到一个节点也返回数组,便于统一遍历(见 JsonCrawlerInterface.php 的接口定义)。
核心 API:从输入到结果的三步流水线
JsonCrawler的内部处理可以概括为"构造校验 → 词法解析 → 逐 token 求值"三步,全部体现在 JsonCrawler.php 的evaluate()私有方法中:
- 输入校验:构造函数只接受
string或resource,否则抛出InvalidArgumentException(JsonCrawler.php 第 62-64 行)。 - 词法解析:将 JSONPath 字符串交给
JsonPathTokenizer::tokenize(),产出一系列JsonPathToken(类型为Name、Bracket、Recursive)。 - 求值:把 JSON 通过
json_decode(..., JSON_THROW_ON_ERROR)解码后,由evaluateTokensOnDecodedData()依序对每个 token 执行evaluateToken(),把上一轮的输出作为下一轮的输入,最终归一化存储结构后返回。
find()方法同时接受字符串和JsonPath对象两种入参(find(string|JsonPath $query): array),字符串会被自动包装成JsonPath对象(JsonCrawler.php 第 67-70 行)。
异常体系
组件为不同的错误场景设计了专门的异常,全部位于Symfony\Component\JsonPath\Exception命名空间:
InvalidJsonPathException:JSONPath 语法错误(词法阶段);InvalidJsonStringInputException:输入的 JSON 字符串无法解码;JsonCrawlerException:求值阶段的语义错误,会把原始查询语句连同错误信息一起抛出(JsonCrawler.php 第 133-137 行)。
比如$.store.book[0,1,]这类首尾带逗号的括号表达式会被直接拒绝("Expression cannot have leading or trailing commas");True/False/Null这种错误大小写的字面量也会在词法校验阶段报错,因为 RFC 9535 要求小写的true/false/null。
JSONPath 语法速查:从源码看每条规则的实现
下面按选择器类型逐一说明,每条都对应 JsonCrawler.php 中evaluateBracket()的具体分支。
1. 属性名与点号导航
$.store.book用点号访问对象属性;*作为属性名时展开当前对象/数组的全部值(evaluateName()中'*' === $name的分支,JsonCrawler.php 第 210-211 行)。词法器对属性名的字符集有严格约束:只允许字母、数字、下划线及高位 Unicode 字符,不能以数字开头(见 JsonPathTokenizer.php 第 260 行 的正则校验)。
2. 数组索引:正数、负数、多索引
- 正索引:
[0]取第一个元素;索引超出范围时返回空数组而不是报错。 - 负索引:
[-1]从数组末尾倒数的第一个元素,等价于count($value) + $index(JsonCrawler.php 第 242 行)。注意-0是非法的。 - 多索引:
[0,1]同时取多个位置的元素,结果按书写顺序返回;['key1','key2']也可以一次取多个对象属性。
索引校验很严格:前导零(如[01])和整数溢出(超出-(2^53)+1到(2^53)-1的安全整数区间)都会被拒绝——这正是 RFC 9535 第 2.1 节的要求,实现在 JsonPathUtils.php 的hasLeadingZero()与isIntegerOverflow()。
3. 切片(slice)
[start:end]与[start:end:step]支持数组切片,语义与 Python 切片一致:[1:3]取下标 1 到 2;[::-1]反向遍历;步长为 0 时返回空数组。RFC 9535 同样禁止切片数字使用前导零、负零或溢出值。切片只对列表型数组(array_is_list())生效,对对象直接返回空数组(JsonCrawler.php 第 290-363 行)。
4. 递归下降(deep scan)
..表示递归下降:$..author会收集文档任意层级中所有名为author的属性值。词法器把..解析为TokenType::Recursive,并且不允许路径以..结尾("descendant segment must be followed by a selector",JsonPathTokenizer.php 第 271-273 行)。求值侧evaluateRecursive()采用深度优先遍历,把遇到的每个对象/数组都纳入结果(JsonCrawler.php 第 809-827 行)。
5. 过滤器表达式:[?(...)]
这是最强大的选择器。求值引擎evaluateFilterExpression()支持:
- 比较运算:
==、!=、>、>=、<、<=。数值、字符串、布尔、null按类型分别比较;字符串比较使用strcmp。Nothing(属性缺失)与0相等、Nothing与Nothing相等,这是 RFC 9535 比较语义的体现(JsonCrawler.php 第 839-884 行)。 - 逻辑运算:
&&、||,以及一元取反!。解析时通过findRightmostLogicalOperator()寻找最右侧运算符来保证结合顺序(JsonCrawler.php 第 616-655 行)。 - 当前节点引用:
@表示当前元素,@.category访问其属性,@['a','d']批量访问属性。 - 绝对路径引用:
$开头可引用文档根(如在过滤器中比较两个远端节点)。 - 函数调用:
match()、search()等(见下一节)。 - 复杂混合表达式:
[?@.a, ?@.b]、[1, ?@.a=='b']这种"过滤器 + 普通选择器"混合的括号表达式也有专门分支支持(isValidMixedBracketExpression(),JsonCrawler.php 第 365-383 行)。
严格性:过滤器中的字面量必须参与比较([?(@.a == 1)]合法,[?(true)]被拒),裸函数调用必须与结果比较("Function result must be compared."),非单一查询(singular query,即包含*、切片、多索引、递归下降的查询)不能参与比较("non-singular query is not comparable")。这些规则在 JsonPathTokenizer.php 的validateBareLiterals()中统一把关。
内置函数:length、count、value、match、search
RFC 9535 定义了五个内置函数,组件全部实现,参数个数由 JsonPathTokenizer.php 第 26-32 行 的RFC9535_FUNCTION_ARITY常量约束:
| 函数 | 参数个数 | 行为 |
|---|---|---|
length(@.title) | 1 | 字符串长度(mb_strlen)、数组元素个数或对象属性个数;不适用时返回Nothing |
count($.store.book) | 1 | 参数查询的节点列表大小;参数必须是查询而非字面量 |
value(@.price) | 1 | 返回节点的值,仅当节点列表大小为 1 时有效 |
match(@.author, "[A-Z].*el.+") | 2 | 正则完整匹配(等价于^...$) |
search(@.title, "Sword") | 2 | 正则部分搜索 |
函数求值逻辑集中在 JsonCrawler.php 的evaluateFunction()。值得注意的细节:
match/search是单一参数函数(SINGULAR_ARGUMENT_FUNCTIONS),参数必须是 singular query,非单一查询会抛异常。- 正则按 RFC 9485 的规则转换后执行:例如
.在字符类外被替换为[^\r\n],避免匹配换行(transformJsonPathRegex(),JsonCrawler.php 第 1207-1229 行)。 - 正则执行前会把
pcre.backtrack_limit临时设为 10000,防止灾难性回溯(REGEX_BACKTRACK_LIMIT常量)。 count()不接受字面量参数,length/match/search要求参数是 singular query,词法阶段即校验(JsonPathTokenizer.php 第 424-450 行)。
// 组合示例:查找价格超过 10 的图书数量 $result = $crawler->find('$.store.book[?(@.price > 10)].title'); // 查找书名中包含 "word" 的图书 $result = $crawler->find('$.store.book[?search(@.title, "word")]');编程式构建路径:JsonPath 值对象
除了直接书写查询字符串,组件还提供不可变的JsonPath值对象,用链式方法安全地构造路径,避免手写字符串的转义与拼接错误(见 JsonPath.php):
use Symfony\Component\JsonPath\JsonPath; // 等价于 $.store.book[2].title $path = (new JsonPath('$')) ->key('store') ->key('book') ->index(2) ->key('title');可用方法一览:
| 方法 | 生成的路径 | 说明 |
|---|---|---|
key('store') | $["store"] | 访问属性,自动做 JSON 转义(\、"、换行、控制字符等) |
index(2) | $[2] | 按索引访问 |
deepScan() | $.. | 追加递归下降 |
all() | $[*] | 展开全部元素 |
first()/last() | $[0]/$[-1] | 首/尾元素 |
slice(1, 4)/slice(0, -1, 2) | $[1:4]/$[0:-1:2] | 切片 |
filter('@.price > 10') | $[?(@.price > 10)] | 过滤器 |
$result = $crawler->find( (new JsonPath('$'))->key('store')->key('book')->filter('@.price > 10') );key()的转义逻辑(JsonPath.php 第 84-103 行)会把换行、制表符、双引号、反斜杠及控制字符逐一转换为 JSON 转义序列,含特殊字符的属性名也能安全纳入路径。
流式处理:对资源流执行查询
JsonCrawler的构造函数接受resource输入。当 JSON 体积很大、不希望一次性加载进内存时,组件会尝试只反序列化路径实际需要的片段:
- 词法器先把路径解析成 token 序列;
- JsonPathUtils::findSmallestDeserializableStringAndPath() 借助
symfony/json-streamer的Splitter(splitDict/splitList)在流上逐层定位目标键/索引的字节边界,把 JSON 裁剪到最小可反序列化片段; - 剩余 token 在裁剪出的片段上继续求值。
$handle = fopen('large.json', 'r'); // 大文件以资源流传入 $crawler = new JsonCrawler($handle); $result = $crawler->find('$.store.book[0].title'); fclose($handle);如果路径中出现了递归下降、过滤器等无法局部裁剪的 token,或裁剪中途失败,代码会优雅回退到整流读取(JsonCrawler.php 第 99-123 行)。前提是安装了symfony/json-streamer——未安装且传入资源流时会抛出LogicException提示先执行composer require symfony/json-streamer。另外,传入资源流时输入流会被 rewind,因此请使用可回绕的流(如文件流、内存流)。
工厂模式:JsonPathCrawler
JsonPathCrawler是一个轻量工厂(JsonPathCrawler.php),它封装可复用的函数提供器与函数元数据,每次通过crawl($raw)生产新的JsonCrawler实例:
$pathCrawler = new JsonPathCrawler($functionsProvider, $functionsMetadata); $jsonCrawler = $pathCrawler->crawl($jsonString); $result = $jsonCrawler->find('$.store.book[0].title');扩展自定义函数
JsonCrawler构造函数的第二、三个参数支持自定义函数:
$functionsProvider:一个 PSR 容器/服务提供器,以函数名为键提供callable;$functionsMetadata:描述每个自定义函数的arity(参数个数)与return_type(FunctionReturnType枚举:Value或其他)。
自定义函数结果参与比较时受返回类型约束:Value类型的函数结果不能用于测试表达式(test expression),非Value类型不能用于比较——这两条分别由validateFunctionTestReturnType()与validateFunctionReturnType()把关(JsonCrawler.php 第 1158-1188 行)。自定义函数执行时抛出的异常会被包装为InvalidJsonPathException并附上函数名与错误信息(JsonCrawler.php 第 780-786 行)。
安全防护:防滥用与防崩溃
组件在求值引擎里内置了三道安全护栏,均定义在 JsonCrawler.php 顶部常量:
| 常量 | 默认值 | 作用 |
|---|---|---|
REGEX_BACKTRACK_LIMIT | 10000 | 限制match/search正则回溯量,防灾难性回溯 |
MAX_FILTER_EXPRESSION_LENGTH | 10000 | 过滤器表达式最大长度 |
MAX_FILTER_EXPRESSION_DEPTH | 100 | 过滤器表达式最大嵌套深度 |
超出长度或深度限制的过滤器会抛出JsonCrawlerException("filter expression is too long or too deeply nested")。对pcre.backtrack_limit的修改会在finally块中恢复,不影响进程其余部分(JsonCrawler.php 第 1190-1200 行)。
合规性:对齐 RFC 9535 与 JSONPath 测试套件
标准对齐
整个组件的语法与语义均以 RFC 9535 为基准:词法器要求表达式必须以$开头、属性名遵循 JSON 命名规则、引号字符串内的控制字符必须转义、Unicode 代理对必须成对出现(validateUnicodeEscape(),JsonPathTokenizer.php 第 567-631 行);求值侧的数字格式、比较规则、singular query 约束也逐条对照 RFC。因此,熟悉 RFC 9535 的开发者可以无缝迁移到该组件。
JSONPath 合规测试套件
组件通过与 JsonPathComplianceTestSuiteTest.php 跑通社区维护的 JSONPath 合规测试套件(CTS)来验证标准一致性。该套件被声明为 composer 仓库依赖(见 composer.json 第 18-30 行),测试从vendor/jsonpath-standard/jsonpath-compliance-test-suite/cts.json读取用例,分别用字符串输入与资源流输入两种模式执行,断言结果包含在预期集合中、且非法选择器必须抛出JsonCrawlerException(JsonPathComplianceTestSuiteTest.php 第 23-68 行)。如果尚未执行composer update导致套件缺失,相关用例会被跳过并给出提示。
如何更新合规测试套件
上游 CTS 仓库有新提交时,README 给出了标准同步流程,这里整理为可执行清单:
- 将
jsonpath-standard/jsonpath-compliance-test-suite的reference字段更新为最新的提交哈希(位于 JsonPath 组件的 composer.json 的repositories段); - 将
version字段更新为该提交的日期(例如当前仓库中的2025.11.23); - 对symfony/symfony 仓库根目录的 composer.json重复上述两步(根文件同样内嵌了该套件的引用);
- 执行
composer update拉取新版本套件; - 运行测试(
phpunit),确保JsonPathComplianceTestSuiteTest全部通过。
通过这套流程,组件可以持续跟踪 JSONPath 标准社区的最新行为定义,任何语法语义的漂移都会在测试中立刻暴露。
小结
Symfony JsonPath 组件把 RFC 9535 标准完整地工程化:JsonCrawler提供字符串与资源流双入口的find()查询;JsonPath值对象支持安全的链式路径构建;内置length/count/value/match/search函数与可扩展的自定义函数体系覆盖了绝大多数 JSON 导航场景;加上 CTS 合规套件的持续校验,开发者可以放心用它替代手写递归遍历代码。对于解析大型 JSON 的场景,配合symfony/json-streamer的局部反序列化还能显著降低内存占用——这使它成为 Symfony 生态中处理 JSON 结构化查询的实用选择。
相关参考文件:
- 组件入口与核心实现:JsonCrawler.php、JsonCrawlerInterface.php
- 路径构建对象:JsonPath.php
- 词法解析器:JsonPathTokenizer.php
- 工具方法与资源流裁剪:JsonPathUtils.php
- 工厂与依赖注入入口:JsonPathCrawler.php
- 依赖与合规套件声明:composer.json
- 合规测试:JsonPathComplianceTestSuiteTest.php
- 后端
- Web框架
【免费下载链接】symfony
The Symfony PHP framework
相关推荐
基于 encoding/json 的流式 JSON 路径导航:exponent-io/jsonpath 深入解析与实战
基于 encoding/json 的流式 JSON 路径导航:exponent io/jsonpath 深入解析与实战 导读 exponent io/jsonp
云原生集群管理虚拟化多集群KubeSphere 仓库中的 exponent-io/jsonpath:基于流式 Token 的 JSON 路径导航与提取实战指南
KubeSphere 仓库中的 exponent io/jsonpath:基于流式 Token 的 JSON 路径导航与提取实战指南 导读 本文围绕 KubeS
虚拟化桌面应用图形学ACE-Step UI完整指南:如何免费生成媲美Suno的专业AI音乐
ACE Step UI完整指南:如何免费生成媲美Suno的专业AI音乐 还在为Suno和Udio的订阅费用烦恼吗?想要完全免费、本地运行的AI音乐生成方案?AC
人工智能AI 应用音频媒体生成本地部署前端后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考