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

时间:2015-08-23 14:14:28

标签: typescript

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

示例:

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'

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

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

console.log(ann.name);        // 'Ann'
console.log(ann.giveName());  // 'Ann'

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

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

3 个答案:

答案 0 :(得分:7)

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

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

var john = cast(/* somejson */, Person);

请参阅the documentation of __proto__ here

答案 1 :(得分:6)

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

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

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

  1. 找到一种描述类之间关系的方法。
  2. 创建一些内容,根据此关系数据自动将json映射到类。
  3. 有很多方法可以解决这个问题,但我会提供一个例子。这应该有助于描述需要做什么。

    假设我们有这门课程:

    class Person {
        name: string;
        child: Person;
    
        public giveName() {
            return this.name;
        }
    }
    

    这个json数据:

    { 
        name: 'John', 
        child: {
            name: 'Sarah',
            child: {
                name: 'Jacob'
            }
        }
    }
    

    要自动将其映射为Person的实例,我们需要告诉JavaScript类型是如何相关的。我们无法使用TypeScript类型信息,因为我们将在编译后将其丢失。一种方法是在描述此类型的类型上使用静态属性。例如:

    class Person {
        static relationships = {
            child: Person
        };
    
        name: string;
        child: Person;
    
        public giveName() {
            return this.name;
        }
    }
    

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

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

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

    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
    

    Playground

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

    替代解决方案

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

    JSON to TypeScript class instance?

答案 2 :(得分:1)

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

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

console.log(ann.name);        // 'Ann'
console.log(ann.giveName());  // 'Ann'

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