11

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

2 Answers 2

15

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!

Sign up to request clarification or add additional context in comments.

Comments

2

In the meantime, there are more elegant and simple ways to achieve this (see https://www.typescriptlang.org/docs/handbook/2/mapped-types.html).

So instead of using a function to return the type, we can now declare types like this:

export type MappedType<PropertyName extends string, AssignedType> = {
    [K in PropertyName]: AssignedType
};

let mapped: MappedType<'foo', boolean>;
mapped = {foo: true};  // OK
mapped = {foo: 123}; // error: Type 'number' is not assignable to type 'boolean'
mapped = {foo: true, bar: 123}; // error: 'bar' does not exist in type ...

// You can also define multiple type-properties and use them like variables
type MultipleTypes = [
    {prop: 'p_bool'; value: boolean},
    {prop: 'p_str'; value: 'abc'}
];

type FirstType = MultipleTypes[0];
let m: MappedType<FirstType['prop'], FirstType['value']>;
m = {p_bool: true}; // OK
m = {p_bool: 123};  // errror, wrong type

Or you can even create properties dynamically based on another string:

type Langs = 'en' | 'de';
export type TranslationsType<PropertyName extends string> = {
    [K in `${PropertyName}_${Langs}`]: string
};

const test: TranslationsType<'bla'> = {
    bla_de: 'Hallo',
    bla_en: 'Hello', // if left out, test would have error: Property 'bla_en' is missing in type... 
    bla_es: 'Hola',  // error 'bla_es' does not exist ....
};

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.