How is it possible to refine a type in TypeScript by applying a succession of filters?

I am looking to filter an array and refine the type based on shared properties within a union type. The elements in the array belong to various types that share some common characteristics.

type TypeA = {name: string; id: string; createdAt: Date};
type TypeB = {name: string; id: string; metaData: Record<string, unknown>};
type TypeC = {id: string};
type TypeD = {name: string; createdAt: Date};
// etc.

type MyUnionType = TypeA | TypeB | TypeC | TypeD;

const hasName = (x: MyUnionType): x is MyUnionType & {name: string} => 'name' in x;
const hasId = (x: MyUnionType): x is MyUnionType & {id: string} => 'id' in x;

function foo(ary: MyUnionType[]) {
    ary.filter(hasName)
       .filter(hasId)
       .map(x => x.name + x.id);  // ❌ Error because it thinks property `id` does not exist
}

I have come up with two possible solutions:

  1. Develop a specific filter for each required combination:
function hasNameAndId(x: MyUnionType): x is MyUnionType & {name: string} {
    return 'name' in x && 'id' in x;
}

This approach may not be practical in the long run, as it involves creating multiple functions for different filter combinations.

  1. Instead of separate filter functions, incorporate the filters directly in the code with the type information:
function foo(ary: MyUnionType[]) {
    ary.filter((x): x is MyUnionType & {name: string} => 'name' in x)
       .filter((x: MyUnionType & {name: string}): x is MyUnionType & {name: string; id: string} => 'id' in x)
       .map(x => x.name + x.id);
}

This alternative can become complicated and hard to maintain.

Answer №1

Utilizing type guard functions directly triggers automatic narrowing in the compiler, achieving the desired effect:

function foo(ary: MyUnionType[]) {
  ary.flatMap(x => hasName(x) && hasId(x) ? x.name + x.id : []) 
}

To make Array.filter() function as a type guard, ensure that the callback aligns precisely with the relevant call signature:

interface Array<T> {
  map<U>(cb: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
}

However, when using

ary.filter(hasName).filter(hasId)
, the element in the array becomes of type MyUnionType & { name: string }, while your callback expects an argument of type MyUnionType. The mismatch prevents proper compatibility and results in bypassing the type guard, leading to unchanged output from filter().


An effective solution to this issue involves crafting generic type guard functions so that invoking the second filter() can instantiate the generic type parameter accordingly. A possible implementation is as follows:

const hasName = <T extends MyUnionType>(x: T): x is T & { name: string } => 'name' in x;
const hasId = <T extends MyUnionType>(x: T): x is T & { id: string } => 'id' in x;

With these adjustments, the second filter() call will succeed; T gets instantiated with MyUnionType & {name: string}, resulting in an array of

(MyUnionType & {name: string}) & {id: string})
, as intended:

function foo(ary: MyUnionType[]) {
  ary.filter(hasName)
    .filter(hasId)
    .map(x => x.name + x.id); // works correctly
}

Link to Playground for code testing

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Adjust the colors of two elements by clicking a button's onclick event

As stated in the title, I have a button in the books component. When this button is clicked, the color of a div within the books component and another div in the navbar component should change. - Below is the code for the books component : export class Bo ...

Applying the spread operator in the map function for copying objects

In my Angular application, I am attempting to copy an object and add a new property using the spread operator. To add the new property, I have created a method called 'addNewProperty(name)' which returns the property and its value. However, when ...

Is your variable being assigned the incorrect type by a Typescript enum?

Within my codebase, I have defined an enum called AlgorithmEnum: export enum AlgorithmEnum { SHA1, SHA256, SHA512 } This enum is utilized as the Algorithm property of a class named Authenticator: export class Authenticator { Type: Type = T ...

The parameter type 'router' cannot be replaced with the type 'typeof ...'. The 'param' property is not included in the type 'typeof'

I'm currently working on a node application using TypeScript and have set up routing in a separate file named 'route.ts' import home = require('../controller/homeController'); import express = require('express'); let ro ...

Tips on including a trash can symbol to rows in a bootstrap table using React

I am working on a table that contains various fields, and I want to add a trash icon to each row so that when it is clicked, the specific row is deleted. However, I am encountering an issue where the trash icon is not showing up on the row and I am unable ...

Angular's import and export functions are essential features that allow modules

Currently, I am working on a complex Angular project that consists of multiple components. One specific scenario involves an exported `const` object in a .ts file which is then imported into two separate components. export const topology = { "topolo ...

Encountered a JSON.Parse error in Angular 9's HttpErrorResponse even though the response was successful

Why am I encountering an error with this code snippet? deleteUser(userId: string) { this.dataService.deleteUser(userId).subscribe((response: string) => { console.log(response); }); } "SyntaxError: Unexpected token f in JSON at p ...

Problems encountered when utilizing $in operator in Mikro ORM MongoDB operations

For my project, I'm utilizing mikro orm with MongoDB to handle database operations in TypeScript. So far, it has proven to be the most effective ORM for MongoDB in this language. However, I've encountered type errors when using $in with Object ID ...

Maintain the active tab selection in Angular 2 even after the page reloads

<nav class="nav-sidebar"> <ul class="nav tabs" id="myTab"> <li class="active" ><a href="#tab3" data-toggle="tab" >Tabl 1</a> </li> <li class="tab2"><a href="#tab2" ...

Transforming a JSON object into XML format

Currently, I am encountering an issue while attempting to convert my JSON object to XML using the xml-js library's json2xml function. When trying to implement this functionality, I keep getting an error message that states: Error: Buffer is not defin ...

Issue with Angular 11: Unable to bind to 'ngForOf' as it is not recognized as a valid property of 'tr' element

My issue lies with a particular page that is not functioning correctly, even though it uses the same service as another working page. The error seems to occur before the array is populated. Why is this happening? I appreciate any help in resolving this p ...

Is there a way to verify within the "if" statement without repeating the code?

Is there a way in javascript (or typescript) to prevent redundant object rewriting within an if statement condition? For example: if (A != null || A != B) { // do something here } // can it be done like this: if (A != null || != B) { // avoid repeating ...

Generate a dynamic interface using properties and options provided in a JavaScript object

Can Typescript support the following scenario: I have a structure where keys represent properties and values are arrays of options for those properties: const options = { foo: [fooOption1, fooOption2, ...], bar: [barOption1, barOption2, ...], ... } ...

Why is it that Angular is unable to locate the component factory for my component, even though I have declared it in the entryComponents option of my Module?

Is there a way to dynamically create and show a component in an ngx-bootstrap's modal? I attempted to declare the component in the entryComponents option of RegMktRiskHomeModule, but it did not work. Currently, I am only able to declare the Scenarios ...

Tips for developing a strongly-typed generic function that works seamlessly with redux slices and their corresponding actions

Currently, I am working with @reduxjs/toolkit and aiming to develop a function that can easily create a slice with default reducers. Although my current implementation is functional, it lacks strong typing. Is there a way to design a function in such a man ...

Encountering a Typescript error with Next-Auth providers

I've been struggling to integrate Next-Auth with Typescript and an OAuth provider like Auth0. Despite following the documentation, I encountered a problem that persists even after watching numerous tutorials and mimicking their steps verbatim. Below i ...

Collaborating on an Angular 2 service among child components that are not contained within the same parent component

Imagine having an Angular 2 setup with a root parent component A, two distinct children components B and C: https://i.sstatic.net/w9bOf.jpg Now, if there is a need to create a shared service called MyService that can be injected into both child component ...

Leverage the template pattern in React and react-hook-form to access a parent form property efficiently

In an effort to increase reusability, I developed a base generic form component that could be utilized in other child form components. The setup involves two main files: BaseForm.tsx import { useForm, FormProvider } from "react-hook-form" expor ...

“What is the process of setting a referenced object to null?”

Here is an example of the code I'm working with: ngOnInit{ let p1 : Person = {}; console.log(p1); //Object { } this.setNull<Person>(p1); console.log(p1); //Object { } } private setNull<T>(obj : T){ obj = null; } My objective is to ...

Vue-Apollo - The 'value' property is not present in the 'Readonly<Ref<Readonly<any>>>' type

MY CURRENT DILEMMA: In my quest to seamlessly integrate vue-apollo v4 with Typescript, I have encountered a challenge. I am in the process of retrieving data from a simple query using useQuery along with useResult. The default return type of useResult i ...