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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/32167593/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 06:46:28  来源:igfitidea点击:

How to do runtime type casting in TypeScript?

typescript

提问by Jochen van Wylick

Currently I'm working on a typescript project and I'm really enjoying the type inference that TypeScript brings to the table. However - when getting objects from HTTP calls - I can cast them to the desired type, get code completion and call functions on them compile time, but those result in errors runtime

目前我正在做一个 typescript 项目,我真的很享受 TypeScript 带来的类型推断。但是 - 从 HTTP 调用获取对象时 - 我可以将它们转换为所需的类型,获取代码完成并在编译时调用它们的函数,但这些会导致运行时错误

Example:

例子:

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'

Although this compiles nicely - and intellisense suggests me to use the function, it gives a runtime exception. A solution for this could be:

尽管这编译得很好 - 并且智能感知建议我使用该函数,但它给出了一个运行时异常。对此的解决方案可能是:

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

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

But that will require me to create constructors for all my types. In paticular, when dealing with tree-like types and/or with collections coming in from the AJAX-call, one would have to loop through all the items and new-up an instance for each.

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

So my question:is there a more elegant way to do this? That is, cast to a type and have the prototypical functions available for it immediately?

所以我的问题是:有没有更优雅的方法来做到这一点?也就是说,转换为类型并立即为其提供原型函数?

采纳答案by David Sherret

Take a look at the compiled JavaScript and you will see the type assertion (casting) disappears because it's only for compiling. Right now you're telling the compiler that the somejsonobject is of type Person. The compiler believes you, but in this case that's not true.

查看编译后的 JavaScript,您会看到类型断言(强制转换)消失了,因为它仅用于编译。现在您告诉编译器该somejson对象的类型为Person。编译器相信你,但在这种情况下,事实并非如此。

So this problem is a runtime JavaScript problem.

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

The main goal to get this to work is to somehow tell JavaScript what the relationship between the classes are. So...

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

  1. Find a way to describe the relationship between classes.
  2. Create something to automatically map the json to classes based on this relationship data.
  1. 找到一种方法来描述类之间的关系。
  2. 创建一些东西以根据此关系数据自动将 json 映射到类。

There's many ways to solve it, but I'll offer one example off the top of my head. This should help describe what needs to be done.

有很多方法可以解决它,但我会举一个我最想知道的例子。这应该有助于描述需要做什么。

Say we have this class:

假设我们有这个类:

class Person {
    name: string;
    child: Person;

    public giveName() {
        return this.name;
    }
}

And this json data:

这个json数据:

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

To map this automatically to be instances of Person, we need to tell the JavaScript how the types are related. We can't use the TypeScript type information because we will loose that once it's compiled. One way to do this, is by having a static property on the type that describes this. For example:

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

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

    name: string;
    child: Person;

    public giveName() {
        return this.name;
    }
}

Then here's an example of a reusable function that handles creating the objects for us based on this relationship data:

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

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

Now the following code will work:

现在以下代码将起作用:

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

操场

Ideally, the best way would be to use something that actually reads the TypeScript code and creates an object that holds the relationship between classes. That way we don't need to manually maintain the relationships and worry about code changes. For example, right now refactoring code is a bit of a risk with this setup. I'm not sure that something like that exists at the moment, but it's definitely possible.

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

Alternative Solution

替代方案

I just realized I already answered a similar question with a slightly different solution (that doesn't involve nested data though). You can read it here for some more ideas:

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

JSON to TypeScript class instance?

JSON 到 TypeScript 类实例?

回答by Paleo

The prototype of the class can be dynamically affected to the object:

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

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

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

See the documentation of __proto__here.

请参阅此处的文档__proto__

回答by Omar Ayoub

You can use Object.assign, for example :

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

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

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

But if you have nested classes, you have to map throw the object and assign for each element.

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