• 处理JSON循环引用序列化与反序列化问题的终极方案


      重要声明:此博借鉴了阿里巴巴 Fastjson 的一点思想

      The important declaration: This mind comes form Fastjson Lib (Alibaba).

      说点『科(fei)普(hua)』先:

    1. 对于web前端,JSON序列化可以说是在 与服务端通讯(ajax+json) ,和使用 localStorage(读 + 写) 时。
    2. 对于服务端,我相信绝大多数人遇到问题是在于输出JSON序列化数据。

      『科(fei)普(hua)』完毕。

      Let's talk about some basic things first:

    1. For the web front-end, JSON is usually used on conmunication with server part, and localStorage.
    2. 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已经告诉了我们答案:

    1. 创建一个对象表示引用对象,它仅有一个 key="$ref",value=对象引用路径
    2. 对象引用路径使用 "$" 表示根对象的引用,使用 "[数字]" 表示数组元素,使用 ".property" 表示对象字段
    3. 形如{ "$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:

    1. 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.
    2. 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.
    3. 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

     

  • 相关阅读:
    Tensorboard返回的网址打不开问题
    css的常用知识点
    js的基础知识
    html的常用标签
    python的进程与线程
    python的socket的学习
    python的异常处理
    python类的相关知识第二部分
    python类的相关知识第一部分
    python装饰器的学习笔记
  • 原文地址:https://www.cnblogs.com/harry-lien/p/13212792.html
Copyright © 2020-2023  润新知