7

Let's say I want to create an interface to describe this type of objects:

let myObj= {
    "count": 3,
    "key1": "foo",
    "key2": "bar",
    "key3": "baz"
};

Those object always have a property count of type number and the rest of the properties are strings

If I define my interface using index signatures like this:

interface MyObect {
    count: number;
    [key: string]: string;
}

I got the compiler error:

[ts] Property 'count' of type 'number' is not assignable to string index type 'string'.

So I have to define it like this:

interface MyObect {
    count: number;
    [key: string]: any;
}

But this definition is not as presise.

Is there a way to enforce the type of extra properties ?

Joseph Garrone
  • 1,662
  • 19
  • 20

2 Answers2

9

I've achieved something like this by using an intersection type:

type MyObject =
{
    count : number
} & {
    [key : string] : string
}

This works (I am using TypeScript 2.3.2) for consuming an object, as follows

// x : MyObject
const count : number = x.count;
const foo : string = x.foo

but as pointed out, this assignment still fails

const x : MyObject = {
    count: 10,
    foo: 'bar'
}

so this may be of use only in some cases.

Oly
  • 2,329
  • 1
  • 18
  • 23
  • Thank you for the hint, I wanted to do this as well but using that method the object are imposible to define without casting as any because this definition state that count is both number and string witch is imposible. :/ – Joseph Garrone Jun 04 '17 at 21:05
  • I've updated my answer; I see your problem, which I'd slightly misunderstood and now acknowledge. – Oly Jun 05 '17 at 08:49
  • What you _can_ be sure of using this method (with a cast `as any as MyObject`) is that it is definitely typesafe to consume (provided you create them properly), so if these are only being created in one place, that may be an acceptable compromise. – Oly Jun 05 '17 at 08:52
6

This question has appeared in lots of subtly different forms over the past two months. Usually as a combination of a dictionary and an array.

As an alternative, you can use an error supression to keep the simplest definition of the type, which the TypeScript compiler is actually perfectly capable of working with.

It uses an error supression comment, which is generally a bad thing. It also requires a small additional effort at initialisation. The rest of the usage is as you would expect.

Here is a spin through your specific example using the dictarry technique.

interface MyObject {
    [key: string]: string;
    // @ts-ignore: I'm creating a Dictarray!
    count: number;
}

let myObj = { count: 0 } as MyObject;

myObj.key1 = "a string";
myObj.count = 4;

// Error, 1 is not a string - as you'd expect
myobj.key2 = 1;

myObj = {
    "count": 3,
    "key1": "foo",
    "key2": "bar",
    "key3": "baz"
} as unknown as MyObject;

const str = myObj.key1;
const num = myObj.count;
Fenton
  • 241,084
  • 71
  • 387
  • 401