Transmitir objeto a la interfaz en TypeScript

94

Estoy tratando de hacer una conversión en mi código desde el cuerpo de una solicitud en express (usando middleware body-parser) a una interfaz, pero no aplica la seguridad de tipos.

Esta es mi interfaz:

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

Este es el código en el que intento hacer el reparto:

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

Y finalmente, el método de servicio que se llama:

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

Puedo pasar cualquier argumento, incluso los que no se acercan a la definición de la interfaz , y este código funcionará bien. Esperaría que, si la conversión del cuerpo de respuesta a la interfaz no es posible, se lance una excepción en tiempo de ejecución como Java o C #.

He leído que en TypeScript la conversión no existe, solo Type Assertion, por lo que solo le dirá al compilador que un objeto es de tipo x, así que ... ¿Me equivoco? ¿Cuál es la forma correcta de hacer cumplir y garantizar la seguridad de tipos?

Elías García
fuente
1
Defina "no funciona". Se preciso. ¿Hay algún error? ¿Cúal? ¿En tiempo de compilación? ¿En tiempo de ejecución? ¿Lo que pasa?
JB Nizet
1
En tiempo de ejecución, el código se ejecuta normalmente, con cualquier objeto que pase.
Elias García
No está claro lo que estás preguntando
Nitzan Tomer
Mi pregunta es cómo convertir el objeto entrante en un objeto escrito. Si la transmisión no es posible, lanza una excepción en tiempo de ejecución, como Java, C # ...
Elias García
¿Responde esto a tu pregunta? Conversión de tipos de TypeScript o JavaScript
Michael Freidgeim

Respuestas:

133

No hay conversión en javascript, por lo que no puede lanzar si "falla".
TypeScript admite la transmisión, pero eso es solo para el tiempo de compilación, y puede hacerlo así:

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

Puede verificar en tiempo de ejecución si el valor es válido y si no arroja un error, es decir:

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

Editar

Como señaló @huyz, no hay necesidad de la aserción de tipo porque isToDoDtoes una protección de tipo, por lo que esto debería ser suficiente:

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

this.toDoService.addToDo(req.body);
Nitzan Tomer
fuente
No creo que necesite el elenco const toDo = req.body as IToDoDto;ya que el compilador de TS sabe que es un IToDoDtoen este punto
huyz
9
para cualquiera que esté buscando una afirmación de tipo en general, no use <>. esto está en desuso. usoas
Abhishek Deb
" No hay conversión en javascript, por lo que no se puede lanzar si" falla ". Creo, más concretamente, que las interfaces en TypeScript no son procesables; de hecho, son 100% azúcar sintético . Hacen que sea más fácil mantener las estructuras conceptualmente , pero no tienen un impacto real en el código transpilado , que es, en mi opinión, increíblemente confuso / anti-patrón, como evidencia la pregunta de OP. No hay ninguna razón por la que las cosas que no coinciden con las interfaces no puedan incluir JavaScript transpilado; es una elección consciente (y pobre, en mi opinión) de TypeScript.
ruffin
Las interfaces de @ruffin no son azúcar sintáctico, pero tomaron una decisión consciente para mantenerlo solo en tiempo de ejecución. Creo que es una gran elección, de esa forma no hay penalización de rendimiento en tiempo de ejecución.
Nitzan Tomer
Tomayto tomahto ? La seguridad de tipos de las interfaces en TypeScript no se extiende a su código transpilado, e incluso antes del tiempo de ejecución, la seguridad de tipos está muy limitada, como vemos en el problema del OP donde no hay seguridad de tipos en absoluto . TS podría decir: "¡Oye, espera, anyno está garantizado que lo estés IToDoDtotodavía!", Pero TS decidió no hacerlo. Si el compilador solo detecta algunos conflictos de tipos, y ninguno en el código transpilado (y tienes razón; debería haber sido más claro @ que en el original), las interfaces son desafortunadamente, en mi opinión, [¿en su mayoría?] Azúcar.
Ruffin
7

Aquí hay otra forma de forzar una conversión de tipos incluso entre tipos e interfaces incompatibles donde el compilador TS normalmente se queja:

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

  // ... do runtime checks here

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

Luego, puede usarlo para forzar a lanzar objetos a un cierto tipo:

import { forceCast } from './forceCast';

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

Tenga en cuenta que omití la parte en la que se supone que debe hacer comprobaciones en tiempo de ejecución antes de lanzar para reducir la complejidad. Lo que hago en mi proyecto es compilar todos mis .d.tsarchivos de interfaz en esquemas JSON y usarlos ajvpara validar en tiempo de ejecución.

Sepehr
fuente
1

Si ayuda a alguien, estaba teniendo un problema en el que quería tratar un objeto como otro tipo con una interfaz similar. Intenté lo siguiente:

No pasó pelusa

const x = new Obj(a as b);

El linter se quejaba de que afaltaban propiedades que existían b. En otras palabras, atenía algunas propiedades y métodos b, pero no todos. Para solucionar esto, seguí la sugerencia de VS Code:

Pasó el deshilachado y las pruebas.

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

Tenga en cuenta que si su código intenta llamar a una de las propiedades que existen en el tipo bque no está implementado en el tipo a, debería darse cuenta de una falla en tiempo de ejecución.

Jason
fuente
1
Me alegro de haber encontrado esta respuesta, pero tenga en cuenta que si está enviando 'x' a través de la red u otra aplicación, podría estar filtrando información personal (si 'a' es un usuario, por ejemplo), porque 'x' todavía tiene todas las propiedades de 'a', simplemente no están disponibles para mecanografiar.
Zoltán Matók
@ ZoltánMatók buen punto. Además, con respecto al envío del objeto serializado a través de la red, hay un argumento para los getters y setters de estilo Java sobre JavaScript gety setmétodos.
Jason