TypeScript 中的类型安全反序列化

问题描述 投票:0回答:3

我有这个示例 TypeScript 代码,应该将一个简单的 JSON 反序列化为类

Person
的实例,然后对其调用
foo
方法,但它不起作用:

class Person {
    name!: string;
    age!: number;

    foo() {
        console.log("Hey!");
    }
}

fetch("/api/data")
    .then(response => {
        return response.json() as Promise<Person>;
    }).then((data) => {
        console.log(data);
        data.foo();
    });

控制台的输出显示该对象处于正确的形状,但它没有被识别

Person

Object { name: "Peter", age: 44 }
​
age: 44
​name: "Peter"
​

因此,当它尝试调用

foo
方法时,它会失败:

未捕获(承诺中)类型错误:data.foo 不是函数

http://127.0.0.1:8000/app.js:14
承诺回调* http://127.0.0.1:8000/app.js:12

我该如何修复它?我应该使用

Object.assign
还是有其他更好/native 解决方案?

let x = (<any>Object).assign(Object.create(Person.prototype), data);
x.foo();
json typescript serialization deserialization
3个回答
3
投票

请记住,TypeScript 只是一种使用类型保护注释 JavaScript 代码的方法。它不做任何额外的事情。例如,说

response.json()
返回的对象应被视为
Promise<Person>
并不意味着它将调用
Person
类的构造函数。相反,您只会得到一个具有名称和年龄的普通旧 JavaScript 对象。

在我看来,您需要为

Person
类创建一个构造函数,该构造函数可以基于与其接口匹配的对象创建 Person 的新实例。也许是这样的?

interface PersonLike {
    name: string;
    age: string;
}

class Person implements PersonLike {
    constructor(data: PersonLike) {
        this.name = data.name;
        this.age = data.age;
    }

    name: string;
    age: string;

    foo() {
        console.log("Hey!");
    }
}

fetch("/api/data")
    .then(response => {
        return response.json() as Promise<PersonLike>;
    }).then((data) => {
        const person = new Person(data);

        person.foo();
    });

我还建议使用类型保护而不是

as
关键字,以防您从更改中获取数据的 API。也许是这样的:

function isPersonLike(data: any): data is PersonLike {
    return typeof data?.name === 'string' && data?.age === 'string';
}

fetch("/api/data")
    .then(response => {
        return response.json();
    }).then((data: unknown) => {
        if (isPersonLike(data)) {
            const person = new Person(data);

            person.foo();
        }
    });

0
投票

...应该将一个简单的 JSON 反序列化为类 Person 的实例,然后...

不幸的是,TypeScript 中的泛型类型只能充当某种模型设计助手。它永远不会被编译成 JavaScript 文件。以您的“获取”代码为例:

fetch("/api/data")
    .then(response => {
        return response.json() as Promise<Person>;
    }).then((data) => {
        console.log(data);
        data.foo();
    });

将上面的 TypeScript 文件编译成 JavaScript 后,我们可以发现代码

as Promise<Person>
被完全删除了:

fetch("/api/data")
    .then(function (response) {
    return response.json();
}).then(function (data) {
    console.log(data);
    data.foo();
});

要实现“类型安全的反序列化”,需要在序列化过程中保存类/原型信息。否则,这些类/原型信息将会丢失。

...或者还有另一个更好/本机的解决方案? ...顺便说一句,如果类字段具有自定义类型,因此它是另一个类的实例怎么办?

不,没有原生解决方案,但您可以使用一些库实现“类型安全”序列化/反序列化。

我制作了一个名为 esserializer 的 npm 模块来自动解决此问题:在序列化期间以纯 JSON 格式保存 JavaScript 类实例值及其类名信息。随后,在反序列化阶段(可能在另一个进程或另一台机器上),esserializer 可以使用相同的类定义递归地反序列化对象实例,并保留所有类/属性/方法信息。对于您的“获取”代码案例,它看起来像:

// Node.js server side, serialization happens here.
const ESSerializer = require('esserializer');
router.get('/api/data', (req, res) => {
  // ...
  res.json(ESSerializer.serialize(anInstanceOfPerson));
});

// Client side, deserialization happens here.
const ESSerializer = require('esserializer');
fetch("/api/data")
    .then(response => {
        return response.text() as Promise<string>;
    }).then((data) => {
        const person = ESSerializer.deserialize(data, [Person, CustomType1, CustomType2]);
        console.log(person);

        person.foo();
    });

0
投票

TypeScript 中最流行的类型安全反序列化库是 io-ts,在撰写本文时,每周下载量超过一百万次。我还可以推荐一个辅助库 io-ts-promise

我们已在中等规模生产后端应用程序中使用它们多年。没有前端使用经验。

令人惊讶的是,类型安全反序列化并不是 TypeScript 语言的一部分。

请记住,函数无法序列化为 JSON、XML 或任何其他流行格式。您不能只是将一些 JSON 对象转换为具有函数的类,并期望它现在具有该类的函数。其他答案已经指出了如何处理这个问题。

© www.soinside.com 2019 - 2024. All rights reserved.