typescripttypestype-inference

Is it possible in Typescript to infer/derive different types based on object properties?


Is it possible in TypeScript, from this data:

const data = {
  field1: {values: ['a', 'b', 'c']},
  field2: {values: ['c', 'd', 'e'], multiple: true}
}

const fields = someFunction(data)

To infer/derive the following return value of someFunction (and hence type of fields), based on whether multiple exists or not (or if it's true or not, if that makes things easier)?

type FieldsType = {
  field1: 'a' | 'b' | 'c',
  field2: ('c' | 'd' | 'e')[]
}

data can be as const if that's what's required.


Solution

  • First you want a type for your source data:

    type Data = Record<string, {
      values: readonly string[],
      multiple?: boolean
    }>
    

    Then you can make a mapped conditional type that accepts that:

    type Fields<T extends Data> = {
      [K in keyof T]:
        T[K]['multiple'] extends true
          ? T[K]['values'][number][]
          : T[K]['values'][number]
    }
    

    This type maps over all keys in T (field1 and field2), and resolve the value type of that property as a conditional type.

    That conditional type says that if T[K]['multiple'] extends true then returns an array of the member type of the values of that property, else return a non-array of that type.

    You can now use that type like:

    function someFunction<T extends Data>(data: T): Fields<T> {
      // TODO: implement this
    }
    

    Which does what you expect:

    const fields = someFunction({
      field1: { values: ['a', 'b', 'c'] },
      field2: { values: ['c', 'd', 'e'], multiple: true }
    } as const)
    
    fields.field1 // ('a' | 'b' | 'c')[]
    fields.field2 // 'a' | 'b' | 'c'
    

    See Playground