重要声明:此博借鉴了阿里巴巴 Fastjson 的一点思想
The important declaration: This mind comes form Fastjson Lib (Alibaba).
说点『科(fei)普(hua)』先:
- 对于web前端,JSON序列化可以说是在 与服务端通讯(ajax+json) ,和使用 localStorage(读 + 写) 时。
- 对于服务端,我相信绝大多数人遇到问题是在于输出JSON序列化数据。
『科(fei)普(hua)』完毕。
Let's talk about some basic things first:
- For the web front-end, JSON is usually used on conmunication with server part, and localStorage.
- For the back-end, I think where the most people have problem is serialization.
Basic things done.
循环引用对象序列化?这似乎是一个老生常谈的问题,但是99.9%的人所谓的『解决』,都是在『逃避』这个问题,不信你搜搜『循环引用 JSON』试试?
后端相关文章一定告诉你要『禁用循环引用检测』,『截断对象』和『配置序列化规则( 各种Filter )』等降( tao )魔( bi )大法
前端相关文章最多就是告诉你可以设置一下序列化深度。
于是导致了数据丢失,花大量时间在序列化和反序列化逻辑上做判断和处理,出现许多难以维护的bug,浪费社会资源。
说到底,JSON不就是数据交换吗?它不支持表示循环引用对象,那就对它的语法进行扩展,让它能够表示不就好了?迎刃而解。
如何表示循环引用/重复引用对象?阿里的Fastjson已经告诉了我们答案:
- 创建一个对象表示引用对象,它仅有一个 key="$ref",value=对象引用路径
- 对象引用路径使用 "$" 表示根对象的引用,使用 "[数字]" 表示数组元素,使用 ".property" 表示对象字段
- 形如{ "$ref":"$.key1.array[3].key" }
Fastjson 是 Java 的库,服务于后端,我在这里用 TypeScript 手写一下它的实现以便前端能够享受这个人类宝贵的精神财富。
首先写个序列化和反序列化方法:( serializeCircular 和 parseCircular )
Serialization Circular? It seems to be a very familiar issue? But what 99.9% of people call "handle" is "escape".Or you can try searching "Circular JSON".
The information about back-end would tell you to "disable circular checking", "cut object" and "create filters" to "h( e )a( s )n( c )d( a )l( p )e" it.
The information about front-end would tell you to change/set the deep/level in the settings of serialization.
And all above would cause losing data, and you will take huge time at the further logics of serialization, fixing bugs. It's a sheer waste.
But all in all, JSON is just for data-exchanging. It doesn't support circular , why not expand the role to make it supports?
How to express circular or repeatition? The answer has already been in the Fastjson lib, which built by Alibaba:
- Create an object to express the circular, which has only one property named "$ref" and the value of it is a path-expression, which express the position of the circular from the root.
- It uses "$" to express the reference of the root, "[index:number]" to express the item of an array, and ".key" to express the key of an common object.
- Just like { "$ref":"$.key1.array[3].key" }
But Fastjson is a Java Lab for back-end, so I implement it by TypeScript for front-end.
At the first, make serialization and parse function: ( serializeCircular and parseCircular )
1 const _parseCircular = (root: any, parent: any, objkey: string | number) => {
2 const obj = parent[objkey];
3 if (null === obj || typeof obj !== "object") {
4 //
5 } else if (Array.isArray(obj)) {
6 for (let i = 0; i < obj.length; i++) {
7 _parseCircular(root, obj, i);
8 }
9 } else if (!!obj["$ref"]) {
10 let paths = (obj["$ref"] as string).split(/.|[|]/).filter(s => !!s);
11 paths.shift();
12 parent[objkey] = paths.reduce((a, b) => a[b], root);
13 } else {
14 Object.keys(obj).forEach(key => {
15 _parseCircular(root, obj, key);
16 });
17 }
18 };
19 const _serializeCircular = (parent: any, base: string, objkey: string | number, obj_key_map: Map<string, any>, result: any) => {
20 const obj = parent[objkey];
21 if (null === obj || typeof obj !== "object") {
22 result[objkey] = obj;
23 } else if (obj_key_map.has(obj)) {
24 result[objkey] = { $ref: obj_key_map.get(obj) };
25 } else {
26 const endFix = Array.isArray(parent) ? `[${objkey}]` : `.${objkey}`;
27 let objrefstr = `${base}${endFix}`;
28 obj_key_map.set(obj, objrefstr);
29 if (Array.isArray(obj)) {
30 result = result[objkey] = [];
31 for (let i = 0; i < obj.length; i++) {
32 _serializeCircular(obj, objrefstr, i, obj_key_map, result);
33 }
34 } else {
35 result = result[objkey] = {};
36 Object.keys(obj).forEach(key => {
37 _serializeCircular(obj, objrefstr, key, obj_key_map, result);
38 });
39 }
40 }
41 };
42 const serializeCircular = (root: any) => {
43 const map = new Map();
44 map.set(root, "$");
45 if (Array.isArray(root)) {
46 let result = [];
47 for (let i = 0; i < root.length; i++) {
48 _serializeCircular(root, "$", i, map, result);
49 }
50 return result;
51 } else if (null !== root && typeof root === "object") {
52 let result = {};
53 Object.keys(root).forEach(key => {
54 _serializeCircular(root, "$", key, map, result);
55 });
56 return result;
57 } else {
58 return root;
59 }
60 };
61 const parseCircular = (root: any): any => {
62 if (Array.isArray(root)) {
63 for (let i = 0; i < root.length; i++) {
64 _parseCircular(root, root, i);
65 }
66 } else if (null !== root && typeof root === "object") {
67 Object.keys(root).forEach(key => {
68 _parseCircular(root, root, key);
69 });
70 }
71 return root;
72 };
然后你可以仅仅只是用在某些特定的地方 ( 推荐 ),如 RPC 和 localStorage
或者直接替换掉原本的 JSON.stringify 和 JSON.parse ( 也推荐 ) ^_^
Then you can just use it at some special places. ( recommend ) such as RPC and localStorage
Or just replace the original JSON.stringify and JSON.parse.( recommend too ) ^_^
1 let stringifyInited = false;
2 if (!stringifyInited && (stringifyInited = true)) {
3 const oriStringify = JSON.stringify;
4 const oriParse = JSON.parse;
5 const serialize = serializeCircular;
6 const parse = parseCircular;
7 JSON.stringify = function (this: any) {
8 const args = Array.from(arguments) as any;
9 args[0] = serialize(args[0]);
10 return oriStringify.apply(this, args);
11 } as any;
12 JSON.parse = function (this: any) {
13 const args = Array.from(arguments) as any;
14 let res = oriParse.apply(this, args);
15 return parse(res);
16 } as any;
17 }
测试:
Test:
1 // 测试 test
2
3 (() => {
4 let abc = {} as any;
5 abc.a = {};
6 abc.b = {};
7 abc.a.b = abc.b;
8 abc.b.a = abc.a;
9 let abcSerialization = JSON.stringify(abc);
10 console.log(abcSerialization);
11 let abcParse = JSON.parse(abcSerialization);
12 console.log(abcParse.a===abcParse.b.a);
13 })();
14
15 // {"a":{"b":{"a":{"$ref":"$.a"}}},"b":{"$ref":"$.a.b"}}
16 // true
最后,需要 Javascript 版本的朋友们,只需把类型限定标记 ( : ??? 和 as ??? ) 去掉。
感谢阅读
At the end, my friends, if need a JavaScript version, just remove the limitation marks ( : ??? 和 as ??? )
Thanks for reading