Deep Omit in TypeScript: A Comprehensive Guide

In TypeScript, working with complex data types often requires the ability to exclude certain properties from an object type. While the built - in 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.

Table of Contents

  1. Fundamental Concepts of Deep Omit
  2. Usage Methods
  3. Common Practices
  4. Best Practices
  5. Conclusion
  6. References

Fundamental Concepts of Deep Omit

The Limitation of 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 Concept

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:

  • If 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.
  • If T is not an object, we simply return T as it is.

Usage Methods

Basic Usage

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; } }

Omitting Multiple Properties

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; } }

Common Practices

Using with API Responses

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'>;

Working with Form Data

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'>;

Best Practices

Use Descriptive Names

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'.

Test the Type

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
};

Consider Performance

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.

Conclusion

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.

References