#Types not narrowing with discriminated union
23 messages · Page 1 of 1 (latest)
Preview:```ts
const address = {
id: '123',
fieldConfig: {
type: 'address'
},
value: {
city: 'asdf'
}
}
const num = {
id: '3456',
fieldConfig: {
type: 'number'
},
value: 123
}
type Field = typeof address | typeof num
const fn = (field: Field) {
...```
you have a few problems here
first of all, if you look at the types of the fieldConfig properties you'll see they are merely { type: string }. there's no type-level difference between address.fieldConfig and num.fieldConfig
you might've meant to use as const on those values
however, even if you do that your narrowing attempt will not work
discriminated unions must follow a specific pattern of having a literal-typed key at the top level
Understand how TypeScript uses JavaScript knowledge to reduce the amount of type syntax in your projects.
the only extra narrowing rule you get with discriminated unions is that x may be narrowed when you check x.field, if field is a discriminant property
when you check field.fieldConfig.type, only field.fieldConfig will ever be narrowed, not field itself. it's not "deep"
if you can move type up to the top level it'll work
Wow, thanks!
Is this behavior documented somewhere? There are lots of examples on TS docs, but nothing explicit about the mechanism ike you’ve described
@floral bramble
when you check field.fieldConfig.type, only field.fieldConfig will ever be narrowed, not field itself. it's not "deep"
I’ve tried to access the fieldConfig based on what you’ve said to see if it’s narrowed, but it doesn’t seem to be
The Playground lets you write TypeScript or JavaScript online in a safe and sharable way.
@hearty helm Here's a shortened URL of your playground link! You can remove the full link from your message.
Preview:```ts
const address = {
id: '123',
fieldConfig: {
type: 'address',
address: 'true'
},
value: {
city: 'asdf'
}
}
const num = {
id: '3456',
fieldConfig: {
type: 'number',
num: true
},
value: 123
}
type Field = typeof address | typeof num
...```
the documentation page i linked to above says this:
When every type in a union contains a common property with literal types, TypeScript considers that to be a discriminated union, and can narrow out the members of the union.
In this case,
kindwas that common property (which is what’s considered a discriminant property ofShape). Checking whether thekindproperty was"circle"got rid of every type inShapethat didn’t have akindproperty with the type"circle". That narrowedshapedown to the typeCircle.
doesn’t mention that the type must not be nested though
right, it says what does work (and that's all that works). i do agree it could spell this out more clearly
you forgot the as consts again. whenever you encounter errors like this a good first move is to hover over the relevant variables (where you'll see fieldConfig is just always { type: string } there)
I mean I’d assume common property inside a type can very well be a nested one, so yeah it’s not clear at all from the docs:
type A = {
property: 'a',
nested: {
property: {
property: 'b'
}
}
}
ah yeah i don't think "contains" implies recursion or anything there. maybe it's clearer when i swap it for "has":
When every type in a union has a common property with literal types
btw, do you know whether this is a limitation of the TS language of sorts, or an intended behaviour?
On a first look it seems like it should work this way 🤔
i believe it's simple for performance reasons. computing every possible permutation at every depth of an object would be expensive/slow. i'll see if i can dig up any source for that
https://github.com/microsoft/TypeScript/issues/18758 is a relevant issue
there was an attempt to implement it that sadly didn't land: https://github.com/microsoft/TypeScript/pull/38839