Guide on creating a Typescript function that exchanges the values of two properties in an object using their names, while ensuring type compatibility

I am attempting to create a function that can switch the values of two properties on an object based on their names. I want the compiler to enforce type compatibility, ensuring that both properties have the same data type:

function swap<T, TKey1 extends keyof T, TKey2 extends keyof T>(obj: T, key1: TKey1, key2: TKey2): void{
        let temp = obj[key1];
        obj[key1] = obj[key2]; 
        obj[key2] = temp;
    }

    let obj = {
        a: 1,
        b: 2,
        c: ""
    }

    swap(obj, "a", "b");    // works fine since both are numbers
    swap(obj, "a", "c");    // should not compile as it's swapping a number with a string
    

I managed to achieve some results with the following approach, but it necessitates passing 'obj' twice:

function swap<T,
        TKey1 extends keyof T,
        TKey2 extends keyof T,
        TIn extends { [p in TKey1|TKey2]: T[TKey1] } >(_:T, obj: TIn, key1: TKey1, key2: TKey2): void{
        let temp = <any>obj[key1];
        obj[key1] = <any>obj[key2]; 
        obj[key2] = temp;
    }

    let obj = {
        a: 1,
        b: 2,
        c: ""
    }

    swap(obj, obj, "a", "b");    // works fine since both are numbers
    swap(obj, obj, "a", "c");    // produces an error, which is expected
    

Alternatively, by leveraging conditional types and returning a function, I can accomplish the desired result. However, there is a risk of forgetting to make the second call:

function swap<T,
        TKey1 extends keyof T,
        TKey2 extends keyof T>(obj: T, key1: TKey1, key2: TKey2):
                                        T[TKey1] extends T[TKey2] ? T[TKey2] extends T[TKey1] 
                                            ? () => void
                                            : never : never {

        return <any>(() => {
            let temp = <any>obj[key1];
            obj[key1] = <any>obj[key2];
            obj[key2] = temp;
        });
    }

    let obj = {
        a: 1,
        b: 2,
        c: ""
    }

    swap(obj, "a", "b")();    // good, both are numbers
    swap(obj, "a", "c")();    // error, as expected
    

If it's possible, how could I simplify the above examples? Is there a way to provide a specific type instead of 'never' that would flag an error to the type system?

P.S. I am aware of the alternative method using '[obj.a, obj.b] = [obj.b, obj.a];', but I prefer to explore other options.

Answer №1

The solution was to apply advanced type filtering on the second key.

For the source code, visit: https://github.com/IKoshelev/ts-typing-util/blob/master/src/Swap.ts

To install via NPM, use npm i ts-typing-util

export type SwappableKeys<T, TKey1 extends keyof T> = Exclude<{
    [key in keyof T]:
    /**/ T[key] extends T[TKey1]
  ...
/**
 * Swap prop values with a check that values have compatible type
 * Example usage in function swap()
 */


Playground Example

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

Generating duplicate IDs in ngForOf loop in Angular

My current setup uses ngForOf to display dates, with each date having an id property which is basically its index + 1. These dates are stored as objects in an array and each date is represented by a component instance. The issue I am facing with ngForOf i ...

Display a JSX string in a React component

I have explored numerous similar questions but haven't found a definitive answer to my query. My dilemma revolves around rendering a JSX template within a string that represents a link, into the binding of a Text component. Here is an excerpt for bet ...

New Entry failing to appear in table after new record is inserted in CRUD Angular application

Working with Angular 13, I developed a basic CRUD application for managing employee data. Upon submitting new information, the createEmployee() service is executed and the data is displayed in the console. However, sometimes the newly created entry does no ...

Using TypeScript to define attributes by merging specified attribute names with variable attribute names

Can a TypeScript type/interface be created with the specified structure below? interface Model { id: number; something: string; somethingElse: Date; [key: string]: string | null; } It essentially consists of both defined attributes and 0 to n und ...

Having trouble getting Tailwind CSS utility classes to work with TypeScript in create-react-app

I have been struggling to troubleshoot this issue. I developed a React application with TypeScript and integrated Tailwind CSS following the React setup guidelines provided on the official Tailwind website here. Although my code and configuration run succ ...

Exploring the functionality of the WHERE function in Firebase with Angular

Current Objective: Our main focus here is to allow users to post within their designated Organization Group. These posts should remain exclusively visible within the specific Organization Group they are posted in. To achieve this, I have attempted to impl ...

How is it possible to receive a TRUE value when the API returns an error message indicating that the requested photo does not exist?

I am currently in the process of learning Angular and Typescript, but I am facing some challenges. I am working on an application that involves displaying a list of photos, as well as allowing users to create, edit, and delete existing photos. However, whe ...

Error: The specified 'supportedValuesOf' property is not present in the 'Intl' type

My current date-time library is dayJS and I am looking to display a comprehensive list of all available time zones. I attempted to utilize the Intl object for this purpose: Intl.supportedValuesOf("timeZone") However, I encountered a typescr ...

Error: The function this.form._updateTreeValidity does not exist

Currently utilizing Angular Forms version 2.0.0, I am in the process of creating a contact us modal that contains a contact form. Upon loading the ContactComponent, an exception is thrown: EXCEPTION: this.form._updateTreeValidity is not a function htt ...

Creating reusable components in Vue.js can enhance code reusability and make development

I am new to Vue.js and WebUI development, so I apologize in advance if I make any mistakes. Currently, I am exploring how to create reusable components using Vue.js. Specifically, I am working on a treeview component with the ability to customize the rend ...

Having trouble with Angular's ActivatedRoute and paramMap.get('id')?

Currently, I am attempting to retrieve information from my server using the object's ID. The ID can be found in the URL as well: http://xyz/detail/5ee8cb8398e9a44d0df65455 In order to achieve this, I have implemented the following code in xyz.compo ...

Tips on personalizing the FirebaseUI- Web theme

Can someone help me find a way to customize the logo and colors in this code snippet? I've only come across solutions for Android so far. if (process.browser) { const firebaseui = require('firebaseui') console.log(firebaseui) ...

Transforming a singular function into a versatile, reusable function with the help of Typescript

In my component, I have a function dedicated to removing duplicate values from an array. const removeDuplicateScenarios = (scenariosList: Scenario[]): Scenario[] => { return _.filter(scenariosList, el => _.filter(scenariosList, e => e.id === el ...

Accepting both array and non-array values in the setter function helps to address the issue of accommodating various

In my Angular component, I have an input that typically works with an array of strings: @Input() names: string[] <my-comp [names]="['Adam', 'Betty']"></my-comp> However, I would like to offer an alternative syntax where t ...

Typescript is unable to detect the buttons

Currently, I am working on a "simple" program in TypeScript and HTML. My comments are in my native language but they are not providing much information. Despite trying various approaches, I can't seem to get my buttons to function properly. I am awar ...

Troubleshooting issue with the spread operator and setState in React, Typescript, and Material-ui

I recently developed a custom Snackbar component in React with Material-ui and Typescript. While working on it, I encountered some confusion regarding the usage of spread operators and await functions. (Example available here: https://codesandbox.io/s/gift ...

Limitation of descendant elements

I'm working on a Modal function component that requires three child function components: Header, Body, and Footer. I want to restrict the Modal component to only accept elements of type Header | Body | Footer as its top-level child elements. <Modal ...

Displaying related objects information from a single object in AngularFire2 can be achieved by following these steps

As a newcomer to Angular and Firebase, I apologize if my question seems basic. I'm seeking guidance on how to display related object information from one object in angularfire2. Specifically, I want to show the role names assigned to a user. Here is ...

Using JavaScript to round up the number

I need help rounding numbers in a specific way: Value Expected 0,523% 1% 2,235% 2,5% -0,081% -0,5% -1,081% -1,5% How can I achieve this using JavaScript? Possible Solution: static round (num) { const abs = Math.abs(num); const sign = num ...

Upgrade to Typescript version 3.2 and exploring the Response body within lib.dom.d.ts

Just recently upgraded to Angular 7 and Typescript 3.2.2, and now one of my Jasmine spec tests is throwing an error. httpMock.expectOne({method: 'PUT'}).flush(new Response({status: 200})); The error message reads: Argument '{ status: ...