Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constraints on interface members in typescript generics

I have a method, that should accepts any object, as long as all its fields are strings or numbers

I made this, which works great with duck typing

static interpolateParams(
    route: string, 
    params: {[key: string] : string | number}) : string {

    const parts = route
        .split("/")
        .map(part => {
            const match = part.match(/:([a-zA-Z09]*)\??/);
            if (match) {
                if (!params[match[1]]) {
                    console.error("route argument was not provided", route, match[1]);
                    return part;
                }

                return params[match[1]];
            }
            else {
                return part;
            }
        })

    return "/" + parts.slice(1).join("/");
}

and call

interpolateParams("/api/:method/:value", {method: "abc", value: 10});

Now I want to make interpolateParams to accept any interface for route params.

interpolateParams<IABCParams>("/api/:method/:value", {method: "abc", value: 10});

Problem is that it still should match constraints for all fields being strings or numbers

Is there a way to specify generic constraint on all fields of given interface to be of certain type?

I tried that

static interpolateParams<T extends {[key: string] : string | number}>(
    route: string, 
    params: T) : string {

and obviously got this

Type 'IABCParams' does not satisfy the constraint '{ [key: string]: string | number; }'.

Index signature is missing in type 'IABCParams'.

Thanks

like image 530
Evgeny Avatar asked Oct 09 '18 22:10

Evgeny


People also ask

What are the constraints in generics?

The where clause in a generic definition specifies constraints on the types that are used as arguments for type parameters in a generic type, method, delegate, or local function. Constraints can specify interfaces, base classes, or require a generic type to be a reference, value, or unmanaged type.

What's the idea behind TypeScript constraints when talking about generics )?

Using type parameters in generic constraints TypeScript 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.

What does the generics constraint of type interface do?

Interface Type Constraint You can constrain the generic type by interface, thereby allowing only classes that implement that interface or classes that inherit from classes that implement the interface as the type parameter.

What TypeScript keyword is used to apply a constraint to a generic type parameter?

When we're using generics, we can use constraints to apply to our generic type parameters. To implement it, all we have to do is use the extends keyword. Before using it, you need to make sure of something — the extends keyword only works with interfaces and classes, otherwise, it will throw an error.

How to apply constraints on generic type parameters in typescript?

In TypeScript we can apply constraints on Generic type parameters (e.g. T) by using keyword extends (e.g. T extends Serializable). Passing anything other than Shape type to drawShape () function will cause compile time error:

What is a generic interface in typescript?

Summary: in this tutorial, you will learn how to develop TypeScript generic interfaces. Like classes, interfaces also can be generic. A generic interface has generic type parameter list in an angle brackets <> following the name of the interface:

What is a type constraint?

A type constraint is a "rule" that narrows down the possibilities of what a generic type could be. For example, in the the send definition above, we declared a type variable T that is not constrained at all. Which is why we were able to call send with values that aren't JSON serializeable. // This compiles ...

How to create a generic type parameter list for an interface?

A generic interface has generic type parameter list in an angle brackets <> following the name of the interface: interface interfaceName<T> { // ... } Code language: TypeScript (typescript) This make the type parameter T visible to all members of the interface. The type parameter list can have one or multiple types. For example:


2 Answers

T's constraint can refer to T (with some restrictions), so you can use a mapped type to generate a constraint that has the same fields as T:

function interpolateParams<T extends {[P in keyof T] : string | number}>(
    route: string, 
    params: T) : string { /*...*/ }

Beware that trickery with circular type parameter constraints can sometimes cause problems, though this scenario will likely be OK.

like image 197
Matt McCutchen Avatar answered Oct 17 '22 23:10

Matt McCutchen


Here is final version, thanks Matt for hint

static interpolateParams(
    route: string, 
    params: {[key: string] : string | number}) : string {

    const parts = route
        .split("/")
        .map(part => {
            const match = part.match(/:([a-zA-Z09]*)\??/);
            if (match) {
                if (!params[match[1]]) {
                    if (part.endsWith("?")) {
                        return null;
                    }

                    console.error("route argument was not provided", route, match[1]);
                    return part;
                }

                return params[match[1]];
            }
            else {
                return part;
            }
        }).filter(p => p && p != "");

    return "/" + parts.join("/");
}

static formatRoute<T extends {[P in keyof T] : string | number}>(
    route: string,
    params: T
) : string {
    return interpolateParams(route, params);
}
like image 40
Evgeny Avatar answered Oct 18 '22 00:10

Evgeny