简体   繁体   中英

How to return object interface with variable as a key in TypeScript? Type is not assignable to type error

I have the following type and interfaces.

type ColVal = [{
    col: string
}, {
    val: string
}]

interface IEquals {
    eq: ColVal,
}

interface INotEquals {
    ne: ColVal,
}

And I have the following function:

const getOperation = (col: string, val: string, operation: string): IEquals | INotEquals => {
    let op: 'eq' | 'ne' = operation === 'Equals' ? 'eq' : 'ne';
    return {
        [op]: [{
            col,
        }, {
            val,
        }]
    };
};

But I get the error Type '{ [x: string]: ({ col: string } | { val: string; })[]; }' is not assignable to 'IEquals | INotEquals'. Type '{ [x: string]: ({ col: string } | { val: string; })[]; }' is not assignable to 'IEquals | INotEquals'.

If I change [op] to ['eq'] or ['ne'] , the error goes away. Does anybody know how to fix this?

Here is the TypeScript playground for you guys to see the issue: Playground .

It's currently a design limitation of TypeScript that computed properties of union types have their key widened all the way to string by the compiler. So an object literal like {[Math.random()<0.5 ? "a" : "b"]: 123} {[Math.random()<0.5 ? "a" : "b"]: 123} is inferred by the compiler to be of the type {[k: string]:number} instead of the more specific {a: number} | {b: number} {a: number} | {b: number} .

Both microsoft/TypeScript#13948 and microsoft/TypeScript#21030 concern this issue. It looks like there was some attempt to address it at one point, microsoft/TypeScript#21070 ) but it fizzled out.

I don't know if it will ever be addressed, but for now you'll have to work around it.


The least disruptive (and least type safe) workaround this is just to assert that the return value is of the appropriate type. In this case the compiler sees the return value is of a type that's so wide that it doesn't even see it as related to IEquals | INotEquals IEquals | INotEquals . So you'll have to assert through some intermediate type... might as well just use any , the ultimate "just make it work" type:

const getOperationAssert = (col: string, val: string, operation: string): IEquals | INotEquals => {
    let op: 'eq' | 'ne' = operation === 'Equals' ? 'eq' : 'ne';
    return {
        [op]: [{
            col,
        }, {
            val,
        }]
    } as any; // 🤓 I'm smarter than the compiler 
};

Another idea is to manually implement a helper function that behaves the way computed properties "should" behave. Like this:

function computedProp<K extends PropertyKey, V>(key: K, val: V): { [P in K]: { [Q in P]: V } }[K];
function computedProp(key: PropertyKey, val: any) {
    return { [key]: val };
}

So if you call computedProp(Math.random()<0.5 ? "a" : "b", 123) , the implementation just makes an object with a computed property, but the typing is such that it returns {a: number} | {b: number} {a: number} | {b: number} . Then your getOperation() becomes:

const getOperationHelper = (col: string, val: string, operation: string): IEquals | INotEquals => {
    let op: 'eq' | 'ne' = operation === 'Equals' ? 'eq' : 'ne';
    return computedProp(op, [{
        col,
    }, {
        val,
    }]);
};

Finally, if you're willing to refactor, you could consider not using computed properties at all. Store your ColVal value in a variable, and then return it as the property of either an object literal with a literal eq key or one with a literal ne key. The compiler can follow that flow much more accurately and can verify it as safe:

const getOperationRefactor = (col: string, val: string, operation: string): IEquals | INotEquals => {
    let op: 'eq' | 'ne' = operation === 'Equals' ? 'eq' : 'ne';
    const colVal: ColVal = [{ col }, { val }];
    return (operation === 'Equals') ? { eq: colVal } : { ne: colVal };
};

Hopefully one of those works for you.

Playground link to code

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM