Home > database >  Inline-hint object literal property without coercion?
Inline-hint object literal property without coercion?

Time:01-05

I have a big object literal and the type inference is perfect except one property, which I have to cast:

const obj = {
    a: 'big',
    ol: 'object',
    with: 'a lot of properties',
    nums: [] as number[], // would be undefined[] or unknown[] without cast
}

This works perfectly well except the as irks me and shows up in my linter and my running total-coercion-counter. I can also do const nums: number[] = [] on a separate line but then I'm using multiple declarations for what's really one object. Writing out the whole interface is unnecessary and verbose. Is there any kind of inline soft-cast or hint so I can get the : number[] benefits with the inline-ness of as? Angle bracket casts also make my linter angry plus I'm using JSX.

CodePudding user response:

You are looking for something like a "widening-only assertion" or a "satisfies operator" as requested in microsoft/TypeScript#7481. The idea is that you'd write something like

const obj = {
  a: 'big',
  ol: 'object',
  with: 'a lot of properties',
  nums: [] satisfies number[] // <-- not valid as of TS4.5
}

and the compiler would contextually interpret [] to be a type assignable to number[], and give an error if it can't. That would be safer than a type assertion (which you're calling "casting" or "coercion") which will allow you to unsafely narrow a value to a type. Currently "hello" as "goodbye" is allowed, but "hello" satisfies "goodbye" would be an error.

Anyway, there is currently no built-in way to do this in TypeScript. There was a recent design discussion in microsoft/TypeScript#46872 and microsoft/TypeScript#46878, and there's a possible implementation at microsoft/TypeScript#46827. But it's not clear when this will make it into the language. For now there are only workarounds.


A common workaround is to have a generic helper identity function:

const satisfies = <T,>(x: T) => x;

And then write satisfies<Type>(value) in place of value satisfies Type:

const obj = {
  a: 'big',
  ol: 'object',
  with: 'a lot of properties',
  nums: satisfies<number[]>([]) // <-- this works
}

/* const obj: {
    a: string;
    ol: string;
    with: string;
    nums: number[];
} */

Now the compiler sees nums as type number[], as desired. This version of satisfies also gives compiler errors in cases where a type assertion succeeds:

"hello" as "goodbye"; // no error
satisfies<"goodbye">("hello") // error!

So this workaround is about as good as it gets in current TypeScript. Yes, it has observable runtime effects since the emitted JavaScript now has an extra function call, but it's a fairly innocuous change. If you're only going to do such an operation once in your code base, then you might as well write const nums: number[] = []. But if you run into this issue at least twice, then the satisfies helper function probably pays for itself.

Playground link to code

  •  Tags:  
  • Related