Skip to content

Instantly share code, notes, and snippets.

@tobloef
Created June 20, 2023 07:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tobloef/d77e93f0cccbb7f1768106fbb2d58894 to your computer and use it in GitHub Desktop.
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.
// 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