【问题标题】:Type safe deserialization in TypeScriptTypeScript 中的类型安全反序列化
【发布时间】:2021-11-21 20:39:54
【问题描述】:

我有这个示例 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 方法时失败:

Uncaught (in promise) TypeError: data.foo is not a function

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


    【解决方案1】:

    请记住,TypeScript 只是一种使用类型保护注释 JavaScript 代码的方法。它没有做任何额外的事情。例如,说 response.json() 返回的对象应该被视为 Promise&lt;Person&gt; 并不意味着它将调用您的 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();
            }
        });
    

    【讨论】:

    • 我在想 TypeScript compiler 在某种程度上做了类似的事情,因为我指定了要应用的泛型类型。顺便说一句,如果类字段具有自定义类型,那么它是另一个类的实例怎么办?我想我必须级联上述方法。
    • 你的意思是如果你的类的属性类型是第二类?在这种情况下是的,您必须在第一个类的构造函数中使用new ClassB 创建第二个类的新实例。
    【解决方案2】:

    ... 应该将简单的 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&lt;Person&gt; 的代码被完全删除了:

    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();
        });
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-05-22
      • 2012-12-08
      • 2022-01-06
      • 2012-03-25
      • 1970-01-01
      • 2022-09-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多