Issue
I'm trying to write a small function to merge two same-type object into a third same-type object but I'm having trouble with the typings.
Here's an example of my situation (also on TS Playground):
interface IndividualDto {
firstnames: string[];
birthdates: Date[];
}
function mergeIndividuals(
individual1: IndividualDto,
individual2: IndividualDto
): IndividualDto {
const mergedIndividual: IndividualDto = { firstnames: [], birthdates: []};
const arrayProperties = [
"firstnames",
"birthdates",
] as const;
arrayProperties.forEach((property) => {
mergedIndividual[property] = [...individual1[property], ...individual2[property]];
});
return mergedIndividual;
}
My idea is to treat all of those property the same way and deal with other properties in another way (not relevent here.)
With this code I have the following error on mergedIndividual[property], though:
Type '(string | MilDate)[]' is not assignable to type 'string[] & MilDate[]'.
Type '(string | MilDate)[]' is not assignable to type 'string[]'.
Type 'string | MilDate' is not assignable to type 'string'.
Type 'MilDate' is not assignable to type 'string'.
TypeScript doesn't seem to be able to understand that because the three objects are of the same type and I'm referencing the same property, they're all compatible for assignment.
Also, I would like to explicitly type arrayProperties so that I cannot add strings which are not keys of IndividualDto. I've tried using const arrayProperties: readonly (keyof IndividualDto)[] but it's less specific than the string union and creates other errors related to other properties not being array types.
Solution
The problem is that TypeScript doesn't know which property property names, just that it's one of the properties of IndividualDto (if you type the array correctly). So mergedIndividual[property] ends up with the typestring[] & MilDate[], which of course you can't assign anything to.
You could split IndividualDto into two interfaces (which you can then combine into a single IndividualDto type) where the types of all of the properties are the same. For instance:
// The properties that are `string[]`
interface IndividualDtoStringArrays {
firstnames: string[];
lastnames: string[];
}
// The properties that are `MilDate[]`
interface IndividualDtoMilDateArrays {
birthDates: MilDate[];
}
// The overall type
type IndividualDto = IndividualDtoStringArrays & IndividualDtoMilDateArrays;
That way, you can use two separate arrays and TypeScript is happy because there's no type ambiguity:
function mergeIndividuals(
individual1: IndividualDto,
individual2: IndividualDto
): IndividualDto {
const mergedIndividual = emptyIndividualDto();
const stringArrayProperties: readonly (keyof IndividualDtoStringArrays)[] = [
"firstnames",
"lastnames",
// ...
];
const milDateArrayProperties: readonly (keyof IndividualDtoMilDateArrays)[] = [
"birthDates",
// ...
];
for (const property of stringArrayProperties) {
mergedIndividual[property] = [...individual1[property], ...individual2[property]];
}
for (const property of milDateArrayProperties) {
mergedIndividual[property] = [...individual1[property], ...individual2[property]];
}
return mergedIndividual;
}
Alternatively: This is a small, well-contained utility function, and even though TypeScript doesn't know the assignment is valid, you do. So you might just go for error suppression via ts-ignore:
function mergeIndividuals(
individual1: IndividualDto,
individual2: IndividualDto
): IndividualDto {
const mergedIndividual = emptyIndividualDto();
const arrayProperties: readonly (keyof IndividualDto)[] = [
"firstnames",
"lastnames",
"birthDates",
];
for (const property of arrayProperties) {
// @ts-ignore
mergedIndividual[property] = [...individual1[property], ...individual2[property]];
}
return mergedIndividual;
}
Error suppression, like type assertion, is best avoided. Your object looks like it just has a couple of types of properties, so I'd probably go with the first solution (separate loops). But this is a viable option in a small, well-contained function like this one.
Answered By - T.J. Crowder
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.