在TypeScript中将对象转换为接口

181

我正在尝试在我的代码中将express请求的正文(使用body-parser中间件)从请求体转换为接口,但它没有强制类型安全。

这是我的接口:

export interface IToDoDto {
  description: string;
  status: boolean;
};

这是我正在尝试执行转换的代码:

@Post()
addToDo(@Response() res, @Request() req) {
  const toDo: IToDoDto = <IToDoDto> req.body; // <<< cast here
  this.toDoService.addToDo(toDo);
  return res.status(HttpStatus.CREATED).end();
}

最后,被调用的服务方法:

public addToDo(toDo: IToDoDto): void {
  toDo.id = this.idCounter;
  this.todos.push(toDo);
  this.idCounter++;
}

我可以传递任何参数,即使与接口定义不匹配,这段代码也能正常运行。如果从响应体到接口的强制转换不可能,我期望像Java或C#一样在运行时会抛出异常。

我看过 TypeScript 的文档,发现它没有类型转换,只有类型断言,所以...我错了吗?如何才能确保类型安全?


1
请定义“它不起作用”的含义。要精确。是否出现错误?哪一个?在编译时?在运行时?会发生什么? - JB Nizet
1
在运行时,代码会正常执行,使用我传递的任何对象。 - Elias Garcia
你的问题不太清楚。 - Nitzan Tomer
我的问题是如何将传入的对象转换为一个类型化的对象。如果无法进行转换,则在运行时抛出异常,就像Java、C#一样... - Elias Garcia
这个回答解决了你的问题吗?TypeScript或JavaScript类型转换 - Michael Freidgeim
4个回答

248

JavaScript 中没有类型转换,因此如果“转换失败”就无法抛出异常。
TypeScript 支持类型转换,但仅限于编译时,您可以像这样实现:

const toDo = req.body as IToDoDto;
// or
const toDo = <IToDoDto> req.body; // deprecated

你可以在运行时检查值是否有效,如果无效则抛出错误,例如:

function isToDoDto(obj: any): obj is IToDoDto {
    return typeof obj.description === "string" && typeof obj.status === "boolean";
}

@Post()
addToDo(@Response() res, @Request() req) {
    if (!isToDoDto(req.body)) {
        throw new Error("invalid request");
    }

    const toDo = req.body as IToDoDto;
    this.toDoService.addToDo(toDo);
    return res.status(HttpStatus.CREATED).end();
}

编辑

正如 @huyz 指出的那样,不需要类型断言,因为 isToDoDto 是一个类型守卫,所以这应该足够了:

if (!isToDoDto(req.body)) {
    throw new Error("invalid request");
}

this.toDoService.addToDo(req.body);

我认为在 const toDo = req.body as IToDoDto; 中你不需要类型转换,因为 TypeScript 编译器此时已经知道它是一个 IToDoDto 类型。 - huyz
24
针对任何寻找类型断言的人,不要使用 <>,因为它已经被弃用了。请使用as - Abhishek Deb
2
“在JavaScript中没有强制类型转换,因此如果“转换失败”,您无法抛出异常。”我认为更重要的是,TypeScript中的接口不可执行;实际上,它们是100%的语法糖。它们使结构在概念上更易于维护,但对编译后的代码没有任何实际影响——这在我的看法中非常令人困惑/反模式,正如OP的问题所证明的那样。没有理由不能在编译后的JavaScript中抛出与接口不匹配的异常;这是TypeScript的一个有意(并且很差,依我之见)的选择。 - ruffin
@ruffin 接口不是语法糖,但他们有意选择仅在运行时保留它。我认为这是一个很好的选择,这样就不会在运行时产生性能损失。 - Nitzan Tomer
在 TypeScript 中,接口所提供的类型安全性并不会延伸到你转译后的代码中,甚至在运行前这种类型安全性也是非常有限的——正如我们在 OP 的问题中所看到的,那里根本没有任何类型安全。TS 可以说:“等等,你的 any 并不能保证是 IToDoDto!”,但 TS 却选择不这样做。如果编译器只能捕获部分类型冲突,并且 _在转译后的代码中没有任何冲突_(你是对的;我在原文中应该更加明确),那么接口就很不幸地成为了[大多数?]糖衣。 - ruffin

10

这是另一种强制类型转换的方法,即使在不兼容的类型和接口之间,在这种情况下TS编译器通常会发出警告:

export function forceCast<T>(input: any): T {

  // ... do runtime checks here

  // @ts-ignore <-- forces TS compiler to compile this as-is
  return input;
}

然后您可以使用它来强制将对象转换为特定类型:

import { forceCast } from './forceCast';

const randomObject: any = {};
const typedObject = forceCast<IToDoDto>(randomObject);

请注意,我省略了你应该在进行强制转换之前进行运行时检查的部分,以减少复杂性。在我的项目中,我将所有的.d.ts接口文件编译成JSON模式,并使用ajv在运行时验证。

10
如果有帮助的话,我最近遇到了这样一个问题:我想将一个对象视为具有类似接口的另一种类型。我尝试了以下操作: 未能通过代码检查
const x = new Obj(a as b);

代码检查器提示 a 缺少在 b 上存在的属性。换句话说,a 具有一些 b 的属性和方法,但不是全部。为了解决这个问题,我按照 VS Code 的建议进行了以下操作:

通过了代码检查和测试

const x = new Obj(a as unknown as b);

请注意,如果您的代码尝试调用在类型a上未实现的类型b上存在的属性之一,您应该意识到运行时错误。

1
我很高兴找到了这个答案,但请注意,如果您正在通过网络或发送给另一个应用程序的是“x”,那么您可能会泄露个人信息(例如,“a”是用户),因为“x”仍然具有“a”的所有属性,只是在TypeScript中不可用。 - Zoltán Matók
@ZoltánMatók 说得好。此外,关于在网络上发送序列化对象,有一个论点认为Java风格的getter和setter优于JavaScript的getset方法。 - Jason

0
JSON.parse(JSON.stringify(api_output)) as BoxT;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接