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.