Home > Back-end >  Type 'string | number' is not assignable to type 'never' in Typescript
Type 'string | number' is not assignable to type 'never' in Typescript

Time:12-06

When I write some code, I have some problems like that:


function getObjectKeys<T extends object>(object: T) {
    return Object.keys(object) as (keyof T)[]
}

const props = {
    propA: 100,
    propB: 'text'
}

const store = { ...props }

getObjectKeys(props).forEach((key) => {
    store[key] = props[key]
})

reported some errors:

const store: {
    propA: number;
    propB: string;
}
Type 'string | number' is not assignable to type 'never'.
  Type 'string' is not assignable to type 'never'.

when I write like this:


getObjectKeys(props).forEach((key) => {
    if (key === 'propA') {
        store[key] = props[key]
    } else if (key === 'propB'){
        store[key] = props[key]
    } else {
        store[key] = props[key]
    }
})

It can work but not so good. how to solve them?

CodePudding user response:

The underlying issue is a type safety improvement implemented by microsoft/TypeScript#30769 when doing assignments to unions of object properties. When faced with code like

type PropKeys = keyof typeof props; // "propA" | "propB"
getObjectKeys(props).forEach((key: PropKeys) => {    
    store[key] = ... // what is allowable here
});

the compiler sees that key is of the union type "propA" | "propB". It doesn't know whether key is "propA" or "propB", so to be safe, it only wants to allow assignments which would work no matter which it turns out to be. That means the intersection of the property types. Since these types are number and string, the intersection is number & string, which reduces to the impossible never type because no values are of both types. And so the compiler cannot allow any assignment to store[key].

The fix in this case is to replace values of union types with values of generic types that are constrained to the union. When the key or object is generic, the safety check from microsoft/TypeScript#30769 is not applied. This is potentially unsafe, but it is allowed, as mentioned in this comment. It looks like this:

getObjectKeys(props).forEach(<K extends PropKeys>(key: K) => {
  store[key] = props[key]; // okay
})

That compiles with no error, and is as close to type safe as you can get. If you change the assignment to something definitely unsafe, like copying props.propA into store[key], you will get a warning again:

getObjectKeys(props).forEach(<K extends PropKeys>(key: K) => {  
  store[key] = props.propA; // error!
})

Playground link to code

CodePudding user response:

You should avoid using object in TS. Its not recommend or ideal solution instead use Record<..., ...>.

Target object store must to be Record<string, any> to get your code working since TS don't know which properties will be added ahead of time.

function getObjectKeys<T extends Record<string, any>>(object: T) {
    return Object.keys(object) as (keyof T)[]
}

const props = {
    propA: 100,
    propB: 'text'
}

const store: Record<string, any> = {}

getObjectKeys(props).forEach((key) => {
    store[key] = props[key]
})

playground link

CodePudding user response:

you can define store as const store: Record<string | number, any> = { ...props }

this way you are telling the compiler that store object can have any string or number as a key and anything as a value

  • Related