Tips for utilizing TypeScript to automatically infer that a property within one type is of the same type

Looking to sort an array of items, each with properties of either string or number type, without mixing types for each property:

type Item = {
  prop1: string;
  prop2: number;
  prop3: string;
  prop4: number;
}

To keep it abstract, I am using propertyName: keyof Item to specify which property to sort by. In my sorting function, I access property values like this:

items.sort((item1, item2) => {
  const value1 = item1[propertyName];
  const value2 = item2[propertyName];
...

both value1 and value2 can be of type string | number, but I need to differentiate between sorting by string and number properties. TypeScript does not automatically infer that the same property for any item is of the same type, leading to the need for type checking logic. I am exploring the most elegant way to perform type checks based on the property types in the Item itself rather than the actual values, like "if the type of property (propertyName) in Item is 'string' then ..."

Any suggestions on how to achieve this would be greatly appreciated. Thank you!

Answer №1

Exploring the concept you're working on, check out this interactive demo here


type Selection<T, U> = {
  [K in keyof T]: T[K] extends U ? K : never
}[keyof T];

type Data = {
  name: string;
  age: number;
  city: string;
  salary: number;
};

type StringFields = Selection<Data, string>;
// StringFields: { name: string; city: string; }

type NumberFields = Selection<Data, number>;
// NumberFields: { age: number; salary: number; }

const dataList: Data[] = [
    {
        name: "John Doe",
        age: 30,
        city: "New York",
        salary: 60000
    },
    {
        name: "Jane Smith",
        age: 25,
        city: "Los Angeles",
        salary: 75000
    },
    {
        name: "Alice Johnson",
        age: 35,
        city: "Chicago",
        salary: 80000
    }
];

const fieldName1: keyof Data = "name";
const fieldName2: keyof Data = "age";

let field: keyof Data;
let stringFields: StringFields;
let numberFields: NumberFields;

dataList.sort((item1, item2) => {

    const value1Name = item1[fieldName1]; 
    const value2Name = item2[fieldName1]; 

    const value1Age = item1[fieldName2]; 
    const value2Age = item2[fieldName2]; 

    const stringValue1 = item1[stringFields];
    const stringValue2 = item2[stringFields];

    const numberValue1 = item1[numberFields];
    const numberValue2 = item2[numberFields];

    const genericValue = item1[field]

    switch (field) {
        case "name":
            const name1 = item1[field]; 
            const name2 = item2[field]; 
            break;

        case "age":
            const age1 = item1[field]; 
            const age2 = item2[field]; 
            break;

        case "city":
            const city1 = item1[field]; 
            const city2 = item2[field]; 
            break;

        case "salary":
            const salary1 = item1[field]; 
            const salary2 = item2[field]; 
            break;

        default:
            break;
    }

    return 1;
});

If the field name is not known at compile time, runtime type narrowing is necessary to properly handle the types.

Answer №2

Introducing a handy util function that fetches the value based on a specific property. Check it out:

function getProperty<T, K extends keyof T>(item: T, property: K): T[K] {
    return item[property];
}

Now, in the sorting algorithm, you can utilize the getProperty function to retrieve values for sorting:

items.sort((item1, item2) => {
    const key1 = 'key1';
    const key2 = 'key2';

    const value1 = getProperty(item1, key1);
    //    ^? string

    const value2 = getProperty(item2, key2);
    //    ^? number

    return -1;
});

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

The @HostListener in Angular2 does not function correctly within a component that is inherited from another component

My Angular2-based client-side application has a base class: abstract class BaseClass { @HostListener('window:beforeunload') beforeUnloadHandler() { console.log('bla'); } } and two similar derived classes: @Component({ ...

What is preventing React CLI from installing the template as TypeScript?

When I run npm init react-app new-app --template typescript, it only generates a Javascript template project instead of a Typescript one. How can I create a Typescript project using the CLI? Current Node JS version: 15.9.0 NPM version: 7.0.15 ...

Issue with modal-embedded React text input not functioning properly

I have designed a custom modal that displays a child element function MyModal({ children, setShow, }: { children: JSX.Element; setShow: (data: boolean) => void; }) { return ( <div className="absolute top-0 w-full h-screen fle ...

Ensure your TypeScript class includes functionality to throw an error if the constructor parameter is passed as undefined

My class has multiple parameters, and a simplified version is displayed below: class data { ID: string; desp: string; constructor(con_ID:string,con_desp:string){ this.ID = con_ID; this.desp = con_desp; } } When I retrieve ...

A long error occurred while using the payloadaction feature of the Redux Toolkit

import { createSlice, PayloadAction, createAsyncThunk } from "@reduxjs/toolkit" import axios, { AxiosError} from "axios" type user = { id: number, token: string } export type error = { error: string } interface authState { user: user | ...

Creating a multipart/form-data POST request in Angular2 and performing validation on the input type File

I am working on sending an image (base64) via a POST request and waiting for the response. The POST request should have a Content-Type of multipart/form-data, and the image itself should be of type image/jpg. This is what the POST request should look like ...

Generate dynamic property values based on calculations

I am facing a challenge with a form that I have designed. Could you guide me on how to dynamically update the value of the calculate field (contingency) whenever the user modifies the values of budget1 and budget2? I have attempted several approaches witho ...

Obtain an array containing only one property value from an array of objects

Within my array of objects, I am looking to extract all the controls and move them to a new array: this.formModel = { sections: [ { title: 'Section 01', controls: [ new FormControlInput({ ...

Issues with command functionality within the VS Code integrated terminal (Bash) causing disruptions

When using Visual Studio Code's integrated terminal with bash as the shell, I have noticed that commands like ng and tsc are not recognized. Can anyone shed some light on why this might be happening? ...

Encountering [Object] error in Angular's ngbTypeahead functionality

Here is the code for my input field in component.html: <input type="text" class="form-control" [(ngModel)]="searchQuery" [ngbTypeahead]="recommends" name="searchQuery" typeaheadOptionField="user ...

Only one choice for discriminated unions in react props

Looking to create a typescript type for react component props, specifically a basic button that can accept either an icon prop or a text prop, but not both. My initial attempt with a discriminated union didn't quite produce the desired outcome: inter ...

Link the chosen selection from a dropdown menu to a TypeScript object in Angular 2

I have a form that allows users to create Todo's. An ITodo object includes the following properties: export interface ITodo { id: number; title: string; priority: ITodoPriority; } export interface ITodoPriority { id: number; name ...

Utilizing React and MobX to dynamically render components based on observable arrays

I'm currently facing a challenge with trying to map an array in order to display components for each entry. Here's my current approach: Here is the structure of my RankStore; const RankStore = observable({ loading: false, error: "", ra ...

The Sanity npm package encounters a type error during the build process

Recently, I encountered an issue with my Next.js blog using next-sanity. After updating all npm packages, I found that running npm run build resulted in a type error within one of the dependencies: ./node_modules/@sanity/types/lib/dts/src/index.d.ts:756:3 ...

In Next.js, the switch button remains in the same state even after the page is refreshed

Is there a solution for this issue? I am currently developing a switch button for a configuration page. The problem arises when I toggle the switch from active to maintenance mode, save it successfully, but upon refreshing the page, the switch reverts back ...

Forwarding from a user interface element in Next.JS

I am currently working on a project utilizing Next.js 13, and I have encountered a situation where I need to invoke a client-side component from a server-side page. The specific component in question is the DeleteAddressAlertDialog which interacts with my ...

Exploring the benefits of leveraging TypeScript with AWS NodeJS for improved stacktrace visibility over traditional JavaScript

I'm contemplating the idea of transitioning my existing JavaScript codebase to incorporate TypeScript in NodeJS. One aspect that I am concerned about is being able to view the stack trace in AWS CloudWatch (request log) in case an error occurs during ...

Troubleshooting Angular Build Errors: Integrating Three.js

Upon setting up a new Angular application and integrating three along with @types/three, I proceeded to create a basic component. However, upon executing ng build --prod, the following errors are displayed: ERROR in node_modules/three/src/core/BufferAttri ...

TypeScript: "The type is generic and can only be accessed for reading." - Error code 2862

Consider this sample JS function that requires type annotations: const remap = (obj) => { const mapped = {}; Object.keys(obj).forEach((key) => { mapped[key] = !!key; }); return mapped; }; I am attempting to add types using generics (in ...

Determining data types through type guarding in Typescript

interface A = { name: string; ... }; interface B = { name: string; ... }; interface C = { key: string; ... }; type UnionOfTypes = A | B | C | ...; function hasName(item: UnionOfTypes) { if ("name" in item) { item; // typescript knows ...