Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript: allow a generic type to only be an object with 'string' properties

Tags:

typescript

I am wondering if it is possible in TS to enforce the type of the properties for a generic. I would like to only allow passing a generic of an object type with 'string' properties. Raise an error if for instance the passed generic interface would contain number or symbols properties.

Here is a POC of what I tried and commented the behaviour I am looking for:

class Test<T extends {[key: string]: any}>{
    private data: T;

    public getValue<K extends keyof T>(key: K): T[K] {
        return this.data[key];
     }
}

// the property is a string = ok
const okay = new Test<{ "aString": string }>();

// the property is a number = should raise an error
const shouldFail = new Test<{ 0: string }>();
like image 462
Flavien Volken Avatar asked Oct 05 '18 06:10

Flavien Volken


People also ask

How do you define a generic object type in TypeScript?

To specify generic object type in TypeScript, we can use the Record type. const myObj: Record<string, any> = { //... }; to set myObj to the Record type with string keys and any type for the property values.

What are some properties of generics in TypeScript?

TypeScript Generics is a tool which provides a way to create reusable components. It creates a component that can work with a variety of data types rather than a single data type. It allows users to consume these components and use their own types.

How do you pass a generic type as parameter 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.

Is there an object type in TypeScript?

In TypeScript, object is the type of all non-primitive values (primitive values are undefined , null , booleans, numbers, bigints, strings). With this type, we can't access any properties of a value.


1 Answers

If the object has a string index we can index the object by number as well, so there is no reason for the compiler to throw an error number keys. This is by design.

declare let skeys: { [key: string]: number }
let v1 = skeys[0] // number 
let v2 = skeys["0"] // number

declare let nkeys: { [key: number]: number }
let v3 = nkeys[0] // number 
let v4 = nkeys["0"] // error 


declare let snkeys: {
    [key: number]: number;
    [key: string]: string | number // The string index has to contain any value reuned by the number index
}
let v5 = snkeys[0] // number 
let v6 = snkeys["0"] // string| number 

We can use a conditional type to force an error if the object contains any non-string keys. The error will not be very pretty, but it is readable and can get the job done:

class Test<T extends { [key: string]: any } & (keyof T extends string ? {} : "T must obnly have string keys") >{
    private data!: T;

    public getValue<K extends keyof T>(key: K): T[K] {
        return this.data[key];
    }
}

// the property is a string = ok
const okay = new Test<{ "aString": string }>();
// Error: Type '{ 0: string; }' does not satisfy the constraint '{ [key: string]: any; } & "T must only have string keys"'.
const shouldFail = new Test<{ 0: string }>();

Note

If you don't have any other constraints on the values of T a simple object type would work as well

class Test<T extends object & (keyof T extends string ? {} : "T must only have string keys") >{ }
like image 124
Titian Cernicova-Dragomir Avatar answered Oct 20 '22 20:10

Titian Cernicova-Dragomir