Home > other >  Typescript error converting a type to anorther
Typescript error converting a type to anorther

Time:05-31

I have a function that formats some data from a type to another. Basically changes some case and flats some fields.

This is my code:

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 protected]"
  },
  otherFields: { lastname: "Snow", city: "Berlin" }
});

And my types:

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

HERE the working code

I get a TypeScript error in formatData:

Type '{ Date: string; UserInfo: { name: string; email: string; }; }' is not assignable to type 'LoginFormatted'. Type '{ Date: string; UserInfo: { name: string; email: string; }; }' is not assignable to type '{ [key: string]: string; }'. Property 'UserInfo' is incompatible with index signature. Type '{ name: string; email: string; }' is not assignable to type 'string'.

What am I doing wrong?

I read these questions (How to define Typescript type as a dictionary of strings but with one numeric "id" property, Property is not assignable to string index in interface), and I tried to modify my code like this but it still doesn't work:

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

CodePudding user response:

I actually think that the other questions linked in the comments have better answers than this, but your question got me curious so I made a couple tries myself to see how TypeScript works in these cases.

Type Definition

On the Type Definition aspect, as far as I can see, everything works smoothly:

 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

So as far as the type Combine goes TypeScript seems to correctly add the Core field to a string-string dictionary.

Making Instances

Now the problem is how do we make an instance of that Combine type, because that's when TypeScript messes up.

// 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!"
}

I guess that's because when you define a dictionary the string-string check kicks in and doesn't let you declare other types, while when you correctly fallow the string-string constraint it realises it's missing the other Core field.

Solutions & Hacks

Now as I said the other questions have quite more in depth and clean answers to this issue, like leaving the dictionary to a separate sub-field without mixing it in with the Core field or making a multiple type dictionary instead of a strict string-string one.

Yet it really feels dumb to have it working like a charm when handling the actual instance but not being able to properly create one.

So I came up with this little "hack" which seems to work

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

So basically you break TS typing and then put it back together.

So in your case would be enough to write something like:

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

To get everything working.

CodePudding user response:

adjust the loggingFormatted to the following

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

this index signature will require Date and UserInfo while allowing extension with other properties.

  • Related