I have a tuple object with nested tuples
const foo = [
{ id: 't1', values: ['a', 'b'] },
{ id: 't2', values: ['a', 'c'] },
{ id: 't3', values: ['b', 'c'] },
] as const;
I want to be able to filter this based on tuple values but also discriminate the type.
this function would give you the right runtime value but gives a type error
var objsWithA = foo.filter(({ values }) => values.includes('a'));
If you bypass the type error using as any
or as never
the resulting type doesn't eliminate t3
var objsWithA = foo.filter(({ values }) => values.includes('a' as never));
Is it possible to get the resulting type to match the returned value? ie
[
{ id: 't1', values: ['a', 'b'] },
{ id: 't2', values: ['a', 'c'] },
]
TypeScript has some support for using the array
filter()
method to narrow the type of the resulting array, by invoking this call signature:meaning the
predicate
argument needs to be a custom type guard function whose return type is a type predicate.Unfortunately TypeScript does not infer type predicates from function implementations, even in straightforward cases like `x => typeof x === "string". There is an open issue at microsoft/TypeScript#38390 asking for such support.
And even if that did happen, it probably wouldn't work for
({values})=>values.includes("a")
; even a direct call to theincludes()
array method doesn't act as a type guard. This request was declined in microsoft/TypeScript#31018 because it's too complex to get it right.That means you'd have to call
filter()
with a callback that you explicitly write to be a custom type guard function, and use it wisely. Here's one possible way to generate the type guard function for a slightly more general case than the one you have:Here
objPropHasArrayContainingStringLiteral(propName, stringLiteral)
produces a type guard function that checks an object for whether or not it has an array property atpropName
which containsstringLiteral
.The type output is using a distributive conditional type to filter a union type
U
to just those members whose property at keyK
is a readonly array containingT
elements (at least, assumingT
is a string literal type and that the array types ofU
themselves hold string literal types. It's tricky to get right, as mentioned above.So for your example it would be
objPropHasArrayContainingStringLiteral("values", "a")
:That works, hooray. But I'm not sure the problem we're solving here is worth that more general solution. If you're only going to do this once, then you might as well just do it all inline:
which is the same thing, just with
K
hardcoded with"values"
andT
hardcoded as"a"
. Essentially it's saying that the callback filters theU
union to just those members whosevalues
property is a readonly array which might be holding a value of type"a"
.And note well, the compiler really doesn't verify the implementation of custom type guard functions. You could change
obj.values.includes("a")
to!obj.values.includes("a")
orobj.values.includes("whoops")
and the compiler wouldn't notice. So again, you have to be careful.Playground link to code