Conditional subtypes using as clause

Conditional subtypes using as clause

Problem Statement

While working on a project. I came across a use case where I need to create a type that needs to be a subtype of a given type including only a particular key.

const type Address = {
Id: string,
name: string,
surname: string,
country: string,
state: string,
landmark: string,
pincode: number,
isHomeAddress: boolean,
isRentedAddress: boolean
}

So, now I want to create a subtype from the above Address type which should only include keys of type boolean.

const type Address ={
isHomeAddress: boolean,
isRentedAddress: boolean
}

 

Solution

The Typescript Mapped Types comes in handy to resolve this kind of problem. Where you want to create a new type transforming a given type.

You can use mapped types to create a type out of the giving type having all of its properties as optional or boolean etc.

Now back to solving our problem.

So, we have our parent type as

type Address = {
    Id: string,
    name: string,
    surname: string,
    country: string,
    state: string,
    landmark: string,
    pincode: number,
    isHomeAddress: boolean,
    isRentedAddress: boolean,
}

and our expected result should be

type OtherInfo = {
    isHomeAddress: boolean,
    isRentedAddress: boolean,
}

 

Let's solve this problem step by step.  

1) Remove unwanted types

First of all, we will mark the unwanted types from the Address as never type.

We use conditional types for it.

type RequiredTypes<T, C> = {
  [Key in keyof T]: T[Key] extends C ? T[Key] : never
}

The above code will check which type in Address type extends the condition(C). It'll assign never type to all other keys not meeting our criteria.  

never is the opposite of any type which means nothing can be assigned to it

The output of the above code will be.

Usage

type MarkAsNever = RequiredTypes<Address, boolean>

 

The MarkAsNever type will look like this

type MarkAsNever = {
    Id: never,
    name: never,
    surname: never,
    country: never,
    state: never,
    landmark: never,
    pincode: never,
    isHomeAddress: boolean,
    isRentedAddress: boolean,
}

As you can see the RequiredTypes type has been assigned never to all other types except for isHomeAddress and isRentedAddress

 

2) Removing keys with type never

Now we are on the last step which is to remove the type with never. For doing that we will use as clause introduced in typescript 4.1.

as clause has a nice feature by which you can filter out keys by producing never via conditional props.  

type RemoveNeverField<T> = { [P in keyof T as T[P] extends never ? never : P]: T[P] };

In the above code we are looping through all the keys of T and if the type of the key is never our condition will return never which will be automatically ignored by the as a clause. So, in the end, we will only have non-never keys.

 

usage

type OnlyBoolean = RemoveNeverField<onlyBoolean>

 

Now the OnlyBoolean type will look like below. Just how we wanted it. :ok_hand:

type OnlyBoolean = {
    isHomeAddress: boolean,
    isRentedAddress: boolean,
}

 

let's summarize all the required steps

1.

type RequiredTypes<T, C> = {
  [Key in keyof T]: T[Key] extends C ? T[Key] : never
}

2.

type RemoveNeverField<T> = { [P in keyof T as T[P] extends never ? never : P]: T[P] };

 

You can further shorten the syntax. By directly using RequiredTypes code inside RemoveNeverField by creating a new type let's say PickByType.

type PickByType<T, C> = RemoveNeverField<{
[Key in keyof T]: T[Key] extends C ? T[Key] : never
}>

 

usage

PickByType<Address, boolean>

 

Congratulations we have created a type that can create a new type containing only a given type. I hope that you have learned something new today.

You can read about mapped types here

Did you find this article valuable?

Support Async Await by becoming a sponsor. Any amount is appreciated!