1
votes

Below I have a function getEntityPropId and it returns a object with one prop, like { 'thingId': 1 } the string thing is passed into the function.

I am curious as to how I can have the function return type include the key thingId and not just any string, because I am passing in everything needed to know the return.

However I get this:

A computed property name in a type literal must refer to an expression whose type is a literal type or a 'unique symbol' type.ts(1170)

export const getEntityPropId = (value: any, entity: string): { [`${entity}Id`]: number } | null => {
  const id = getEntityId(value, entity)
  if (id !== null) return { [`${entity}Id`]: id }
  return id
}

I'd be ok having to setup an enum if necessary.

enum EntityPropIds {
  thing = 'thingId',
}

export const getEntityPropId = (value: any, entity: EntityPropIds): { [EntityPropIds[entity]]: number } | null => {
  const id = getEntityId(value, entity)
  if (id !== null) return { [EntityPropIds[entity]]: id }
  return id
}

Is this possible?

1

1 Answers

2
votes

Dynamic property names on a type-level are not yet possible in TypeScript (although there is an open issue in discussion on Github)

However, as you've suggested, if you create a type to map entity to entityId, it is possible:

interface EntityIdMapping {
  thing: 'thingId';
}

export const getEntityPropId = <T extends keyof EntityIdMapping>(value: any, entity: T): {[P in EntityIdMapping[T]]: number} | null => {
  const id = getEntityId(value, entity)
  if (id !== null) return { [`${entity}Id`]: id }
  return id
}

Instead of an enum I'm using a interface, because those are a little easier to deal with and don't introduce any runtime code.

Next I've added a generic type for the entity argument, which must be a key of the mapping interface.

For the return value we're using a mapped type which loops over all types in EntityIdMapping[T]. In case of T = 'thing', this only returns one entry for the matching property in the interface.

Playground