Python中用正则匹配嵌套JSON指定路径的重复键
解决嵌套JSON中指定路径重复键的正则匹配问题(无灾难性回溯)
首先,你之前用的"a"[^}]*"key"方案有两个核心问题:一是会错误匹配嵌套层级里的同名键,二是[^}]*在处理大量内容时容易触发灾难性回溯。要解决这个问题,我们需要用平衡组/递归正则来精确跟踪JSON的嵌套层级,确保只匹配指定路径上的目标键。
核心思路
正则需要精确匹配每个路径段之间的内容,保证不会跨越未闭合的{}层级——也就是说,在找到下一个路径键之前,所有打开的{都必须被闭合,这样就能确保我们始终在目标路径的层级内,不会跑到嵌套的子对象里。
正则方案(分引擎)
不同正则引擎的平衡组写法略有不同,下面给出两种常用的实现:
1. .NET 正则引擎(支持命名捕获栈)
对于路径中的每个中间节点(比如a、b),我们用命名栈来计数{}的层级,确保匹配到的内容始终在当前层级内:
通用连接正则(MATCH_EVERYTHING_IN_BETWEEN_REGEX):
(?:(?:[^{}]|(?<open>\{)|(?<-open>\}))*(?(open)(?!)))
这个片段的作用是:
- 匹配非
{}的任意字符,或者遇到{就压入栈,遇到}就弹出栈 (?(open)(?!))确保栈为空时才停止匹配,也就是不会留下未闭合的{
2. PCRE 正则引擎(支持递归)
PCRE用递归模式匹配完整的嵌套对象,写法更简洁:
通用连接正则:
(?:(?:[^{}]|\{(?:[^{}]|(?R))*\})*)
这个片段的作用是:
- 匹配非
{}的任意字符,或者匹配一个完整的嵌套对象(通过递归匹配内部的{})
循环构建最终正则
你可以通过循环拼接路径段和连接正则来生成最终的匹配规则:
- 初始化正则字符串为空
- 遍历路径中的每个节点(除了最后一个):
- 拼接
"${当前节点}":\s* - 拼接上面的连接正则片段
- 拼接
- 最后拼接
"${最后一个节点}":\s*"((?:\\.|[^"\\])+)"(这里用(?:\\.|[^"\\])+替代[^"]+,支持转义双引号)
示例测试
用你给出的JSON字符串:
d = { "a": { "b": { "c": { "d": "v1", "key": "v2" } }, "c": { "g": "v3", "key": "v4" }, "key": "v5" } }
路径
a→key(.NET版本):"a":\s*(?:(?:[^{}]|(?<open>\{)|(?<-open>\}))*(?(open)(?!)))"key":\s*"((?:\\.|[^"\\])+)"匹配结果:捕获到
v5,对应a直接层级的key。路径
a→b→c→key(.NET版本):"a":\s*(?:(?:[^{}]|(?<open>\{)|(?<-open>\}))*(?(open)(?!)))"b":\s*(?:(?:[^{}]|(?<open>\{)|(?<-open>\}))*(?(open)(?!)))"c":\s*(?:(?:[^{}]|(?<open>\{)|(?<-open>\}))*(?(open)(?!)))"key":\s*"((?:\\.|[^"\\])+)"匹配结果:捕获到
v2,对应最深层级的key。路径
a→c→key(.NET版本):"a":\s*(?:(?:[^{}]|(?<open>\{)|(?<-open>\}))*(?(open)(?!)))"c":\s*(?:(?:[^{}]|(?<open>\{)|(?<-open>\}))*(?(open)(?!)))"key":\s*"((?:\\.|[^"\\])+)"匹配结果:捕获到
v4,对应a下c层级的key。
关键优势
- 无灾难性回溯:平衡组/递归是线性匹配,每一步都明确处理
{}层级,不会像[^}]*那样尝试大量无效回溯。 - 精确路径匹配:严格跟踪层级,不会错误匹配嵌套在子对象里的同名键。
- 可扩展:通过循环轻松构建任意深度的路径正则,适配无限嵌套的JSON。
注意事项
- 确保你的正则引擎支持平衡组/递归:比如.NET、PCRE、Python的
regex库(标准re库不支持)。 - 如果JSON中有转义字符(比如
\"),一定要用(?:\\.|[^"\\])+来捕获值,避免提前终止匹配。
内容的提问来源于stack exchange,提问作者loxosceles
相关产品推荐
相关产品推荐

