mariossimou.dev
HomeBlogContact

Building your own DeepPartial utility in Typescript

Published on 18th December 2022

Front-end DevelopmentBack-end Development

Introduction

Typescript provides a wide range of utilities to use during development, which we should utilise as much as we can, and try to avoid re-creating our own abstractions. But, those utilities do not cover all type of problems that developers are trying to solve. One such case, is when we write units tests. During unit testing, we often provide partial information to our functions, but Typescript doesn't like that. For example, if your logic is a function that accepts an object of type Person and returns the fullname of that person, Typescript will request to provide the full structure of that person during testing. However, we only need an object with the firstName and lastName and we can ignore everything else. Ideally, we could be stricter defining the signature of the function, which means that the type of the parameter should ask only for the firstName and lastName, but often we create a single type within the codebase, with some additional properties, and re-use it in different files. As such, we understand what Typescript is saying to us, but it's one of those cases that we know better. For that reason we want to silent Typescript and it's possbile with a couple of ways. We can either use a type assertion or have a utility that makes the properties of our dummy object optional. In the latter case, if the object is only one level deep, we can get away with the Partial utility, but if our object is multiple levels deep, then we need to create our own abstraction. In this post we will show how to create a DeepPartial utility.

DeepPartial

In the code below, we create a few auxility types to support the DeepPartial implementation. The UknownObject is straightforward and I don't need to explain it. The PartialArray is a generic type that accepts an array of unknown types, and we use conditional types to evaluate the input. If the input is an array of objects, then iterate over the array, extract the object element using the index, and pass it down to the PartialObject type. If not, then leave the type as it is. The key here is how we iterate over the array. In Javascript, an Array extends from the Object prototype, which means that an Array is an object with the index and the element as key-value pairs.

Moving on, the PartialObject is also a generic. It receives an object as an input, and we iterate over its values. If the value is also an object, then we recall the PartialObject generic. If the value is an array of uknown types, then we pass it down to the PartialArray type. Otherwise, we keep the type of the value as it is.

Then, we create the DeepPartial generic type that checks if the input is an object. If that's the case, then pass it down to the PartialData, otherwise don't do anything with it.

At the end we create an initial person and then use the DeepPartial utility to create a dummy person. As you can, Typescript doesn't complain about the type, even if we have an object that is three levels deep.

type UnknownObject = Record<string,   unknown>

type PartialArray<TArray extends unknown[]> = TArray extends UnknownObject[] ? {[TArrayIndex in keyof TArray]: PartialObject<TArray[TArrayIndex]> } : TArray 

type PartialObject<TObject extends UnknownObject> = Partial<{
    [TObjectKey in keyof TObject]: TObject[TObjectKey] extends UnknownObject ? PartialObject<TObject[TObjectKey]> : TObject[TObjectKey] extends unknown[] ? PartialArray<TObject[TObjectKey]> : TObject[TObjectKey]
}>

type DeepPartial<TData> = TData extends UnknownObject ? PartialObject<TData> : TData

const person = {
    firstName: 'Foo',
    lastName: 'Bar',
    age: 23,
    metadata: {
        nationality: 'Polish',
        occupation: 'Actor',
        bankAccountDetails: {
            balance: 11111,
            currency: 'euro'
        }
    }
}

const mockPerson: DeepPartial<typeof person> = {
    firstName: 'John',
    lastName: 'Doe',
    metadata: {
        bankAccountDetails: {
            balance: 0
        }
    }
}

Summary

In summary, this article shows how to create your own DeepPartial utility, which can be used during unit tests, but it's not limited to that case only. Generics and conditional types greatly helped us to achieve that.

Designed by
mariossimou.dev
All rights reserved © mariossimou.dev 2023