Typescript interface type with optional keys of different types and strictNullChecks

You stated that all properties on CreepPlan have Interval-type values when you wrote:

interface CreepPlan {
  [partName: string] : Interval;
}

i.e., any and every string-index-accessible property of CreepPlan will be an Interval. This applies to move as well, since foo['move'] is the same as foo.move.

However, looking at Interval, there's no required structure to it:

interface Interval {
  min?: number,
  max?: number
}

There are no required fields in that contract, but it does require an object. It will happily accept an empty object, but it requires some object that could have a min and max properties.

Your MoveSpeed type, on the other hand, allows undefined:

Property 'move' of type '"min" | "road" | "full" | undefined' is not assignable to string index type 'Interval'.

Thus, Interval must be an object with no required properties (which string can easily meet) but does not allow undefined, which MoveSpeed does allow.

Your types are disjoint, but it's not obvious until you resolve them out once or twice.


As described in the typescript docs if you use indexable properties then all the return type of all the other properties should be assignable to the return type of the index return type. That's because foo['bar'] and foo.bar are the same.

So why does this work?

interface CreepPlan {
    [partName: string]: Interval;
    move: MoveSpeed;
}

Because any MoveSpeed is a valid Interval value (it is an interval with undefined both min/max.

Why doesn't this work?

interface CreepPlan {
     [partName: string]: Interval;
     move?: MoveSpeed;
 }

move? has type MoveSpeed plus the undefined type. If is has the undefined value then it is not assignable to Interval so the compiler correctly shows error.

How can you fix this? Simply make the indexer have undefined as a valid value:

interface CreepPlan {
    [partName: string]: Interval | undefined;
    move?: MoveSpeed;
}

Tags:

Typescript