bsr
bsr

Reputation: 58662

typescript interface definition with an unknown property key

How to express an interface (IResponse), with one property has a string key (which is not known statically). Below, the key values can be anything like books, chairs, etc. All other keys and types are known statically. Below implementation gives error. I guess the error is because the index signature on the IResponse makes all the property values to be IValue[]. Is there a way to do this?

export interface IMeta{}
export interface IValue{}
export interface IResponse {
     meta: IMeta;
     [index: string]:IValue[];
}

 export class Response implements IResponse {
    meta:IMeta;
    values:IValue[];
    //books:IValue[];
    //anything:IValue[];
 }

Upvotes: 15

Views: 28571

Answers (4)

laaksom
laaksom

Reputation: 2210

Old question, but here is how I solved the issue for myself.

export interface Foo {
    [key: string]: any;
}

{ something: 0 } as Foo => valid

{ other: 'hello' } as Foo => valid

Edit: January 14, 2023: There is a better way to do this for type safety if you know what the possible shapes are:

type ApiResponse<Type> = {
  [Property in keyof Type]: Type[Property];
};

type BookResponse = { title: string; }

const myBookResponse: ApiResponse<BookResponse> = { title: 'foo' } // OK
const myBookResponse: ApiResponse<BookResponse> = { title: 42 } // ERROR
const myBookResponse: ApiResponse<BookResponse> = { title: 'foo', taco: true } // ERROR


Upvotes: 43

Alex Bausk
Alex Bausk

Reputation: 718

There seem to be no exact replies unfortunately, only approximations of a solution.

The solution is to use intersection type over two interfaces, one that defines static metadata and one that defines dynamic keys:

interface IStatic {
  staticflag: boolean;
}

interface IDynamic {
  [x: string]: string | number;
}

type Complex = IStatic & IDynamic;

const a: Complex = {
  staticflag: false,
  dynamic1: 'a',
};

Upvotes: -2

David DIVERRES
David DIVERRES

Reputation: 1926

Without interface:

const myFunc = ((myObjectWithSomeKeys: { [key: string]: any }) => {
});

Upvotes: 2

Fenton
Fenton

Reputation: 250812

If you define one interface for the known types and a separate one for the "unknown" types, you could use a type assertion to tell the compiler the one you wanted to use.

It isn't ideal, but you are working in a edge case (i.e. not entirely dynamic, not entirely static).

export interface IMeta{}
export interface IValue{}
export interface IFunkyResponse {
     [index: string]:IValue[];
}
export interface IResponse {
     meta: IMeta;
}

export class Response implements IResponse {
    meta:IMeta;
    values:IValue[];
    books:IValue[];
    anything:IValue[];
}

You can type-assert between <IResponse> resp and <IFunkyResponse> resp to access one or the other of the styles.

Upvotes: 13

Related Questions