你不知道的 JSON API

1,175 阅读5分钟

原文链接:宿雪冷音

JSON

历史

道格拉斯·克罗克福德(没错,就是那个提出原型式继承的人)在2001年发现了 JSON 。道格拉斯自称发现,而不是发明,是因为它本来就存在。实际上,早在1996年,就已经出现了 JSON 的雏形。
JSON 是一种语法,用来序列化对象、数组、数值、字符串、布尔值和 null 。它基于 JavaScript 语法,它的语法是 JavaScript 表达式语法的一个子集。
它需要遵循下面两个原则:

  • 字符串必须使用双引号,字符串字面量是无效的('obj'
  • 属性键也必须使用双引号。

JSON.stringify()

使用方法为JSON.stringify(value, replacer?, space?)

可选参数replacer

用于转换前替换参数value。具体如下:

  • 节点访问函数,会在值被转为字符串之前转换树节点的值:
//序列化时,碰到数值,则乘以2
function replacer(key, value){
    if(typeof value === 'number'){
        value = 2 * value
    }
    return value
}
//调用
JSON.stringify({ a: 5, b: [2, 3] }, replacer)
//结果
"{"a":10,"b":[4,6]}"
  • 属性键白名单,用于隐藏那些非数组对象内属性不在这个列表中的所有属性:
JSON.stringify({ foo: 1, bar: {foo: 1, bar: 1} }, ['bar'])
//结果
"{"bar":{"bar":1}"
//对数组来说是无效的
JSON.stringify([2, 4], ['0'])
//结果
"[2,4]"

可选参数space

它会影响输出格式,可以插入新行并通过数组和对象的嵌套增加缩进:

数字

如果是一个数字, 则在字符串化时每一级别会比上一级别缩进多这个数字值的空格,小于0解释成0,大于10解释成10:

JSON.stringify({ foo: 1, bar: {foo: 1, bar: 1} }, null, 4)
//输出
"{
    "foo": 1,
    "bar": {
        "foo": 1,
        "bar": 1
    }
}"

字符串

如果是一个字符串,则每一级别会比上一级别多一个用该字符串形成的缩进(或该字符串的前十个字符):

JSON.stringify({ foo: 1, bar: {foo: 1, bar: 1} }, null, "----")
//输出
"{
----"foo": 1,
----"bar": {
--------"foo": 1,
--------"bar": 1
----}
}"

JSON.stringify 忽略的数据

  • 只考虑自身可枚举属性
var obj = Object.defineProperty({}, 'foo', {enumerable: false, value: 7})
JSON.stringify(obj) // "{}"
  • 忽略不被支持的值,即除了对象、数组、数值、字符串、布尔值和 null以外的任何值。如函数,Symbol值,undefined等,将返回undefined。如果属性值是这些值,该属性直接被忽略,在数组中被解析成null
JSON.stringify(function (){}) // undefined
JSON.stringify({foo: function (){} }) // "{}"
JSON.stringify([function (){}]) // "[null]"

toJSON(key)方法

如果一个被序列化的对象拥有 toJSON 方法,那么该 toJSON 方法就会覆盖该对象默认的序列化行为:不是那个对象被序列化,而是调用 toJSON 方法后的返回值会被序列化:

var x = {
    toJSON: function (){
        return {x:1}
    }
}
JSON.stringify(x) // "{"x":1}"
JSON.stringify({a: x}) // "{"a":{"x":1}}"

toJSON()方法可以接收一个位置参数key,它始终是字符串,有以下值:

  • 根位置,""
  • 属性值,值为属性键
  • 数组元素,值为索引字符串
var x = {
    toJSON: function (key){
        console.log(key)
        return 1
    }
}
JSON.stringify(x) // 打印 ""
JSON.stringify({a: x}) // 打印 "a"
JSON.stringify([x]) // 打印 "0"

JSON.parse()

使用方法JSON.parse(text, reviver?)

JSON.parse(""string"") // Uncaught SyntaxError: missing ) after argument list
JSON.parse('"string"') // "string"
JSON.parse('1') // 1
JSON.parse('[1]') // [1]
JSON.parse('[1,]') // Uncaught SyntaxError。不允许用逗号作为结尾
JSON.parse('{"x":1}') // {x:1}

""stirng""是不被js支持的,尽管虽然是标准的JSON字符串。你可以使用'"string"'代替。如果确实需要这样的形式,可以使用JSON.stringify("")

revier 参数

它是一个节点访问函数。它可以用来转换解析后的数据:

//转换JSON字符串中的日期
function dateReviver(key, value){
    if (typeof value === 'string') {
        var x = Date.parse(value)
        if (!isNaN(x)) {
            return new Date(x)
        }
    }
    return value
}
var str = '{ "name": "suxue", "date": "2019-04-21T22:00:00.00z"}'
JSON.parse(str, dateReviver) // {name: "suxue", date: Mon Apr 22 2019 06:00:00 GMT+0800 (中国标准时间)}

节点访问函数

JSON.parse()JSON.stringify()都可以传入一个函数转换数据:

  • JSON.stringify()可以在转换成JSON前改变数据。
  • JSON.parse()解析JSON,并可以后处理生成的结果数据。

节点访问函数结构如下:

function nodeVisitor(key, value) // this 指向当前节点的父元素

key值和toJSON()接收的key是一样的。 根节点root是不存在父元素的。当root被访问时,一个伪父元素被创建,此时的参数值是:

  • this 指向 {'': root},root 指根节点
  • key''
  • valueroot
function nodeVisitor(key, value) {
    console.log(this, key, value)
    return value
}
//第一次 this: {"":{x:1}}, key:"", value:{x:1}
// 第二次 this: {x:1}, key : "x", value: 1
JSON.stringify({x:1},nodeVisitor)
// 第一次:this: {"":{x:1}} key:"" value:{x:1}
// 第二次:this:{a: 1} key: "x" value :{a:1}
// ...
JSON.stringify({x:{a:1}},nodeVisitor)

节点访问函数必须指定其返回值:

  • 返回value,不做任何修改。
  • 返回一个不同值,替换当前节点。
  • 返回undefined,此时当前节点被删除。
//不指定返回值,根节点直接被删除
function nodeVisitor(key, value) {
    console.log(this, key, value)
}
JSON.stringify({x:1},nodeVisitor) // undefined

利用节点访问函数,你可以检查JSON方法是如何遍历数据的。

JSON 如何访问数据

JSON.stringify()

采用前序遍历算法(父元素先于子元素),先访问特殊的根节点。

JSON.stringify({x:1},nodeVisitor)// 看value值顺序 {x:1} => 1
JSON.stringify({x:{a:1}},nodeVisitor)// {x:{a:1}} => {a:1} => 1

JSON.parse()

采用的后序遍历算法(子元素先于父元素),叶节点先被访问。

JSON.parse('{"x":1}', nodeVisitor) // 看value值顺序 1 => {x:1}
JSON.parse('{"x":{"a":1}}', nodeVisitor) // 1 => {a:1} => {x:{a:1}