49

I have a simple code:

const allTypes = { jpg: true, gif: true, png: true, mp4: true };
const mediaType = url.substring(url.lastIndexOf('.') + 1).toLowerCase();
return Boolean(allTypes[mediaType]);

TypeScript is complaining:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ jpg: boolean; gif: boolean; png: boolean; mp4: boolean; }'.
  No index signature with a parameter of type 'string' was found on type '{ jpg: boolean; gif: boolean; png: boolean; mp4: boolean; }'.  TS7

I think I need to treat mediaType as keyof typeof allTypes, but don't know how.

For sake of completion, the complete code is:

// these are all the types of media we support
const allTypes = { jpg: true, gif: true, png: true, mp4: true };

const MediaGallery = () => {
    const classes = useStyles();
    const [ filters, setFilters ] = useState(allTypes);
    return (
        <div className={classes.root}>
            {mediaList
                .filter(({ url }) => {
                    const type = url.substring(url.lastIndexOf('.') + 1).toLowerCase();
                    return Boolean(filters[type]);
                })
                .map(({ url, caption, hash }) => <Media url={url} caption={caption} key={hash} />)}
            <FiltersPanel onFiltersChanged={(newFilters: any) => setFilters(newFilters)} />
        </div>
    );
};

4 Answers 4

83

All you need is to define the index signature:

const allTypes: {[key: string]: boolean} = { jpg: true, gif: true, png: true, mp4: true };

Indexable Types

Similarly to how we can use interfaces to describe function types, we can also describe types that we can “index into” like a[10], or ageMap["daniel"]. Indexable types have an index signature that describes the types we can use to index into the object, along with the corresponding return types when indexing. Let’s take an example:

interface StringArray {
  [index: number]: string;
}

let myArray: StringArray;
myArray = ["Bob", "Fred"];

let myStr: string = myArray[0];

Above, we have a StringArray interface that has an index signature. This index signature states that when a StringArray is indexed with a number, it will return a string.

Utility type: Record<Keys, Type>

Another solution is to use the TypeScript utility type Record<Keys, Type>:

Constructs an object type whose property keys are Keys and whose property values are Type. This utility can be used to map the properties of a type to another type.

const allTypes: Record<string, boolean> = { jpg: true, gif: true, png: true, mp4: true };

for (const key of Object.keys(allTypes)) {
  console.log(`${key}: ${allTypes[key]}`);
}
Sign up to request clarification or add additional context in comments.

1 Comment

I would suggest looking at the answer from LCC below. The problem with Record<> and unconstrained Index Signatures is that you are widening the type to all possible records. So if I asked for "allTypes["not-a-picture"]", TSC would assume the resulting value would be a boolean, even though it would be "undefined". And if you did a check against "undefined", ESLint would tell you that it was an unnecessary check, even though it's necessary. You're mapping Keys to Values. Why not use a Map<>?
33

You can use indexable types, but this widens the type of allTypes to contain any (string) key, when it looks like you have a limited list of keys that you want to support.

A better solution - allowing you to use the proper type of allTypes - is (as you already indicated in your question) to tell the compiler your assumption that mediaType is one of the keys of the type of allTypes with a type assertion:

return Boolean(allTypes[mediaType as keyof typeof allTypes]);

This type is in this case equivalent to the union type "jpg" | "gif" | "png" | "mp4", but it is computed automatically.

(How you ensure that your assumption is correct at runtime is a separate concern, of course.)

2 Comments

This make sense, as TS is explicitly suggesting the same 'string' can't be used to index type '{ jpg: boolean; gif: boolean; png: boolean; mp4: boolean; }'.
This works for JSON objects as well.
3

Here's a type predicate utility function which safely checks whether a given string is a property of an object:

function isKeyOf<O extends Record<string, unknown>>(
  object: O,
  key: string | number | symbol,
): key is keyof O {
  return key in object;
}

Use it like this:

return isKeyOf(allTypes, mediaType) ? allTypes[mediaType] : false
//                                    ^^^^^^^^^^^^^^^^^^^
// In this branch TypeScript knows that mediaType is a key of allTypes

Comments

-1

my solution:

{ // in tsconfig.json
  ...
  strict: true; // optional
}

let args = {};
Object.keys(args).forEach((key: string): void => {
  const value = (args as {[key: string]: any})[key];
});

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.