18

I'm using Typescript with strict null checking enabled. When I try to compile the following code I get the error "type 'null' cannot be used as an index type."

function buildInverseMap(source: Array<string | null>) {
    var inverseMap: { [key: string]: number } = {};
    for (let i = 0; i < source.length; i++) {
        inverseMap[source[i]] = i;
    }
}

Obviously inverseMap cannot have null as a key because the type constraint disallows it. However if I change the type of inverseMap to this:

var inverseMap: { [key: string | null]: number } = {};

I get the error "Index signature parameter type must be 'string' or 'number'." This is odd because in Javascript it is legal to use null as an index. For example, if you run the following code in your browser:

var map = {};
map[null] = 3;
map[null];

The output is 3. Is there a way to make this happen in Typescript or is Typescript not smart enough to do this?

Aaron
  • 354
  • 1
  • 3
  • 13
  • i'm being a bit surprised that you can set a 'null' index in javascript ! I check and it worked. Would have bet it will throw me some kind of error. Maybe typescript is logic and just don't allow that – sheplu Sep 04 '17 at 19:32
  • 1
    I think this might me a bug. Originally they restricted the index parameter to string and I'm guessing didn't change the validation when they added strict null checking. – Titian Cernicova-Dragomir Sep 04 '17 at 19:43

3 Answers3

22

Object keys in JavaScript are, believe it or not, always strings (okay, or Symbols). (See this answer also). When you pass a non-string value as a key, it gets coerced into a string first. So, in

var map = {};
map[null] = 3;
map[null];

You are actually setting map["null"]. Observe:

console.log(map["null"]===map[null]); // true

So, in TypeScript they actively decided to only allow the string or number type as index signatures. Probably because most of the time, anyone trying to index into an object using something like null is indicative of an error.

In your case, you can do something like this:

function buildInverseMap(source: Array<string | null>) : {[key: string] : number} {
    var inverseMap: { [key: string]: number } = {};
    for (let i = 0; i < source.length; i++) {
        inverseMap[String(source[i])] = i; // coerce to string yourself
    }
    return inverseMap;
}

Note how we coerce source[i] to string ourselves, and that makes TypeScript happy. If you remember to wrap the key with String() whenever you might use it with null, it should work for you:

const inverseMap = buildInverseMap(['a', 'b', null, 'c']);
const aIndex = inverseMap['a'];
const nullIndex = inverseMap[String(null)];

Hope that helps! Good luck.

jcalz
  • 264,269
  • 27
  • 359
  • 360
7

Javascript won't allow for null or undefined as keys in objects, you are not aware of it but you're getting the string value of them, so:

let a = {};
a[null] = "i am null";
a[undefined] = "i am undefined";
console.log(a[null] === a["null"]); // true;
console.log(a[undefined] === a["undefined"]); // true;

In typescript, they decided that indexes can only be of type string and number (though when using numbers as keys it also gets converted into strings), which is why you're having those errors.

In your case I'd just do this:

inverseMap[source[i] || "null"] = i;
Nitzan Tomer
  • 155,636
  • 47
  • 315
  • 299
4

doing map[null] = 3; has exactly the same effect as doing map['null'] = 3; Therefore you might just use 'null' in typescript if that works for you.

Julian
  • 33,915
  • 22
  • 119
  • 174