likes
comments
collection
share

真的了解JSON.parse方法吗?

作者站长头像
站长
· 阅读数 20

问题引出

const str = "{\"error\":false,\"#result\":\"\\\"\"}"
const obj = JSON.parse(str) // {"error": false, "#result": "\""}
console.log(obj['#result']) //  '"'

从输出结果上看,并不是我们所预期的。预期是希望 '\"'。 反引号去哪了?

JSON.parse 使用方法

JSON.parse() 方法用来解析 JSON 字符串,构造由字符串描述的 JavaScript 值或对象。提供可选的 reviver 函数用以在返回之前对所得到的对象执行变换 (操作)。

JSON.parse(text[, reviver])

text: 要被解析成 JavaScript 值的字符串,关于 JSON 的语法格式。

reviver 可选。转换器,如果传入该参数 (函数),可以用来修改解析生成的原始值,调用时机在 parse 函数返回之前。

官方举例:

JSON.parse('{}');              // {}
JSON.parse('true');            // true     变成了boolean类型
JSON.parse('"foo"');           // "foo"
JSON.parse('[1, 5, "false"]'); // [1, 5, "false"]。 "false" 还是字符串类型
JSON.parse('null');            // null.  变成了null类型
JSON.parse('\"');              // 会报错
JSON.parse('"\12222"')        //'R22'
JSON.parse("\"\12222\"");     // 'R22'

后边关于反斜杠结果已经出现有意思现象了。 下文中会做相关解释。

使用 reviver 函数

如果指定了 reviver 函数,则解析出的 JavaScript 值(解析值)会经过一次转换后才将被最终返回(返回值)。

  • 解析值本身以及其所包含的所有属性,会按照一定的顺序(从最里层开始,一层层向外,最终达到最顶层,也就是解析值本身)分别去调用reviver函数。在函数执行的过程中,this会指向当前属性所在的对象,当前属性名key和属性值value会作为第一个和第二个参数传入reviver函数中。如果当前函数执行返回的结果为undefined,则当前属性会从其所属的对象中删除,如果返回了其他值,则返回的值会成为当前属性新的属性值value。

  • 当遍历到最顶层,也就是解析值时,传入reviver函数的参数将是空字符串(此时已经没有真正的属性)和当前的解析值(有可能已经被修改),此时this的值就是{"": 解析值}。因此在编写reviver函数时,需要注意此特例。

  • 如果解析值中,有属性刚好为空字符串"",则该属性的值不会被reviver处理

JSON.parse('{"x": 1, "": 2}', function(k,v) {
    if (k === '') return v; // 如果遇到了空字符串的key,则直接返回属性值。
    return v + 1; // 其他情况,则属性值加1
}); // {x: 2, "": 2};

JSON.parse('{"1": 1,"2": {"3": 3,"4": ["5", {"6": 6}]}, "7": 7}', function(k, v) {
    console.log(k, this); // 输出当前的属性key,
    return v // 返回原始值,相当于是没有处理
})
// 1 第一层对象key 1,this指向第一层对象
// 3 第二层对象key 3,this指向第二层对象
// 0 第二层对象的数组index 0,this指向数组
// 6 第三层对象key 6,this指向第三层对象
// 1 第二层对象的数组index 1,this指向数组
// 4 第二层对象key 4,this指向第二层对象
// 2 第一层对象key 2,this指向第一层对象
// 7 第一层对象key 7,this指向第一层对象
//  // 最后一个属性会是一个空字符串,this指向解析值本身

真的了解JSON.parse方法吗?

最上层的key是 ""。

真的了解JSON.parse方法吗?

注意项:

JSON.parse() 不允许用逗号作为结尾
// both will throw a SyntaxError
JSON.parse("[1, 2, 3, 4, ]");
JSON.parse('{"foo" : 1, }');

JSON规范

JSON是一种语法,用来序列化对象、数组、数值、字符串、布尔值和null,它是基于JavaScript语法,但是又与之不同。也就是说Javascript不是JSON,JSON也不是Javascript,两者不可等同。为了便于梳理JSON规范的具体细则,见下表:

注:JSON字符串未说明,默认使用单引号

真的了解JSON.parse方法吗?

JSON.parse(''); // SyntaxError,待解析的字符串不能为空
JSON.parse('{"x": 1}'); // {x: 1}, 属性必须用双引号括起来
JSON.parse('{"x": 1,}'); // SyntaxError,最后一个属性不能有逗号
JSON.parse('[1,2,]'); // SyntaxError,最后一个属性不能有逗号
JSON.parse('001'); // SyntaxError, 数字禁止有前导0
JSON.parse('11a'); // SyntaxError,不符合数值格式
JSON.parse('1.'); // SyntaxError,如果有小数点,则小数点后至少有一位数字
JSON.parse('"\n"'); // SyntaxError,控制字符不能被解析
JSON.parse(null); // null
JSON.parse(undefined); // SyntaxError
JSON.parse(NaN); // SyntaxError
JSON.parse(Infinity); // SyntaxError

异常处理方法

使用JSON.parse()是很可能遇到抛出异常的,js遇到异常,则会停止执行后面的代码,导致页面显示不正常。一般来说处理异常有两种方式:

  • 判断是否符合JSON规范。JSON规范还是有点多的,每个都判断不是很现实,一般判断是否为undefined以及字符串是否为空就能解决大多数不符合JSON规范的情况

  • 使用try/catch,将可能会产生异常的JSON.parse()使用try/catch代码块包裹,并在catch中处理异常情况。

问题深究

JSON.parse 将一个 JSON 字符串转换为 JavaScript 对象。

JSON.parse('{"hello":"\world"}')
/*
{
  hello: "world"
}
*/

从结果上来看,"\world" 变成了 "world"。

继续运行相关代码

JSON.parse('{"hello":"\\world"}') 
// 会出现错误
//Uncaught SyntaxError: Bad escaped character in JSON at position 11
//    at JSON.parse (<anonymous>)
//    at <anonymous>:1:6

JSON.parse('{"hello":"\\\world"}') 
//三个的时候依然会出现错误

JSON.parse('{"hello":"\\\\world"}')
//四个的时候 结果如下:
/*
{
    "hello": "\\world"
}
*/

我们换个思路,把 JSON.parse 去掉,只输出 JavaScript 字符串:

> 'hello'
'hello'
> '\hello'
'hello'
> '\\hello'
'\\hello'
> '\\\hello'
'\\hello'
> '\\\\hello'
'\\\\hello'

把上面的规则带入到之前的 JSON.parse 代码,问题就解决了。

我们看看 JSON 的字符串解析规则:

真的了解JSON.parse方法吗?

根据这个规则,我们解析一下 "\hello",第 1 个字符是反斜杠(\),所以在引号后面走最下面的分支(红线标注):

第 2 个字符是 h,但是反斜杠后面只有 9 条路,这个不属于任何一条路,所以这个是个非法字符。

不只是 JSON,在很多语言中都会抛出类似 Error:(7, 27) Illegal escape: '\h' 的错误。

但是不知道为什么 JavaScript 偏偏可以解析这个非法转义字符,而解决方式也很暴力:直接忽略。

问题的根源就是** JavaScript 和 JSON 对转义字符的处理方式不同,导致了难以发现的 bug。JSON 遇到不能转义的字符直接抛出异常,而 JavaScript 遇到不能转义的字符直接解释为对应的非转义版本**。

本文引用: