Type alias Prune<T, N>

Prune<T, N = NotIncluded> is a utility type that recursively removes properties of type N (defaulting to NotIncluded) from the object type T. Useful for filtering out unwanted or excluded properties in complex, deeply nested object structures, where you need to clean up or transform types where certain properties should be excluded based on specific generic type conditions.

Type Parameters

Example

Consider the following example where Prune is used to exclude properties marked as NotIncluded:

type OrderData = Prune<
UserOrderDetails<
OrderStatus.DELIVERED,
UserActionType.PLACE_ORDER,
ProductType.PHYSICAL,
PaymentMethod.PAYPAL
>
>;

Where UserOrderDetails is defined as:


export enum OrderStatus {
PENDING = 'PENDING',
SHIPPED = 'SHIPPED',
DELIVERED = 'DELIVERED',
CANCELLED = 'CANCELLED',
RETURNED = 'RETURNED',
}

export enum PaymentMethod {
CREDIT_CARD = 'CREDIT_CARD',
PAYPAL = 'PAYPAL',
BANK_TRANSFER = 'BANK_TRANSFER',
CASH_ON_DELIVERY = 'CASH_ON_DELIVERY',
CREDITS = 'CREDITS',
}

export enum UserActionType {
PLACE_ORDER = 'PLACE_ORDER',
CANCEL_ORDER = 'CANCEL_ORDER',
RETURN_ORDER = 'RETURN_ORDER',
RATE_PRODUCT = 'RATE_PRODUCT',
WRITE_REVIEW = 'WRITE_REVIEW',
}

export enum ProductType {
DIGITAL = 'DIGITAL',
PHYSICAL = 'PHYSICAL',
}

interface ShippingDetails {
address: string;
city: string;
postalCode: string;
country: string;
deliveryDate: Maybe<string>;
}

type ProductPricing<T extends ProductType> = {
basePrice: number;
discount: number;
finalPrice: number;
inCountryDiscount: IfEquals<T, ProductType.PHYSICAL, number, NotIncluded>;
};

type ProductDetails<T extends ProductType> = {
type: T;
productId: string;
productName: string;
quantity: number;
pricing: ProductPricing<T>;
physicalDescription: IfEquals<
T,
ProductType.DIGITAL,
{
color: string;
size: string;
},
NotIncluded
>;
};

type PaymentBreakdown = {
baseAmount: number;
tax: number;
discount: number;
finalAmount: number;
};

interface PaymentDetails<PM extends PaymentMethod> {
method: PM;
transactionId: Maybe<string>;
amountPaid: number;
breakdown: IfEquals<PM, PaymentMethod.CREDITS, string, PaymentBreakdown>;
}

type UserOrderDetails<
OS extends OrderStatus,
UAT extends UserActionType,
PT extends ProductType,
PM extends PaymentMethod,
> = {
orderId: string;
userId: string;
products: ProductDetails<PT>[];
orderStatus: OS;
shipping: IfEquals<
PT,
ProductType.PHYSICAL,
{
details: IfEquals<
OS,
OrderStatus.SHIPPED | OrderStatus.DELIVERED,
ShippingDetails,
NotIncluded
>;
deliveredOn: IfEquals<OS, OrderStatus.DELIVERED, Date, NotIncluded>;
returnedOn: IfEquals<OS, OrderStatus.RETURNED, Date, NotIncluded>;
},
NotIncluded
>;
payment: PaymentDetails<PM>;
actions: {
type: UAT;
timestamp: number;
metadata: IfEquals<
UAT,
| UserActionType.PLACE_ORDER
| UserActionType.CANCEL_ORDER
| UserActionType.RETURN_ORDER,
{
ipAddress: string;
deviceType: string;
},
NotIncluded
>;
}[];
};

Any property marked as NotIncluded will be excluded from the resulting OrderData type, not as undefined or null, but completely excluded as if it never existed on the type

 const testOrderData: OrderData = {
orderStatus: OrderStatus.DELIVERED,
actions: [
{
timestamp: 1663725600000,
// Type 'UserActionType.RATE_PRODUCT' is not assignable to type 'UserActionType.PLACE_ORDER'.ts(2322)
type: UserActionType.PLACE_ORDER,
// Object literal may only specify known properties, and 'metadata' does not exist in type 'OmitExactlyByTypeDeep<{ type: UserActionType.PLACE_ORDER; timestamp: number; metadata: NotIncluded; }, NotIncluded>'.ts(2353)
// metadata: {
// ipAddress: '127.0.0.1',
// deviceType: 'desktop',
// },
},
],
orderId: '123',
userId: 'abc',
products: [
{
// Type 'ProdcutType.DIGITAL' is not assignable to type 'ProdcutType.PHYSICAL'.ts(2322)
type: ProductType.PHYSICAL,
productId: 'abc',
productName: 'Test Product',
quantity: 1,
pricing: {
basePrice: 10,
discount: 0,
finalPrice: 10,
// If the product type is digital, the 'physicalDescription' property should be omitted
// Object literal may only specify known properties, and 'inCountryDiscount' does not exist in type 'OmitExactlyByTypeDeep<ProductPricing<ProductType.DIGITAL>, NotIncluded>'.ts(2353)
// physicalDescription: {
// color: 'Red',
// size: 'Large',
// },
inCountryDiscount: 87,
},
},
],
payment: {
amountPaid: 10,
// If the PaymentMethod is CREDITS, the 'breakdown' property should be a string, no Breakdown object should exist
// Type '{ baseAmount: number; tax: number; discount: number; finalAmount: number; }' is not assignable to type 'string'.
breakdown: {
baseAmount: 10,
tax: 2,
discount: 0,
finalAmount: 12,
},
// Type 'PaymentMethod.BANK_TRANSFER' is not assignable to type 'PaymentMethod.PAYPAL'.ts(2322)
// method: PaymentMethod.BANK_TRANSFER,
method: PaymentMethod.PAYPAL,
transactionId: '1234567890',
},
shipping: {
deliveredOn: new Date(),
},
};

In this example, Prune removes all properties within UserOrderDetails that are marked with NotIncluded, resulting in a cleaned-up type structure.