14

How can I fix the following type error?

Argument of type 'Query' is not assignable to parameter of type 'Input'. Index signature is missing in type 'Query'.(2345)

I am using the Input type as a generic type (catch-all) for query strings. When I feed an input of type Query, the error is thrown but the underlying JavaScript code runs just fine.

TypeScript Playground

interface Query {
    lorem: "0" | "1"
    ipsum: string
}

const query: Query = {
    lorem: "0",
    ipsum: "Hello world"
}

interface Input {
    [key: string]: string
}

const test = (input: Input) => {
    return input["lorem"]
}

test(query)
sunknudsen
  • 6,356
  • 3
  • 39
  • 76

1 Answers1

47

Solution 1: Use type instead of interface

That is because the type [key: string]: string is not compatible with your Query interface, the latter of which contains two allowed keys: lorem and ipsum, while the former contains any arbitrary string as key. This is an issue with TypeScript interfaces in general: a specific interface cannot be saved into a more generic interface.

However, a specific type can be saved into a more generic type, so a quick solution will be to simply convert your interface to types:

type Query = {
    lorem: "0" | "1"
    ipsum: string
}

const query: Query = {
    lorem: "0",
    ipsum: "Hello world"
}

type Input = {
    [key: string]: string
}

const test = (input: Input) => {
    return input["lorem"]
}

test(query)

Solution 2: Spread the variable when calling test()

An alternative solution will simply to keep the interfaces, but use ES6 object spread to deconstruct/spread the variable query before passing it into test(). By doing that, you will force TypeScript to recognize { ...query } as indexable.

This solution is smart but a bit hack-ish, because you have to probably add a comment in the code to explain why you can't just use test(query) instead of test({ ...query }):

interface Query {
    lorem: "0" | "1"
    ipsum: string
}

const query: Query = {
    lorem: "0",
    ipsum: "Hello world"
}

interface Input {
    [key: string]: string
}

const test = (input: Input) => {
    return input["lorem"]
}

test({ ...query })

There is an extended thread on discussion on TypeScript's GitHub repo: might be a good place to read a little more about it.

Terry
  • 63,248
  • 15
  • 96
  • 118
  • Thanks for the thorough answer Terry. Looks like this is a strange edge case in TypeScript. – sunknudsen Mar 15 '20 at 23:03
  • How would you go about typing a query string variable (one-level deep object with `string` properties)? Is `[key: string]: string` all we have? – sunknudsen Mar 15 '20 at 23:29
  • @sunknudsen If it's one-level deep then `[key: string]: string` is correct. – Terry Mar 15 '20 at 23:34
  • I opted for the first approach, but now I have a comment linking here that explains why I'm using a type instead of an interface :) – Dmitry Minkovsky Oct 31 '20 at 00:28
  • 2
    With regard to option 2, it always bothers me when solutions to typing quirks have runtime side effect (like making shallow copies of inputs) which can lead to real bugs. As long as you're throwing away most of the type information (via the spread operator), why not just use `test(query as any as Input)` – Jthorpe May 09 '22 at 21:31