Home > Back-end >  How can I construct a generic type using properties from another type
How can I construct a generic type using properties from another type

Time:10-28

I want to be able to construct a type that accepts the type of another object and returns a different type based on certain properties in that object. This will be easier to explain in code:

// these describe the type of inputs a UI would display
// and their returned values
// e.g. a colorHex UI control should return a string
type Inputs = {
  colorHex: string
  yearPicker: number
}

// colorHex | yearPicker
type InputTypes = keyof Inputs

// Describes an input. It has a type to define the UI and a label
type Input = {
  type: InputTypes
  label: string
}

// Describes a composition of inputs. Has an ID and an object with Inputs
type Composition = {
  id: string
  inputs: Record<string, Input>
}

// Describes a map of compositions
type Compositions = Record<string, Composition>

const comps: Compositions = {
  short: {
    id: 'short',
    inputs: {
      bgColor: {
        type: 'colorHex',
        label: 'BG Color',
      },
      year: {
        type: 'yearPicker',
        label: 'Count',
      },
    },
  },
}

// I want to recieve a composition and return a map of input key and the resulting input value type
type InputProps<T extends Composition> = {
  [P in keyof T['inputs']]: Inputs[T['inputs'][P]['type']]
}

// The input prop types for comps.short
type ShortProps = InputProps<typeof comps.short>

// What I want ShortProps to equal
type Expected = {
  bgColor: string
  year: number
}

// ultimately what I want to do with this
const someFn = (props: ShortProps) => {
    // props === { bgColor: string; year: number }
}

// this is correct
someFn({ bgColor: '#000', year: 2020 })

// this is incorrect and should result in a type error
someFn({ bgColor: 0, year: '2020' })

Here is a playground link. Notice that the last line does not give a type error when it should.

CodePudding user response:

You only need to make sure that comps satisfies (or "matches") the type Compositions. Before this, you were overwriting the type information of the object by explicitly annotating it with Compositions. Currently in TS 4.8, we have to use a helper function for this:

function comp<C extends Compositions>(c: C) { return c }

const comps = comp({
  short: {
    id: 'short',
    inputs: {
      bgColor: {
        type: 'colorHex',
        label: 'BG Color',
      },
      year: {
        type: 'yearPicker',
        label: 'Count',
      },
    },
  },
});

However, in 4.9 , we can use satisfies keyword:

const comps = {
  short: {
    id: 'short',
    inputs: {
      bgColor: {
        type: 'colorHex',
        label: 'BG Color',
      },
      year: {
        type: 'yearPicker',
        label: 'Count',
      },
    },
  },
} satisfies Compositions;

Now you'll get an error on the last line, as desired.

Playground (4.8-)

Playground (4.9 )

  • Related