5
interface modal {
    name: string;
    check: boolean;
}

type Key = "name" | "check";

const obj: modal = fn();

function fn():any {
    return {
        name: "hello"
    }
}

class Modal {
    name: string;
    check: boolean;
    constructor() {
        this.name = "";
        this.check = false;
        Object.keys(obj).forEach((key: string) => {
            this[key as keyof modal] = obj[key as keyof modal];
        })
    }
}

I get an error at this[key as keyof modal]

Error message: Type 'string | boolean' is not assignable to type 'never'. Type 'string' is not assignable to type 'never'. enter image description here

2
  • Inside the Modal class, keyof modal won't allow you to index into a Modal object even though it happens to have the same keys as modal, an unrelated interface. You need keyof Modal. Commented Jan 18, 2022 at 9:42
  • You mean change to this[key as keyof Modal] instead of this[key as keyof modal]? Unfortunately, it does not work neither Commented Jan 18, 2022 at 9:51

1 Answer 1

4

Object.keys always return string[], no matter what. Of course, it is expected that it returns Array<keyof typeof obj> in this case, but it is not true. Please see this list of issues on github.

First thing that should be done in this case is type assertion.

(Object.keys(obj) as Array<keyof typeof obj>)

However, this is not the end. There is still an error here:

this[key] = obj[key]; // error

In general, TS does not like mutations. Please see my article and this answer.

Type of this[key] and obj[key] is string | boolean.

Please see this code:

type Key = "name" | "check";

let _name: Key = 'name'
let _check: Key = 'check'
obj[_name] = obj[_check] // error

Above code is almost equal to yours, except, your mutation is inside of iterator and mine is not. There is no binding between iteration index and type of key.

See example:

(Object.keys(obj) as Array<keyof typeof obj>)
  .forEach((key, index) => {
    if (index === 0) {
      const test = key // keyof modal and not "name"
    }
  })

It is correct behavior, because even JS specification does not give you a guarantee that first key is name. JS engine reserves the right to return you keys in any order. Sure, in 99.99% cases you will get the expected order, but it does not mean that you have a guarantee.

So, why do we have never in the error message ? TypeScript makes an intersection of expected keys (of union) because it is safer to get common type. Intersection of string & boolean - gives you never, this is why you have this error message.


I believe the best you can do without using type assertions is to call reduce:

interface modal {
  name: string;
  check: boolean;
}

type Key = "name" | "check";

const obj: modal = fn();

function fn(): any {
  return {
    name: "hello"
  }
}

class Modal implements modal {
  name: string;
  check: boolean;
  constructor() {
    this.name = "";
    this.check = false;
    const result = (Object.keys(obj) as Array<keyof typeof obj>)
      .reduce((acc, key) => ({
        ...acc,
        [key]: obj[key]
      }), this)
    Object.assign(this, result)
  }
}

Playground It worth using implements modal or capitalize modal interface.

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

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.