3

Let's say I have the following:

type Field = {
  _id: string;
  value: string;
}

const fields = [
  { _id: 'a', value: '2' },
  { _id: 'b', value: '3' }
] as const;

I would like to define a type ValueById<T extends readonly [...Field[]]> which produces:

{
  a: '2',
  b: '3',
}

I've gotten partially to where I want to be with this:

export type ValueById<T extends readonly [...Field[]]> = {
  [Z in T[number]['_id']]: T[number]['value']
}

type NewType = ValueById<typeof fields>

But unfortunately this produces a type of:

{
  a: '2' | '3'
  b: '2' | '3'
}

Is there a way to narrow down the type of the values here?

Cyril Silverman
  • 479
  • 4
  • 12
  • 1
    Is `{ a: '2' } & { b: '3' }` good enough? https://www.typescriptlang.org/play/index.html#code/MYewdgzgLgBAZgSwKYBsAmEYF4YB5SRQB8A2gFAwwDeMA+gmgFwwDkAhiwDQwBubKAVyTMWAJhYwAvpwrU6DEQCMuvfkJEBmCZLIBdMmSgBPAA5IYAMWToIAFVPmcxsyDjxrGANyGHlj9j9UDHszEjABAFtFJAAnXW8fMxgASTQANTUkAFUwBHBcWyIA2xgkAA8oJDAMQPQYAH45EgAtGAQwGFsSFno0Fl1dZi6WPkEkfqkYZjAkHliE53McvLBbEGSwSpiIJGAoFdwsoqxZAAos0oqqmrYwIwaYU4BrZiyASmwinhAGKZgZuYxD7lSrVTCnZ7MdpwWIpD5YL4-NAfRrJP4A+YGcomEAxWCLGAZMYAISMqQCy3Aaw2Wx2ewOqSJQkpYFwViCRCInhgAHoeXI2CJxJMAGRyRSabQGFBIWBwEAgZhMpCk8k4GiC1jibgS1haKRAA – Aleksey L. May 04 '20 at 10:35
  • Oooh, super close. Might be good enough for my use case. I'll give it a shot, thanks! – Cyril Silverman May 04 '20 at 10:49

1 Answers1

1

First we'll need to extract array item type:

const fields = [
  { _id: 'a', value: '2' },
  { _id: 'b', value: '3' }
] as const;

type Field = typeof fields[number];
// {
//     readonly _id: "a";
//     readonly value: "2";
// } | {
//     readonly _id: "b";
//     readonly value: "3";
// }

Now we can create a union of "id-value" pairs using distributive conditional types

type IdValueUnion<T> = T extends Field ? { [Z in T['_id']]: T['value'] } : never;
// IdValueUnion<Field>
//
// {
//     a: "2";
// } | {
//     b: "3";
// }

We're pretty close, but we need intersection instead of union:

type UnionToIntersection<U> =
  (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
// UnionToIntersection<IdValueUnion<Field>>
// {
//     a: "2";
// } & {
//     b: "3";
// }

Last step is "compact" the intersection by just remapping keys to values:

export type Compact<T> = { [K in keyof T]: T[K] };

export type ValueById = Compact<UnionToIntersection<IdValueUnion<Field>>>;
// 
// {
//     a: "2";
//     b: "3";
// }

let foo: ValueById = { a: '2', b: '3' }

Playground

Aleksey L.
  • 35,047
  • 10
  • 74
  • 84