前言
XML曾经一度成为互联网传输数据的事实标准。第一代Web服务很大程度是以XML为基础。但是,有人认为XML过于冗杂和啰嗦。
2006年,Douglas Crockford 在国际互联网工程任务组(IETF)指定了JavaScript简谱(JSON, JavaScript Object Notation)标准,即RFC 4627。但实际上,JSON在2001年就开始使用了。JSON是JavaScript的严格子集,利用JavaScript中的几种模式来表示结构化数据。Crockford将JSON作为XML替代的一个方案提出,因为JSON可以直接传给eval()而不需要创建DOM。
理解JSON最关键的一点是要把它当成一种数据格式,而不是变成语言。JSON不属于JavaScript,它们只是拥有相同的语法而已。JSON也不是只能在JS中使用,它是一种通用数据格式。很多数据都有解析和序列号JSON的内置能力。
理解JSON语法
JSON语法支持三种类型的值。
- 简单值:字符串、数值、布尔值和
null。不支持undefined - 对象:有序键/值对,键是字符串,值可以是1,2,3中的任何一种。
- 数组:通过数值索引访问的有序列表,值可以是1,2,3中的任何一种
总结就是键只能是字符串,值可以是JS中的七种类型中除去 undefined和symbol。对象中的最后一个键/值对不能有逗号。
简单值
如 5,"wuzequn"这种都是简单值,与JS的差别是JSON中的字符串必须用双引号包起来,无论是作为属性还是作为值。
对象
JSON与JS对象的主要差别是:
- 属性必须用双引号包起来
- 最后一组键值对不能带逗号
- 没有分号
数组
语法规则在除了前两者的区别外基本和JS一致
解析JSON
JSON的迅速流行不仅仅因为其语法与JS类似,很大程度还因为JSON可以被直接解析成可用的JS对象。与解析为DOM的XML相比,这个优势可以说是碾压。为此,JS开发者可以很方便的使用JSON数据。因此,JSON出现后就迅速成为了Web服务的事实序列化标准。
JSON对象
早期的JSON解析器基本上相当于JS的eval函数。因为JSON是JS语法的子集,所以eval()可以解析、解释,并将其作为对象或数组返回。ES5新增了JSON全局对象,正式引入解析JSON的能力。这个对象在所有主流浏览器包括Node中都得到了支持。其兼容性如下:
JSON对象有两个方法:
stringify()和 parse(),这两个方法分别将JS序列化成JSON字符串,以及将JSON解析为原生JS值。例如:
const t = {
a: 1,
b: 2,
c: '3'
}
console.log(JSON.stringify(t));
结果为:
{"a":1,"b":2,"c":"3"}
如果将序列化的结果作为参数传入parse(),可以得到一个原对象的深克隆。
注意这种深克隆被网友戏称为破产版的深克隆,因为其有如下两个致命缺点:
- 如果存在循环引用将会抛出异常
- 不支持正则、函数等复杂数据类型
序列化选项
JSON.stringify()方法除了接收要序列化的对象,还可以接受两个参数。这两个参数可以用于指定序列化JS对象的方式。第一个参数是过滤器,类型可以是数组或函数,第二个参数用于控制结果的缩进。如下:
过滤器
对于如下代码
const book = {
title: 'NB',
edition: 4,
year: 2021,
authors: ['wzq','rock']
}
console.log(JSON.stringify(book,(key,val)=>{
switch(key) {
case 'authors':
return val.join(',')
case 'year':
return 5000
case 'edition':
return undefined
default:
return val
}
}));
输出结果为:
{"title":"NB","year":5000,"authors":"wzq,rock"}
从上面的结果可以看到,值为undefined的属性直接被删掉了,这也从另一个维度解释了为什么JS对于对象不存在的属性输出结果为undefined。
缩进
JSON.stringify()的第三个参数可以控制缩进和空格,这也是为什么经常能看到这种代码:
JSON.stringify(a,null,4);
的原因。
其实第三个参数除了是数字(表示几个空格),还可以是字符串,表示用这个字符串缩进,如:
const book = {
title: 'NB',
edition: 4,
year: 2021,
authors: ['wzq', 'rock']
};
console.log(
JSON.stringify(
book,
(key, val) => {
switch (key) {
case 'authors':
return val.join(',');
case 'year':
return 5000;
case 'edition':
return undefined;
default:
return val;
}
},
'----'
)
);
输出:
{
----"title": "NB",
----"year": 5000,
----"authors": "wzq,rock"
}
注意这个字符串的长度不能超过10,超出部分将被截断,如果传入的数字大于10,则会自动设置为10。
toJSON
toJSON方法可以用来自定义JSON序列化的方式。经典的例子就是Date对象的toJSON方法,可以将JS的Date对象转换为ISO 8601日期字符串(本质上与调用toIOSString()一致)
const book = {
title: 'NB',
edition: 4,
year: 2021,
authors: ['wzq', 'rock'],
toJSON() {
return this.title;
}
};
如果对book对象序列化,得到的是字符串NB。
toJSON()方法可以返回任意序列化值,都可以起到相应的作用。如果对象被嵌在另一个对象中,则undefined会变为null
toJSON()方法可以与过滤函数一起使用,其二者的执行顺序如下:
- 如果可以获取实际的值,则调用
toJSON方法获取实际的值,否则使用默认的序列化。 - 如果提供了第二个参数,则应用过滤。传入过滤函数的值就是第1步返回的值。
- 第2步返回的每个值都会进行相应地进行序列化。
- 如果提供了第三个参数,则相应地进行缩进
看如下代码:
const p = {
r: 2,
q: {
r: 1,
toJSON: () => {
return this.r;
}
}
};
console.log(
JSON.stringify(
p,
(key, val) => {
switch (key) {
case 'r':
return 'r';
default:
return val;
}
},
4
)
);
输出结果:
{
"r": "r"
}
JSON.stringify 使用的6个tips
为了方便查看JSON.stringify的输出结果,这里封装了一个stringify函数
/**
* @description 打印JSON.stringify后的对象
* @param {any} obj
*/
function stringify(obj) {
console.log(JSON.stringify(obj));
}
1、数组中的undefined、函数、symbol值会被转化成null,对象中的会被忽略,直接转化会成undefined
stringify({a: undefined, b: 1}); // {"b":1}
stringify({a: () => {}}); // {}
stringify({a: Symbol(1)}); // {}
stringify({[Symbol(1)]: 1}); // {}
stringify([undefined, () => {}, Symbol(1)]); // [null,null,null]
stringify(() => {}); // undefined
stringify(Symbol(1)); // undefined
stringify(undefined); // undefined
2、boolean、number、string 的包装对象会自动转换成原始值
stringify([new Boolean(1), new Number(1), new String(1)]); // [true,1,"1"]
stringify({a: new String(1), b: new Boolean(1), c: new Number('1')}); // {"a":"1","b":true,"c":1}
3、NaN Infinity null 都会被当成null
stringify([null, NaN, Infinity]); // [null,null,null]
stringify(NaN); // null
stringify(Infinity); // null
stringify({a: null, b: NaN, c: Infinity}); // {"a":null,"b":null,"c":null}
4、Date对象会自动被转化成字符串
stringify(new Date()); //"2021-12-23T08:56:38.396Z"
stringify([new Date()]); // ["2021-12-23T09:42:16.369Z"]
stringify({a: new Date()}); // {"a":"2021-12-23T09:42:16.369Z"}
5、Set,Map 对象会变成{}
stringify(new Set([1, 2, 3])); // {}
const map = new Map();
map.set('stringify', 1);
stringify(map); // {}
stringify([map]); // [{}]
stringify({a: map}); //{"a":{}}
6、BigInt对象转换报错
stringify(BigInt(9007199254740991)); // Do not know how to serialize a BigInt
解析选项
JSON.parse()也可以接受一个额外的参数receiver,类型为函数,这个函数会对每个键值对都调用一次。
receiver函数如果返回
undefined,则结果会把相应的键删除。如果返回其他任何值,则该值就会成为相应键的值插入到结果中。receiver经常用于把日期字符串转换为Date对象。如:
const day = {
week: 2,
now: "2021-12-14"
}
const dayString = JSON.stringify(day)
console.log(dayString);
const dayCopy = JSON.parse(dayString,(key,val)=> key === 'now' ? new Date(val) : val);
console.log(dayCopy.now.getFullYear());
输出:
{"week":2,"now":"2021-12-14"}
2021
tips
JSON.parse加上try catch比较保险
JSON5
JSON for Humans
这是引用自json5.org的一句话,意译过来就是更人性化的json
特性总结
JSON5支持了以下JSON不支持的ECMAScript5.1特性。
对象
- 对象的键可以是任何ES 5.1 的合法标志符
- 对象可以有尾逗号
数组
- 数组可以有尾逗号
字符串
- 字符串可以是单引号包裹
- 可以通过换行符支持多行字符串
- 字符串可以包含转义字符
数字
- 数字可以是16进制
- 数字可以有前导小数点和尾部小数点
- 数字可以是 IEEE 754的正无穷(
Infinity)负无穷(-Infinity)以及NaN - 数字可以以
+开头
注释
- 单行注释和多行注释都支持
空格
- 支持多余的空格
示例
{
// comments
unquoted: 'and you can quote me on that',
singleQuotes: 'I can use "double quotes" here',
lineBreaks: "Look, Mom! \
No \\n's!",
hexadecimal: 0xdecaf,
leadingDecimalPoint: .8675309, andTrailing: 8675309.,
positiveSign: +1,
trailingComma: 'in objects', andIn: ['arrays',],
"backwardsCompatible": "with JSON",
}
更多细节请自行查看官方文档
总结
- JSON的语法是JS的子集,值不能为
undefined,可以是字符串、数值、布尔值、对象和null,键只能为带引号的字符串。 JSON.stringify()可以接受三个参数,第一个是要被序列化的JS对象;第二个是一个函数replacer,其参数为key和value,可以指定key返回的value;第三个是缩进格式,数字代表空格数量,字符串则用相应的字符串填充缩进,注意缩进长度不能超过10。- 如果想自定义对象的序列化结果可以在该对象上添加
toJSON()方法,序列化时会基于这个方法返回适当的JSON表示。 JSON.parse()可以接收两个参数,第一个参数是被序列化的JSON字符串,第二个参数是receiver函数,参数也是key和value。receiver函数如果返回undefined则该键会被删除,返回其他的会被作为对应键的值插入到结果中。JSON5比JSON更加灵活,是JSON的超集,支持很多ES5.1的语法