Garrett
Garrett

Reputation: 11745

Create an object type in Typescript derived from another object's values using a generic type

How can you generically construct a type derived from an object following the format below:

export const IMMEDIATE_FAMILY_MEMBERS_LIST_FIELDS = [
  { name: 'Relationship', type: 'dropdown', options: FAMILY_MEMBERS.NAMES },
  { name: 'Full name of family member as shown in passport', type: 'text' },
  {
    name: 'Country family member lives in',
    type: 'dropdown',
    options: COUNTRY.NAMES,
  },
  {
    name: 'Immigration status in that country',
    type: 'dropdown',
    options: IMMIGRATION_STATUS_OPTIONS,
  },
] as const

Into this:

export type ImmediateFamilyMembersListType = Array<{
  Relationship: FamilyMembersType
  'Full name of family member as shown in passport': string
  'Country family member lives in': CountryType
  'Immigration status in that country': keyof typeof IMMIGRATION_STATUS_OPTIONS
}>

Except without manually typing it every time for every object?

Here's my best attempt, but it's not generic nor does the A extends B ? string[] : string even work:

type ImmediateFamilyMembersListFieldsType = typeof IMMEDIATE_FAMILY_MEMBERS_LIST_FIELDS
type ImmediateFamilyMembersListType = Record<ImmediateFamilyMembersListFieldsType[number]['name'], ImmediateFamilyMembersListFieldsType[number]['type'] extends 'dropdown' ? string[] : string>[]

Instead, it just resolves to:

type ImmediateFamilyMembersListType = Record<"Relationship" | "Full name of family member as shown in passport" | "Country family member lives in" | "Immigration status in that country", string>[]

Upvotes: 2

Views: 173

Answers (1)

Here you have util type:

enum FAMILY_MEMBERS {
    NAMES = 'names'
}

enum COUNTRY {
    NAMES = 'names'
}

enum IMMIGRATION_STATUS_OPTIONS { a = 'a' }

type Options = COUNTRY | IMMIGRATION_STATUS_OPTIONS | FAMILY_MEMBERS

type FamilyMembersType = {}
type CountryType = {}

export const IMMEDIATE_FAMILY_MEMBERS_LIST_FIELDS = [
    { name: 'Relationship', type: 'dropdown', options: FAMILY_MEMBERS.NAMES },
    { name: 'Full name of family member as shown in passport', type: 'text', options: '' },
    {
        name: 'Country family member lives in',
        type: 'dropdown',
        options: COUNTRY.NAMES,
    },
    {
        name: 'Immigration status in that country',
        type: 'dropdown',
        options: IMMIGRATION_STATUS_OPTIONS,
    },
] as const

export type ImmediateFamilyMembersListType = Array<{
    Relationship: FamilyMembersType
    'Full name of family member as shown in passport': string
    'Country family member lives in': CountryType
    'Immigration status in that country': keyof typeof IMMIGRATION_STATUS_OPTIONS
}>

type Values<T> = T[keyof T]

type Data = typeof IMMEDIATE_FAMILY_MEMBERS_LIST_FIELDS


type Elem = { readonly name: string; readonly type: string; readonly options: any }

type MapObject<El extends Elem> = {
    [P in Values<Pick<El, 'name'>> & string]: El['options']
}

type Mapper<Arr extends ReadonlyArray<Elem>, Result extends Record<string, any> = {}> =
    Arr extends []
    ? Result : Arr extends [infer H]
    ? H extends Elem
    ? Result & MapObject<H> : never : Arr extends readonly [infer H, ...infer Tail]
    ? Tail extends ReadonlyArray<Elem>
    ? H extends Elem
    ? Mapper<Tail, Result & MapObject<H>> : never : never : never

type Result = Mapper<Data>

I 've mocked some types because I don't know nothing about them.

I have used any in few places, feel free to replace it with meaningful type. Like I said, it is because I'm not aware about all constraints, but I believe that Mapper type should help you

Upvotes: 1

Related Questions