2

I have a string union type like

type example = "foo" | "bar" | "baz";

Is it possible to turn that type into an object like below?

const example = {
  foo: "foo",
  bar: "bar",
  baz: "baz"
};

I want to try and keep it as DRY as possible and not have to create a separate object with those values hardcoded.

Steven Cao
  • 23
  • 4

2 Answers2

3

I do not think if you can create an object using type definition because everything is compiled down to JavaScript that does not have strong type info. If you wanted to create another type object based on the union, you can do this:

type example = "foo" | "bar" | "baz";
type exampleObject = {[key in example]: string}
// equivalent to
// type exampleObject = {
//     foo: string;
//     bar: string;
//     baz: string;
// }

// You can use the object type
const example: exampleObject = {
  foo: "foo",
  bar: "bar",
  baz: "baz"
};

TS Playground link: https://tsplay.dev/w6BOrw

But as the comment says, if you want to restrict the values as given in your example, you can do this:

type exampleObjectExact = {[key in example]: key}
// equivalent to
// type exampleObjectExact = {
//     foo: "foo";
//     bar: "bar";
//     baz: "baz";
// }

You can also use built in utility type: Record:

type Result = Record<example, example>;

Seeing your own answer, it seems like you have slightly changed the problem definition. Initial question was about the union type to object type conversion. I assumed you did not have any control over the union type, perhaps it was coming from a library or something. But, if you have control over this, you could do this (which @Aleksey rightly mentioned):

const example = ['foo', 'bar', 'baz'] as const;

type exampleObjExact = { [key in typeof example[number]]: key }
// is equivalent to
// type exampleObjExact = {
//     foo: "foo";
//     bar: "bar";
//     baz: "baz";
// }

const x = example.reduce<exampleObjExact>((cum, v) => ({...cum, v}), {} as any)

TS Playground: https://tsplay.dev/WPjreN

Nishant
  • 54,584
  • 13
  • 112
  • 127
  • There is one problem with this solution. We can assign any values to any key. eg. "baz" can be assigned to 'foo' key – Shreekesh Murkar Oct 21 '21 at 04:56
  • I think the OP wanted values to be string, but if you wanted keys matching values, I have updated the answer for that too. :) @ShreekeshMurkar – Nishant Oct 21 '21 at 05:22
  • Thanks for the response! Unfortunately, the answer still duplicates the foo/bar/baz part multiple times which I am trying to avoid. I was able to figure a way to get the typing correct without duplicating it. I'll leave it in an answer below. – Steven Cao Oct 21 '21 at 06:28
0

I was able to figure out a solution to my problem, although it is rather long-winded.

const buildReflectionFromArray = <T>(values: readonly T[]): Record<string, T> => values.reduce((acc, curr) => {
  Reflect.set(acc, String(curr), curr);
  return acc;
}, {});

const exampleType = ['foo', 'bar', 'baz'] as const;
type ExampleType = typeof exampleType[number];
// Casting the response here to enabled autocomplete from editors
const exampleTypes = buildReflectionFromArray<ExampleType>(exampleType) as Record<ExampleType, ExampleType>;

TS Playground Link: https://www.typescriptlang.org/play?jsx=0#code/MYewdgzgLgBARgVwJYBsAmAlApgMxV4KJcAMQCcQBbAQTLIEMBPGAXhgB4AVAPgAoA3eigRYIALhhks9NOBTNOAbQC6ASgnZQZNO2hkkYAOYAaGD1bcYg4aIB0UtAmBZevesGCngCOqoswAbwAoGBhsPAIoWwgsKDcPUwBlKH0jXm9fLx8yVQBuEMlYnzAYd2B8gF9TAIq8oKDQSFgsAA96SgAHfE5GDqxWGEUAchwQECHTIbh6MgmYKfoALyHlUogYRuh8qF7+gFE2zu7dgZ2+kBwYVvaurB6+xTAESjgsMmV8gHpPmABhemgBkMMCgAAt+lIIB1wDEYOCpCCQFcwPQ4Pg0KUEFAQKAjrF+jgKJQrmgkNiyBAGjDmodbvdRANEKhMLh8IRiGByFRaAxGOwDjdjn0+Nc8fS-ACwgQQNp+bShVhTAKxbtuLkgA

Steven Cao
  • 23
  • 4
  • Just curious, why did you use `Reflect` ? – captain-yossarian from Ukraine Oct 21 '21 at 07:28
  • I wasn't able to directly set the values using something like `acc[String(curr)] = curr;` due to typing issues so I decided to use that instead. I think something like `Object.assign(acc, { [String(curr)]: curr });` would work too but I think with Reflect it looks cleaner semantically. – Steven Cao Oct 21 '21 at 07:44
  • please see this answer https://stackoverflow.com/questions/69631285/how-to-assert-matching-types-when-iterating-over-an-object/69631394#69631394 . I doubt that you need to use `Reflect` here – captain-yossarian from Ukraine Oct 21 '21 at 07:47
  • 2
    You could do something like https://www.typescriptlang.org/play?jsx=0#code/MYewdgzgLgBApgDwIYFsAOAbOAVAnmuGAXhgG0ByAMxBHIBoZyAjJAJ3sZYC9yBdGJBBihIUANwAoKPkIB5JgCtiMAN5kA0jACWYGNIIhK8ZOix4CpMAFcUTOK168AXDE0BfCRJHQYCZYlRMHBkAOlY4ABMrYDgAClikYGAGYABKYgA+GFiVELzE5OE3VIYVNwEheQVUzwQQ6hAJOpZWWpCkJmAYAHpu+FZWIA – Aleksey L. Oct 21 '21 at 07:53
  • @AlekseyL. Exactly! – Nishant Oct 21 '21 at 08:01
  • @AlekseyL. Ah yes that seems way better. Thanks! – Steven Cao Oct 21 '21 at 08:01