0

I got this error:

Type '{ [key: string]: any; }' is not assignable to type 'T'.
  '{ [key: string]: any; }' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{ [key: string]: any; }'.(2322)

from this piece of code:

function getValue ():{[key: string]: any}  {
    return {key:'value'}
}

class Foo<T extends {[key: string]: any}> {
    public readonly data?: T

    constructor() {
        this.data = getValue()
    }
}

Does anyone know why and how to solve this error?

yaquawa
  • 6,690
  • 8
  • 35
  • 48
  • 1
    Does this answer your question? [could be instantiated with a different subtype of constraint 'object' - ts(2322)](https://stackoverflow.com/questions/56505560/could-be-instantiated-with-a-different-subtype-of-constraint-object-ts2322) –  Feb 19 '21 at 13:41
  • TLDR: What do you think the compiler should do if you pass `interface A { key: { sub: number } }` as the type parameter? This is exactly what it tells you: return type of `getValue` can differ from what `data` expects because there is *no direct relation* between `T` and `ReturnType`. – Oleg Valter is with Ukraine Feb 19 '21 at 14:42

2 Answers2

0

Are you looking to store a dictionary of items with type T? Then maybe this is what you want?:

function getValue ():{[key: string]: any}  {
    return {key:'value'}
}

class Foo<T> {
    public readonly data?: {[key: string]: T}

    constructor() {
        this.data = getValue()
    }
}
Andreas Turku
  • 438
  • 3
  • 9
0

The compiler complains that you did not establish a direct relationship between the return type of the getValue function and the data instance property. extends clause only guarantees that at a minimum the generic type parameter is assignable to the provided constraint, but does not bound it otherwise.

Moreover, your getValue function returns a constant of type { key : 'value' }. Therefore, when you assign the return type of a call to getValue to this.data, the compiler looks if the latter is a supertype of the former and sees that you only guaranteed data to be { [key: string]: any }, or, in plain English:

"some kind of object with any number of keys of type string and values of any type"

It should be obvious that data can have nothing in common with the { key : 'value' } type. Now, take a look at what happens if you explicitly tell the compiler that T should conform to the return type of the getValue instead:

class Foo<T extends {[key: string]: any}> {
    public readonly data?: T | ReturnType<typeof getValue>;

    constructor() {
        this.data = getValue(); //OK
    }
}

Now the compiler is happy because it can establish the relationship, but you become limited to objects with single key key and values of type string. Frankly, from your snippet, it is unclear why do you need to make the class generic at all:

class Foo {
    public readonly data?: ReturnType<typeof getValue>;

    constructor() {
        this.data = getValue(); //OK
    }
}