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

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

 

  『科普』:

 

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

  循环引用对象序列化?这似乎是一个老生常谈的问题,但是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 )

 

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

  basic things:

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. 

  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 Lib for back-end, so I implement it by TypeScript for front-end.

  At the first, make serialization and parse function: ( serializeCircular and parseCircular )

 

const _parseCircular = (root: any, parent: any, objkey: string | number) => {
  const obj = parent[objkey];
  if (null === obj || typeof obj !== "object") {
    //
  } else if (Array.isArray(obj)) {
    for (let i = 0; i < obj.length; i++) {
      _parseCircular(root, obj, i);
    }
  } else if (!!obj["$ref"]) {
    let paths = (obj["$ref"] as string).split(/\.|\[|\]/).filter(s => !!s);
    paths.shift();
    parent[objkey] = paths.reduce((a, b) => a[b], root);
  } else {
    Object.keys(obj).forEach(key => {
      _parseCircular(root, obj, key);
    });
  }
};
const _serializeCircular = (parent: any, base: string, objkey: string | number, obj_key_map: Map<string, any>, result: any) => {
  const obj = parent[objkey];
  if (null === obj || typeof obj !== "object") {
    result[objkey] = obj;
  } else if (obj_key_map.has(obj)) {
    result[objkey] = { $ref: obj_key_map.get(obj) };
  } else {
    const endFix = Array.isArray(parent) ? `[${objkey}]` : `.${objkey}`;
    let objrefstr = `${base}${endFix}`;
    obj_key_map.set(obj, objrefstr);
    if (Array.isArray(obj)) {
      result = result[objkey] = [];
      for (let i = 0; i < obj.length; i++) {
        _serializeCircular(obj, objrefstr, i, obj_key_map, result);
      }
    } else {
      result = result[objkey] = {};
      Object.keys(obj).forEach(key => {
        _serializeCircular(obj, objrefstr, key, obj_key_map, result);
      });
    }
  }
};
const serializeCircular = (root: any) => {
  const map = new Map();
  map.set(root, "$");
  if (Array.isArray(root)) {
    let result = [] as any[];
    for (let i = 0; i < root.length; i++) {
      _serializeCircular(root, "$", i, map, result);
    }
    return result;
  } else if (null !== root && typeof root === "object") {
    let result = {};
    Object.keys(root).forEach(key => {
      _serializeCircular(root, "$", key, map, result);
    });
    return result;
  } else {
    return root;
  }
};
const parseCircular = (root: any): any => {
  if (Array.isArray(root)) {
    for (let i = 0; i < root.length; i++) {
      _parseCircular(root, root, i);
    }
  } else if (null !== root && typeof root === "object") {
    Object.keys(root).forEach(key => {
      _parseCircular(root, root, key);
    });
  }
  return root;
};

 

 

  然后你可以仅仅只是用在某些特定的地方 ,如 RPC 和 localStorage,或者直接替换掉原本的 JSON.stringify 和 JSON.parse

  Then you can just use it at some special places.  such as RPC and localStorage, Or just replace the original JSON.stringify and JSON.parse.

let stringifyInited = false;
if (!stringifyInited && (stringifyInited = true)) {
  const oriStringify = JSON.stringify;
  const oriParse = JSON.parse;
  const serialize = serializeCircular;
  const parse = parseCircular;
  JSON.stringify = function (this: any) {
    const args = Array.from(arguments) as any;
    args[0] = serialize(args[0]);
    return oriStringify.apply(this, args);
  } as any;
  JSON.parse = function (this: any) {
    const args = Array.from(arguments) as any;
    let res = oriParse.apply(this, args);
    return parse(res);
  } as any;
}

 

  测试:

  Test:

 

// 测试 test
(() => {
  const abc = {} as any;
  abc.a = {};
  abc.b = {};
  abc.a.b = abc.b;
  abc.b.a = abc.a;
  const abcSerialization = JSON.stringify(abc);
  console.log(abcSerialization);
  const abcParse = JSON.parse(abcSerialization);
  console.log(abcParse.a === abcParse.b.a);
})();
// {"a":{"b":{"a":{"$ref":"$.a"}}},"b":{"$ref":"$.a.b"}}
// true

 

 

 

 

  最后,需要 Javascript 版本的朋友们,使用 tsc 命令将 TypeScript 编译成 JavaScript 即可。

  At the end, my friends, please use "tsc" command to compile TypeScript to JavaScript if need a JavaScript version.

 

# 安装 typescript / install typescript
npm install -g typescript
# 编译 ts 文件 / compile ts file
tsc typescript.ts

 

 

 

  感谢阅读 Thanks for reading

 

posted @ 2020-06-30 22:36  本木大人丿  阅读(1346)  评论(0编辑  收藏  举报