Willem van der Veen
Willem van der Veen

Reputation: 36650

Typescript, don't lose type information using generics

In the code below the getValue method on the DataBankService class is losing type information (see example). Is it possible keeping the type information?

https://stackblitz.com/edit/typescript-rx-playground-gqnzuq?file=index.ts

import { Subject } from "rxjs/";

export interface DataObject<T> {
    value: T;
    observable: Subject<T>;
}

export class DataBankService {
    private dataBank: DataBankProperties;

    constructor() {
        this.dataBank = new DataBankProperties();
    }

    public getValue<T extends keyof DataBankProperties, V>(property: T) {
        return this.dataBank[property].value;
    }
}

 class DataBankProperties {

    money: DataObject<number> = this.createDataObject();
    token: DataObject<string> = this.createDataObject();
    testament: DataObject<object> = this.createDataObject();
 

    private createDataObject<T>() {
        return {
            value: null,
            observable: new Subject<T>(),
        };
    }
}

const dataBank = new DataBankService();

// token is now type >> token: string | number | object
const token = dataBank.getValue('token');
// I would like this to be type string

// token is now type >> token: string | number | object
const money = dataBank.getValue('money');
// I would like this to be type number

Upvotes: 1

Views: 630

Answers (2)

Uriah Ahrak
Uriah Ahrak

Reputation: 31

I would suggest creating a type for inferring the generic type of DataObject:

type DataObjectTypeInference<T> = T extends DataObject<infer U> ? U : never;

And then use it like that:

public getValue<K extends keyof DataBankProperties>(property: K) {
    return this.dataBank[property].value as DataObjectTypeInference<
        DataBankProperties[T]
    >;
}

First, you define a type which looks after the generic type of DataObject of the current type instance. I personally prefer not to couple between property names and like the comment before me. I like it to be inferred by TypeScript, so if you decide to change value key to another name, it won't affect the type inference.

After that, you make a type assertion of the variable to be DataBankProperties[T] which gives you the type of DataObject and now you use the type we built and infer the generic type of DataObject.

Upvotes: 0

mbdavis
mbdavis

Reputation: 4020

You can do this with an assertion on the return type - using your generic as the key for DataBankProperties. I've renamed it K for convention (as in K for key)

public getValue<K extends keyof DataBankProperties>(property: K): DataBankProperties[K]['value'] {
   return this.dataBank[property].value;
}

enter image description here

enter image description here

Upvotes: 1

Related Questions