TypeScript: Determining the data type of a field by referencing another field

Hypothetically, imagine having an interface structured in the following manner:

interface Cat {
  weight: number;
  name: string;
  adoptable: boolean;
}

The objective is to create a separate interface that serves the purpose of setting values on Cat:

interface CatUpdate {
  field: keyof Cat;
  value: ???
}

It is critical for TypeScript to validate that the value corresponds to the appropriate type based on the specified field. For instance, the following scenario should be deemed incorrect:

const x: CatUpdate = {
  field: "name",
  value: 12
}

The query arises on how to specifically define the type of CatUpdate.value.

Answer №1

My suggestion would be to redefine CatUpdate as a union type rather than an interface, shown below:

interface Cat {
  weight: number;
  name: string;
  adoptable: boolean;
}

type PropUpdate<T extends object> = {
  [K in keyof T]: { field: K; value: T[K] }
}[keyof T];


type CatUpdate = PropUpdate<Cat>;
/* 
type CatUpdate = {
    field: "weight";
    value: number;
} | {
    field: "name";
    value: string;
} | {
    field: "adoptable";
    value: boolean;
}
*/

This should be suitable for the specified use case:

const x: CatUpdate = {
  field: "name",
  value: 12
} // error!

You can also combine these updates (CatUpdate | DogUpdate) if necessary, although it's important to consider whether such composition is appropriate based on your scenario (Can Cat and Dog be distinguished from each other? That is, is Cat & Dog impossible? If Cat & Dog is possible, then CatUpdate | DogUpdate can only update objects that are both Cat & Dog. If it's not possible, then consider using a discriminated union instead like

(CatUpdate & {kind: "CatUpdate"}) | (DogUpdate & {kind: "DogUpdate"})
.)

Link to code

Answer №2

One option is to utilize a generic type:

interface CatUpdate<T extends keyof Cat> {
  field: T;
  value: Cat[T];
}

This approach works, but it does require a bit more verbosity when creating the object.

// Issue
const x: CatUpdate<"name"> = {
  field: "name",
  value: 12
}

// No problem
const y: CatUpdate<"name"> = {
  field: "name",
  value: "Dave"
}

An alternative solution could involve using a function for instantiation that can deduce the type based on the inputs:

function getCatUpdate<T extends keyof Cat>(field: T, value: Cat[T]): CatUpdate<T> {
    return {
        field,
        value
    }
}

You could then call this function like so:

// Issue
const x = getCatUpdate("name", 12);

// No problem
const y = getCatUpdate("name", "Dave");

Answer №3

Regrettably, TypeScript doesn't offer the specific feature you seek; however, you can manually define the types as follows:

type CatUpdate 
    = { field: 'weight', value: number }
    | { field: 'name', value: string }
    | { field: 'adoptable', value: boolean }

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 process for automatically including type declarations for a library in the tsconfig.json file in TypeScript 2.0?

TypeScript 2.0 introduces a new method of accessing type declarations through npm packages within the @types scope. npm i --save-dev @types/lodash As mentioned in this answer, TypeScript can be directed to search for type declaration files by including a ...

Validate if the program is currently running as "ionic serve" before implementing a conditional statement

Is there a method to determine if the ionic serve CLI is currently active (indicating it's not running on a physical device) within the code and use it as a condition? My problem: I have a Cordova plugin that returns a response to Cordova. When usin ...

What is the significance of the initial "this" parameter in Typescript?

I came across this method signature: export function retry<T>(this: Observable<T>, count: number = -1): Observable<T> { return higherOrder(count)(this) as Observable<T>; } The first parameter is this and it is typed as Observabl ...

Issue with Angular 4: Mega menu does not automatically close when a menu item is selected from within it

I am currently working on an Angular 4 project that includes a mega menu. My issue is that when I click on a menu within the mega menu, I want it to close. However, in my current setup, the menu always remains open even after clicking on a specific option. ...

An error is encountered with the 'this' keyword in the map operator of Angular

I am in the process of developing a custom validator for a Slug form control and have encountered an issue with my code. ngOnInit() { this.slugCtrl.setAsyncValidators(this.slugValidator); } slugValidator(control: AbstractControl) { const obs1 = c ...

Guide to implementing lazy loading and sorting in p-Table with Angular2

I recently implemented lazy loading in my application and now I am having trouble with sorting items. When lazy loading is disabled, the sorting feature works perfectly fine. However, I need help to make both lazy loading and sorting work simultaneously. C ...

Angular: Implementing a Dark and Light Mode Toggle with Bootstrap 4

Looking for suggestions on the most effective way to incorporate dark mode and light mode into my bootstrap 4 (scss) angular application. Since the Angular cli compiles scss files, I'm not keen on the traditional method of using separate css files for ...

Using TypeScript to create an array of classes and instantiating objects

I have a similar class structure and need to create a set of properties. In C#, I can easily use a list or any collection type, but how can I achieve this in TypeScript? class Films implements IFilms { public Id: string; public get fId(): string { ...

Filtering function that works without specific knowledge of keys

I'm working on a JavaScript method to filter a list dynamically without knowing the specific key (s). I've made some progress, but I'm stuck and not sure how to proceed. The foreach loop I have isn't correct, but I used it for clarity. ...

Why is the "typeof" function important in TypeScript member typings?

The definitions in the Electron typescript include an interface named MainInterface. This interface includes familiar members like app and autoUpdater, as well as less familiar ones such as BrowserView, BrowserWindow, and ClientRequest. One specific quest ...

Is it possible to preserve the spoken "text" through Ionic Speech Recognition for future reference?

After successfully implementing Ionic Speech Recognition in my project using the Ionic framework documentation, I am now facing a challenge with saving the text or audio input using any form input method like ngmodel or formcontrol. My attempts to bind th ...

The type does not have a property named 'defaultProps'

I have a Typescript React class component structured like this: import React, { Component } from 'react'; interface Props { bar?: boolean; } const defaultProps: Partial<Props> = { bar: false, }; class Foo extends Component<Props& ...

The Angular Material date picker's keyboard input format is functional only in Chrome

I'm facing a challenge with the date picker component from Angular Material. When I try to manually type in a date like "2019.12.20" instead of selecting it, the input only works in Google Chrome. But when I tested it on Edge and Firefox, the date val ...

Loading a large quantity of items into state in React Context using Typescript

I am currently working on a React context where I need to bulk load items into the state rather than loading one item at a time using a reducer. The issue lies in the Provider initialization section of the code, specifically in handling the api fetch call ...

Combining rxjs events with Nestjs Saga CQRS

I am currently utilizing cqrs with nestjs Within my setup, I have a saga that essentially consists of rxjs implementations @Saga() updateEvent = (events$: Observable<any>): Observable<ICommand> => { return events$.pipe( ofType(Upd ...

Tips for passing certain optional parameters while excluding others without resorting to undefined or empty values like ""

Is there a way to invoke the function while omitting certain optional parameters without resorting to undefined and empty strings? import { MouseEvent } from "react"; import { DialogType } from "editor-constants"; export interface Dial ...

Explore the functionalities of RxJS filter operators by applying them to a stream of arrays

Is it possible to work with a stream of arrays, such as filtering, and then emit the arrays again without concatenating the elements of the arrays or using regular array operators? For example, if I have an observable containing a single array, I can perfo ...

Encountering a dependency tree error while attempting to install generic-ui with npm

While attempting to add generic-ui to my Angular project using the command: npm i @generic-ui/ngx-grid @generic-ui/fabric @generic-ui/hermes I encountered the following error : $ npm i @generic-ui/ngx-grid @generic-ui/fabric @generic-ui/hermes npm ERR! co ...

There was a DOMException in Angular because the transaction is not active when trying to execute 'getAll' on 'IDBObjectStore'

private get ctxMessage() { const messageTransaction = this.db.transaction('messages', 'readwrite'); const messageStore = messageTransaction.objectStore('messages'); return { messageTransaction, messageStore }; } ...

Issue with change detection in Angular array authentication

I am currently working with a parent / child relationship consisting of two components: "Properties" - the parent, and "listingsList" - the child. Below is the parent template: <div class="row"> <div class="col-9 p-2"> ...