Back to overview

Understanding DeepPartial (Sparse) TypeScript Utility Type

AngularTypeScript
article cover

This is a sophisticated TypeScript utility type. In the TypeScript community, this specific pattern is most commonly known as DeepPartial<T>. Here is a breakdown of how Sparse<T> works and a concrete example of how it applies to a specific function signature.

Breaking Down Sparse<T>

The type definition uses Conditional Types and Mapped Types with Recursion.

type Sparse<T> = T extends object
  ? {
      [P in keyof T]?: Sparse<T[P]>;
    }
  : T;

T extends object ? ... : T

This is the check. Is the type T an object (like an interface, class, or literal { a: 1 })?

  • If No (it's a primitive): If T is a string, number, boolean, or null, it returns T as is.
  • If Yes (it's an object): It enters the mapped type block { ... }.

[P in keyof T]?

  • This iterates over every property key (P) inside the object T.
  • The ? modifier makes every single property optional (can be undefined).

Sparse<T[P]>

  • The Recursion: Instead of just returning the property's type T[P], it calls Sparse again on that property.
  • This allows the type to go deep into nested objects, making their properties optional as well.

Difference from standard Partial<T>

TypeScript's built-in Partial<T> is shallow. It makes top-level properties optional, but if you provide a nested object, that nested object must be complete. Sparse<T> makes everything optional, all the way down.

Concrete Example

Let's imagine the MyDomainModel represents a User Profile with nested data.

The Domain Model

interface MyDomainModel {
  id: string;
  profile: {
    username: string;
    email: string;
    preferences: {
      theme: 'dark' | 'light';
      notifications: boolean;
    };
  };
  tags: string[];
}

The Resulting Sparse<MyDomainModel>

Because of the recursion, Sparse<MyDomainModel> effectively becomes this:

// Conceptual expansion of Sparse<MyDomainModel>
interface SparseResult {
  id?: string; // Optional
  profile?: {
    // Optional
    username?: string; // Optional
    email?: string; // Optional
    preferences?: {
      // Optional
      theme?: 'dark' | 'light'; // Optional
      notifications?: boolean; // Optional
    };
  };
  tags?: string[]; // Optional (Arrays are objects, but usually treated as a unit)
}

Applying it to the Function

The function formStateToPartialDomainModel is likely designed to take a form (which might only have a few fields filled out) and convert it into a "patch" object to update the domain model.

Here is how you might use it:

// 1. The input: A form state where the user only changed the theme
// (Assuming FieldState is a wrapper from a library like React Hook Form)
const currentFormState = {
  dirtyFields: {
    profile: {
      preferences: {
        theme: true, // Only this field was touched
      },
    },
  },
  values: {
    profile: {
      preferences: {
        theme: 'dark',
      },
    },
    // Note: 'notifications', 'email', and 'id' might be missing or ignored
  },
};

// 2. The Function
function formStateToPartialDomainModel(
  formState: any, // simplified for example
): Sparse<MyDomainModel> {
  // Logic to extract only changed values...
  return {
    profile: {
      preferences: {
        theme: 'dark',
      },
    },
  };
}

// 3. Usage
const patch = formStateToPartialDomainModel(currentFormState);

// This is VALID because Sparse allows deep nesting of optional properties.
// A standard Partial<MyDomainModel> would have thrown an error here
// because 'notifications' is missing from 'preferences'.

Summary Table

FeaturePartial<T>Sparse<T>
Top Level PropertiesOptionalOptional
Nested ObjectsRequired (Must be complete)Optional (Can be partial)
Use CaseSimple object creationPatches, deep updates, large forms

References