在编写程序时,需要将一个接口的所有返回值,作为另一个接口返回值中的data传递出去,发送的时候,遇到对象存在循环引用,在将对象进行 json 序列化的时候发生报错。如下图:
什么是循环引用
结构体中的某一字段的值,为上层字段值的引用,代码示例如下:
const x = { a: 8 };
const b = { x };
b.y = b; // 循环引用
JSON.stringify(b); // 触发报错
如何解决
解决思路,是去除循环引用对象,用"[Circular]"替换。
方法一: 参考MDN上的方法,使用JSON.stringify()
Implement a solution by yourself, which will require finding and replacing (or removing) the cyclic references by serializable values.
The snippet below illustrates how to find and filter (thus causing data loss) a cyclic reference by using the replacer parameter of JSON.stringify()
该示例的目的是将循环引用的对象用"[Circular]"替换,从而解除循环引用。
function getCircularReplacer() {
const ancestors = [];
return function (key, value) {
if (typeof value !== "object" || value === null) {
return value;
}
// `this` is the object that value is contained in,
// i.e., its direct parent.
while (ancestors.length > 0 && ancestors.at(-1) !== this) {
ancestors.pop();
}
if (ancestors.includes(value)) {
return "[Circular]";
}
ancestors.push(value);
return value;
};
}
JSON.stringify(circularReference, getCircularReplacer());
补充:简单介绍下JSON.stringify()
link
之前的用法中,只是为了简单将json转换成string,其实还可以指定一个replacer函数,有选择性的替换值,或者指定的 replacer 是数组,则可选择性地仅包含数组指定的属性。
语法规范
/**
* Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
* @param value A JavaScript value, usually an object or array, to be converted.
* @param replacer A function that transforms the results.
* @param space Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.
*/
stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string;
/**
* Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
* @param value A JavaScript value, usually an object or array, to be converted.
* @param replacer An array of strings and numbers that acts as an approved list for selecting the object properties that will be stringified.
* @param space Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.
*/
stringify(value: any, replacer?: (number | string)[] | null, space?: string | number): string;
参数:
value
将要序列化成 一个 JSON 字符串的值。
replacer
可选
如果该参数是一个函数,则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理;如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中;如果该参数为 null 或者未提供,则对象所有的属性都会被序列化。
space
可选
指定缩进用的空白字符串,用于美化输出(pretty-print);如果参数是个数字,它代表有多少的空格;上限为 10。该值若小于 1,则意味着没有空格;如果该参数为字符串(当字符串长度超过 10 个字母,取其前 10 个字母),该字符串将被作为空格;如果该参数没有提供(或者为 null),将没有空格。
返回值
- 一个表示给定值的 JSON 字符串。
异常
- 当在循环引用时会抛出异常(“cyclic object value”)(循环对象值)
- 当尝试去转换 `[BigInt]类型的值会抛出异常(BigInt 值不能 JSON 序列化)
注意点:
- 非数组对象的属性不能保证以特定的顺序出现在序列化后的字符串中。
- 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。
- 所有以 symbol为属性键的属性都会被完全忽略掉,即便
replacer
参数中强制指定包含了它们。 - 其他类型的对象,包括Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性。
// 不可枚举的属性默认会被忽略:
JSON.stringify(
Object.create(
null,
{
x: { value: 'x', enumerable: false },
y: { value: 'y', enumerable: true }
}
)
);
// "{"y":"y"}"
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
方法二: 使用WeakSet
link
原理和方法一类似,唯一知识点在于使用了WeakSet。
WeakSet
对象允许你将弱保持对象存储在一个集合中。
WeakSet
对象是一些对象值的集合。且其与 Set 类似,WeakSet
中的每个对象值都只能出现一次。在 WeakSet
的集合中,所有对象都是唯一的。
它和 Set对象的主要区别有:
WeakSet
只能是对象的集合,而不能像Set
那样,可以是任何类型的任意值。WeakSet
持弱引用:集合中对象的引用为弱引用。如果没有其他的对WeakSet
中对象的引用,那么这些对象会被当成垃圾回收掉。
用法,检测循环引用
递归调用自身的函数需要一种通过跟踪哪些对象已被处理,来应对循环数据结构的方法。
customStringify() {
const seen = new WeakSet();
return (key: any, value: object) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return '[Circular]';
}
seen.add(value);
}
return value;
};
}