Reputation: 361
I have a function that filters out some keys from a Record
and I want the type safety to prevent me from accessing filtered out keys.
What I got to express that is:
// Filters out the result of OnlySelected to remove never from filtered out keys
type OmitNever<T> = { [K in keyof T as T[K] extends never ? never : K]: T[K] }
// Only keeps the keys that are in the type V
type OnlySelected<T extends object, V> = {
[K in keyof T]-?: K extends V ? T[K] : never
}
Which works great for simple use cases:
const a: A = {
a: 1,
b: "2",
c: "3",
d: "4",
e: "5",
}
type MyType = OmitNever<OnlySelected<A, "a" | "b">>;
// type MyType = {
// a: number;
// b: string;
//}
But now if I try to use theses types in a generic function I have to means to convert the type of the keys that I want to keep to an union type. So I have to provide the type by hand and it's sad to have to repeat the keys to keep twice just to be type safe:
const filterRecord = <T extends Record<any, any>, TO_KEEP>(record: T, keys: Array<keyof T>) => {
return Object.keys(record)
.reduce((acc, it) => {
if (keys.includes(it)) {
acc[it as keyof OmitNever<OnlySelected<T, TO_KEEP>>] = record[it];
}
return acc;
}, {} as OmitNever<OnlySelected<T, TO_KEEP>>)
}
const res = filterRecord<A, "d" | "e">(a, ["d", "e"]);
console.log(res.d)
console.log(res.e)
console.log(res.a)
Anyone knows the solution or a better design?
See the playground here.
Upvotes: 2
Views: 117
Reputation: 1074168
You can define filterRecord
's TO_KEEP
as extends keyof T
, then use TO_KEEP[]
as the parameter type. TypeScript will infer correctly then (but keep reading):
const filterRecord = <T extends Record<any, any>, TO_KEEP extends keyof T>(
record: T,
keys: TO_KEEP[]
) => {
return Object.keys(record).reduce((acc, it) => {
if ((keys as readonly string[]).includes(it)) {
acc[it as keyof OmitNever<OnlySelected<T, TO_KEEP>>] = record[it];
}
return acc;
}, {} as OmitNever<OnlySelected<T, TO_KEEP>>);
};
Note that I did have to add a broadening type assertion within the implementation there (keys as readonly string[]
) in order to use includes
, but that's harmless.
Side note: Unless you're using it for some other purpose, you can avoid needing OmitNever
by changing your definition of OnlySelected
slightly (but keep reading):
type OnlySelected<T extends object, V> = {
[K in keyof T as K extends V ? K : never]-?: T[K];
// −−−−−−−−−−^^^^^^^^^^^^^^^^^^^^^^^^^^^−−−−−^^^^
};
But, OnlySelected
seems like it's doing the same thing the built-in Required
and Pick
do, so you could just use them(or define OnlySelected
in terms of Required
and Pick
). (Playground example) (Thanks to caTS for pointing out the Required
part, I'd missed the flag mod and just mentioned Pick
before.)
Upvotes: 1