1

I have an enum:

enum REPORT_PARAMETERS {
    DEFECT_CODE = 'DEFECT_CODE',
    ORGANIZATION = 'ORGANIZATION'
}

And I have an object and a map of functions, which use this enum as keys:

interface Form {
  [REPORT_PARAMETERS.DEFECT_CODE]: number;
  [REPORT_PARAMETERS.ORGANIZATION]: string;
}

const form = {
  [REPORT_PARAMETERS.DEFECT_CODE]: 1,
  [REPORT_PARAMETERS.ORGANIZATION]: '1'
}

const formMappers: {
    [key in REPORT_PARAMETERS]: () => Form[key];
} = {
  [REPORT_PARAMETERS.DEFECT_CODE]: () => 123,
  [REPORT_PARAMETERS.ORGANIZATION]: () => '123'
}

Then I want to iterate enum to map form:

const reports: REPORT_PARAMETERS[] = [
     REPORT_PARAMETERS.DEFECT_CODE,
    REPORT_PARAMETERS.ORGANIZATION
]

reports.forEach((type) => {
  // At this point I assume that 'type' is a specific value of enum,
  // so I try to use this value to index both objects, but it doesn't work
  form[type] = formMappers[type]();
})

Here is TypeScript Playground to see it in action

amitei
  • 13
  • 3
  • This question doesn't have much to do with enums; you can dispense with enums and see [the same behavior](https://tsplay.dev/wO8LMN). – jcalz Aug 13 '21 at 14:05
  • @jcalz how would you name it then? Should I rename it? – amitei Aug 13 '21 at 18:16
  • Maybe "assigning properties in an object by iterating through its keys" or something? Not sure. See [microsoft/TypeScript#38735](https://github.com/microsoft/TypeScript/issues/38735) and [microsoft/TypeScript#32693](https://github.com/microsoft/TypeScript/issues/32693) – jcalz Aug 13 '21 at 19:12

1 Answers1

0

UPDATED after @jcalz comment

This is classical example.

Here you can find official explanation.

When an indexed access T[K] occurs on the source side of a type relationship, it resolves to a union type of the properties selected by T[K], but when it occurs on the target side of a type relationship, it now resolves to an intersection type of the properties selected by T[K]. Previously, the target side would resolve to a union type as well, which is unsound.

Error:

Type 'string | number' is not assignable to type 'never'.

means that string & number resolves to never because it is impossible to represent such type.

If you want to fix it, you need to avoid mutation:

enum REPORT_PARAMETERS {
  DEFECT_CODE = 'DEFECT_CODE',
  ORGANIZATION = 'ORGANIZATION'
}

interface Form {
  [REPORT_PARAMETERS.DEFECT_CODE]: number;
  [REPORT_PARAMETERS.ORGANIZATION]: string;
}

const form = {
  [REPORT_PARAMETERS.DEFECT_CODE]: 1,
  [REPORT_PARAMETERS.ORGANIZATION]: '1'
}

const formMappers: {
  [key in REPORT_PARAMETERS]: () => Form[key];
} = {
  [REPORT_PARAMETERS.DEFECT_CODE]: () => 123,
  [REPORT_PARAMETERS.ORGANIZATION]: () => '123'
}

const reports: REPORT_PARAMETERS[] = [
  REPORT_PARAMETERS.DEFECT_CODE,
  REPORT_PARAMETERS.ORGANIZATION
]

const result = reports.reduce((acc, type) => ({
  ...acc,
  [type]: formMappers[type]()

}), form)

Playground

You can find more information about mutations in typescript in my blog

  • 1
    "It resolves to intersection because objects are contravariant in their key types." Is this right? When you *read* an object from a union of key types, you get a *union* of property types, so `keyof` contravariance seems to be irrelevant here. – jcalz Aug 13 '21 at 14:03
  • Thanks for detailed explanation, it totally worked for me! – amitei Aug 13 '21 at 14:03