Type 'string | number' is not assignable to type 'never' in switch statement

Viewed 600

I have an interface that describe the data structure from BE, and a class has method for transforming that BE data structure to our FE data structure and vice versa,but during that typescript throw Type 'string | number' is not assignable to type 'never'. Type 'string' is not assignable to type 'never' error. How do i resolve it, and why it happen ?

interface BEStructure {
  a?: string;
  b?: number;
  ...
}

class FEStructure {
  a: string = null;
  b: number = null;
  ...

  static convertToBE(fe: Partial<FEStructure>): BEStructure  {
    const be: BEStructure = {};

    Object.keys(fe).forEach(key => {
      switch (key) {
        case 'a':
        case 'b': 
          // My others logic
          // ERROR HERE
          be[key] = fe[key];
          break;
      default:
          be[key] = fe[key];
      }
    });

    return be;
  }
}

I created a reproduce here

2 Answers

TypeScript assignment doesn't seem to be clever enough to do the case analysis itself in this case. In the statement be[key] = fe[key], the right-hand side gets type string | number, while the left-hand side is assignable by type string & number which evaluates to never, i.e., it can never be assigned.

Here's one solution, which defines an assign generic function of the correct type:

interface BEStructure {
  a?: string;
  b?: number;
}

function assign<T, Key extends keyof T>(obj: T, key: Key, value: T[Key]): void {
  obj[key] = value;
}

class FEStructure {
  a?: string;
  b?: number;

  static convertToBE(fe: FEStructure): BEStructure  {
    const be: BEStructure = {};

    Object.keys(fe).forEach(key => {
      switch (key) {
        case 'a':
        case 'b': 
          //be[key] = fe[key];
          assign(be, key, fe[key])
          break;
      }
    });

    return be;
  }
}

Playground link

Please provide reproducable example. I'm unable to reproduce your error.

I think @Linda Paiste is right

I'd willing to bet that this is because of this be[key] = fe[key]; mutation.

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.

According to official explanation be[key] in be[key] = fe[key]; resolves to be['a' & 'b'] which resolves to be[never] which in triggers an error:Type 'string' is not assignable to type 'never'. Because intersection of 'a' & 'b' gives never

See these answers:

Assigning properties in an object by iterating through its keys ,

How to selectively assign from one Partial to another in typescript ,

Why can I index by string to get a property value but not to set it? ,

TypeScript: Why can't I assign a valid field of an object with type { a: "a", b: "b" }

My article dedicated to TS mutations.

Related