Skip to main content

PickDeep and PickSelection

While both are still supported, PickSelection is now the recommended utility type. This blog post will explain why.

What does PickDeep do?

As noted above, PickDeep is like Pick but supports nested objects. Below is an example demonstrating this and explaining why it's useful.

import {PickDeep} from '@augment-vir/common';
import {assert} from 'chai';
import {test} from 'mocha';

// 1. a type with a several levels of nesting
type User = {
name: string;
address: {
streetAddress: string;
country: string;
phoneNumber: {
countryCode: string;
number: string;
};
};
};

// 2. a function using `PickDeep`
function validateUserCountryCode(
user: PickDeep<User, ['address', 'phoneNumber', 'countryCode']>,
): boolean {
return !!user.address.phoneNumber.countryCode;
}

test('validateUserCountryCode', () => {
// 3. testing the function
assert.isTrue(
validateUserCountryCode_PickDeep({
address: {
phoneNumber: {
countryCode: '1',
},
},
}),
);
});

You can try this in the TypeScript browser playground here.

Here's what's going on in that above code.

  1. We have a User type which contains several levels of nesting.
  2. We have a function that uses PickDeep so its input requires only the exact data needed.
  3. While testing that function, we only need to provide the data exactly needed by the function.

With just Pick<User, 'address'>, we would've needed to fill in both streetAddress and country in the test code.

Limitations of PickDeep

Clashing sub-keys

The most obvious limitation of PickDeep is clashing sub keys. See the following example:

import {PickDeep} from '@augment-vir/common';

type User = {
name: string;
company: {
name: string;
};
region: {
name: string;
coordinates: {
x: number;
y: number;
};
};
};

const example: PickDeep<User, ['company' | 'region', 'name' | 'coordinates']> = {
company: {
name: 'Biz',
},
region: {
name: 'Earth',
coordinates: {
x: 10,
y: 5,
},
},
};

In the above example (playground link here), if we only care about company.name, we still have to provide region.name.

TypeScript's recursion nerfing

However, the real killer of PickDeep is TypeScript itself. Since my creation of PickDeep at the beginning of 2023, the TypeScript type checker has been repeatedly nerfed in its ability to handle recursive types. The modern PickDeep (which still works sometimes) has accordingly been nerfed to the point where it no longer supports auto completion in the keys array. Despite that, TypeScript still frequently panics when using PickDeep with "type instantiation is excessively deep and possibly infinite" errors, sometimes even on objects that are obviously not possibly infinite (or even very deep).

An alternative: PickSelection

Due to TypeScript's arbitrary and unpredictable "excessively deep" errors, I sought out a new means entirely of accomplishing a deeply picked utility type. Thus was born PickSelection.

The goal of PickSelection is the same: allow picking arbitrary sub keys of a nested object. However, the method is totally different. Rather than accepting a list of key strings (as PickDeep does), it accepts a selection set. In other words, it accepts an object of booleans with keys and nestings matching exactly (with Partial) the original type that's being picked from. Below is an example that shows how to use PickSelection for all the previous PickDeep examples.

import {PickSelection} from '@augment-vir/common';

// from the first PickDeep example
type User = {
name: string;
address: {
streetAddress: string;
country: string;
phoneNumber: {
countryCode: string;
number: string;
};
};
};

function validateUserCountryCode(
user: PickSelection<User, {address: {phoneNumber: {countryCode: true}}}>,
): boolean {
return !!user.address.phoneNumber.countryCode;
}

// from the second PickDeep example
type User2 = {
name: string;
company: {
name: string;
};
region: {
name: string;
coordinations: {
x: number;
y: number;
};
};
};

const example: PickSelection<User2, {company: {name: true}; region: {coordinates: true}}> = {
company: {
name: 'Biz',
},
region: {
// notice that we no longer need to provide the region name property
coordinates: {
x: 10,
y: 5,
},
},
};

This example (playground link here) demonstrates how you can accomplish the same things as PickDeep with a more concise and precise interface, with auto-complete support! Also, so far, TypeScript seems to be happier with PickSelection over PickDeep.