序列化
序列化是将数据类型转换为适合传输或存储的格式的过程,例如。反序列化是撤销这一过程。这是无损完成的,这意味着数据可以在不丢失数据类型信息或数据本身的情况下转换为序列化目标。
在JavaScript中,序列化通常是在JavaScript对象和JSON之间。JSON只支持字符串、数字、布尔值、对象和数组。另一方面,JavaScript支持许多其他类型,如BigInt、ArrayBuffer、类型化数组、Date、自定义类实例等等。现在,要使用JSON向服务器传输JavaScript数据,你需要一个序列化过程(在客户端)和一个反序列化过程(在服务器上),如果服务器将数据作为JSON发送给客户端,则反之亦然。使用`JSON.parse`和`JSON.stringify`往往不能满足这个要求,因为它不是无损的。
这个序列化过程对于非琐碎的数据是绝对必要的,因为JSON甚至对于像日期这样的基本类型都会丢失其信息。一个 "新日期 "最终被序列化为JSON:
const json = JSON.stringify(new Date);
//'"2022-05-13T20:48:51.025Z"
中的字符串,可以看出,JSON.stringify的结果是一个JSON字符串。如果再次用JSON.parse反序列化,结果不是一个`日期’对象,而是一个字符串。
const value = JSON.parse('"2022-05-13T20:48:51.025Z"');
//"2022-05-13T20:48:51.025Z"
尽管有各种变通方法来教JSON.parse反序列化日期对象,但它们容易出错,而且性能很差。为了使这种情况和许多其他类型的类型安全的序列化和反序列化,需要一个序列化过程。
有四个主要功能。serialize
, cast
/deserialize`和`validatedDeserialize
。在这些函数的引擎盖下,默认使用`@deepkit/type`的全局可用的JSON序列化器,但也可以使用自定义的序列化目标。
Deepkit Type支持自定义序列化目标,但已经配备了强大的JSON序列化目标,该目标将数据序列化为JSON对象,然后可以使用JSON.stringify正确而安全地转换为JSON。通过`@deepkit/bson`,BSON也可以作为一个序列化目标。如何创建一个自定义的序列化目标(例如为数据库驱动),可以在自定义序列化器一节中找到。
请注意,尽管蚕食者也检查数据的兼容性,但这些验证与验证中的验证是不同的。只有`cast`函数在成功反序列化后还会调用Validation章节中的完整验证过程,如果数据无效会抛出一个错误。
另外,validatedDeserialize`可以用来在反序列化后进行验证。另一种方法是手动调用`validate`或`validates`函数来处理来自`deserialize`函数的反序列化数据,见Validation。
当错误发生时,来自序列化和验证的所有函数都会从
@deepkit/type’抛出一个`ValidationError'。
序列化
import { serialize } from '@deepkit/type';
class MyModel {
id: number = 0;
created: Date = new Date;
constructor(public name: string) {
}
}
const model = new MyModel('Peter');
const jsonObject = serialize<MyModel>(model);
//{
// id: 0,
// created: '2021-06-10T15:07:24.292Z',
// name: 'Peter'
//}
const json = JSON.stringify(jsonObject);
函数`serialize`根据默认情况用JSON序列化器将传递的数据转换为JSON对象,即:字符串、数字、布尔值、对象或数组。然后可以使用JSON.stringify将其结果安全地转换为JSON。
反序列化
`deserialize`函数默认使用JSON序列化器将传递的数据转换为适当的指定类型。JSON序列化器希望得到一个JSON对象,即字符串、数字、布尔值、对象或数组。
import { deserialize } from '@deepkit/type';
class MyModel {
id: number = 0;
created: Date = new Date;
constructor(public name: string) {
}
}
const myModel = deserialize<MyModel>({
id: 5,
created: 'Sat Oct 13 2018 14:17:35 GMT+0200',
name: 'Peter',
});
//from JSON
const json = '{"id": 5, "created": "Sat Oct 13 2018 14:17:35 GMT+0200", "name": "Peter"}';
const myModel = deserialize<MyModel>(JSON.parse(json));
如果已经传递了正确的数据类型(例如,在`created’的情况下是一个Date对象),那么这将被视为是。
不仅是一个类,而且任何TypeScript类型都可以被指定为第一个类型参数。因此,即使是基元或非常复杂的类型也可以被传递:
deserialize<Date>('Sat Oct 13 2018 14:17:35 GMT+0200');
deserialize<string | number>(23);
软类型转换
反序列化过程中实现了软类型转换。这意味着字符串类型的字符串和数字或字符串类型的数字可以被接受并自动转换。这很有用,例如,当数据通过一个URL被接受并传递给反序列化器时。由于URL总是一个字符串,Deepkit Type仍然会尝试解决Number和Boolean的类型。
deserialize<boolean>('false')); //false
deserialize<boolean>('0')); //false
deserialize<boolean>('1')); //true
deserialize<number>('1')); //1
deserialize<string>(1)); //'1'
以下软类型转换已经内置于JSON序列化器中:
-
number|bigint。Number或Bigint接受String、Number和BigInt。 如果需要转换,它将使用`parseFloat`或`BigInt(x)`。
-
boolean:布尔型接受数字和字符串。0,'0','false’被解释为`false'。1,'1','真’被解释为`真'。
-
字符串。字符串接受Number、String、Boolean等。所有非字符串的值都会自动用`String(x)`转换。
也可以禁用软转换:
const result = deserialize(data, {loosely: false});
如果数据是无效的,它将不会尝试转换,而是抛出一个错误消息。
Custom Serializer
默认情况下,`@deepkit/type`带有一个JSON序列化器和TypeScript类型的类型验证。你可以对此进行扩展,添加或删除序列化功能,或改变验证的方式,因为验证也与序列化器相关。
新连载机
一个序列化器只是一个带有注册序列化器模板的`Serializer`类的实例。串行器模板是为JIT串行器过程创建JavaScript代码的小函数。对于每一种类型(字符串、数字、布尔值等),都有一个单独的串行器模板,负责返回数据转换或验证的代码。这段代码必须与用户使用的JavaScript引擎兼容。
只有在编译器模板函数的执行过程中,你(或你应该)才能完全接触到完整的类型。这个想法是,你应该把转换类型所需的所有信息直接嵌入到JavaScript代码中,从而产生高度优化的代码(也称为JIT优化的代码)。
在下面的例子中,一个空的序列化器被创建。
import { EmptySerializer } from '@deepkit/type';
class User {
name: string = '';
created: Date = new Date;
}
const mySerializer = new EmptySerializer('mySerializer');
const user = deserialize<User>({ name: 'Peter', created: 0 }, undefined, mySerializer);
console.log(user);
$ ts-node app.ts
User { name: 'Peter', created: 0 }
正如你所看到的,没有任何东西被转换("创建 "仍然是一个数字,但我们已经将其定义为 "日期")。为了改变这种情况,我们为日期类型的反序列化添加一个序列化器模板。
mySerializer.deserializeRegistry.registerClass(Date, (type, state) => {
state.addSetter(`new Date(${state.accessor})`);
});
const user = deserialize<User>({ name: 'Peter', created: 0 }, undefined, mySerializer);
console.log(user);
$ ts-node app.ts
User { name: 'Peter', created: 2021-06-10T19:34:27.301Z }
现在我们的序列化器将该值转换为一个Date对象。
为了对序列化做同样的处理,我们注册另一个序列化模板。
mySerializer.serializeRegistry.registerClass(Date, (type, state) => {
state.addSetter(`${state.accessor}.toJSON()`);
});
const user1 = new User();
user1.name = 'Peter';
user1.created = new Date('2021-06-10T19:34:27.301Z');
console.log(serialize(user1, undefined, mySerializer));
{ name: 'Peter', created: '2021-06-10T19:34:27.301Z' }
我们的新序列化器现在可以在序列化过程中正确地将日期从Date对象转换为字符串。
实例
要看更多的例子,你可以看看Deepkit Type中包含的链接:https://github.com/deepkit/deepkit-framework/blob/master/packages/type/src/serializer.ts#L1688[JSON序列化器]的代码。
扩展一个串行器
如果你想扩展一个现有的序列化器,你可以使用类的继承来实现。这样做的原因是,应该编写蚕食者在构造函数中注册其模板。
class MySerializer extends Serializer {
constructor(name: string = 'mySerializer') {
super(name);
this.registerTemplates();
}
protected registerTemplates() {
this.deserializeRegistry.register(ReflectionKind.string, (type, state) => {
state.addSetter(`String(${state.accessor})`);
});
this.deserializeRegistry.registerClass(Date, (type, state) => {
state.addSetter(`new Date(${state.accessor})`);
});
this.serializeRegistry.registerClass(Date, (type, state) => {
state.addSetter(`${state.accessor}.toJSON()`);
});
}
}
const mySerializer = new MySerializer();