Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic type using a string variable as a property name

Tags:

typescript

I would like to be able to construct a type dynamically with a property name which is one of the specified parameters. While I can construct the actual object, I cannot seem to construct the actual type. I would like to use this type for composition alter

export function mapProp<AssignedType>(value: AssignedType, propertyName: string) {

  type ReturnType = {
    [propertyName]: value
  }; // errors on this line

  return {
    [propertyName]: value
  };
}

The error emitted is as follows:

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

Example playground: http://www.typescriptlang.org/play/#src=%0D%0Aexport%20function%20mapProp%3CAssignedType%3E(value%3A%20AssignedType%2C%20propertyName%3A%20string)%20%7B%0D%0A%0D%0A%20%20type%20ReturnType%20%3D%20%7B%0D%0A%20%20%20%20%5BpropertyName%5D%3A%20value%0D%0A%20%20%7D%3B%0D%0A%0D%0A%20%20return%20%7B%0D%0A%20%20%20%20%5BpropertyName%5D%3A%20value%0D%0A%20%20%7D%3B%0D%0A%7D

like image 595
Cory Dolphin Avatar asked Jul 13 '18 17:07

Cory Dolphin


People also ask

How do you dynamically access an object property in C#?

In C#, you can access dynamic properties by obtaining a PropertyObject reference from the specific object reference using the AsPropertyObject method on the object.

How do you add a dynamic property in TypeScript?

Use an index signature to dynamically add properties to an object, e.g. const obj: {[key: string]: any} = {} . Index signatures are used when we don't know all of the names of a type's properties and the type of their values ahead of time. Copied!


1 Answers

I think the closest you're going to get is something like this:

export function mapProp<PropertyName extends string, AssignedType>(
  value: AssignedType, 
  propertyName: PropertyName
) {

  type ReturnType = {
    [K in PropertyName]: AssignedType
  }; 
  // equivalent to Record<PropertyName, AssignedType>

  return {
    [propertyName]: value
  } as ReturnType;

}

In this case you'd be using a mapped type instead of a type with an index signature. The addition of the PropertyName generic type parameter allows the narrowing of the key past string, if you pass it a string literal:

const thing = mapProp(123, "abc");
thing.abc; // number
thing.def; // error

In that case ReturnType is known to be equivalent to {abc: number}. If all you know is that the key is a string at compile time, then you get this:

declare const propName: string;
const stuff = mapProp(123, propName);
stuff.abc; // number
stuff.def; // number 

Now ReturnType is equivalent to {[k: string]: number}, meaning it accepts any string key (and gives it a number value). This might not be what you want, but it's the best the compiler can do in this case.

Also note that without using a type assertion (as ReturnType), computed properties usually end up as string indexes instead of something more specific. This is currently a design limitation of TypeScript. There have been some attempts to deal with this, but nothing has made it into the language yet.

Hope that helps; good luck!

like image 148
jcalz Avatar answered Sep 21 '22 16:09

jcalz