如何在TypeScript中进行运行时类型转换?

Joc*_*ick 21 typescript

目前我正在开发一个打字稿项目,我真的很喜欢TypeScript带来的类型推断.但是 - 当从HTTP调用获取对象时 - 我可以将它们转换为所需类型,获取代码完成并在它们上编译函数编译时间,但这些会导致错误运行时

例:

class Person{
    name: string;

    public giveName() {
        return this.name;
    }

    constructor(json: any) {
        this.name = json.name;
    }
}

var somejson = { 'name' : 'John' }; // Typically from AJAX call
var john = <Person>(somejson);      // The cast

console.log(john.name);       // 'John'
console.log(john.giveName()); // 'undefined is not a function'
Run Code Online (Sandbox Code Playgroud)

虽然这很好地编译 - 并且intellisense建议我使用该函数,但它给出了运行时异常.解决方案可能是:

var somejson = { 'name' : 'Ann' };
var ann = new Person(somejson);

console.log(ann.name);        // 'Ann'
console.log(ann.giveName());  // 'Ann'
Run Code Online (Sandbox Code Playgroud)

但这需要我为我的所有类型创建构造函数.特别是,当处理类似树的类型和/或来自AJAX调用的集合时,必须循环遍历所有项并为每个项新建一个实例.

所以我的问题是:有更优雅的方式来做到这一点吗?也就是说,施放到一个类型并立即可用于它的原型功能?

Pal*_*leo 7

类的原型可以动态地影响对象:

function cast<T>(obj: any, cl: { new(...args): T }): T {
  obj.__proto__ = cl.prototype;
  return obj;
}

var john = cast(/* somejson */, Person);
Run Code Online (Sandbox Code Playgroud)

请参阅此处的文档__proto__.


Dav*_*ret 6

看看编译好的JavaScript,你会看到类型断言(强制转换)消失,因为它只用于编译.现在你告诉编译器该somejson对象是类型的Person.编译器相信你,但在这种情况下,这不是真的.

所以这个问题是运行时JavaScript问题.

让它工作的主要目标是以某种方式告诉JavaScript类之间的关系是什么.所以...

  1. 找到一种方法来描述类之间的关系.
  2. 创建一些内容以根据此关系数据自动将json映射到类.

有很多方法可以解决它,但我会提供一个例子.这应该有助于描述需要做什么.

说我们有这个班:

class Person {
    name: string;
    child: Person;

    public giveName() {
        return this.name;
    }
}
Run Code Online (Sandbox Code Playgroud)

而这个json数据:

{ 
    name: 'John', 
    child: {
        name: 'Sarah',
        child: {
            name: 'Jacob'
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

要自动将其映射为实例Person,我们需要告诉JavaScript类型是如何相关的.我们不能使用TypeScript类型信息,因为一旦编译它们就会松开它.一种方法是在描述此类型的类型上使用静态属性.例如:

class Person {
    static relationships = {
        child: Person
    };

    name: string;
    child: Person;

    public giveName() {
        return this.name;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,这是一个可重用函数的示例,它根据此关系数据处理为我们创建对象:

function createInstanceFromJson<T>(objType: { new(): T; }, json: any) {
    const newObj = new objType();
    const relationships = objType["relationships"] || {};

    for (const prop in json) {
        if (json.hasOwnProperty(prop)) {
            if (newObj[prop] == null) {
                if (relationships[prop] == null) {
                    newObj[prop] = json[prop];
                }
                else {
                    newObj[prop] = createInstanceFromJson(relationships[prop], json[prop]);
                }
            }
            else {
                console.warn(`Property ${prop} not set because it already existed on the object.`);
            }
        }
    }

    return newObj;
}
Run Code Online (Sandbox Code Playgroud)

现在,以下代码将起作用:

const someJson = { 
        name: 'John', 
        child: {
            name: 'Sarah',
            child: {
                name: 'Jacob'
            }
        }
    };
const person = createInstanceFromJson(Person, someJson);

console.log(person.giveName());             // John
console.log(person.child.giveName());       // Sarah
console.log(person.child.child.giveName()); // Jacob
Run Code Online (Sandbox Code Playgroud)

操场

理想情况下,最好的方法是使用实​​际读取TypeScript代码的内容并创建一个保存类之间关系的对象.这样我们就不需要手动维护关系并担心代码更改.例如,现在重构代码对于此设置来说有点风险.我不确定此刻存在类似的东西,但这绝对是可能的.

替代方案

我刚刚意识到我已经用稍微不同的解决方案回答了类似的问题(虽然不涉及嵌套数据).你可以在这里阅读更多的想法:

JSON到TypeScript类的实例?


小智 5

您可以使用 Object.assign,例如:

var somejson = { 'name' : 'Ann' };
var ann = Object.assign(new Person, somejson);

console.log(ann.name);        // 'Ann'
console.log(ann.giveName());  // 'Ann'
Run Code Online (Sandbox Code Playgroud)

但是如果你有嵌套的类,你必须映射抛出对象并为每个元素分配。