TypeScript: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type

TypeScript Version: 3.0.1

Code


import Sequelize from 'sequelize';
import { DbInterface } from'typings/DbInterface';
import { UserFactory } from './User';
import { PostFactory } from './Post';
import { CommentFactory } from './Comment';

export const createModels = (sequelizeConfig: any): DbInterface => {
  const { database, username, password, params } = sequelizeConfig;
  const sequelize = new Sequelize(database, username, password, params);

  const db: DbInterface = {
    sequelize,
    Sequelize,
    Comment: CommentFactory(sequelize, Sequelize),
    Post: PostFactory(sequelize, Sequelize),
    User: UserFactory(sequelize, Sequelize)
  };

  Object.keys(db).forEach(modelName => {
    if (db[modelName].associate) { //Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'DbInterface'. No index signature with a parameter of type 'string' was found on type 'DbInterface'.
      db[modelName].associate(db); //Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'DbInterface'. No index signature with a parameter of type 'string' was found on type 'DbInterface'.
    }
  });

  return db;
};

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 12
  • Comments: 16 (1 by maintainers)

Most upvoted comments

This is a question, not a bug, please use SO for questions. (see this it will help)

// bad
const _getKeyValue = (key: string) => (obj: object) => obj[key];

// better
const _getKeyValue_ = (key: string) => (obj: Record<string, any>) => obj[key];

// best
const getKeyValue = <T extends object, U extends keyof T>(key: U) => (obj: T) =>
  obj[key];

Bad - the reason for the error is the object type is just an empty object by default. Therefore it isn’t possible to use a string type to index {}.

Better - the reason the error disappears is because now we are telling the compiler the obj argument will be a collection of string/value (string/any) pairs. However, we are using the any type, so we can do better.

Best - T extends empty object. U extends the keys of T. Therefore U will always exist on T, therefore it can be used as a look up value.

Just found this blog post, which was very helpful, and wanted to paste some code here that I think is slightly more straight forward than the code included here so far. If you have the following object:

const unitsOfTime = {
  millisecond: 1,
  second: 60,
  hour: 60 * 60,
  day: 24 * 60 * 60,
  month: 30 * 24 * 60 * 60,
  year: 365 * 24 * 60 * 60
};

You can declare types for the keys and values in that object like so:

const unitsOfTime: { [unit: string]: number } = {
  millisecond: 1,
  second: 60,
  hour: 60 * 60,
  day: 24 * 60 * 60,
  month: 30 * 24 * 60 * 60,
  year: 365 * 24 * 60 * 60
};

@greenlaw110 Try to get the key value by using @alexandermckay’s method:

const LEVEL_DEBUG = 1;
const LEVEL_FINE = 2;


const LOG_LEVELS: {[key:string]: number} = {
  debug: LEVEL_DEBUG,
  fine: LEVEL_FINE,
};

let logLevel:string = 'debug';
if (!logLevel) {
  logLevel = 'info';
}

const getKeyValue = <T extends object, U extends keyof T>(obj: T) => (key: U) =>
  obj[key];
let _level = getKeyValue(LOG_LEVELS)(logLevel);
console.log(_level); // 1

In addition to the answers, people could also try to use interfaces 😃 Here is an example, hope it helps:

export interface IMapping {
  [propertyName: string]: string;
}

const mapping: IMapping = {
  apples: '$5',
  oranges: '$4',
}

// some code

const fruit: string = await getFruitFromInput();
const price: string = mapping[fruit];
return price;

I am a bit confused by this specific type checking. It seems to me that:

  1. it is not actually protecting anything (people come up with hacks to bypass the error)
  2. it does not match the reality of javascript where accessing a missing key returns undefined.

Going further with the example above:

const unitsOfTime = {
  millisecond: 1,
  second: 60,
  hour: 60 * 60,
  day: 24 * 60 * 60,
  month: 30 * 24 * 60 * 60,
  year: 365 * 24 * 60 * 60
};

unitsOfTime[someStringInput] // Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '...'

I see 3 ways of bypassing this:

// Adding a looser type:
const unitsOfTime: { [unit: string]: number } = {...}
// wrong because it makes it possible to also use a string in cases where I might not want to accept a string that is not specific.

// Augmenting the type of the indexing key:
const time = unitsOfTime[someStringInput as keyof typeof unitsOfTime]
// even worse because it communicates the idea that the key **IS** one of unitsOfTime even though it might not be.
// Typescript won't tell the difference but there is a case where things will break.

// Addind a looser type at indexing site:
const time = (unitsOfTime as Record<string, number>)[someStringInput]
// ok, but it shouldn't be necessary...

…What I would expect: As far as I know, accessing a key that does not exist in javascript will always return undefined. So in my opinion, accessing such key should actually emit a type that is a union of undefined and all other values in the object. But certainly not ‘any’.

This way I could do;

const unitsOfTime = {...}

const time = unitsOfTime[someStringInput] // type of time is: number | undefined
if (!time) {
  // handle missing unit
}
// type of time is: number
// continue with time

For those of you having this issue with typescript:

// error

export interface IFilterData {
    categories: string[];
    genders: string[];
    brands: string[];
    discounts: string[];
}
const selectedFilters: IFilterData = someInitialValues;
for (let key in selectedFilters) {
    // you will get the error here
    selectedFilters[key].map(filter => {});
}

Like @alexandermckay said, we need to specify that the current record’s keys are of type “string” //fix

export interface IFilterData extends Record<string, any> {
    categories: string[];
    genders: string[];
    brands: string[];
    discounts: string[];
}

So extend your interfaces with Record<string, any>, or generic types to get rid of any.

Hope this helps.

Hey, if I do this my object will have the following shape when I hove over it:

const unitsOfTime: {
    [unit: string]: number;
}

so auto-complete is no longer available, am I missing something?

Same question here, I have

const LEVEL_DEBUG = 1;
const LEVEL_FINE = 2;

const LOG_LEVELS = {
  info: LEVEL_INFO,
  debug: LEVEL_DEBUG,
  fine: LEVEL_FINE,
};

let logLevel: string = environment.logLevel;
if (!logLevel) {
  logLevel = 'info';
}
let _level = LOG_LEVELS[logLevel.toLowerCase()]; // here it complains 

In addition to the answers, people could also try to use interfaces 😃 Here is an example, hope it helps:

export interface IMapping {
  [propertyName: string]: string;
}

const mapping: IMapping = {
  apples: '$5',
  oranges: '$4',
}

// some code

const fruit: string = await getFruitFromInput();
const price: string = mapping[fruit];
return price;

Unfortunately, weakly typed solutions make autocompletion no longer work.