4
votes

I'm really curios if that somehow a design gap in TypeScript. Let's say I have a function, that takes any objects that fulfills this interface

interface Params {
    [key: string]: string | number | boolean | undefined | null;
}

So the key must be a string and the type of the properties can be a primitive or void.

If I now specify some method that takes a specific interface which as such fulfills the Params interface I get the error, that the other interface doesn't have an index signature. So the other interface may simply look like.

interface Foo {
    bar: string;
}

I also tried to change the signature to Record<string, string | number | boolean | undefined | null> but that also gives the missing index signature error.

My whole code then may look like this. To give a full picture. So I need to have the key and value type of the object specified to be able to do certain operations in Object.entries

function specificFunction(obj: Foo) {
  genericFunction(obj);
}

function genericFunction(params: Params) {
  Object.entries(params).forEach(([key, value]) => {
    …
  })
}

EDIT: Important note, the behavior of the specificFunction should stay the same, as it should be used to narrow done the allowed interface, because in this special case only objects with certain properties are allowed to ensure a certain result.

1

1 Answers

4
votes

genericFunction expects type with index signature.

Foo does not have index signature by the default because it is an interface.

But there is a small hint. If you use type keyword to declare Foo instead of interface - it will work.

Why?

Because type's have index signature by the default.

See this answer.

So, next code will compile:

interface Params {
    [key: string]: string | number | boolean | undefined | null;
}

// use type here instead of interface
type Foo = {
    bar: string;
}

function specificFunction(obj: Foo) {
  genericFunction(obj);
}

function genericFunction(params: Params) {
  Object.entries(params).forEach(([key, value]) => {

  })
}

Playground

UPDATE 2 If you don't have control over Params and Foo, you can use next utility type:

interface Params {
    [key: string]: string | number | boolean | undefined | null;
}

interface Foo {
    bar: string;
}

type Values<T> = T[keyof T]

type MakeIndexed<T> = {
    [P in keyof T]: T[P]
}

function specificFunction(obj: MakeIndexed<Foo>) {
    genericFunction(obj);
}

function genericFunction(params: Params) {
    Object.entries(params).forEach(([key, value]) => {

    })
}