Mapping a list of records by a nested attribute using Typescript generic types

In my current project, I am implementing a unique custom type called "RecordsByType", which is currently undefined in the code snippet below.

Within this snippet, I have various types that represent database records, each with its type defined within a nested attribute named ["attributes"]["type"]. For instance, take a look at the Account type. Unfortunately, this specific aspect cannot be altered or modified.

It's worth noting that there will be numerous different "RecordTypes" beyond just Account or User.

type RecordTypeName = 'Account' | 'User';
type RecordType = Account | User;
type RecordKey = keyof DbRecord | keyof Account | keyof User;

interface DbRecord {
    attributes: {
        type: RecordTypeName;
    };
}

interface Account extends DbRecord {
    attributes: {
        type: 'Account';
    };
    name: string;
}

interface User extends DbRecord {
    attributes: {
        type: 'User';
    };
    email: string;
}

const account1: Account = { attributes: { type: 'Account' }, name: 'A1' };
const account2: Account = { attributes: { type: 'Account' }, name: 'A2' };
const user: User = { attributes: { type: 'User' }, email: '<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="91f4fcf0f8fdd1f4fcf0f8fdbff2fefc">[email protected]</a>' };

type RecordsByType = unknown;

// The following line contains initial values to demonstrate the expected type structure,
// but it should also be able to initialize without any values as '= {};' would suffice.
const recordsByType: RecordsByType = {
    Account: [account1, account2],
    User: [user]
};

// This line should compile successfully, resulting in an array of Accounts rather than generic DbRecords[] or RecordType[].
const accounts: Account[] = recordsByType['Account'] || [];

// The next line should trigger a compilation error since the user object is not an instance of Account.
recordsByType['Account']?.push(user);

This related question may offer some insights, although my situation involves a nested key "attribute" and is not as generic.

First Attempt:
Initially, I attempted to define the RecordsByType type using Partial>, but encountered issues where the result was incorrectly inferred as DbRecord[].

type RecordsByType = Partial<Record<DbRecord['attributes']['type'], DbRecord[]>>;
// => Type DbRecord[] is not assignable to type Account[]

Second Attempt:
I then tried matching the "type" value with the record's actual value, only to face similar complications.

type RecordsByType<
    T extends K['attributes']['type'],
    K extends RecordType
> = Partial<Record<T, K[]>>;
// The following line results in RecordType[], not Account[], causing a compilation error
const accounts: Account[] = recordsByType['Account'] || [];

Final Thoughts:
My goal is to establish a connection between each record's ["attributes"]["type"] and the corresponding "key" value while being able to retrieve a list of specific implementations for each key instead of a generic type.

Answer №1

For this task, the first step is to create a union of all potential types (Acccount, User, etc.):

type AllTypes = Account | User /* | SomethingElse | AnotherThing etc. */;

Next, we define RecordsByType as a mapped type that maps the types from the attributes property as keys to arrays of the corresponding type obtained by extracting anything assignable to { attributes: { type: Key } } from AllTypes:

type RecordsByType = {
    [Key in AllTypes["attributes"]["type"]]?: Extract<AllTypes, { attributes: { type: Key } }>[];
};

Once this is done, the following code compiles successfully:

const accounts: Account[] = recordsByType["Account"] || [];

However, the following code fails as expected:

recordsByType["Account"]?.push(user);

Playground link


(By the way, in more recent code, it's advisable to use nullish coalescing [??] instead of logical OR [||] in

recordsByType["Account"] || []
, given its availability now:
recordsByType["Account"] ?? []
)

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

Issue: Using the command 'typings search' successfully locates a package, however, when attempting to install it using 'typings install', the process fails

I am currently attempting to install the Google Auth2 typings using 'typings': > typings search gapi.auth2 This command returns: NAME SOURCE HOMEPAGE DESCRIPTION VERSIONS UPDATED gapi.auth2 d ...

Mastering the TypeScript syntax for executing the MongoDB find method

Having trouble properly typing the find method of MongoDB in my TypeScript project that involves MongoDB. Here's the snippet I'm working on: import { ReitsType } from '@/app/api/types/reits'; import { NextRequest, NextResponse } from &a ...

Reduce the size of log messages in cypress

I am looking to shorten the cypress messages to a more concise string, for instance: Cypress log Transform to: -assert expected #buy-price-field to have value 17,169.00. Is there a way to achieve this? I have searched through the documentation but hav ...

Build a stopwatch that malfunctions and goes haywire

I am currently using a stopwatch that functions well, but I have encountered an issue with the timer. After 60 seconds, I need the timer to reset to zero seconds and advance to one minute. Similarly, for every 60 seconds that pass, the minutes should chang ...

What is the purpose of using 'ref' in conjunction with useCallback instead of just utilizing useCallback on its own?

While working on my React project, I came across some libraries that used 'useCallback' in a different way than I'm used to. Below is the code snippet showcasing this approach. Despite my initial thoughts, I still believe there's no sig ...

Using TypeScript: Retrieve enum type value in type definition

I'm encountering an issue while attempting to define a specific type based on the value of an enum. enum E { A = "apple", B = "banana", C = "cherries" } // Defining the type EnumKey as 'A' | 'B ...

Guide on sorting an array within a specific range and extracting a sample on each side of the outcome

I need a simple solution for the following scenario: let rangeOfInterest = [25 , 44]; let input = [10, 20, 30, 40, 50, 60]; I want to extract values that fall between 25 and 44 (inclusive) from the given input. The range may be within or outside the inpu ...

A guide on successfully sending parameters to Angular routes

Currently, I am delving into Angular and exploring various basic concepts such as routing, Observables (and subscribing to them), making HTTP requests, and utilizing routing parameters. One scenario I have set up involves sending a HTTP GET request to JSON ...

Exploring the Differences Between Native Script and Ionic: A Guide to Choosing the Right Framework for Your Hybrid Apps

When deciding between Ionic and Native Script for hybrid app development, which technology would you recommend? Or do you have another suggestion knowing that I am familiar with Angular 6? Also, I am looking for a Native Script tutorial, preferably in vide ...

how to enhance Request type in Express by adding a custom attribute

In my quest to create a custom middleware function in Node.js using TypeScript, I am facing an issue where I am trying to save a decoded JSON web token into a custom request property called 'user' like so: function auth(req: Request, res: Respo ...

Limit the frequency of function calls in Typescript

Update: After some research, I've learned that throttle has the capability to drop excess function invocations, making it unsuitable for my needs. I am still seeking an idiomatic solution to process every item in a queue at an appropriate pace without ...

How to Remove onFocus Warning in React TypeScript with Clear Input Type="number" and Start without a Default Value

Is there a way to either clear an HTML input field of a previous set number when onFocus is triggered or start with an empty field? When salary: null is set in the constructor, a warning appears on page load: Warning: The value prop on input should not ...

Utilize TypeScript to narrow down function parameters within a callback by evaluating other parameters

I'm currently working with traditional node callbacks. For example: myFunction('foo', (err: Error|null, data?: Buffer) =>{ if (err) { // typeof err is Error // typeof data is Buffer|undefined } else { // typeof err is nul ...

Building a continuous timer loop in Angular using RxJS that adapts to changing durations within an array's objects

I am experimenting with a scenario where I read the data, loop based on the duration. For example, starting with "Adam" first, play Adam for a 15-second timer, then move on to the next beginner "Andy" and play Andy for 15 seconds. Once we reach group "int ...

Tips for sending parameters in Next.js without server-side rendering

I followed the documentation and tried to pass params as instructed here: https://nextjs.org/docs/routing/dynamic-routes However, I encountered a strange issue where the received params are not in string format. How is it possible for them to be in an arr ...

Arrange elements within an array according to a specific property and the desired sorting sequence

Looking for a way to sort an object array in Angular 16+ based on status. The desired status order is: [N-Op, Used, Unknown, Op] Here's the sample data: const stockList = [ { 'heading': 'SK', 'status': &a ...

Do not display large numbers within an HTML card

I have https://i.sstatic.net/DkowD.png this card here and displaying dynamic data inside it. The number is quite large, so I would like it to appear as 0.600000+. If a user hovers over the number, a tooltip should display the full number. How can I achieve ...

Configuring Typescript target and library to utilize Promise.allSettled on outdated web browsers

I am currently using TypeScript version 4.3.5 and Node.js version 14.18.1 in my project. The code I am working on is compiled to target both old and new browsers by setting target=es5 in the tsconfig file. I make use of both Promise.all and Promise.allSett ...

Please indicate the data type in Vue using Typescript

I'm currently struggling with Vue (3) + Typescript while attempting to specify a data property with a specific type. I have created a .d.ts file, but unfortunately, it hasn't helped. This is what I'm trying: import Modeler from 'bpmn-js ...

What is the best way to set a boolean value for a checkbox in a React project with Typescript?

Currently, I am working on a project involving a to-do list and I am facing an issue with assigning a boolean value to my checkbox. After array mapping my to-dos, the checkbox object displays 'on' when it is unchecked and a 'Synthetic Base E ...