Omit
utility type is useful for removing properties at the top - level, it falls short when dealing with nested objects. This is where the concept of Deep Omit
comes in. Deep Omit allows us to recursively remove properties from an object type at any level of nesting. In this blog post, we will explore the fundamental concepts of Deep Omit in TypeScript, how to use it, common practices, and best practices.Omit
The built - in Omit
type in TypeScript takes two generic parameters: the original type and a union of property names to omit. For example:
type Person = {
name: string;
age: number;
address: {
street: string;
city: string;
};
};
type PersonWithoutAge = Omit<Person, 'age'>;
// PersonWithoutAge is { name: string; address: { street: string; city: string; } }
However, if we want to omit a property from the nested address
object, Omit
won’t work directly.
Deep Omit is a custom type that recursively traverses an object type and removes the specified properties at any level of nesting. It uses conditional types and mapped types in TypeScript to achieve this.
type DeepOmit<T, K extends string> = T extends object
? {
[P in keyof T as P extends K ? never : P]: DeepOmit<T[P], K>;
}
: T;
In this type definition:
T
is an object, we use a mapped type to iterate over its keys. If a key P
is in the set of keys K
to omit, we exclude it. Otherwise, we recursively apply DeepOmit
to the value of that key.T
is not an object, we simply return T
as it is.Let’s use the DeepOmit
type we defined above to omit a nested property.
type Person = {
name: string;
age: number;
address: {
street: string;
city: string;
};
};
type PersonWithoutStreet = DeepOmit<Person, 'street'>;
// PersonWithoutStreet is { name: string; age: number; address: { city: string; } }
We can also omit multiple properties by providing a union of property names.
type PersonWithoutAgeAndStreet = DeepOmit<Person, 'age' | 'street'>;
// PersonWithoutAgeAndStreet is { name: string; address: { city: string; } }
When working with API responses, we might want to remove sensitive or unnecessary data before using it in our application.
// API response type
type APIResponse = {
user: {
id: number;
name: string;
privateInfo: {
email: string;
phone: string;
};
};
meta: {
timestamp: number;
};
};
// Omit sensitive data
type SafeAPIResponse = DeepOmit<APIResponse, 'email' | 'phone'>;
When handling form data, we might want to remove some fields that are not relevant for a particular operation.
type FormData = {
personal: {
firstName: string;
lastName: string;
ssn: string; // Sensitive data
};
contact: {
email: string;
phone: string;
};
};
type SafeFormData = DeepOmit<FormData, 'ssn'>;
When defining the keys to omit, use descriptive names. For example, instead of using single - letter keys, use full property names like 'userEmail'
or 'productPrice'
.
Before using a DeepOmit
type in production code, test it thoroughly. You can use TypeScript’s type assertions to check if the resulting type is as expected.
type TestType = {
a: {
b: string;
c: number;
};
d: boolean;
};
type ResultType = DeepOmit<TestType, 'b'>;
// Assert the type
const test: ResultType = {
a: {
c: 123
},
d: true
};
Deep Omit operations can be computationally expensive for very large and deeply nested objects. If performance is a concern, consider alternative approaches such as filtering the data at runtime instead of using type - level operations.
Deep Omit in TypeScript is a powerful tool for working with complex object types. It allows us to recursively remove properties from nested objects, which is not possible with the built - in Omit
type. By understanding the fundamental concepts, usage methods, common practices, and best practices, you can effectively use Deep Omit in your TypeScript projects to manage and manipulate data types.