4

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

Ethan Vu
  • 2,911
  • 9
  • 25
  • do you have a field called `never` in DB? What is the type of never? – balderman Aug 16 '21 at 14:59
  • @balderman `never` is a TypeScript type. Not yet sure why it's implicitly occurring here though... – edemaine Aug 16 '21 at 14:59
  • @balderman [Use of never keyword in typescript](https://stackoverflow.com/q/42291811) – VLAZ Aug 16 '21 at 15:01
  • @edemaine OK. Thanks. I was not aware of never. – balderman Aug 16 '21 at 15:02
  • 1
    None of the other [_"...is not assignable to type 'never'"_](https://www.google.com/search?q=%22is+not+assignable+to+type+%27never%27%22+site%3Astackoverflow.com) questions helped? – Andreas Aug 16 '21 at 15:04
  • If you encounter such problems, try to reduce the error to the minimum. Your problem seems to be the same as with [this shortened example](https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgILIN4ChnLgfgC5kBnMKUAcwG4dkAjI5EAVwFt7paBfLUSWIhQAhTHTjEyFEDTr1irDlyy8sCAPYgyDYqIC8Y3BOQByGOvUmANHOIBGFWs3bj6AxlUB5egCsICMAA6AGsIAE8SAAp6AEpA8ygAUUQAC0jQsOQ9AD5DZGAYZHTwrL0DEzgTZAAfauQM0vL6Exi6IwBtDIBdLIZO8K6VGKA). – A_A Aug 16 '21 at 15:06
  • I'm not sure yet why this error occurs, but a simple workaround would be to wrap it into `if (key === 'a') be[key] = fe[key]` and `if (key === 'b') be[key] = fe[key]` – A_A Aug 16 '21 at 15:07
  • Is the `BEStructure` the same but optional? Have you tried `Partial` as a type? – crashmstr Aug 16 '21 at 15:10
  • @crashmstr I also tried that, but I think it not the case – Ethan Vu Aug 16 '21 at 15:14
  • @Andreas I'm still digging, but none help so far :( – Ethan Vu Aug 16 '21 at 15:16
  • @A_A thanks ! that workaround would work, but it could have more property in the switch statement – Ethan Vu Aug 16 '21 at 15:18
  • Checking the other questions is something you should really do before asking the question. It's part of the research phase. See [ask]; the first section is titled "Search, and research..." Note that your definition of `FEStructure` does not match that of `BEStructure` in terms of the types of `a` and `b`. In `FEStructure` they are essentially required, but allow null. In `BEStructure` they are optional and therefore allow undefined, but, if you have `strictNullChecks` enabled, not null. – Heretic Monkey Aug 16 '21 at 15:30
  • 1
    The TS error happens because the switch case applies to both a and b at once creating an impossible type condition as it can’t be both string and number. If you separate the two you won’t have an error. – Linda Paiste Aug 16 '21 at 15:42
  • @LindaPaiste Surely it's a string OR a number in the body of the case statement? key can't be both a and b simultaneously? It looks to me like the compiler is just getting confused, not that there's anything fundamentally wrong? – Rich N Aug 16 '21 at 15:54
  • 1
    @Rich N fe[key] is string OR number but be[key] requires string AND number because that’s the only safe way to assign to a property which could require either type. It’s just a TS error, not an actual runtime problem, so it could be bypassed with an as assertion. It’s a limitation of the compiler, which doesn’t understand that whichever type be[key] needs is the same type that fe[key] is. – Linda Paiste Aug 16 '21 at 16:00

2 Answers2

1

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

edemaine
  • 2,699
  • 11
  • 20
0

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.