Discovering the data type of interface properties in TypeScript

Consider the following interface along with an object that implements it:

interface IData {
    a: TypeA;
    b: TypeB;
    c: boolean;
    d: string;
}

const myObject: { data: IData } = {
    data: {
         a: valueA,
         b: valueB,
         c: true,
         d: "valueD"
    }
}

Now, I want to create a function that retrieves a single property from this object and automatically infers its return type:

function getProperty(obj: { data: IData }, field: keyof IData){
    return obj.data[field];
}

const myStringField = getProperty(myObject, "d"); // should be of type string
const myBoolField = getProperty(myObject, "c"); // should be of type boolean

How can we define the getProperty function so that it correctly infers the return types instead of TypeA | TypeB | boolean | string?


Here is another example involving more complex scenarios:

interface IValue<T> {
    value?: T;
}

interface IData2 {
    a?: IValue<string>;
    b?: IValue<number>;
    c?: IValue<boolean>;
    d?: IValue<string>;
}

function getPropertyValue<T extends keyof IData2>(field: T, data: IData2) {
    return data[field] ? data[field]!.value : undefined; // The '!' indicator is necessary for compilation
}

const sampleData: IData2 = {
    a: { value: 'a' },
    b: { value: 2 },
    c: { value: false },
};

const value1 = getPropertyValue('a', sampleData); // expecting type `string`
const value2 = getPropertyValue('b', sampleData); // expecting type `number`
const value3 = getPropertyValue('b', sampleData); // expecting type `boolean`
const value4 = getPropertyValue('b', sampleData); // expecting type `undefined`

Answer №1

To specify the exact literal value of field, you must inform the typesystem. One way to achieve this is by utilizing a generic function, such as the one below:

interface IMyData {
    c: boolean;
    d: string;
}

const myObj: { data: IMyData } = {
    data: {
         c: true,
         d: "someValueD"
    }
}

function getField<T extends keyof IMyData>(obj: { data: IMyData }, field: T){
    return obj.data[field];
}

const myFieldStr = getField(myObj, "c"); // expected type boolean
const myFieldBool = getField(myObj, "d"); // expected type string

In a more general context:

function pluck<T, K extends keyof T>(obj : T, key : K) {
    return obj[key];
}

const foo = pluck({ bar: "asd", baz: 5 }, "bar"); // infers string

Answer №2

To tackle the more complex scenario, one potential approach is to create a new type that can extract the specific type from the generic:

type ExtractedType<S> = S extends IMyValue<infer T> ? T : never;

You can then incorporate this type into your function like so:


function fetchValue<T extends keyof IMyData2>(field: T, data: IMyData2) {
    // previous solution: return data[field] ? data[field]!.value : undefined;
    return data[field]?.value as ExtractedType<IMyData2[T]>;
}

By implementing this change, the return values of the function will now be accurately typed:

const result1 = fetchValue('a', sampleData); // string | undefined
const result2 = fetchValue('b', sampleData); // number | undefined
const result3 = fetchValue('c', sampleData); // boolean | undefined
const result4 = fetchValue('d', sampleData); // string | undefined

While not the most elegant solution, it certainly gets the job done!

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

Transforming JavaScript to TypeScript in Angular: encountering error TS2683 stating that 'this' is implicitly of type 'any' due to lacking type annotation

While in the process of migrating my website to Angular, I encountered an error when attempting to compile the JS into TS for my navbar. After searching around, I found similar issues reported by other users, but their situations were not quite the same. ...

Error: The function visitor.visitUnaryOperatorExpr is not defined as a function

I recently started developing an Angular app with a purchased template and collaborating with another developer. Initially, I was able to successfully build the project for production using ng build --prod. However, when trying to build it again yesterday, ...

The library "vue-property-decorator" (v10.X) is causing issues with resolving in Webpack despite being successfully installed

Encountered an error message Module not found: Error: Can't resolve './decorators/Emit' while attempting to import functionality from the library vue-property-decorator. The package is installed and accessible, ruling out a simple installati ...

Utilizing useClass in Angular's APP_INITIALIZER

Before my application starts up, I require some API data and am currently handling it with APP_INITIALIZER and useFactory. However, I aim to enhance the structure by separating all the code from app.module.ts: app.module.ts import { NgModule} from '@ ...

The React Functional Component undergoes exponential re-renders when there is a change in the array

I'm encountering a problem with one of my functional components. Essentially, it maintains an array of messages in the state; when a new message is received from the server, the state should update by adding that new message to the array. The issue ar ...

Incorporate the teachings of removing the nullable object key when its value is anything but 'true'

When working with Angular, I have encountered a scenario where my interface includes a nullable boolean property. However, as a developer and maintainer of the system, I know that this property only serves a purpose when it is set to 'true'. Henc ...

What is the process for combining and compressing an Angular 2 application?

I am currently trying to concatenate and minify an angular2 application. My approach so far involved concatenating all my *.js files (boot.js, application.js then all components) into one file and injecting it into my index.html. I also removed the <s ...

In the world of three js, objects vanish from view as the camera shifts

I am attempting to display a d3 force graph in three.js using standard Line and BoxGeometry with a photo texture applied. When the force graph is updated, I call the draw function which is triggered by: controls.addEventListener('change', () =&g ...

Convert an interface type variable to a string representation

I'm just starting to learn angular and I'm attempting to convert my response from an interface type into a string so that I can store it in session storage. However, when I try to save them, they are being stored as [object] in the session storag ...

Issue: An object with keys {} is not suitable as a React child, causing an error

I am new to TypeScript and seeking help from the community. Currently, I am working on a to-do list project where I am using React and TypeScript together for the first time. However, I encountered an error that I cannot decipher. Any assistance would be g ...

Unable to increase value in interface field

I am working with an array of objects that represent interfaces. Once this array is created, each object needs to have a value incremented inside it. Specifically from 'x1' to 'x2'. After the iteration, all the 'x1' and &apo ...

Tips for Dealing with Empty Rows in Arrays

How can I remove rows from an array in Alasql where all key values are null? Here is the array data: [ 0:{Name:"ABC1",No:5,BalanceDue:5000,Notes1:null,Notes2:null,CurrencyId:"2",Date:"06/01/2018"} 1:{Name:"ABC2",No:6,BalanceDue:6000,Notes1:null,Notes2: ...

Testing in Cypress to determine if one date is half a year before another can be achieved using date manipulation functions and assertions

I'm currently working on testing an SVG graph with dynamic dates on the x-axis. For demonstration purposes, let's assume that the early date is 'Feb 22' and the later date is 'Aug 22'. While I can extract values from each ele ...

Angular - Automatically update array list once a new object is added

Currently, I'm exploring ways to automatically update the ngFor list when a new object is added to the array. Here's what I have so far: component.html export class HomePage implements OnInit { collections: Collection[]; public show = t ...

Check the TypeScript file for correct type declarations

I have customized the code inspired by this particular guide in order to showcase uploaded images within an angular application. public uploadFile = (files) => { if (files.length === 0) { return; } var mimeType = files[0].type; i ...

Installation and execution of TypeScript jQuery / Bootstrap definition file on a local machine using npm typings: A step-by-step guide

Struggling to set up TypeScript jQuery and Bootstrap definition files in my new project using npm typings. Below are the steps I followed: 1- Open cmd, navigate to my project folder, and enter the following commands: npm install typings --global typings ...

Exploring the Benefits of Angular 2 Beta Typings within Visual Studio (ASP.NET 4)

During my initial experiences with Angular 2.0 alpha versions, I utilized the files from DefinitelyTyped to incorporate typings for TypeScript in Visual Studio. The process was straightforward - simply adding the d.ts files to the project. However, as we t ...

I am in need of a customized 'container' template that will display MyComponent based on a specific condition known as 'externalCondition'. MyComponent includes the usage of a Form and formValidation functionalities

container.html <div ngIf="externalCondition"> <!--Initially this is false. Later became true --!> <my-component #MyComponentElem > </my-component> <button [disabled]= "!myComponentElemRef.myDetailsF ...

Dealing with the challenge of having multiple types in a single dynamic type within Typescript when using

I had the idea to use various types under a single 'dynamic' type, so I created: export type ICandidate = | ICandidatePlain | ICandidateTalented | ICandidateExperienced The reason for this is because objects in the candidates array may ha ...