Created
May 26, 2022 17:50
-
-
Save vezaynk/3cdfbcd766c33fd417c37da5a4733184 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
interface SpacingProps { | |
__typename: string, | |
property1: { | |
__typename: string; | |
a: 2; | |
nested: { | |
__typename: string, | |
x: 3 | |
} | |
}, | |
property2: { | |
__typename: string; | |
b: 3; | |
}, | |
} | |
type NestedOmit<T,K extends string>=Omit<{ | |
[key in keyof T]: NestedOmit<Omit<T[key],K>, K>; | |
}, K>; | |
const test: NestedOmit<SpacingProps, '__typename'> = { | |
property1: { | |
a: 2, | |
nested: { | |
x: 3 | |
} | |
}, | |
property2: { | |
b: 3 | |
} | |
}; |
Still not perfect. The above doesn't work with functions for some reason:
type NestedOmit<T, K extends string> = Omit<
{
[key in keyof T]: T[key] extends Object
? null extends T[key]
? NestedOmit<Omit<NonNullable<T[key]>, K>, K> | null
: NestedOmit<Omit<T[key], K>, K>
: T[key];
},
K
>;
Not a proper example, but here's my playground where I was trying stuff out
The above version hits a limitation when working with levels which do not have a key to omit, but whose children do. This fixes that edge case:
type NestedOmit<T, K extends string> = NonNullable<T> extends { [prop in K]: any }
? Omit<
{
[key in keyof T]: null extends T[key]
? NestedOmit<NonNullable<T[key]>, K> | null
: NestedOmit<T[key], K>;
},
K
>
: {
[key in keyof T]: null extends T[key]
? NestedOmit<NonNullable<T[key]>, K> | null
: NestedOmit<T[key], K>;
};
Final refactor:
type NestedOmitHelper<T, K extends string> = {
[key in keyof T]: null extends T[key]
? NestedOmit<NonNullable<T[key]>, K> | null
: NestedOmit<T[key], K>;
}
type NestedOmit<T, K extends string> = NonNullable<T> extends { [prop in K]: any }
? Omit<
NestedOmitHelper<T, K>,
K
>
: NestedOmitHelper<T, K>;
More changes!
Omit doesn't distribute unions. This forces it:
type UnionOmit<T, K extends string> = T extends unknown ? Omit<T, K> : never;
type NestedOmitHelper<T, K extends string> = {
[key in keyof T]: null extends T[key]
? NestedOmit<NonNullable<T[key]>, K> | null
: NestedOmit<T[key], K>;
};
type NestedOmit<T, K extends string> = NonNullable<T> extends {
[prop in K]: any;
}
? UnionOmit<NestedOmitHelper<T, K>, K>
: NestedOmitHelper<T, K>;
Doesn;t catch the top level yet though :hmm:
Covariants were having issues. This fixes them:
type NullUnionOmit<T, K extends string | number | symbol> = null extends T
? UnionOmit<NonNullable<T>, K> | null
: UnionOmit<T, K>;
type UnionOmit<T, K extends string | number | symbol> = T extends unknown ? Omit<T, K> : never;
type NestedOmitHelper<T, K extends string> = {
[key in keyof T]: NestedOmit<T[key], K>
};
type NestedOmit<T, K extends string> = NonNullable<T> extends {
[prop in K]: any;
}
? NullUnionOmit<NestedOmitHelper<T, K>, K>
: NestedOmitHelper<T, K> | T;
NonNullable is unncessary:
type NullUnionOmit<T, K extends string | number | symbol> = null extends T
? UnionOmit<NonNullable<T>, K> | null
: UnionOmit<T, K>;
type UnionOmit<T, K extends string | number | symbol> = T extends unknown ? Omit<T, K> : never;
type NestedOmitHelper<T, K extends string> = {
[key in keyof T]: NestedOmit<T[key], K>
};
type NestedOmit<T, K extends string> = T extends {
[prop in K]: any;
}
? NullUnionOmit<NestedOmitHelper<T, K>, K>
: NestedOmitHelper<T, K> | T;
Final version for realsies:
type UnionOmit<T, K extends string | number | symbol> = T extends unknown
? Omit<T, K>
: never;
type NullUnionOmit<T, K extends string | number | symbol> = null extends T
? UnionOmit<NonNullable<T>, K>
: UnionOmit<T, K>;
type SafeOmit<T, K extends string | number | symbol> = T extends {
[P in K]: any;
}
? NullUnionOmit<T, K>
: T;
type RecursiveOmit<T, K extends string | number | symbol> = T extends {
[P in K]: any;
}
? SafeOmit<{ [P in keyof T]: RecursiveOmit<T[P], K> }, K>
: {
[P in keyof T]: RecursiveOmit<T[P], K>;
};
const cleanSolarSystem: RecursiveOmit<SolarSystem, "__typename"> = {
//__typename: "SolarSystem",
id: 123,
name: "The Solar System",
star: {
//__typename: "Planet",
id: 123,
inhabitants: null,
name: "Sun",
size: 9999,
},
planets: [
{
//__typename: "Planet",
id: 123,
name: "Earth",
size: 12345,
inhabitants: [
{
//__typename: "LifeForm",
id: 123,
name: "Human",
},
],
},
],
};
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The above seems to break for nullable types. This fixes it: