Ton Broekhuizen
Ton Broekhuizen

Reputation: 13

Why does TypeScript not check for undefined when looking up properties from objects?

TypeScript allows defining interfaces for objects that can contain any property:

interface NumberHash {
    [key: string]: number;
}

let numbers: NumberHash = {
    zero: 0,
    one: 1,
    pi: 3.14
};

When looking up properties from such an object, they are returned as numbers, as is expected.

let key = 'pi';
let pi = numbers[key];

console.log(pi.toFixed(1));  // all is well

However, when looking up unknown properties, they are still returned as numbers, even though they are actually undefined.

let key = 'foo';
let foo = numbers[key];  // `foo` becomes `undefined`

console.log(foo.toFixed(1));  // errors at runtime

The code above throws a TypeError at runtime, but not during compilation. Why does TypeScript not catch this error during compilation?

Upvotes: 1

Views: 299

Answers (2)

Paleo
Paleo

Reputation: 23692

With the strictNullChecks compiler option, the interface NumberHash could be declared as below:

interface NumberHash {
    [key: string]: number | undefined;
}

Then:

let numbers: NumberHash = {};
let foo = numbers['foo'];

console.log(foo.toFixed(1));  // error at compile time: Object is possibly 'undefined'

See also:

Upvotes: 1

Nathan Friend
Nathan Friend

Reputation: 12804

In your example, it's quite clear what properties will and will not exist on your NumberHash object - they're explicitly specified in the object literal that is assigned to your numbers variable. It's reasonable to think that this is something TypeScript could guard against at compile-time.

However, take a more dynamic example:

interface NumberHash {
    [key: string]: number;
}

let numbers: NumberHash = {};

const max = Math.random() * 100;
for (var i = 0; i < max; i++) {
    numbers[i.toString()] = i;
}

let foo = numbers["42"];

// is foo undefined?  Who knows! Depends
// on what Math.random() returned.
console.log(foo.toFixed(1));   

At compile time, the compiler can't know what will be returned by Math.random() at runtime. Because the compiler must handle both your example and my example, it leaves it up to the developer to ensure the key exists.

If you know the properties that will exist in your NumberHash object at compile time, you can rewrite this data structure as a class:

class Numbers {
    public static readonly zero: number = 0;
    public static readonly one: number = 1;
    public static readonly pi: number = Math.PI;
}

// both of these will compile without errors
let foo = Numbers['zero'];
let bar = Numbers.one;

// Compiler error: Element implicitly has an 'any' type 
// because type 'typeof Numbers' has no index signature.
let baz = Numbers['foo'];

Upvotes: 0

Related Questions