When I have to iterate on the keys of mappedTypes, I always struggle to make Typescript narrow the type of the value associated with the key I am iterating on and always finish with a ts-ignore
...
A code sample demonstrating the issue worths a 1000 words :) Here it is !
type Person = {
firstName?: string
lastName?: string
age?: number
}
type Update<T> = {
before: T,
after: T,
}
type Updates = { [Key in keyof Person]: Update<Person[Key]> }
/**
* Dummy transformers for illustration purposes
*/
const transformers : {[Key in keyof Person]: (value: Person[Key]) => Person[Key]} = {
firstName: value => value?.toUpperCase(),
lastName: value => value?.toUpperCase(),
age: value => typeof value === 'undefined'? 0 : value 2
}
/**
* Input: {firstName: 'david', age: 23}
* Output: {firstName: {before: 'david', after: 'David'}, age: {before: 22, after: 24}}
* @param person
*/
function enrichPerson (person: Person): Updates {
return Object.keys(person).reduce(
(previousUpdates, key) => {
const typedKey = key as keyof Person
const result = { ...previousUpdates }
result[typedKey] = {
before: person[typedKey], // <--- Typing problem Here
after: transformers[typedKey](person[typedKey]), // <--- Typing problem Here
}
return result
},
{} as Updates
)
}
export {}
What can I do to narrow the type of the value ? If it is not possible, what pattern would you use in this case ?
Thanks a lot for your help ! This problem is haunting me !
CodePudding user response:
I have added one more type Updatetable<Type>
and another function update
to make assignment type safe. I hope this works for you:
type Person = {
firstName?: string;
lastName?: string;
age?: number;
};
type Update<T> = {
before: T;
after: T;
};
type Updatetable<Type> = { [Key in keyof Type]: Update<Type[Key]> };
type Updates = Updatetable<Person>;
/**
* Dummy transformers for illustration purposes
*/
const transformers: { [Key in keyof Person]: (value?: Person[Key]) => Person[Key] } = {
firstName: (value) => value?.toUpperCase(),
lastName: (value) => value?.toUpperCase(),
age: (value) => (typeof value === "undefined" ? 0 : value 2),
};
/**
* Input: {firstName: 'david', age: 23}
* Output: {firstName: {before: 'david', after: 'David'}, age: {before: 22, after: 24}}
* @param person
*/
function enrichPerson(person: Person): Updates {
return Object.keys(person).reduce<Updates>((previousUpdates, key) => {
const typedKey = key as keyof Person;
const result: Updates = { ...previousUpdates };
update(result, typedKey, { before: person[typedKey], after: person[typedKey] });
return result;
}, {});
}
function update<Type, Key extends keyof Type>(result: Updatetable<Type>, key: Key, value: Update<Type[Key]>): void {
result[key] = value;
}
export {};
CodePudding user response:
Credits to @okan aslan for pointing me on the right direction. Thanks again ! Here is a modified version of the code sample that solves the problem.
Look at the introduction of the two helper functions update
and applyTransformer
.
type Person = {
firstName?: string;
lastName?: string;
age?: number;
};
type Update<T> = {
before: T;
after: T;
};
type Updatetable<Type> = { [Key in keyof Type]: Update<Type[Key]> };
type Updates = Updatetable<Person>;
type Transformer<Type, Key extends keyof Required<Type>> = (x: Type[Key]) => Type[Key]
type Transformers<Type> = { [Key in keyof Required<Type>]: Transformer<Type, Key> }
function update<T, K extends keyof T> (obj: Updatetable<T>, key: K, newValue: Update<T[K]>): void {
obj[key] = newValue
}
function applyTransformer<T, K extends keyof Required<T>> (
transformers: Transformers<T>,
key: K,
input: T[K]
): T[K] {
return transformers[key](input)
}
/**
* Dummy transformers for illustration purposes
*/
const transformers: { [Key in keyof Required<Person>]: (value?: Person[Key]) => Person[Key] } = {
firstName: (value) => value?.toUpperCase(),
lastName: (value) => value?.toUpperCase(),
age: (value) => (typeof value === 'undefined' ? 0 : value 2),
}
/**
* Input: {firstName: 'david', age: 22}
* Output: {firstName: {before: 'david', after: 'David'}, age: {before: 22, after: 24}}
* @param person
*/
function enrichPerson (person: Person): Updates {
return Object.keys(person).reduce<Updates>((previousUpdates, key) => {
const typedKey = key as keyof Person
const result: Updates = { ...previousUpdates }
update(result, typedKey, {
before: person[typedKey],
after: applyTransformer(transformers, typedKey, person[typedKey])
})
return result
}, {})
}
export {}