Created
June 20, 2023 07:16
-
-
Save tobloef/d77e93f0cccbb7f1768106fbb2d58894 to your computer and use it in GitHub Desktop.
DeepOmit<T, K> utility type. Like Omit<T, K>, but allows you to omit nested props with dot notation.
This file contains hidden or 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
// Examples | |
type ExampleType = { | |
string: string, | |
interface: ExampleInterface, | |
class: ExampleClass, | |
array: Array<ExampleInterface>, | |
tuple: [ExampleClass, ExampleInterface], | |
function: () => void, | |
nested1: { | |
nested1_1: { | |
nested1_1_1: string, | |
nested1_1_2: number, | |
}, | |
nested1_2: { | |
nested1_2_1: number, | |
nested1_2_2: string, | |
}, | |
}, | |
}; | |
interface ExampleInterface { | |
field: number, | |
method(): void, | |
} | |
class ExampleClass { | |
private privateField: number = 0; | |
public publicField: number = 0; | |
private privateMethod() {}; | |
public publicMethod() {}; | |
public arrowFunction = () => {}; | |
} | |
const example: DeepOmit<ExampleType, "array.field" | "nested1.nested1_1.nested1_1_1"> = { | |
string: "", | |
interface: { | |
field: 123, | |
method: () => {} | |
}, | |
class: new ExampleClass(), | |
array: [ | |
{ | |
// field: 111, | |
method: () => {} | |
}, | |
{ | |
// field: 222, | |
method: () => {} | |
} | |
], | |
tuple: [ | |
new ExampleClass(), | |
{ | |
field: 123, | |
method: () => {} | |
} | |
], | |
function: () => {}, | |
nested1: { | |
nested1_1: { | |
// nested1_1_1: "", | |
nested1_1_2: 123, | |
}, | |
nested1_2: { | |
nested1_2_1: 123, | |
nested1_2_2: "", | |
}, | |
} | |
} | |
// keys-as-dot-notation.ts | |
export type DefaultIgnoredTypes = string | number | Function; | |
export type KeysAsDotNotation< | |
T, | |
IgnoredTypes = DefaultIgnoredTypes, | |
Key extends keyof T = keyof T | |
> = ( | |
T extends IgnoredTypes | |
? never | |
: T extends Array<infer ElementType> | |
? DistributeDotNotation<ElementType, IgnoredTypes> | |
: IsUnion<T> extends true | |
? DistributeDotNotation<T, IgnoredTypes> | |
: Key extends string | |
? ( | |
Key | | |
`${Key}.${KeysAsDotNotation<T[Key], IgnoredTypes>}` | |
) | |
: never | |
); | |
type DistributeDotNotation<T, IgnoredTypes> = T extends any | |
? KeysAsDotNotation<T, IgnoredTypes> | |
: never; | |
// is-union.ts | |
export type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true; | |
// union-to-intersection.ts | |
export type UnionToIntersection<Union> = ( | |
Union extends unknown | |
? (k: Union) => void | |
: never | |
) extends ((k: infer Intersection) => void) | |
? Intersection | |
: never | |
// last-in-union.ts | |
type LastInUnion<Union> = UnionToIntersection< | |
Union extends unknown ? (x: Union) => 0 : never | |
> extends (x: infer Last) => 0 | |
? Last | |
: never; | |
// union-to-tuple.ts | |
type UnionToTuple<Union, Last = LastInUnion<Union>> = ( | |
[Union] extends [never] | |
? [] | |
: [...UnionToTuple<Exclude<Union, Last>>, Last] | |
); | |
// deep-omit.ts | |
export type DeepOmit< | |
T, | |
OmittedKeys extends KeysAsDotNotation<T, IgnoredTypes>, | |
IgnoredTypes = DefaultIgnoredTypes | |
> = ( | |
// For some reason we have to do the check twice | |
(OmittedKeys extends never ? never : OmittedKeys) extends never | |
? T | |
: IsUnion<OmittedKeys> extends true | |
? UnionToTuple<OmittedKeys> extends Array<KeysAsDotNotation<T, IgnoredTypes>> | |
? DeepOmitWithArrayOfKeys<T, UnionToTuple<OmittedKeys>, IgnoredTypes> | |
: never | |
: T extends Array<unknown> | |
? DeepOmitInArray<T, OmittedKeys, IgnoredTypes> | |
: DeepOmitInObject<T, OmittedKeys, IgnoredTypes> | |
); | |
type DeepOmitInArray< | |
T extends Array<unknown>, | |
OmittedKeys, | |
IgnoredTypes = DefaultIgnoredTypes | |
> = { | |
[K in keyof T]: OmittedKeys extends KeysAsDotNotation<T[K], IgnoredTypes> | |
? DistributeDeepOmit<T[K], OmittedKeys, IgnoredTypes> | |
: T[K] | |
} | |
type DeepOmitInObject< | |
T, | |
OmittedKeys, | |
IgnoredTypes = DefaultIgnoredTypes | |
> = { | |
[ObjectKey in keyof T as NeverIfKeyOmitted<ObjectKey, OmittedKeys>]: ( | |
ObjectKey extends OmittedKeys | |
? never | |
: DeepOmitInsideProp<ObjectKey, T[ObjectKey], OmittedKeys, IgnoredTypes> | |
) | |
} | |
type NeverIfKeyOmitted<Key, OmittedKeys> = ( | |
OmittedKeys extends `${string}.${string}` | |
? Key | |
: Key extends OmittedKeys | |
? never | |
: Key | |
); | |
type DeepOmitInsideProp< | |
ObjectKey, | |
PropType, | |
OmittedKeys, | |
IgnoredTypes = DefaultIgnoredTypes | |
> = ( | |
OmittedKeys extends `${infer Key}.${infer Rest}` | |
? ObjectKey extends Key | |
? Rest extends KeysAsDotNotation<PropType, IgnoredTypes> | |
? DeepOmit<PropType, Rest, IgnoredTypes> | |
: PropType | |
: PropType | |
: PropType | |
) | |
type DistributeDeepOmit< | |
T, | |
OmittedKeys extends KeysAsDotNotation<T, IgnoredTypes>, | |
IgnoredTypes | |
> = ( | |
T extends any | |
? DeepOmit<T, OmittedKeys, IgnoredTypes> | |
: never | |
); | |
type DeepOmitWithArrayOfKeys< | |
T, | |
OmittedKeys, | |
IgnoredTypes = DefaultIgnoredTypes, | |
> = ( | |
OmittedKeys extends [] | |
? T | |
: OmittedKeys extends [infer K, ...infer Rest] | |
? K extends KeysAsDotNotation<T, IgnoredTypes> | |
? Rest extends Array<KeysAsDotNotation<DeepOmit<T, K, IgnoredTypes>, IgnoredTypes>> | |
? DeepOmitWithArrayOfKeys<DeepOmit<T, K, IgnoredTypes>, Rest, IgnoredTypes> | |
: never | |
: never | |
: never | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment