一、JSON.stringify()
JSON.stringify()方法将一个JavaScript对象或值转换为JSON字符串,如果指定了一个replacer 函数,则可以选择性地替换值,或者指定的replacer是数组,则可选择性地仅包含数组指定的属性。
JSON.stringify(value[, replacer [, space]]);
/*
value:
将要序列化成一个JSON字符串的值。
replacer【可选】:
如果该参数是一个函数,则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理;
如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的JSON字符串中;
如果该参数为null或者未提供,则对象所有的属性都会被序列化。
space【可选】:
指定缩进用的空白字符串,用于美化输出(pretty-print);
如果参数是个数字,它代表有多少的空格;上限为10。该值若小于1,则意味着没有空格;
如果该参数为字符串(当字符串长度超过10个字母,取其前10个字母),该字符串将被作为空格;
如果该参数没有提供(或者为null),将没有空格。
返回值:
一个表示给定值的JSON字符串。
*/
/*
1、布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。
2、undefined、任意的函数以及symbol值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成null(出现在数组中时)。
函数、undefined被单独转换时,会返回undefined,如JSON.stringify(function(){}) or JSON.stringify(undefined)。
所有以symbol为属性键的属性都会被完全忽略掉,即便replacer参数中强制指定包含了它们。
3、其他类型的对象,包括Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性。
*/
JSON.stringify({}); // '{}'。
JSON.stringify(true); // 'true'。
JSON.stringify("foo"); // '"foo"'。
JSON.stringify([1, "false", false]); // '[1,"false",false]'。
JSON.stringify({ x: 5 }); // '{"x":5}'。
JSON.stringify({ x: 5, y: 6 });
// "{"x":5,"y":6}"。
JSON.stringify([new Number(1), new String("false"), new Boolean(false)]);
// '[1,"false",false]'。
JSON.stringify({ x: undefined, y: Object, z: Symbol("") });
// '{}'。
JSON.stringify([undefined, Object, Symbol("")]);
// '[null,null,null]'。
JSON.stringify({ [Symbol("foo")]: "foo" });
// '{}'。
JSON.stringify({ [Symbol.for("foo")]: "foo" }, [Symbol.for("foo")]);
// '{}'。
JSON.stringify({ [Symbol.for("foo")]: "foo" }, function (k, v) {
if (typeof k === "symbol") {
return "a symbol";
}
});
// undefined。
const obj = {
[Symbol.for("global")]: "global",
normal: "ok",
name: {
age: 456,
},
};
JSON.stringify(obj, (key, value) => {
console.log("Replacer called with:", { key, value });
return value;
});
/*
Replacer called with: {
key: '',
value: { normal: 'ok', name: { age: 456 }, Symbol(global): 'global' }
}
Replacer called with: { key: 'normal', value: 'ok' }
Replacer called with: { key: 'name', value: { age: 456 } }
Replacer called with: { key: 'age', value: 456 }
分析:
1、JSON.stringify会对对象进行递归的深度优先遍历,
每个键值对(包括根对象)都会调用一次replacer。
注意:遍历只针对“字符串键”的可枚举自有属性(Symbol键、不可枚举属性、原型链属性会被忽略)。
2、为什么key===""时,值能看到Symbol属性?
当key===""时,replacer接收到的是原始对象引用,但后续序列化会过滤Symbol。
key = ""(根对象)。
value = obj(就是你传入的原始对象)。
此时obj确实包含Symbol属性,所以console.log显示了它。
但这只是“传入replacer的值”,不代表它会被序列化!
注意:你无法专门处理key为空字符串的情况,因为JSON对象也可能包含空字符串的键。
3、replacer遍历顺序是怎样的?
replacer是在“进入节点时”被调用的,而不是“离开节点时”,
也就是说,JSON.stringify的遍历逻辑是:前序调用replacer,后序构造JSON。
具体执行流程如下:
1. 开始处理根对象obj。
立即调用replacer("", obj) → 所以根对象的日志最先出现。
得到返回值(比如还是obj)。
2. 然后遍历这个返回值的所有字符串键属性(normal, name)。
对每个属性值:
如果是对象(如{ age: 456 }),就递归进入该对象。
调用replacer("name", { age: 456 })。
然后遍历它的属性age。
调用replacer("age", 456)。
3. 所有子节点处理完后,才把结果组装成JSON字符串。
所以:
replacer的调用顺序是:前序(pre-order)。
JSON字符串的构造顺序是:后序(post-order)。
------------------------------------------------------------------------------------
调用replacer 前序遍历(Pre-order) 先访问父节点 → 再访问子节点 → 所以key: ""最先被打印。
生成最终JSON 后序遍历(Post-order) 先序列化子节点 → 再拼接父节点。
------------------------------------------------------------------------------------
根据ECMAScript规范§24.5.2 JSON.stringify:
对输入值value,先调用replacer("", value)得到newVal。
如果newVal是对象,则对其每个字符串键的可枚举属性:
获取属性值propValue。
递归调用内部序列化过程(其中会调用replacer(key, propValue))。
所以“根replacer”必须最先调用,才能确定后续遍历的对象是什么!
*/
// 不可枚举的属性默认会被忽略:
JSON.stringify(
Object.create(null, {
x: { value: "x", enumerable: false },
y: { value: "y", enumerable: true },
}),
);
// "{"y":"y"}"。
“toJSON”方法:
如果一个被序列化的对象拥有toJSON方法,那么该toJSON方法就会覆盖该对象默认的序列化行为:不是该对象被序列化,而是调用toJSON方法后的返回值会被序列化,例如:
var obj = {
foo: "foo",
toJSON: function () {
return "bar";
},
};
JSON.stringify(obj); // '"bar"'。
JSON.stringify({ x: obj }); // '{"x":"bar"}'。
// ----------------------------------------------------------------
const obj = {
[Symbol.for("global")]: "global",
normal: "ok",
name: {
age: 456,
},
toJSON: function() {
return this[Symbol.for("global")];
}
};
console.log(JSON.stringify(obj, null, 2)); // "global"。
// ----------------------------------------------------------------
const obj = {
[Symbol.for("global")]: "global",
normal: "ok",
name: {
age: 456,
},
toJSON: function() {
return { [Symbol.for("global")] : this[Symbol.for("global")]};
}
};
console.log(JSON.stringify(obj, null, 2)); // {}。
二、JSON.parse()
静态方法JSON.parse()用来解析JSON字符串,构造由字符串描述的JavaScript值或对象。可以提供一个可选的reviver函数,用于在返回结果对象之前对其进行转换。
JSON.parse(text);
JSON.parse(text, reviver);
/*
text:
要被解析成JavaScript值的字符串。
reviver【可选】:
如果是函数,则规定了最初由解析产生的每个值在返回前的转换方式。不可调用的值将被忽略。调用该函数时需要输入以下参数:
key:
与值相关的键。
value:
解析产生的值。
context【可选】:
上下文对象,用于保存与当前正在恢复的表达式相关的状态。每次调用reviver函数时,它都是一个新对象。只有在恢复原始值时才会传递该对象,而当value是对象或数组时则不会传递。
它仅包含以下属性:
source:
代表此值的原始JSON字符串。
返回值:
与给定的JSON text相对应的Object、Array、string、number、boolean或者null值。
*/
JSON.parse(
'{"p": 5}',
(key, value) =>
typeof value === "number"
? value * 2 // 为数值返回value * 2。
: value, // 其他值返回原值。
);
// { p: 10 }。
JSON.parse('{"1": 1, "2": 2, "3": {"4": 4, "5": {"6": 6}}}', (key, value) => {
console.log(key);
return value;
});
// 1
// 2
// 4
// 6
// 5
// 3
// ""
/*
与JSON.stringify()的replacer参数类似,
对于数组和对象,reviver最后一次调用的是以空字符串作为key、以根对象作为value的根值。
*/
/*
注意:
1、你无法专门处理key为空字符串的情况,因为JSON对象也可能包含空字符串的键。
在实现reviver时,需要非常精确地知道每个键需要进行哪种转换。
2、reviver是在解析数值后运行的。
因此,举例来说,JSON文本中的数字已经被转换为JavaScript数字,在此过程中可能会丢失精度。
一种不损失精度地传输大型数字的方法是将其序列化为字符串,然后将其还原为BigInt,或其他适当的任意精度格式。
*/
const bigJSON = '{"gross_gdp": 12345678901234567890}';
const bigObj = JSON.parse(bigJSON, (key, value, context) => {
if (key === "gross_gdp") {
// 忽略value,它已经损失了精度。
console.log(value); // 输出:12345678901234567000。
console.log(BigInt(context.source)); // 输出:12345678901234567890n。
return BigInt(context.source);
}
return value;
});
三、JSON函数搭配使用举例
在与JSON.stringify()的replacer配对时使用reviver:
// Map通常被序列化为没有属性的对象。
// 我们可以使用替换器来指定要序列化的条目。
const map = new Map([
[1, "one"],
[2, "two"],
[3, "three"],
]);
const jsonText = JSON.stringify(map, (key, value) =>
value instanceof Map ? Array.from(value.entries()) : value,
);
console.log(jsonText);
// [[1,"one"],[2,"two"],[3,"three"]]
const map2 = JSON.parse(jsonText, (key, value) =>
Array.isArray(value) && value.every(Array.isArray) ? new Map(value) : value,
);
console.log(map2);
// Map { 1 => "one", 2 => "two", 3 => "three" }。
7249

被折叠的 条评论
为什么被折叠?



