Restrict the keys to only properties that have an array data type

Is there a way to limit the keyof operator to only accept keys of a specified type in TypeScript?

interface Data {
  items: string[];
  name: string;
}

// I want to restrict the keyof operator to only allow keys where the value is of type `F`
type Key<T, F = any> = keyof T & string;

interface TextField<T> {
   input: 'text'
   key: Key<T, string>
}

interface ArrayField<T> {
  input: 'text',
  key: Key<T, Array>
}

type Field<T> = TextField<T> | ArrayField<T>;

// Should NOT error
const config: Field<Data>[] = [
   {
      input: 'text'
      key: 'name'
   },
   {
      input: 'array',
      key: 'items'
   }
];

// Should error because the items is not a string and name is not an array
const config: Field<Data>[] = [
   {
      input: 'text'
      key: 'items'
   },
   {
      input: 'array',
      key: 'name'
   }
]

Answer №1

If you're searching for a mapped type with key remapping combination, we can achieve this by utilizing `key remapping` to retain only the desired properties:

type Key<T, F = any> = keyof {
  [K in keyof T as T[K] extends F ? K : never]: T[K];
};

Let's run some tests:

interface Data {
  items?: string[];
  name: string;
}

// "name"
type Case1 = Key<Data, string>

To acquire an array type, we'll utilize `readonly unknown[]` as it encompasses mutable arrays:

// false
type Case1 = readonly number[] extends number[] ? true : false
// true
type Case2 = number[] extends readonly number[] ? true : false

Testing:

// type Case2 = never
type Case2 = Key<Data, readonly unknown[]>

Observe how `never` is returned? While `items` matches the type criterion, the issue lies in its optional nature. Optional objects are broader than required ones.

// false
type Case1 = { a?: string } extends { a: string } ? true : false;
// true
type Case2 = { a: string } extends { a?: string } ? true : false;

To address this, let's employ the built-in NonNullable utility type:

// string[] | undefined
type Case1 = Data['items']
// string[]
type Case2 = NonNullable<Data['items']>

Let's update `Key` and other types:

type Key<T, F = any> = keyof {
  [K in keyof T as NonNullable<T[K]> extends F ? K : never]: T[K];
};

interface TextField<T> {
  input: 'text';
  key: Key<T, string>;
}

interface ArrayField<T> {
  input: 'array';
  key: Key<T, readonly unknown[]>;
}

Final testing:

// no error
const config: Field<Data>[] = [
  {
    input: 'text',
    key: 'name',
  },
  {
    input: 'array',
    key: 'items',
  },
];

// error because the items is not a string and name is not an array
const config2: Field<Data>[] = [
  {
    input: 'text',
    key: 'items',
  },
  {
    input: 'array',
    key: 'name',
  },
];

Playground Link

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

Switching from a Promise to an Observable using React-Redux and TypeScript

I am struggling to grasp the concept of storing a Promise response into Redux. I believe finding a solution to this issue would greatly benefit me. Currently, I am utilizing a library that returns a Promise, and I wish to save this response - whether it i ...

Mastering the use of Action.Submit in adaptive cards to simulate user input

I am trying to implement MessageFactory.SuggestedActions within my "welcomeCard" adaptive card. Essentially, in my adaptive card (welcome card), I have several buttons for the user to click on, each with an Action.Submit type. { "type" ...

Typescript's intriguing concept of objects containing arrays inside other objects

I have a structure similar to this and I am trying to create definitions for types/interfaces, but I am facing issues in making it work correctly. layoutsSet: { 1: { "lg": [ { i: "1", x: 0, ...

Utilize mui-tiptap to effortlessly upload images to your content

My current project involves using the mui-tiptap module in React 18 with Vite. Specifically, I am facing an issue with uploading a local image to my backend. The problem seems to lie in my frontend code, as I am encountering a TypeScript error that is prev ...

Facing the dreaded "ECONNREFUSED" error when trying to connect NodeJS and InfluxDb

I'm encountering an issue when trying to connect to my InfluxDB instance running in a Docker container. To get the InfluxDB image, I used the following command: docker pull influxdb:2.4.0 When I run it locally using Docker Desktop, everything works ...

I'm looking for a way to merge the functionalities of tsc build watch and nodemon into a single Node.js

Currently, I have two scripts in my code: "scripts": { "build": "tsc -p . -w", "watchjs": "nodemon dist/index.js" } I need to run these two scripts simultaneously with one command so that the build ...

Setting up WebPack for TypeScript with import functionality

A tutorial on webpack configuration for typescript typically demonstrates the following: const path = require('path'); module.exports = { ... } Is it more advantageous to utilize ES modules and configure it with import statements instead? Or is ...

Use Typescript to access and utilize the value stored in local storage by using the

Trying to retrieve the language setting from localHost and implement it in a translation pipe as shown below: transform(value: string): string {... localStorage.setItem("language", JSON.stringify("en")); let language = JSON.parse(loca ...

There is an issue with the type candidate in the Notion API, resulting in

In this instance, the troublesome code causing issues is displayed below: import {Client, LogLevel} from "@notionhq/client"; const notion = new Client({ auth: process.env.NOTION_TOKEN, logLevel: process.env.NODE_ENV !== 'product ...

Error TS2532 in TypeScript indicates that there is a possibility that the object is undefined

Yesterday, WebStorm 2020.1 was installed on my computer. All of a sudden, I started encountering numerous TS2532 errors. How is it even possible for this to be "undefined"? Doesn't selectedOwner && prevent that? I attempted to eliminate thi ...

TypeScript test framework for testing API calls in VS Code extensions

My VS Code extension in TypeScript uses the Axios library for API calls. I have written tests using the Mocha framework, which are run locally and through Github Actions. Recently, I integrated code coverage reporting with `c8` and I am looking to enhanc ...

Troubleshooting Angular 6: Issues with Route Guards not functioning as expected

Striving to enhance frontend security by restricting access to specific IDs. The goal is to redirect anyone trying to access routes other than /login/:id to a page-not-found error message if not already logged in, but encountering some issues. Below are t ...

Struggling with establishing connection logic between two database tables using Prisma and JavaScript

I'm facing a perplexing logic problem that is eluding my understanding. Within the context of using next-connect, I have a function designed to update an entry in the database: .put(async (req, res) => { const data = req.body; const { dob ...

What practical applications exist for preserving JSX post-transpilation of a tsx file?

While troubleshooting another issue, I decided to delve into Typescript's documentation on JSX usage. I discovered that some options involve converting JSX while others do not. I am puzzled as to why one would need to preserve JSX in transpiled file ...

Using React JS to Sort an Array Based on a Specific String

Here I am again, this time dealing with reactjs. I have a json object containing two "rows", labeled as Description and ubication. My goal is to filter the array based on the Description field. How can I achieve this? The description is in text format, f ...

Utilizing the Next.js "Link" Element as a Personalized React Component Using Typescript

When attempting to utilize the "Link" element as a custom react component that I modified with typescript to enhance its functionality, I encountered a recurring issue in my project. Each time I used it, I had to include a property named props which contai ...

Using lambda expressions to sort through an array of objects in React

My goal is to create a delete button that removes items from a list and updates the state variable accordingly. public OnDeleteClick = (): void => { const selectionCount = this._selection.getSelectedCount(); let newArray = this.state.items; for ...

What is the best way to utilize a React type as props for a custom component?

To make my component work properly, I am required to pass the inputmode as props from an external source. The acceptable values for <input inputMode=<valid values>> are outlined in the React types (node_modules@types\react\index.d.ts) ...

retrieve a shared string from an array when toggled

Regarding the use of SelectionModel for mat-checkbox, a function is called on each click: toggleSelection(row) { this.selection.toggle(row); console.log("Selection"); console.log("this", this.selection.selected); this.selection.selected.f ...

Obtain a value that is not defined

Good day, I am encountering an issue with my data not accepting an undefined value. Below is the code snippet: interface IModalContatos { dados: IContatos; onSave(dados: IContatos): void; onClose(): void; } When passing this data to my modal, I rece ...