Búsqueda de sitios web

Error de Typescript al convertir un tipo a otro


Tengo una función que formatea algunos datos de un tipo a otro. Básicamente cambia algunas mayúsculas y minúsculas y aplana algunos campos.

Este es mi código:

const formatData = (data: Login): LoginFormatted => {
  return {
    Date: data.date,
    UserInfo: {
      name: data.user.name,
      email: data.user.email
    },
    ...data.otherFields
  };
};

const doThat = (data: Login) => {
  const dataFormatted = formatData(data);
  return dataFormatted;
};

const dataFormatted = doThat({
  date: "2000-12-01",
  user: {
    name: "Mary",
    email: "[email "
  },
  otherFields: { lastname: "Snow", city: "Berlin" }
});

Y mis tipos:

export type Login = {
  date: string;
  user: {
    name: string;
    email: string;
  };
  otherFields?: { [key: string]: string };
};

export type LoginFormatted = {
  Date: string;
  UserInfo: {
    name: string;
    email: string;
  };
} & { [key: string]: string };

AQUÍ el código que funciona

Recibo un error de TypeScript en formatData:

El tipo '{ Date: string; UserInfo: { name: string; email: string; }; }' no se puede asignar al tipo 'LoginFormatted'. El tipo '{ Date: string; UserInfo: { name: string; email: string; }; }' no se puede asignar al tipo '{ [key: string]: string; }'. La propiedad 'UserInfo' es incompatible con la firma de índice. El tipo '{ name: string; email: string; }' no se puede asignar al tipo 'string'.

¿Qué estoy haciendo mal?

Leí estas preguntas (Cómo definir un tipo de Typescript como un diccionario de cadenas pero con una propiedad numérica "id", La propiedad no se puede asignar al índice de cadena en la interfaz), y traté de modificar mi código así pero todavía no funciona:

export interface Login {
  date: string;
  user: {
    name: string;
    email: string;
  };
  otherFields?: Other;
}

export type LoginFormatted = {
  Date: string;
  UserInfo: {
    name: string;
    email: string;
  };
} & Other;

interface Other {
  [key: string]: string;
}

Respuesta #1

De hecho, creo que las otras preguntas enlazadas en los comentarios tienen mejores respuestas que esta, pero tu pregunta me despertó la curiosidad, así que hice un par de intentos yo mismo para ver cómo funciona TypeScript en estos casos.

Definición de tipo

En cuanto al aspecto de la Definición de Tipo, hasta donde puedo ver, todo funciona sin problemas:

 type Combine = {
     Core: {
         Name: string;
         Colour: string;
     }
 } & { [key: string]: string }

 // then writing
 const combine: Combine = {... we'll see this...}
 combine.Core.Name // is prompted and doesn't give any error
 combine["attachment"] // this also doesn't give any error

Así que, en lo que respecta al tipo Combine, TypeScript parece agregar correctamente el campo Core a un diccionario de cadena-cadena.

Crear instancias

Ahora el problema es cómo hacemos una instancia de ese tipo Combine, porque ahí es cuando TypeScript se equivoca.

// this gives the same error as you reported
const combine: Combine = {
    Core: {
       Name: "Wheatley",
       Colour: "Blue"
    }
}

// while this says "hey, it must include a Core field!" ಠ_ಠ
const combine: Combine = {
    attachment: "nope!"
}

Supongo que eso es porque cuando defines un diccionario, se activa la verificación de cadena-cadena y no te permite declarar otros tipos, mientras que cuando sigues correctamente la restricción de cadena-cadena, se da cuenta de que falta el otro campo Core.

Soluciones y trucos

Ahora, como dije, las otras preguntas tienen respuestas bastante más profundas y claras a este problema, como dejar el diccionario en un subcampo separado sin mezclarlo con el campo Core o hacer un diccionario de tipo múltiple en lugar de uno estrictamente de cadena a cadena.

Sin embargo, realmente se siente tonto que funcione de maravilla al manejar la instancia real pero no poder crear una adecuadamente.

Así que se me ocurrió este pequeño "truco" que parece funcionar

const combine: Combine = {
    Core: {
       Name: "Wheatley",
       Colour: "Blue"
    }
    attachment: "nope!"
} as unknown as Combine

Así que básicamente rompes la tipificación de TS y luego la vuelves a armar.

Así que en tu caso sería suficiente escribir algo como:

const formatData = (data: Login): LoginFormatted => {
  return {
    Date: data.date,
    UserInfo: {
      name: data.user.name,
      email: data.user.email
    },
    ...data.otherFields
  } as unknown as LoginFormatted;
};

Para que todo funcione.

Respuesta #2

ajusta el loggingFormatted a lo siguiente

export type LoginFormatted = {
  [key: string]: string;
  Date: string;
  UserInfo: {
    name: string;
    email: string;
  };
};

esta firma de índice requerirá Date y UserInfo mientras permite la extensión con otras propiedades.