Ensuring that the designated key of an object is an extension of Array

What is the best way to ensure that the specified keyof a type extends an array?

Consider the following type example:

type TestType = {
    arrayKey: Array<number>,
    notArrayKey: number
}

There is also a function that accesses the specified key of the type:

const getKey = <T>(object: T, key: keyof T): Array<any> => {
    return object[key]
}

How can we ensure that only the valid input for key is arrayKey, as it extends Array, and not "notArrayKey", which does not extend Array?

Answer №1

Behemoth's response contains helpful IntelliSense suggestions for the key if you decide to follow the parameter order of (object, key).

To put it simply, by switching the parameters, you can utilize generic constraints to verify that the object argument adheres to a type with an array value at the specified key:

TS Playground

function getValueInObj <
  Key extends string,
  Obj extends Record<Key, readonly any[]>,
>(key: Key, object: Obj): Obj[Key] {
  return object[key];
}

const obj = {
  arrayKey: [1, 2, 3],
  nonArrayKey: '123',
};

getValueInObj('arrayKey', obj); // OK

getValueInObj('nonArrayKey', obj); /*
                             ~~~
Argument of type '{ arrayKey: number[]; nonArrayKey: string; }' is not assignable to parameter of type 'Record<"nonArrayKey", readonly any[]>'.
  Types of property 'nonArrayKey' are incompatible.
    Type 'string' is not assignable to type 'readonly any[]'.(2345) */

Building upon the previous response:

The type mapping can be achieved in the generic constraint to assign a type parameter name for use in the return type.

In the code snippet below, when the obj value is passed as the first argument to the function, the generic type Obj transforms into the type of that object, while the type parameter Key becomes a union of all its properties with value types extending readonly any[] (indicating an immutable array with elements of any type). This union is represented as 'arrayKey' | 'anotherArrayKey', requiring the value provided to key to be one of those strings, otherwise a compiler error will be triggered.

TS Playground

function getValueInObj <
  Obj extends Record<PropertyKey, any>,
  Key extends keyof {
    [
      K in keyof Obj as Obj[K] extends readonly any[]
        ? K
        : never
    ]: unknown; // The "unknown" type used here as the value doesn't matter
  },            // because it goes unused: it could be any
>(object: Obj, key: Key): Obj[Key] {
  return object[key];
}

const obj = {
  arrayKey: [1, 2, 3],
  anotherArrayKey: ['a', 'b', 'c'],
  nonArrayKey: '123',
};

getValueInObj(obj, 'arrayKey'); // number[]

getValueInObj(obj, 'nonArrayKey'); /*
                   ~~~~~~~~~~~~~
Argument of type '"nonArrayKey"' is not assignable to parameter of type '"arrayKey" | "anotherArrayKey"'.(2345) */

Answer №2

One way to selectively filter the keys of the TestType object based on their values is by using a Mapped Type. This involves checking if the value of a key is an extension of Array<number> (T[K] extends Array<number>). Let's illustrate this with the keys present in the TestType object:

  1. Does TestType.arrayKey extend Array<number>? --> Yes, this is a valid key
  2. Does TestType.notArrayKey extend Array<number>? --> No, this is not a valid key because it has the type number

type TestType = {
  arrayKey: Array<number>;
  notArrayKey: number;
};

const test = {
  arrayKey: [0],
  notArrayKey: 0
};

const getKey = <T extends object>(
  object: T,
  key: {
    [K in keyof T]: T[K] extends Array<number> ? K : never;
  }[keyof T]
): T[keyof T] => {
  return object[key];
};

getKey<TestType>(test, "arrayKey"); // this works
getKey<TestType>(test, "notArrayKey");
                     // ~~~~~~~~~~~~ --> Argument of type
// '"notArrayKey"' is not assignable to parameter of type '"arrayKey"'

Defining the return type of the function as Array<number> seems challenging. It might not be achievable since there's no guarantee that every T object will have a property of type Array<number>. However, I'm open to being proven wrong!

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

What is the method for copying table data, including input text as cells, to the clipboard without using jQuery?

I have a basic table that looks like this: <table> <thead> <tr> <th>Savings</th> </tr> </thead> <tbody> <tr> <td>Savings <button type="button" (click)=" ...

Changing a password on Firebase using Angular 5

I am in the process of developing a settings feature for user accounts on an application I've been working on. One key functionality I want to include is the ability for users to update their password directly from the account settings page. To enable ...

Define the output type of an arrow function within an interface signature

How can I inform typescript that I will be utilizing an interface containing a function called "foo" which always returns a string. The implementation of the function will be specified by the object implementing the interface. For example: export interfac ...

Utilizing object as props in ReactJS with TypeScript

I'm new to working with ReactJS, and I made the decision to use typescript for my current project. The project is an application that fetches movies from an API and displays them. Take a look at the app: import React from 'react'; import &a ...

What is the best way to find out if an array index is within a certain distance of another index?

I'm currently developing a circular carousel feature. With an array of n items, where n is greater than 6 in my current scenario, I need to identify all items within the array that are either less than or equal to 3 positions away from a specific inde ...

Anticipating the desired data types for Jasmine arguments

Lately, I've been in the process of upgrading my Angular version from 10 to 13 in order to utilize TypeScript 4.6. However, during this upgrade, I made some errors with types in my tests and I'm wondering if others have encountered similar issues ...

Encountering a navCtrl problem in Ionic 3 while attempting to utilize it within a service

I am currently working on a feature to automatically route users to the Login Page when their token expires. However, I am encountering an issue with red lines appearing under certain parts of my code. return next.handle(_req).do((event: HttpEvent< ...

Exploring the Mechanism Behind the Successful String Interpolation of a Variable Imported in my Angular 2 Application

After making some modifications to my Angular 2 application, I encountered a situation where something started functioning properly sooner than I expected. This has left me puzzled about why it's working in its current state. Specifically, I have an a ...

Challenges with Type Casting in TypeScript

Upon reviewing a specific piece of code, I noticed that it is not producing any compile time or run time errors even though it should: message: string // this variable is of type string -- Line 1 <br> abc: somedatatype // lets assume abc is of some ...

What can be done to prevent the angular material select from overflowing the screen?

I have integrated an Angular Material Select component into my application, which can be found here: https://material.angular.io/components/select/overview. The issue I am facing is that when the select element is positioned near the bottom of the screen a ...

Transformation of Ionic 2 ScreenOrientation Plugin

Can someone assist me with this issue? A while back, my Ionic 2 app was functioning correctly using the ScreenOrientation Cordova plugin and the following code: window.addEventListener('orientationchange', ()=>{ console.info('DEVICE OR ...

How can we create external labels for a polar chart in ng2-charts and chart.js, with a set position outside the circular rings?

Currently, I am working on creating a polar chart using Angular along with chart.js version 2.8.0 and ng2-charts version 2.3.0. In my implementation, I have utilized the chartjs-plugin-datalabels to show labels within the polar chart rings. However, this p ...

The ArgsTable component is not displayed in Storybook when using Vite, Typescript, and MDX

I'm struggling to display the table with props on a MDX documentation page. No matter what I try, the table only shows: "No inputs found for this component. Read the docs >" Despite trying various methods, I can't seem to get it to work. I h ...

"Navigating through events with confidence: the power of event

Imagine I am developing an event manager for a chat application. Having had success with event maps in the past, I have decided to use them again. This is the structure of the event map: interface ChatEventMap { incomingMessage: string; newUser: { ...

Create an array with individual key-type pairs for each generic element, then iterate through the array

Consider the enum and type declarations in the code below: enum MyEnum { FIRST, SECOND }; type MyType = { firstKey: string | null, secondKey: boolean, thirdKey: MyEnum } Next, a variable is declared using the type as follows: let glob ...

Using React TypeScript, describe the type of ref and mouse event

I am facing an issue with my navbar that I want to hide when clicking outside the sidenav. I came across a useful code snippet that can help me achieve this, but I need to ensure I use the correct types while implementing it in TypeScript. This particular ...

The implementation of the "setValue" function from react-hook-form resulted in the generation of over 358,000 TypeScript diagnostics for various types

In my experience, I have frequently used react-hook-forms and `setValue` in various parts of my application without encountering any issues. However, I recently came across a problem while compiling in a newly created branch based on the main branch. Desp ...

Angular 2's ng-required directive is used to specify that

I have created a model-driven form in Angular 2, and I need one of the input fields to only show up if a specific checkbox is unchecked. I was able to achieve this using *ngIf directive. Now, my question is how can I make that input field required only whe ...

Creating and handling Observable of Observables in RxJS: Best practices

Within my Angular application, there are multiple services that have dependencies on each other. To manage this, I have created a dependency map as shown in the example below: let accountInitialization$: Observable<void>; let productInitialization$: ...

Is the child constantly updating due to a function call?

Having difficulty navigating the intricacies where a child keeps re-rendering due to passing a function from the parent, which in turn references an editor's value in draftjs. function Parent() { const [doSomethingValue, setDoSomethingValue] = Re ...