Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to constrain a generic type to be a subset of keyof in TypeScript?

In the current version (2.1) of TypeScript I can constrain a method argument on a generic class to be a property of the generic type.

class Foo<TEntity extends {[key:string]:any}> {
    public bar<K extends keyof TEntity>(key:K, value:TEntity[K]) { }
}

Is it possible in the current type system to constrain the key part even further to be a subset where the value of the key is of a certain type?

What I'm looking for is something along the lines of this psuedo code.

class Foo<TEntity extends {[key:string]:any}> {
    public updateText<K extends keyof TEntity where TEntity[K] extends string>(key:K, value:any) {
        this.model[key] = this.convertToText(value);
    }
}

EDIT

For clarification I added a more complete example of what I'm trying to achieve.

type object = { [key: string]: any };

class Form<T extends object> {
    private values: Partial<T> = {} as T;

    protected convert<K extends keyof T>(key: K, input: any, converter: (value: any) => T[K])
    {
        this.values[key] = converter(input);
    }

    protected convertText<K extends keyof T>(key: K, input: any)
    {
        this.values[key] = this.convert(key, input, this.stringConverter);
    }

    private stringConverter(value: any): string
    {
        return String(value);
    }
}

Demo on typescriptlang.org

convertText will give an error saying that Type 'string' is not assignable to type 'T[K]'.

Given

interface Foo {
    s: string
    n: number
}

The compiler can tell that this will work

this.convert('s', 123, v => String(v));

and this will not

this.convert('n', 123, v => String(v));

I'm hoping I can constrain the convertText method to keys where the value is of type string to get type safety on the key parameter.

like image 229
opex Avatar asked Jan 17 '17 00:01

opex


People also ask

What does Keyof do in TypeScript?

keyof is a keyword in TypeScript which is used to extract the key type from an object type.

How do you pass a generic type in TypeScript?

Assigning Generic ParametersBy passing in the type with the <number> code, you are explicitly letting TypeScript know that you want the generic type parameter T of the identity function to be of type number . This will enforce the number type as the argument and the return value.

What is generic type in TypeScript?

Generics allow creating 'type variables' which can be used to create classes, functions & type aliases that don't need to explicitly define the types that they use. Generics makes it easier to write reusable code.

What is constraints in TypeScript?

Using type parameters in generic constraintsTypeScript allows you to declare a type parameter constrained by another type parameter. The following prop() function accepts an object and a property name. It returns the value of the property.


1 Answers

It is possible (using a non-class example here). The following will ensure that T[P] is a string.

function convertText<T extends {[key in P]: string }, P extends keyof T>(data: T, field: P & keyof T) {
    // ...
}

The idea is to narrow the type of T to only the fields inferred in P and set the exact type you want, in this case string.

Test:

let obj = { foo: 'lorem', bar: 2 };
convertText(obj, 'foo');
convertText(obj, 'bar'); // fails with: Type 'number' is not assignable to type 'string'.
like image 153
altschuler Avatar answered Sep 24 '22 05:09

altschuler