DEV Community

Prithpal Sooriya
Prithpal Sooriya

Posted on

How To Create a Type-Safe Implicit Pick

This article discusses the implementation of an implicit pick, the reason for it & what makes it special.

Here is a Typescript Playground with examples that will be used as a reference. Can also be found via my Github Repo.

GitHub logo Prithpal-Sooriya / ts-implicit-pick

Example of an implicit pick.

Why an implicit Pick? Why not Partial?

Partial is great if you want to create an object with some of the values from a given interface. However when the object is used (via property access or through some consuming type), the object still is a Partial - as in all properties are optional, even if you have provided a value.
Example of Partial

In these cases, what we really want is a Picked object - an object with the properties we want "picked" out of the original interface.

The Verbosity of Pick

Pick is perfect, it gives us the exact strict type that we want, however (as shown) it is very verbose.
Example of Pick

For each prop we want, we need to write it for the type as well as for the object.
For small objects, this might not be much of an issue - however this can become very large the more props we want.

So now lets design an implicit pick!

1st Implicit Pick - Okay, But No IntelliSense 😢

Here is the design of the initial implicit Pick.

const buildImplicitPickVer1 =
  <T>() =>
  <K extends keyof T>(props: Pick<T, K>): Pick<T, K> =>
    props;

// Usage
const pickProduct = buildImplicitPickVer1<Product>();
const implicitProduct = pickProduct({ id: '1', ... })
Enter fullscreen mode Exit fullscreen mode

Breakdown:

  • <T>() =>
    • The is a factory function part that allows you to build a pick on whatever type you provide it.
  • <K extends keyof T>
    • We have a generic type K that is constrained to the type given in the factory.
  • (props: Pick<T, K>): Pick<T, K>
    • this parameter gets inferred as the developer types in the keys of their object.
    • Invalid keys will give us an error (since does not match the Generic type)
    • Invalid values for the key will give us an error, since it won't match the Picked object values.

This is exactly what we want - a type-safe implicit pick! Refactored changes (renaming/removing) on the interface will propagate through to the objects too!

Well... after some usage I found that it didn't really give a good Developer Experience (DX).

IntelliSense/auto-complete (via CTRL + SPACE) doesn't give us any useful information on what props we can use.
Implicit Pick with no IntelliSense

Only once we start typing do we get errors if a key does not match the interface, we aren't able to get a list of all keys that we can use.

This is because our parameter type in our factory function Pick<T, K> relies on keys given. Lets fix that!

Implicit Pick with Great Dev Experience!

Here is the the solution:

const buildImplicitPick =
  <T>() =>
  <K extends keyof T>(props: Partial<T> & Pick<T, K>): Pick<T, K> =>
    props;
Enter fullscreen mode Exit fullscreen mode

The small change that made the huge difference is the intersection type Partial<T> & Pick<T, K>

  • The Partial<T> give us the ability to get back our auto complete for keys.
  • Intersecting is with the Pick & Pick<T, K> ensures that we get the correct type for our key.

Intersection above means that we take only the props/types that match in both types given.

type A = { a: number | undefined }
type B = { a: number }
type C = A & B; // will be { a: number } since that is what both types above have.
Enter fullscreen mode Exit fullscreen mode

Whats awesome is that we can see the IntelliSense working in real time!
When we CTRL + SPACE to see what props are available, they are all optional because of the Partial.

Implicit Pick With IntelliSense info for the keys

But as soon as we select a property to use, it becomes required:

  • because the Generic K keys are updated;
  • subsequently so is the Pick<T, K>
  • and finally the intersection Partial<T> & Pick<T, K> enforces are type to be required.

Implicit Pick With IntelliSense info for the value

Conclusion

Above shows how to write a type-safe, refactor-safe implicit Pick function with useful IntelliSense information.

The function itself is rather simple, but the main takeaway for me is to try/test out different type implementations to provide better IntelliSense information & better developer experience (DX).

Discussion (0)