JSON 在很多地方被用于不同服务/系统之间的数据交换。如今,所有主流编程语言都内置了对 JSON 数据解析和序列化的库支持。在这篇文章中,我们来谈谈 JavaScript 中的 JSON.stringify()。
首先来看一个对象转换处理的小例子。有一个需求是将下面这个对象:
javascript
const todayILearn = {
_id: 1,
content: '今天学习 JSON.stringify(),我很开心!',
created_at: 'Mon Nov 25 2019 14:03:55 GMT+0800 (中国标准时间)',
updated_at: 'Mon Nov 25 2019 16:03:55 GMT+0800 (中国标准时间)'
}
转换为下面这个对象:
javascript
const todayILearn = {
id: 1,
content: '今天学习 JSON.stringify(),我很开心!',
createdAt: 'Mon Nov 25 2019 14:03:55 GMT+0800 (中国标准时间)',
updatedAt: 'Mon Nov 25 2019 16:03:55 GMT+0800 (中国标准时间)'
}
这里的变化是:
- 将属性名"_id"改为"id"。
- 将属性名"created_at"和"updated_at"改为"createdAt"和"updatedAt"。
- 值保持不变。
有很多方法可以实现上述需求。
- 遍历所有属性
javascript
const todayILearnTemp = {};
for (const [key, value] of Object.entries(todayILearn)) {
if (key === "_id") todayILearnTemp["id"] = value;
else if (key === "created_at") todayILearnTemp["createdAt"] = value;
else if (key === "updatedAt") todayILearnTemp["updatedAt"] = value;
else todayILearnTemp[key] = value;
}
console.log(todayILearnTemp);
// 输出:
// { id: 1,
// content: '今天学习 JSON.stringify(),我很开心!',
// createdAt: 'Mon Nov 25 2019 14:03:55 GMT+0800 (中国标准时间)',
// updated_at: 'Mon Nov 25 2019 16:03:55 GMT+0800 (中国标准时间)'
// }
这种方法创建了一个新的临时变量,遍历所有属性并引入了很多 if - else 语句,一点也不优雅且不可扩展。 2. 删除并创建属性 可以添加三个新属性并删除三个不需要的属性。
javascript
todayILearn.id = todayILearn._id;
todayILearn.createdAt = todayILearn.created_at;
todayILearn.updatedAt = todayILearn.updated_at;
delete todayILearn._id;
delete todayILearn.created_at;
delete todayILearn.updated_at;
console.log(todayILearn);
// 输出:
// {
// content: '今天学习 JSON.stringify(),我很开心!',
// id: 1,
// createdAt: 'Mon Nov 25 2019 14:03:55 GMT+0800 (中国标准时间)',
// updatedAt: 'Mon Nov 25 2019 16:03:55 GMT+0800 (中国标准时间)'
// }
它与上面的方法有相同的缺点,并且属性的顺序也改变了。 3. 使用 JSON.stringify() JSON.stringify() 可以用来将不需要的属性名替换为想要的属性名。
javascript
const mapObj = {
_id: "id",
created_at: "createdAt",
updated_at: "updatedAt"
};
JSON.parse(
JSON.stringify(todayILearn).replace(
/_id|created_at|updated_at/gi,
matched => mapObj[matched])
)
// {
// id: 1,
// content: '今天学习 JSON.stringify(),我很开心!',
// createdAt: 'Mon Nov 25 2019 14:03:55 GMT+0800 (中国标准时间)',
// updatedAt: 'Mon Nov 25 2019 16:03:55 GMT+0800 (中国标准时间)'
// }
现在看起来优雅多了。
现在让我们来了解一下 JSON.stringify() 的主要规则,以便在需要时能够正确有效地使用它。
规则 1
如果undefined
、函数和符号出现在对象属性值、数组中或作为独立值,它们的输出值会有所不同。 猜猜下面代码片段的输出会是什么。
javascript
const data = {
a: "aaa",
b: undefined,
c: Symbol("dd"),
fn: function() {
return true;
}
};
JSON.stringify(data); // 输出:?
// "{"a":"aaa"}"
当undefined
、函数和符号作为对象属性值出现时,在序列化时会被忽略。
那如果出现在数组中呢?
javascript
JSON.stringify(["aaa", undefined, function aa() {
return true
}, Symbol('dd')]) // 输出:?
// "["aaa",null,null,null]"
当undefined
、函数和符号出现在数组中时,它们会被转换为null
。
如果它们独立出现呢?
javascript
JSON.stringify(function a (){console.log('a')})
// undefined
JSON.stringify( undefined)
// undefined
JSON.stringify(Symbol('dd'))
// undefined
当undefined
、函数和符号独立出现时,它们将是undefined
。
规则 2
如果不是数组对象,序列化后的属性可能不会保持原始顺序。
javascript
const data = {
a: "aaa",
b: undefined,
c: Symbol("dd"),
fn: function() {
return true;
},
d: "ddd"
};
JSON.stringify(data); // 输出:?
// "{"a":"aaa","d":"ddd"}"
JSON.stringify(["aaa", undefined, function aa() {
return true
}, Symbol('dd'),"eee"]) // 输出:?
// "["aaa",null,null,null,"eee"]"
正如规则 1 所提到的,一些属性如果是undefined
、函数或符号之一,在序列化时会被忽略。因此顺序可能无法保持。
规则 3
如果一个对象实现了toJSON()
函数,JSON.stringify() 的输出值将是toJSON()
函数的返回值。
javascript
JSON.stringify({
say: "hello JSON.stringify",
toJSON: function() {
return "today i learn";
}
})
// "today i learn"
规则 4
NaN
、Infinity
或null
将被序列化为null
。
javascript
JSON.stringify(NaN)
// "null"
JSON.stringify(null)
// "null"
JSON.stringify(Infinity)
// "null"
规则 5
布尔值、数字或字符串对象将被转换为它们相应的原始值。
javascript
JSON.stringify([new Number(1), new String("false"), new Boolean(false)]);
// "[1,"false",false]"
规则 6
只有可枚举的属性才能被序列化,包括Map
/Set
/WeakMap
/WeakSet
等。
javascript
JSON.stringify(
Object.create(
null,
{
x: { value: 'json', enumerable: false },
y: { value: 'stringify', enumerable: true }
}
)
);
// "{"y","stringify"}"
规则 7
很多人可能知道,深度克隆一个对象最简单但最粗暴的方法是使用JSON.parse(JSON.stringify(obj))
。但如果要转换的对象包含循环引用,它会有一些意想不到的行为。
javascript
const obj = {
name: "loopObj"
};
const loopObj = {
obj
};
obj.loopObj = loopObj;
function deepClone(obj) {
return JSON.parse(JSON.stringify(obj));
}
deepClone(obj)
/**
VM44:9 Uncaught TypeError: Converting circular structure to JSON
--> starting at object with constructor 'Object'
| property 'loopObj' -> object with constructor 'Object'
--- property 'obj' closes the circle
at JSON.stringify (<anonymous>)
at deepClone (<anonymous>:9:26)
at <anonymous>:11:13
*/
如果对象中有循环引用,在序列化时会抛出异常。
规则 8
即使定义了替换器(replacer)来处理符号,任何以符号作为键的属性都将被忽略。
javascript
JSON.stringify({ [Symbol.for("json")]: "stringify" }, function(k, v) {
if (typeof k === "symbol") {
return v;
}
})
// undefined
替换器可以是一个函数或一个数组。如果是一个函数,它可以用来覆盖上面的很多规则。
javascript
const data = {
a: "aaa",
b: undefined,
c: Symbol("dd"),
fn: function() {
return true;
}
};
// 不使用替换器时
JSON.stringify(data);
// "{"a":"aaa"}"
// 使用替换器时
JSON.stringify(data, (key, value) => {
switch (true) {
case typeof value === "undefined":
return " undefined";
case typeof value === "symbol":
return value.toString();
case typeof value === "function":
return value.toString();
default:
break;
}
return value;
})
// "{"a":"aaa","b":" undefined","c":"Symbol(dd)","fn":"function() {\n return true;\n }"}"
如果替换器是一个数组,它包含了在序列化时应该保留的属性列表,其余的将被删除。
javascript
const jsonObj = {
name: "JSON.stringify",
params: "obj,replacer,space"
};
JSON.stringify(jsonObj, ["params"]);
// "{"params":"obj,replacer,space"}"
希望你现在对 JSON.stringify() 有了更好的理解。