Retrieving the exact data type of an element within an array structure based on a specified key and object

I have been working on a helper function called get_item which is supposed to return a single item from an object's property. However, I am facing difficulties in getting the correct typings for it.

// without typings
function get_item(foo, key, index) {
    const array = foo[key];
    // check if the index exists
    if (index in array === false) throw new Error("Item " + index + " not found for " + key);
    return array[index];
}
// usage example 
const item = get_item({bar:[0,1,2]}, 'bar', 0);

Despite my efforts, I seem to struggle when trying to infer the ArrayItemType of array.

Although returning the type of array based on the provided key is not an issue, as demonstrated here.

However, I specifically want to work with properties of type Array in my foo object and retrieve the type of a single item in the selected array using the key. Unfortunately, TypeScript seems to lose track of the relation to the key property when accessing the array with the index operator.

Typescript 4.5.4 Playground (same code as below)

/**
 * I want to get the item at position 'index' 
 * from the properties with name 'key'
 * of the structure 'Foo'
 */
function get_item<
    KEY extends keyof Foo,  // "zoo" | "bar"
>(foo: Foo, key: KEY, index:number)
    : ArrayItemType<Foo[KEY]> // determines the type of a single element for the property 'key' beeing number|string
{
    const array: Foo[KEY] = foo[key];   // this can be Array<number> or Array<string> depending on 'key'
    // check if the index exists
    if (index in array === false) throw new Error("Item " + index + " not found for " + key);
    const item = array[index];          // at this point the type seems to have collapsed to number|string,
                                        // discarding the fact that we know the exact type of 'array' for a given 'key'
    return item;    // Error
                    // Type 'string | number' is not assignable to type 'ArrayItemType<Foo[KEY]>'. 
                    // Type 'string' is not assignable to type 'ArrayItemType<Foo[KEY]>'.
}

type ArrayItemType<ARRAY> = ARRAY extends Array<infer ITEM> ? ITEM : never;

/**
 * Some interface with a few properties of type Array<any>
 */
interface Foo {
    bar : Array<number>;
    zoo : Array<string>;
}

const foo : Foo = {
    bar: [0,1],
    zoo: ["a", "b"],
};
// determines correct types here, this is the way i want it
const number_item :number = get_item(foo, 'bar', 1);
const string_item :string = get_item(foo, 'zoo', 1);

Although adding as ArrayItemType<Foo[KEY]> resolves the issue, I prefer to minimize such type castings especially when the reason behind their necessity eludes me (leading me to believe I misunderstood something in my code).

So, could there be something I am overlooking?
Is this scenario potentially undoable?
Or might the problem lie within KEY extends keyof Foo, allowing unexpected values?

Answer №1

Here's a method you can use:

TS Playground

/** Throws if value is undefined */
function getItem <
  K extends PropertyKey,
  T extends Record<K, readonly unknown[]>,
  I extends number,
>(obj: T, key: K, index: I): T[K][I] extends undefined ? never : T[K][I] {
  const arr = obj[key];
  if (!Array.isArray(arr)) throw new Error(`Property "${key}" not found`);
  const value = arr[index];
  if (typeof value === 'undefined') throw new Error(`Value not found at index ${index}`);
  return value;
}

getItem({bar: [2, 4, 6]}, 'bar', 0); // number
getItem({bar: [2, 4, 6] as const}, 'bar', 0); // 2

interface Foo {
  bar: number[];
  zoo: string[];
}

const foo: Foo = {
  bar: [0, 1],
  zoo: ['a', 'b'],
};

getItem(foo, 'bar', 1); // number
getItem(foo, 'zoo', 1); // string
getItem({hello: ['world'] as const}, 'hello', 1); // never, will throw

Just keep in mind the behavior of index signatures and arrays in TypeScript:

getItem(foo, 'zoo', 2); // string but actually never because it will throw
  • Typescript: accessing an array element does not account for the possibility of undefined return values
  • noUncheckedIndexedAccess

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

Receiving the error notification from a 400 Bad Request

I'm having trouble showing the errors returned from an API on my Sign up form when a user submits invalid data. You can check out the error response in the console here: console ss This is my approach in RegisterComponent.ts: onSubmit() { this.u ...

A step-by-step guide on retrieving the present date and time using TypeScript

Details This is my first time creating a VSCode extension using TypeScript, and I am having trouble displaying the current date/time in an information message. My Attempts I have searched through VSCode key bindings for any references to date/time, as w ...

Exploring ways to fetch an HTTP response using a TypeScript POST request

I have been looking at various questions, but unfortunately, none of them have provided the help I need. The typescript method I am currently working with is as follows: transferAmount(transfer: Transfer): Observable<number> { return this.http .po ...

Specify the key type when using the syntax "key in ObjectName"

I need assistance in setting the key type of an object. Here is what I have tried: type TypeSample = { [key: string]: string } In addition, I want to specify that the keys should come from an enum like this: enum EnumSample { 'ok' = &a ...

What could be causing my "Swiper" component to malfunction in a TypeScript React project?

In my React project, I decided to incorporate the Swiper library. With multiple movie elements that I want to swipe through, I began by importing it as follows: import Swiper from 'react-id-swiper'; Utilizing it in my code like this: <div cla ...

What is the best way to close ngx-simple-modal in angular7 when clicking outside of the modal component?

Can anyone help me with closing the modal in my angular7 app when the user clicks outside of the modal component? I have been using the ngx-simple-modal plugin, and I tried implementing the following code: this.SimpleModalService.addModal(LinkPopupCompone ...

What is the best method for enhancing Express types?

One of my files is named types/express.d.ts declare namespace Express { export interface Response { respondWith(data: any): Response; } } I also have this code snippet in app.js Express.response.respondWith = function(data) { return this.json( ...

Tips on programmatically filtering angular lists

Is there a way to programmatically filter an Angular list? I'm currently working on a project where I need to filter subcategories by clicking on categories. For example, when clicking on "Drinks," I want to display items like Coke, Fanta, Pepsi... ...

Issue during deployment: The type 'MiniCssExtractPlugin' cannot be assigned to the parameter type 'Plugin'

I'm working on deploying a Typescript / React project and have completed the necessary steps so far: Created a deployment branch Installed gh-pages for running the deployed application Added a deploy command as a script in the package.j ...

An issue has occurred: Uncaught (in promise): NullInjectorError: R3InjectorError(AppModule)[NavbarComponent -> NavbarComponent

I've been working on implementing Google Auth login with Firebase, but I keep encountering an issue when trying to load another component or page after logging in. I've spent the entire day trying to debug this problem and it's really frustr ...

What strategies can I use to steer clear of the pyramid of doom when using chains in fp-ts?

There are times when I encounter a scenario where I must perform multiple operations in sequence. If each operation relies solely on data from the previous step, then it's simple with something like pipe(startingData, TE.chain(op1), TE.chain(op2), TE. ...

Visual Studio Code is unable to identify the TypeScript module located within the `node_modules` directory

After cloning the tslint repository, running npm install and grunt as instructed in the README, I opened the folder in Visual Studio Code (0.9.1). Upon inspecting a .ts file such as src/rules/typedefRule.ts, TypeScript errors appeared regarding the require ...

Class-transformer malfunctioning when dealing with an array

I am facing an issue with serializing a controller response when working with arrays. UserSerialization import { Role } from '@prisma/client'; import { Exclude, Expose } from 'class-transformer'; export const GROUP_USER_ME = 'GRO ...

Issue with i18next and React Router: The URL does not update properly when switching languages

I have encountered an issue while trying to update the app URL when switching between languages. Manually adding the language code in the URL (e.g., "http://localhost:3001/es/forgot-password") works correctly and loads the components with the selected lang ...

Experimenting with retrieving input from other components while implementing setTimeout

In continuation of the previous question (linked here), I am still working on tutorials for Angular testing using the same files. The current issue revolves around the setTimeout function. Within both ngOnInit and ngAfterViewInit, I have included a setTim ...

How to implement and utilize a history-object interface in React with Typescript?

Can you help me with setting up an interface for a history object in my component? Currently, it is typed as any and I want to type it appropriately. Object: https://i.sstatic.net/Sru8R.png Here's the code snippet: import React, { useState } from &a ...

Angular version 5 and above introduces a new feature called "openFromComponent" within the Snackbar component, facilitating seamless communication

Angular (v5.2.10) Snackbar --| Introduction |-- I am facing a scenario where an Angular component named "Parent" is initializing an Angular Material Snackbar known as snackBar. The snackbar is being passed in the component called SnackbarMessage, which ...

What is the best way to save Azure Service Bus Message IDs with a package-internal type of 'Long' in MongoDB?

Currently, I am utilizing Azure Service Bus (@azure/service-bus) within a TypeScript-based nest.js service to schedule messages for delivery at a future date. In case the need arises, I must also have the ability to cancel these scheduled messages before t ...

Unable to retrieve information from a Firestore collection in Angular

Currently, I'm developing an Angular project that is connected to a Firestore database. In summary, the pages are stored in the database, and I have created a PageComponent that displays a URL like this: page/'id' and showcases the correspon ...

The comparison between importing and requiring mutable values for export

I'm exploring the distinction between import and require in relation to exporting and importing mutable values. Picture a file a.ts: export let a = 1; export function f() { a = 2; } Next, we have three versions of a main file, index1.ts: import { ...