TypeScript interface that contains a key referencing itself

My goal is to update an object with a specific interface programmatically. I have defined the interface as follows:

interface ITypes {
  num: number;
  str: string;
  bol: boolean;
};

const obj: Partial<ITypes> = {}; // This is the object I want to update programmatically

I am looking for a way to specify a key and a val in order to update my obj. The key should be limited to one of the keys from the ITypes interface, and the value should match the type specified at the selected key from the interface. Initially, I achieved this by using the following code:

const key: keyof ITypes = "num"; // The key must be from the ITypes interface
const val: ITypes[typeof key] = 1; // The val must match the type specified at 'num' - 'number'
obj[key] = val;

Although the above method works, I now aim to define both the key and val within an object, which requires me to create an interface. However, I encountered challenges with defining the type for val. Here's what I've attempted so far:

interface IUpdatePayload {
  key: keyof ITypes;
  val: ITypes[typeof this.key]; // The error 'Type 'any' cannot be used as an index type.' occurs here
};
const updatePayload: IUpdatePayload = {key: "num", val: "1"}; // It should flag an error that 'val' is not a number
obj[updatePayload.key] = updatePayload.val; 

I tried referencing the interface's key type using typeof this.key, as suggested in this answer, but it resulted in the error

Type 'any' cannot be used as an index type.
. Perhaps this occurred because the key was not yet defined with a value like in the initial working example using variables. My question is, is there a solution to enable val to adopt the type determined by key as outlined in the ITypes interface?

Answer №1

To generate a union of key/value pairings from any type, you can utilize a mapped type:

type KVPairs<T, K extends keyof T = keyof T> = 
    K extends keyof T
        ? { key: K, val: T[K] } 
        : never;

You can then define your custom union like this:

// {key: "num", val: number} | {key: "str", val: string} | {key: "bol", val: boolean}
type IUpdatePayload = KVPairs<ITypes>;

This type also offers the flexibility to choose a subset of keys if needed:

// {key: "num", val: number} | {key: "str", val: string}
type NumOrStr = KVPairs<ITypes, "num"|"str">

However, it's important to note that error messages may not always be straightforward when assigning object literals to the union type:

const obj: IUpdatePayload = { key: "num", val: "1" };
/*
Type '{ key: "num"; val: string; }' is not assignable to type 'IUpdatePayload'.
  Type '{ key: "num"; val: string; }' is not assignable to type '{ key: "bol"; val: boolean; }'.
    Types of property 'key' are incompatible.
      Type '"num"' is not assignable to type '"bol"'.
*/

playground

Answer №2

If you want to simplify the process, consider utilizing a generic interface:

interface IUpdatePayload<K extends keyof ITypes> {
  key: K;
  val: ITypes[K];
}

const updatePayload: IUpdatePayload<"num"> = {
  key: "num",
  val: "1"
//~~~ Type 'string' is not assignable to type 'number'.
};
obj[updatePayload.key] = updatePayload.val; 

An issue with this approach is that you need to mention the generic argument "num" (the property being updated). To tackle this, you can employ a helper function that infers this argument for you:

const updateObj = <K extends keyof ITypes>(updatePayload: IUpdatePayload<K>): void => {
  obj[updatePayload.key] = updatePayload.val; 
}
updateObj({key: "num", val: "1"}) // error
updateObj({key: "num", val: 1}) // ok

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

In Angular, any newly added item is not deletable until the page is refreshed

I am currently developing a project in Angular 12 and utilizing JSON-server to store the data. The CRUD operations are functioning as expected. However, I have encountered an issue where after adding a new item through the form, the delete and update butt ...

Updating reactive form fields with setValue or patchValue does not result in the fields being refreshed

This is a simplified version of my code snippet: ngOnInit() { //initialize form fields this.form = this.builder.group({ name: '', age: '', location: '', }); //Calling the service this. ...

Retrieve the Directive function from the Controller

Struggling with AngularJs and Typescript while developing an app. I'm encountering issues accessing a method in a directive from the controller class. Added 'control:=,' to the directive's scope but it's resulting in an error. The ...

What is the significance of the code statement in the Angular ng2-table package?

Could you please explain the functionality of this specific code line? this.rows = page && config.paging ? this.changePage(page, sortedData) : sortedData; ...

What is the process for downloading a .docx file encoded in Base64?

Trying to download a .docx file received from the backend. The object being received is shown below: https://i.sstatic.net/nHKpn.png Download attempt using the following code: const blob = new Blob([fileSource.FileData], { type: fileSource.FileType }); ...

Is it possible to use Firebase auth.user in order to retrieve the signed-in user directly?

As I develop a webapp with NextJS v13.4 and firebase as my backend using the firebase web modular api, I came across a statement in the documentation: "The recommended way to get the current user is by setting an observer on the Auth object." ...

Encountered difficulty locating the module path 'stream/promises'

When importing the following in a typescript nodejs app import { pipeline } from "stream/promises"; Visual Studio Code (vscode) / eslint is showing an error message Unable to resolve path to module 'stream/promises' https://i.sstatic. ...

Token authentication in Angular 4

I need to retrieve data from a URL after posting the username and password. However, I encounter an error when trying to get the token using the GET method. The error message is: : Response for preflight has invalid HTTP status code 405. @Component({ ...

Jest is unable to handle ESM local imports during resolution

I am encountering an issue with my Typescript project that contains two files, a.ts and b.ts. In a.ts, I have imported b.ts using the following syntax: import * from "./b.js" While this setup works smoothly with Typescript, Jest (using ts-jest) ...

What could be causing this TypeError to appear in my Angular unit testing?

this.columnDefs.forEach((columnDef) => { columnDef.floatingFilter = this.hasFloatingFilter; }); this.gridApi.setColumnDefs(this.columnDefs); ERROR: 'ERROR', TypeError: this.gridApi.setColumnDefs is not a function TypeError: this.gridApi.set ...

When attempting to deploy my app, I encountered a CORS error with Nest.js

Currently, I am in the process of building a Nest.js - React.js application. However, I am encountering a cors error despite having cors enabled in my main.ts file of Nest.js. While my application functions smoothly on localhost and an IP address in produ ...

Error in Typescript/React: Unable to access the property 'MaxEmailLength' as it is undefined

I am facing an unusual problem with TypeScript. I have two static classes that are mutually referencing each other and causing issues. Class ValidationHelper (single file) import { ValidationErrors } from '../dictionary/ValidationErrors'; ex ...

Executing an R script from C code

Is it possible to integrate an R script into C code? I have come across the R API for C (Chapter 6 of the 'Writing R Extensions' manual), but my understanding is that it only allows for calling the C implementation of R. While I could execute th ...

The Authorization Header sent by Angular is always null

I've been attempting to send a request with an authorization header using Angular to communicate with a Spring backend. export class TokenInterceptor implements HttpInterceptor{ constructor(public sharedService : SharedService){} intercept(r ...

Modeling with Nested Objects in TypeScript and Angular

ERROR: Unable to access 'applicants' property of undefined object When my component initializes, I am attempting to execute the following code: application: application; ngOnInit(): void { this.application.applicants[0].type = "1"; <-- T ...

Assign a default value to empty elements in an array

Here is an example of fetching data in an array: [ { "Id": 111, "Name": "Test 1", "Email": "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="8de8e0ece4e1bccde9e2e0ece4e3a3e3e8f9">[email protect ...

React-router-dom PrivateRoute component version 6.8.0

I have created a custom PrivateRoute for my chat app. This is how my PrivateRoute looks: import { useState, useEffect } from 'react'; import { Route, Navigate } from 'react-router-dom'; import axios from 'axios'; interface Pr ...

Stop any ongoing search requests in Angular 7 using Ng2SmartTable

My current setup involves Angular version 7.0.1 and ng2-smart-table version 1.4.0. The issue I'm facing is that each search within the table triggers a new API request to fetch data. What I actually want is for only the latest search request to be pro ...

Sparks of brilliance illuminate the syntax of Typescript

I've been experimenting with using Spark in conjunction with TypeScript, and I've run into an issue. When I include multiple lines of code like this: Spark.get("/facture", (req, res) => { chalk.red('Hello test'); chalk.gree ...

Utilizing asynchronous operations dependent on the status of a separate entity

Dealing with asynchronous operations in Vue has been a challenge for me. Coming from a C# background, I find the async-await pattern more intuitive than in JavaScript or TypeScript, especially when working with Vue. I have two components set up without us ...