Understanding DeepPartial (Sparse) TypeScript Utility Type
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
Tis a string, number, boolean, or null, it returnsTas 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 objectT. - 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 callsSparseagain 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
| Feature | Partial<T> | Sparse<T> |
|---|---|---|
| Top Level Properties | Optional | Optional |
| Nested Objects | Required (Must be complete) | Optional (Can be partial) |
| Use Case | Simple object creation | Patches, deep updates, large forms |