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
是''
value
是root
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}