I have been trying to make a declaration for the RecursiveOmit and RecursivePick for cloning methods JSON.parse(JSON.stringify(obj, [‘myProperty’]))
type RecursiveKey<T> = T extends object ? keyof T | RecursiveKey<T[keyof T]> : never;
type RecursivePick<T, K> = {
[P in keyof T]: P extends K ? (T[P] extends object ? RecursivePick<T[P], K> : T[P]) : never
}
type RecursiveOmit<T, K> = {
[P in keyof T]: P extends K ? never : (T[P] extends object ? RecursiveOmit<T[P], K> : T[P])
}
const clone = <T, K extends RecursiveKey<T>>(object: T, whiteListedProperties: K[]): RecursivePick<T, K> => {
return JSON.parse(JSON.stringify(object, whiteListedProperties as (string | number)[]));
}
const cloneWithBlackList = <T, K extends RecursiveKey<T>>(object: T, blackListedProperties: K[]): RecursiveOmit<T, K> => {
return JSON.parse(JSON.stringify(object, (key: string, value: any): any => blackListedProperties.includes(key as K) ? undefined : value));
};
const c = {
a: {
a: 1,
b: 2,
c: 3
},
b: {
a: 1,
b: 2
}
}
const cc = clone(c, ['b']);
cc.b.a = 2 // error a shouldn't exists on cc.b
cc.b.b = 2; // b should exists on cc.b
cc.a.c = 2 // error c shouldn't exists on cc.a
const cb = cloneWithBlackList(c, ['b']);
cb.a.a = 2; // a should exists on cb.a
cb.b.b = 3; // error b shouldn't exists on cb
cb.a.c = 2; // c should exists on cb.a
I have been trying all kinds of variations of this and different/similar answers from other questions. But I was not able to get it working.
Anyone has any idea what I am doing wrong?
2
Answers
This seems like a bug in TypeScript to me.I have not found any issues on GitHub pertaining to this issue, but nevertheless, the workaround I have found is as follows. First, clean up the type definitions with key-remapping, andExtract
/Exclude
:Then we can change the functions, using 5.0
const
generics:Playground
As you can see from hovering over the function call, the "problem" is that
K
is inferred to be"a" | "b" | "c"
(i.e.RecursiveKey<typeof c>
) and not just"b"
.You can fix that by being explicit (
clone<typeof c, "b">(c, ["b"])
orclone(c, ['b' as const])
), but also TypeScript 5.0 has just the solution for this problem:const
type parameters:(updated playground)