2015-08-23 95 views
14

目前我正在开发一个打字稿项目,我非常喜欢TypeScript带来的类型推断。然而 - 从HTTP获取对象调用时 - 我可以将它们转换成所需的类型,获取代码完成和调用它们的功能编译时间,但那些导致错误运行如何在TypeScript中执行运行时类型转换?

例子:

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调用的集合时,必须遍历所有项目并为每个项目新建一个实例。

所以我的问题:有没有更好的方法来做到这一点?也就是说,投射到一个类型并且立即有可用的原型函数?

+0

类型转换纯粹是编译器/ IDE的提示。它永远不会将方法添加到您的纯Javascript对象。所以你需要将JSON数据实例化为一个Person对象,这是没有办法的。您可以轻松编写一个实用程序函数来为您实例化对象的数组/树。在那个笔记上,演员如何帮助你处理一系列对象?你仍然需要迭代数组并转换每个元素,对吧? –

回答

5

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

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

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

  1. 找到一种方法来描述类之间的关系。
  2. 根据这个关系数据创建一些东西来自动将json映射到类。

有很多方法可以解决这个问题,但我会举一个例子来说明问题。这应该有助于描述需要完成的工作。

说我们有这个类:

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?

+0

似乎应该有一种方法可以跳过使用'objType'参数,因为您已经在函数定义中指定了泛型......也许不是,我正在阅读有关打字稿泛型以查看是否有方法只需要传递'json'参数... –

+0

@DannyBullis唯一的问题是,它编译为JavaScript时,类型信息(包括泛型)在运行时消失。它可以通过分析打字稿代码本身,然后从中产生一些代码来解决。这是一个更复杂的解决方案。我知道该怎么做,但我只需要开始写作(可能在假期中我会这样做......我已经创建了一个提醒)。 –

+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

J'aime lire la documentation en francais;)只要班级没有任何箭头功能,此解决方案就可以工作。 –

+0

哈哈!抱歉!我编辑过。属性(包括箭头函数)存储在对象实例中,而不是存储在原型中。是的,该解决方案不会在对象中添加任何属性。 – Paleo