1

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.

1 Answer 1

1

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+)

Sign up to request clarification or add additional context in comments.

3 Comments

Aha, that's awesome, thank you! I had removed the explicit annotation in a previous iteration but then I kept getting a 2344 error because Typescript was not inferring typeof comps.short correctly. But either of those issues solves the problem, thank you very much!
I wasn't sure I'd fully understood what @caTS meant by "overwriting". This dev.to article helped me understand why the question code didn't work but this answer's code does.
@JSmart523 The object you assign to comps inherently has a type, but if you add an explicit type annotation, you lose that type and now the type of the variable is the type you annotated it with. That's why I called it "overwriting" - you just overwrote the original type that was to be used with a new type.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.