TypeScript Bracket notation causes index signature error

eye-catch JavaScript/TypeScript

TypeScript shows an error when we access an object with brackets like obj[key] to get the value. How can we safely resolve it?

Sponsored links

no index signature with a parameter of type ‘string’ was found on type

The following example shows an error where string value is used to get the property value.

interface MyType {
    value: number;
    foo: {
        value: string;
    };
}

const obj: MyType = {
    value: 1,
    foo: { value: "value" },
};

{
    const key = "foo";
    console.log(obj[key]); // OK
    const key2: string = "foo";
    console.log(obj[key2]); // NG
    // Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'MyType'.
    // No index signature with a parameter of type 'string' was found on type 'MyType'.ts(7053)
}

If the data type is not specified compiler doesn’t show an error because it interprets foo is key of MyType. We might write such logic in a function and it will look like this.

function disp(key: string) {
    console.log(obj[key]);      // NG
    console.log(obj["foo"]);    // OK
}

function disp2(key: keyof MyType) {
    console.log(obj[key]);      // OK
    console.log(obj["foo"]);    // OK
}

If the argument data type is a string it shows an error but it doesn’t when the type is key of the object.

Sponsored links

Solutions to get object value

Easiest solution with any keyword

Let’s see another example. Using any is the easiest way.

function getValueOf(object: any, prop: string): unknown {
    return object[prop];
}

Trial and error without using any type

Let’s consider it deeply without using any type. We need to check if it has the target property.

function getValueOf1(object: unknown, prop: string): unknown {
    const found = Object.prototype.hasOwnProperty.call(object, prop);
    if (found) {
        return object[prop]; // Object is of type 'unknown'.ts(2571)
    }
}

It shows an error because object is an unknown data type. We are not sure if the object is really an object. Therefore, let’s make sure if it is an object. However, the following code doesn’t work.

function isObject(object: unknown): object is Object {
    return object !== null && typeof object === "object";
}

function getValueOf2(object: unknown, prop: string): unknown {
    if (!isObject(object)) {
        return object;
    }

    const found = Object.prototype.hasOwnProperty.call(object, prop);
    if (found) {
        return object[prop];
        // Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Object'.
        // No index signature with a parameter of type 'string' was found on type 'Object'.ts(7053)
    }
    return undefined;
}

The error message is different from the previous example because the compiler knows that the object is an Object type. However, prop variable isn’t key of the object. String can’t be specified there.

Solution without using any type

Object doesn’t support string for the index. Then, let’s create the object that supports string key.

function isMyObject(object: unknown): object is MyObject {
    return object !== null && typeof object === "object";
}
interface MyObject {
    [key: string]: unknown;
}
function getValueOf3(object: unknown, prop: string): unknown {
    if (!isMyObject(object)) {
        return object; // primitive value
    }

    const found = object.hasOwnProperty(prop);
    if (found) {
        return object[prop];
    }
    return undefined;
}

The compiler knows that the object variable is an Object type. Therefore, we can directly call hasOwnProperty function. Is this simpler? It’s up to you which way to choose.

function getValueOf3(object: unknown, prop: string): unknown {
    if (!isMyObject(object)) {
        return object; // primitive value
    }
    return object.hasOwnProperty(prop) ? object[prop] : undefined;
}

Define the concrete return type

The example above returns an unknown data type, so it is not nice to use it. Following code improves the point. It provides return data type and the compiler can understand the possible return data type. You can’t make a mistake using different class when you want to check whether it is the target class or not.

function getValueOf<T>(object: T, key: string): T[keyof T] | undefined {
    if (isKeyOf(object, key)) {
        return object[key];
    }
    return undefined;
}

function isKeyOf<T>(object: T, key: any): key is keyof T {
    return key in object
}

Check this post if you want to learn more about Generics.

End

We can easily implement by using any data type but let’s try to define concrete data type as much as possible. Its implementation is I think more difficult but it makes the code safer. It means that we can detect coding errors during coding time but not at run time.

The functions above are published, so you can download and it.

yutolity
Utility functions. Latest version: 1.4.0, last published: 2 years ago. Start using yutolity in your project by running `...

Comments

Copied title and URL