如何在 TypeScript 中进行运行时类型转换?
Posted
技术标签:
【中文标题】如何在 TypeScript 中进行运行时类型转换?【英文标题】:How to do runtime type casting in TypeScript? 【发布时间】:2015-11-17 00:12:12 【问题描述】:目前我正在做一个 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'
虽然这编译得很好 - 并且智能感知建议我使用该函数,但它给出了运行时异常。解决方案可能是:
var somejson = 'name' : 'Ann' ;
var ann = new Person(somejson);
console.log(ann.name); // 'Ann'
console.log(ann.giveName()); // 'Ann'
但这需要我为所有类型创建构造函数。特别是,在处理树状类型和/或来自 AJAX 调用的集合时,必须遍历所有项目并为每个项目新建一个实例。
所以我的问题:有没有更优雅的方法来做到这一点?也就是说,转换为一个类型并立即为其提供原型函数?
【问题讨论】:
类型转换纯粹是对编译器/IDE 的提示。它永远不会向您的普通 javascript 对象添加方法。因此,您需要将 JSON 数据实例化为Person
对象,这是没有办法的。您可以轻松编写一个实用函数来为您实例化对象数组/树。在那张纸条上,投射将如何帮助您处理一组对象?您仍然需要遍历数组并转换每个元素,对吧?
【参考方案1】:
@DavidSherret 的 accepted answer 不适用于 TypeScript 3.5.1 及以后的版本。有关详细信息,请参阅问题#31661。
以下是在 TypeScript 上测试的更新版本4.3.2
function instanceFromJSON<T>(
Type: new(): T; ,
jsonObj: Record<string, unknown>,
): T
const obj = new Type();
for (const prop in jsonObj)
if (Object.prototype.hasOwnProperty.call(jsonObj, prop))
const key = prop as keyof T;
obj[key] = jsonObj[prop] as T[keyof T];
return obj;
【讨论】:
【参考方案2】:你可以使用Object.assign,例如:
var somejson = 'name' : 'Ann' ;
var ann = Object.assign(new Person, somejson);
console.log(ann.name); // 'Ann'
console.log(ann.giveName()); // 'Ann'
但是如果你有嵌套类,你必须映射抛出对象并为每个元素分配。
【讨论】:
【参考方案3】:类的原型可以动态影响到对象:
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。
【讨论】:
J'aime lire la documentation en francais ;) 只要类没有任何箭头函数,此解决方案就可以工作。 哈哈!对不起!我编辑了。属性(包括箭头函数)存储在对象实例中,而不是原型中。是的,此解决方案不会在对象中添加任何属性。【参考方案4】:查看编译后的 JavaScript,您会看到类型断言(强制转换)消失了,因为它仅用于编译。现在你告诉编译器somejson
对象的类型是Person
。编译器相信你,但在这种情况下,这不是真的。
所以这个问题是一个运行时 JavaScript 问题。
让这个工作的主要目标是以某种方式告诉 JavaScript 类之间的关系是什么。所以...
-
找到一种方法来描述类之间的关系。
创建一些东西以根据此关系数据自动将 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?
【讨论】:
似乎应该有一种方法可以跳过使用objType
参数,因为您已经在函数定义中指定了泛型......也许不是,我正在阅读关于打字稿泛型现在看看是否有办法只需要传入json
参数...
@DannyBullis 唯一的问题是因为它编译为 JavaScript 类型信息(包括泛型)在运行时消失。它可以通过分析打字稿代码本身然后从中生成一些代码来解决。这是一个更复杂的解决方案。我知道该怎么做,但我只需要抽出时间来写它(可能在假期我会这样做......我已经创建了一个提醒)。
啊,我才意识到这一点。我会继续前进。祝你好运,节日快乐。
@DannyBullis 谢谢,你也是。我稍后会在这里评论。
一个缺点是 Person 需要一个空的构造函数,并且由于 Typescript 允许单个构造函数,这是一个限制。以上是关于如何在 TypeScript 中进行运行时类型转换?的主要内容,如果未能解决你的问题,请参考以下文章
如何修复'Typescript“正在进行类型检查......”花费太长时间'?
如何在 TypeScript 中 document.getElementById() 方法的类型转换期间处理 ESLint 错误?