Reputation: 855
I ran into this problem in my code:
interface Wide {
prop: string | undefined
}
interface Narrow {
prop: string
}
class Foo {
prop: string
constructor({ prop }: Narrow) {
this.prop = prop
}
}
const array = [{ prop: undefined }, { prop: 'foo' }]
array.forEach((obj: Wide) => {
if (obj.prop && typeof obj.prop === 'string' && obj.prop !== undefined) {
console.log(new Foo({ ...obj }))
// Type 'undefined' is not assignable to type 'string'.
}
})
Normally, I would think that Typescript would be able to deduce that if the if
condition is passed, it means that the current obj
that it is iterating on has a defined property prop
with the type string
. But I can't seem to get it to work.
Upvotes: 2
Views: 414
Reputation: 376
Looks like the compiler can correctly narrow the type of obj.prop
:
if (obj.prop && typeof obj.prop === 'string' && obj.prop !== undefined) {
console.log(new Foo({ prop: obj.prop }))
}
But isn't smart enough to narrow down the type of the whole obj
in this case. You can get it to work if you rewrite Wide
to be a union:
type Wide = Narrow | {prop: undefined}
then the second variant will get filtered by the if statement.
Upvotes: 1
Reputation: 9893
That's because the obj
is in Wide
type even with string
value (not undefined
). So in this case you know more than ts compiler so you can use type assertion like this:
if (obj.prop && typeof obj.prop === 'string' && obj.prop !== undefined) {
let narrow:Narrow = obj as Narrow;
console.log(new Foo(narrow))
}
}
Upvotes: 1
Reputation: 646
You could fix it by providing a custom type predicate.
function isNarrow<T extends { prop: unknown }>(obj: T): obj is T & Narrow {
return typeof obj.prop === 'string'
}
array.forEach((obj: Wide) => {
if (obj.prop && isNarrow(obj)) {
console.log(new Foo({ ...obj }))
}
})
Note a few things:
obj.prop
part of the condition, but note that it does not accept the empty string. The obj.prop !== undefined
part was not necessary because obj.prop
cannot be undefined if its type is 'string'
.An alternative solution is to just use type assertions.
array.forEach((obj: Wide) => {
if (obj.prop && typeof obj.prop === 'string') {
console.log(new Foo({ ...obj } as Narrow))
}
})
Upvotes: 1
Reputation: 4352
You need to use Type guard (type predicate)
interface Wide {
prop: string | undefined
}
interface Narrow {
prop: string
}
class Foo {
prop: string
constructor({ prop }: Narrow) {
this.prop = prop
}
}
const array = [{ prop: undefined }, { prop: 'foo' }]
function isNarrow(obj: Wide | Narrow): obj is Narrow {
return typeof obj.prop === 'string';
}
array.forEach((obj: Wide) => {
if (isNarrow(obj)) {
console.log(new Foo({ ...obj }));
}
})
Upvotes: 3