skip to Main Content

I’m trying to change the shape of Item to meet a complex interface NewItem by calling transformData(). However I’m getting the following error.

The error message is "Element implicitly has an ‘any’ type because expression of type ‘string’ can’t be used to index type ‘{}’." Here’s a simplified version of my code that still produces the error:

interface Item {
  itemName: ItemName;
  quantity: number;
  itemBreakdown ? : [];
}

type ItemName = 'Dog' | 'Plant' | 'Unknown'

interface NewItem {
  animal: number;
  plant: number;
  food: number;
  unknown: number;
  itemBreakdown ? : {
    catergory1 ? : [ItemDetails]
    catergory2 ? : [ItemDetails]
  };
  error ? : Error | Error[]
}

private transformData(items: Item[]): NewItem {
  const newItem: NewItem = {};

  items.reduce((acc, item) => {
    const { itemName, quantity } = item;
    // Error thrown by line below
    acc[this.mapItemName(itemName)] = quantity;
    return acc;
  }, newItem);

  return newItem;
}

private mapItemName(itemName: string): string {
  switch (itemName) {
    case 'Dog':
      return 'animal';
    case 'Plant':
      return 'plant';
    default:
      return 'unknown';
  }
}
TS7053: Element implicitly has an any type because expression of type string can't be used to index type NewItem
No index signature with a parameter of type string was found on type NewItem

I think I understand the issue but I’m not sure what’s the right way to solve it.
The issue is that I’m passing in a key of type string to {}: NewItem, where NewItem has no index signature of key[string] : number.

I can’t assign key[string] : number to NewItem Interface because what about the other values?
I don’t want to set type as any either or turn off strict mode.

What’s the correct way to solve this without losing type safety?

2

Answers


  1. Chosen as BEST ANSWER

    Silly me all I had to do was not to define a return type...


  2. Your mapItemName should be of the type of keyof NewItem. Moreover we can map ItemName to a new type NewItemKey ensuring the result exists in NewItem‘s keys:

    Playground

    interface Item {
      itemName: ItemName;
      quantity: number;
      itemBreakdown ? : [];
    }
    
    type ItemName = 'Dog' | 'Plant' | 'Food' | 'Unknown';
    type NewItemKey<T extends ItemName> = T extends 'Dog' ? 'animal' : T extends 'Plant' ? 'plant' : T extends 'Food' ? 'food' : 'unknown';
    
    interface NewItem {
      animal: number;
      plant: number;
      food: number;
      unknown: number;
      itemBreakdown ? : {
        catergory1 ? : any[]
        catergory2 ? : any[]
      };
      error ? : Error | Error[]
    }
    
    class Reshape{
    
        private transformData(items: Item[]) {
            return items.reduce((acc, item) => {
                const { itemName, quantity } = item;
                acc[this.mapItemName(itemName)] = quantity;
                return acc;
            }, {} as NewItem);
        }
    
        private mapItemName(itemName: ItemName): NewItemKey<typeof itemName> {
            switch (itemName) {
                case 'Dog':
                    return 'animal';
                case 'Plant':
                    return 'plant';
                case 'Food':
                    return 'food';
                default:
                    return 'unknown'
            }
        }
    }
    

    But the whole construct isn’t type safe, using {} as Newitem we skip required fields animal, plant, etc. Either make them optional or ensure that Item[] passed to transformData contains all items needed to fill NewItem properly (I guess that’s not something trivial but achievable).

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search