TS 中的序列化和反序列化

3,583 阅读2分钟

前段开发中不可避免的会与 API 沟通,理想情况下,我们可以在前端代码中直接定义 API 的数据类型,这样前端代码就可以直接使用,但是,理想归理想,实际过程中,API 返回的数据结构跟前端直接使用的数据结构尝尝不会完全相同,举个例子:

// API 返回的数据:
[
  {
    name: 'zhangsan',
    sex: 'male',
  },
]

// 前端定义类型:
interface User {
  name: string,
  gender: string,
}

这里就是第一个问题,往往会出现同一个字段的不同名字,出现的原因,当然可能是,前后端不同的命名习惯,不同的 API 系统或者 version, 我们如果为了方便,前段的类型完全按照 API 来定义就有点被牵着鼻子走了。当然,一个简单的方法是,我们可以写一个 convert 方法:

class User {
  name: string;
  gender: string;
  
  constructor(json: any) {
    Object.assign(this, json);
    this.gender = json.sex;
  }
  
  toJson() {
  	return ({
      ...this,
      sex: this.gender
    });
  }
}

当然,你甚至可以定义两个类型: apiUser, User, 然后手动维护两者的 map 关系。

最偷懒的,莫过于,API 是什么样子,类型就定义成什么样子。

刚刚的方案其实有一个问题,一是需要人每次做重复的工作,写一段粗看不知道为什么要写的逻辑,而是,本质上,是 a => b 名字的转换关系,而我们却不得不每次都要写 a=>b, b=>a 的手动转化。费时费力。

有没有一种可能性,我只需要描述,User 中,a 与 b 的对应关系,方法就可以自动帮助我做这样的转换。当然是可以的。

首先,我们知道,interface 在运行过程中是丢失的,只在编译过程中对 TS 有提示效果,编译成 JS 运行后就变成不可知的了。所以,我们基本上只能通过 class 来定义。那如何描述 class 上某个字段对应于 json 的某个字段呢?

熟悉 Angular 的话可能就很明白了,Decorators,它可以有效的帮助我们给 class, function, property 添加额外信息。举个例子:

class User {
  name: string;
  
  @bindJsonProp('sex')
  gender: string;
}

这样,我们只需要增加一行,就可以描述清楚,gendersex 的对应关系。剩下的转换关系,我们都可以交给一个独立的方法来做:

const user = convertToType(User)(jsonData);

// ...
// ...

const jsonData = convertToJson(user);

当然,我们甚至可以做类型的转换,比如:

API 中 Date 为 string,我们要转化为 string:

class User {
  name!: string;
  
  @bindJsonProp({ name: 'sex'})
  gender!: string;

  @bindJsonProp({
    typeConverter: dateStringConverter
  })
  date!: Date;
}

感兴趣的可以看一下这里的实现: github.com/winfa/ts-js…

以上。