15

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[];
 }
2
  • Not that I know of. However, since the properties can be named anything and are defined on the class anyway, do they really need to be in an interface to begin with? Commented May 28, 2014 at 14:39
  • thanks. without the definition, I may not be able to access the property like d["books"], with implicit any set. It will give TS7017 "Index signature of object type implicitly has an 'any' type. The interface define input from an external source, which is of format jsonapi.org/format Commented May 28, 2014 at 15:34

4 Answers 4

43

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


Sign up to request clarification or add additional context in comments.

2 Comments

This should be the selected answer. Thank you for sharing!
This amounts to turning off typechecking completely except for checking that key is a string. It is not a solution at all.
13

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.

2 Comments

Did you mean something like typescriptlang.org/Playground . Can you please show how to typecast.
That's the idea - you won't like the type assertion, but bear in mind this isn't your typical case... var tmp = (<IFunkyResponse><any>d);
2

Without interface:

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

Comments

-2

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',
};

1 Comment

This does not work. The compiler is complaining about staticflag being incompatible with string|number. See: typescriptlang.org/play?#code/…

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.