在空格之后使用正则表达式前瞻
我在尝试编写一个简单的脚本来处理JSON5——思路是把它转换成标准JSON,然后让 JSON.parse() 处理剩下的部分。我使用正则表达式来完成转换。
其中有一部分是处理未加引号的键。对于这种情况,我用双引号把未加引号的键括起来。下面给出一个带有示例的片段:
var sample = `
unquoted: "this is the first",
"quoted": "this is the second"
`;
var result = sample.replaceAll(/^\s*(?!")(.*?):/gm, '[$1]');
console.log(result);
// Result:
// [unquoted] "this is the first",
// [ "quoted"] "this is the second"
我知道它还没有写完,因为还没有对第二个双引号的测试。在片段中,我用方括号来代替双引号,以便跟踪发生了什么。
我觉得需要使用负向前瞻来定位未加引号的键。
问题在于,负向前瞻似乎与前面的 \s* 项发生冲突。正则表达式能正确识别出第一个未加引号的键,但却把第二个被引号包围的键前面的空格当作表达式的一部分。如果把该项改成 \s\s,就可以工作,但(a)我不能确定每行开头有多少空格,以及(b)它会错误地识别未加引号的键。
在空格之后使用负向前瞻的技巧是什么?
注: 这个问题并不是关于如何解析JSON5,因此也没有在标签中提及。它关乎正则表达式,以及前瞻在这种情况下的行为。
解决方案
当你的正则表达式 ^\s*(?!")(.*?): 在一个以若干空格开头、随后是一个双引号,并且后面还存在某处冒号的字符串上执行时,情况如下:
- 首先,运行引擎用
\s*捕获所有起始空格 - 然后负向前瞻
(?!")失败,因为下一个输入确实是",于是发生回溯,回退一个位置,减少在步骤1 中\s*已经匹配的内容。 - 现在负向前瞻成功,因为下一个输入字符就是回退到的位置的空格。
- 剩余的输入用
(.*?):读取,直到包括第一个冒号在内的部分。
为了避免匹配到,你可以采取几种做法:
- 将
(?!")改成(?!["\s]),这样回溯会一直进行,直到不再存在可回溯的情况,此时就不会有匹配。 - 不进行负向前瞭瞭前瞻,而是直接 消费 不是双引号的字符(也不是空格),像这样
^\s*([^"\s].*?):。 - 鉴于未加引号的键不能包含空格或双引号(以及其他限制),你也可以把选项2 简化为
^\s*([^"\s:]*):。
不过,请注意这并不能覆盖所有可能的键匹配情况:
- 键并不一定每个都在单独的一行。例如,
{ a: 1, b: 2 }是有效的JSON5,而你的正则表达式可能不会匹配b:。 - 冒号前可能有空白字符,这些并不属于键。
- JSON5可能有注释块,字符串字面量也可能跨多行(通过在行末用反斜杠来结束行),因此在行首你可能会匹配到包含冒号的文本,从而错误地将其识别为键。
- JSON5也可能存在单引号的键,你也应该将其转换,并确保其中的双引号在尚未被转义时会被正确转义。
总的来说,最好为此使用一个经过充分测试的库。例如,json5.org 就提供了一个这样的库。
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。