你可能错过的关于 JSON 的一些事情
JSON 的属性
JSON 对象的基本用法
自定义 Stringify 和 Parse 的行为
深度复制对象
立即在http://jauyeung.net/subscribe/订阅我的电子邮件列表
在 Twitter 上关注我:https://twitter.com/AuMayeung
更多文章请访问https://medium.com/@hohanga
JSON 代表 JavaScript 对象表示法。它是一种序列化数据的格式,这意味着它可以用于在不同数据源之间传输和接收数据。在 JavaScript 中,有一个JSON
实用程序对象,它提供了将 JavaScript 对象转换为 JSON 字符串以及将 JSON 字符串转换为 JavaScript 对象的函数。该JSON
实用程序对象无法被构造或调用——只有 2 个静态方法: 和stringify
,用于parse
在 JavaScript 对象和 JSON 字符串之间进行转换。
JSON 的属性
JSON 是一种用于序列化对象、数组、数字、布尔值和 的语法null
。它基于 JavaScript 对象语法,但它们并不相同。并非所有 JavaScript 对象属性都能转换为有效的 JSON,并且 JSON 字符串必须格式正确才能转换为 JavaScript 对象。
对于对象和数组,JSON 属性名称必须使用双引号括起来的字符串,并且禁止使用对象的尾随逗号。数字不能以零开头,小数点后必须至少跟一位数字。不支持NaN
和Infinity
,JSON 字符串不能包含undefined
或 注释。此外,JSON 不能包含函数。
任何 JSON 文本都必须包含有效的 JavaScript 表达式。在某些浏览器引擎中,JSON 中的字符串字面量和属性键允许使用 U+2028 行分隔符和 U+2029 段落分隔符,但在 JavaScript 代码中使用它们会导致 SyntaxError。这两个字符可以用 解析JSON.parse
为有效的 JavaScript 字符串,但传入 时会失败eval
。
除了 JSONNumber 或 JSONString 之外,其他任何地方都可以包含无意义的空格。数字内部不能包含空格,字符串会被解释为字符串中的空格或导致错误。JSON 中唯一有效的空格字符是制表符 (U+0009)、回车符 (U+000D)、换行符 (U+000A) 和空格 (U+0020)。
JSON 对象的基本用法
实用程序对象上有两个方法JSON
。一个用于stringify
将 JavaScript 对象转换为 JSON 字符串,另一个parse
用于将 JSON 字符串转换为 JavaScript 对象。
该parse
方法将字符串解析为 JSON,并使用一个函数作为第二个参数,该函数可选地将 JSON 实体转换为您指定的 JavaScript 实体,并返回生成的 JavaScript 对象。如果字符串包含 JSON 语法中不允许的实体,则会引发 SyntaxError。此外,传递给 的 JSON 字符串中不允许使用尾部逗号JSON.parse
。例如,我们可以像以下代码中一样使用它:
JSON.parse('{}'); // {}
JSON.parse('false'); // false
JSON.parse('"abc"'); // 'abc'
JSON.parse('[1, 5, "abc"]'); // [1, 5, 'abc']
JSON.parse('null'); // null
第一行将返回一个空对象。第二行将返回false
。第三行将返回'abc'
。第四行将返回[1, 5, "abc"]
。第五行将返回null
。由于我们传入的每一行都是有效的 JSON,因此它返回了我们期望的结果。
自定义 Stringify 和 Parse 的行为
或者,我们可以将一个函数作为第二个参数传入,以便将值转换为我们想要的任何值。传入的函数将键作为第一个参数,值作为第二个参数,并在操作完成后返回该值。例如,我们可以这样写:
JSON.parse('{"a:": 1}', (key, value) =>
typeof value === 'number'
? value * 10
: value
);
然后我们得到{a: 10}
返回值。如果值的类型是数字,则函数返回原始值乘以 10。
该JSON.stringify
方法可以接受一个函数作为第二个参数,该函数将 JavaScript 对象中的实体映射到 JSON 中的其他内容。默认情况下,所有函数实例undefined
和不受支持的原生数据(例如函数)都会被移除。例如,如果我们编写以下代码:
const obj = {
fn1() {},
foo: 1,
bar: 2,
abc: 'abc'
}
const jsonString = JSON.stringify(obj);
console.log(jsonString);
然后,我们看到fn1
运行后 JSON 字符串中的 会被移除,JSON.stringify
因为 JSON 语法不支持函数。对于undefined
,我们可以从以下代码中看到undefined
属性将被移除。
const obj = {
fn1() {},
foo: 1,
bar: 2,
abc: 'abc',
nullProp: null,
undefinedProp: undefined
}
const jsonString = JSON.stringify(obj);
console.log(jsonString);
undefinedProp
不在记录的 JSON 字符串中,因为它已被删除JSON.strinfiy
。
NaN
而且,转换成JSON字符串后Infinity
都变成:null
const obj = {
fn1() {},
foo: 1,
bar: 2,
abc: 'abc',
nullProp: null,
undefinedProp: undefined,
notNum: NaN,
infinity: Infinity
}
const jsonString = JSON.stringify(obj);
console.log(jsonString);
我们看到:
'{“foo”:1,”bar”:2,”abc”:”abc”,”nullProp”:null,”notNum”:null,”infinity”:null}'
NaN
并且Infinity
都取代了null
原来的值。
对于不支持的值,我们可以使用 replacer 函数(可选传入第二个参数)将它们映射到支持的值。replace 函数将属性的键作为第一个参数,将值作为第二个参数。例如,保留 、 或 函数的一种方法NaN
是Infinity
将它们映射到字符串,如以下代码所示:
const obj = {
fn1() {},
foo: 1,
bar: 2,
abc: 'abc',
nullProp: null,
undefinedProp: undefined,
notNum: NaN,
infinity: Infinity
}
const replacer = (key, value) => {
if (value instanceof Function) {
return value.toString();
}
else if (value === NaN) {
return 'NaN';
}
else if (value === Infinity) {
return 'Infinity';
}
else if (typeof value === 'undefined') {
return 'undefined';
}
else {
return value; // no change
}
}
const jsonString = JSON.stringify(obj, replacer, 2);
console.log(jsonString);
console.log
在最后一行运行后jsonString
,我们看到:
{
"fn1": "fn1() {}",
"foo": 1,
"bar": 2,
"abc": "abc",
"nullProp": null,
"undefinedProp": "undefined",
"notNum": null,
"infinity": "Infinity"
}
该replace
函数的作用是使用 转换的对象中的键和值进行额外的解析JSON.stringify
。它会检查 是否为value
函数,如果是,则将其转换为字符串并返回。同样,对于NaN
、Infinity
和undefined
,我们也执行了同样的操作。否则,我们按原样返回值。
该函数的第三个参数JSON.stringfy
接受一个数字,用于设置要在 JSON 输出中插入的空格数量,以提高输出的可读性。第三个参数也可以接受任何将代替空格插入的字符串。请注意,如果我们将一个包含空格以外内容的字符串作为第三个参数,则可能会创建一个“JSON”,而这并不是一个有效的 JSON 字符串。
例如,如果我们写:
const obj = {
fn1() {},
foo: 1,
bar: 2,
abc: 'abc',
nullProp: null,
undefinedProp: undefined,
notNum: NaN,
infinity: Infinity
}
const replacer = (key, value) => {
if (value instanceof Function) {
return value.toString();
}
else if (value === NaN) {
return 'NaN';
}
else if (value === Infinity) {
return 'Infinity';
}
else if (typeof value === 'undefined') {
return 'undefined';
}
else {
return value; // no change
}
}
const jsonString = JSON.stringify(obj, replacer, 'abc');
console.log(jsonString);
然后console.log
将是:
{
abc"fn1": "fn1() {}",
abc"foo": 1,
abc"bar": 2,
abc"abc": "abc",
abc"nullProp": null,
abc"undefinedProp": "undefined",
abc"notNum": null,
abc"infinity": "Infinity"
}
这显然不是有效的 JSON。JSON.stringify
它会抛出“循环对象值” TypeError。此外,如果对象有BigInt
值,转换也会失败,并抛出“BigInt 值无法在 JSON 中序列化” TypeError。
JSON.stringify
另外,请注意,如果将符号用作对象的键,则符号会被自动丢弃。因此,如果我们有:
const obj = {
fn1() {},
foo: 1,
bar: 2,
abc: 'abc',
nullProp: null,
undefinedProp: undefined,
notNum: NaN,
infinity: Infinity,
[Symbol('foo')]: 'foo'
}
const replacer = (key, value) => {
if (value instanceof Function) {
return value.toString();
}
else if (value === NaN) {
return 'NaN';
}
else if (value === Infinity) {
return 'Infinity';
}
else if (typeof value === 'undefined') {
return 'undefined';
}
else {
return value; // no change
}
}
const jsonString = JSON.stringify(obj, replacer, 2);
console.log(jsonString);
我们得到:
{
"fn1": "fn1() {}",
"foo": 1,
"bar": 2,
"abc": "abc",
"nullProp": null,
"undefinedProp": "undefined",
"notNum": null,
"infinity": "Infinity"
}
Date 对象会使用与返回值相同的字符串转换为字符串date.toISOString()
。例如,如果我们输入:
const obj = {
fn1() {},
foo: 1,
bar: 2,
abc: 'abc',
nullProp: null,
undefinedProp: undefined,
notNum: NaN,
infinity: Infinity,
[Symbol('foo')]: 'foo',
date: new Date(2019, 1, 1)
}
const replacer = (key, value) => {
if (value instanceof Function) {
return value.toString();
}
else if (value === NaN) {
return 'NaN';
}
else if (value === Infinity) {
return 'Infinity';
}
else if (typeof value === 'undefined') {
return 'undefined';
}
else {
return value; // no change
}
}
const jsonString = JSON.stringify(obj, replacer, 2);
console.log(jsonString);
我们得到:
{
"fn1": "fn1() {}",
"foo": 1,
"bar": 2,
"abc": "abc",
"nullProp": null,
"undefinedProp": "undefined",
"notNum": null,
"infinity": "Infinity",
"date": "2019-02-01T08:00:00.000Z"
}
date
我们可以看到,转换为 JSON 后,该属性的值现在是一个字符串。
深度复制对象
我们还可以使用JSON.stringify
withJSON.parse
来对 JavaScript 对象进行深层复制。例如,要对不带库的对象进行深层复制,你JSON.stringify
可以JSON.parse
:
const a = { foo: {bar: 1, {baz: 2}}
const b = JSON.parse(JSON.stringfy(a)) // get a clone of a which you can change with out modifying a itself
这会执行对象的深层复制,这意味着对象的所有层级都会被克隆,而不是引用原始对象。之所以能做到这一点,是因为JSON.stringfy
将对象转换为不可变的字符串,并且在JSON.parse
解析字符串时会返回该对象的副本,从而返回一个不引用原始对象的新对象。