Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Right way to manage Arrays with intersecting types and narrow them back down

When using arrays with different Types in typescript I tend to run into issues with properties that are not on all Types.

I ran into the same issue for different types of sections on a page, different roles of users with different properties and so on.

Here is an example with animals:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

For example, if you have types Cat, Dog and Wolf:

export type Cat = {
    animal: 'CAT';
    legs: 4;
}
export type Dog = {
    animal: 'DOG',
    legs: 4,
    requiresWalks: true,
    walkDistancePerDayKm: 5
}
export type Wolf = {
    animal: 'WOLF',
    legs: 4,
    requiresWalks: true,
    walkDistancePerDayKm: 20
}
type Animal = Cat | Dog | Wolf;


const animals: Animal[] = getAnimals();
animals.forEach(animal => {
    // here i want to check if the animal requires a walk
    if (animal.requiresWalks) {
        // Property 'requiresWalks' does not exist on type 'Animal'. Property 'requiresWalks' does not exist on type 'Cat'.
        goForAWalkWith(animal)
    }
});
// The type "AnimalThatRequiresWalks" does not exist and i want to know how to implement it
goForAWalkWith(animal: AnimalThatRequiresWalks) {

}

As commented above, property requiresWalks can not be used to narrow down the type.

Also imagine we have 20 animals. I am having difficulties implementing types that may extend animals like "AnimalThatRequiresWalks" which may have multiple properties related to walking animals.

What is a clean implementation to join these types with a type "AnimalThatRequiresWalks" (with properties "requiresWalks true" and "walkDistancePerDayKm") and how can i properly narrow it down to "AnimalThatRequiresWalks"?

>Solution :

You have two questions:

  1. How do you check requiresWalks to see if the animal requires a walk?

  2. How do you define the type for animal in goForAWalkWith?

Re #1: You test to see if the object has the property before you try to use it (this is a specific type of narrowing the handbook calls in operator narrowing):

animals.forEach(animal => {
    if ("requiresWalks" in animal && animal.requiresWalks) {
// −−−−−^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        goForAWalkWith(animal)
    }
});

Re #2, you can extract from the Animal union all types that are assignable to {requiresWalks: true} via the Extract utility type:

function goForAWalkWith(animal: Extract<Animal, {requiresWalks: true}>) {
// −−−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // ...
}

Extract<Animal, {requiresWalks: true}> is Dog | Wolf.

Playground link

You con’t have to do that inline if you don’t want to, you can define a type alias for it, then use the alias:

type AnimalThatRequiresWalks = Extract<Animal, {requiresWalks: true}>;
// ...
function goForAWalkWith(animal: AnimalThatRequiresWalks) {
    // ...
}

Playground link

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading