I have the following two interfaces, one which allows a nullable vin
, the other that doesn't:
interface IVehicle {
vin: string | null;
model: string;
}
interface IVehicleNonNullVin {
vin: string;
model: string;
}
I want to able to convert a model from IVehicle
to IVehicleNonNullVin
in an execution path where I'm able to infer that vin
is not null
.
Consider this example:
const myVehicle: IVehicle = {
vin: 'abc123',
model: 'm3'
};
const needsVin = (_: IVehicleNonNullVin) => false;
if (myVehicle.vin === null) {
throw new Error('null');
} else {
needsVin(myVehicle);
// ~~~~~~~~~~~~~~~~~~~ 'IVehicle' is not assignable to 'IVehicleNonNullVin'
}
Which returns the following error:
Argument of type 'IVehicle' is not assignable to parameter of type 'IVehicleNonNullVin'.
Types of property 'vin' are incompatible.
Type 'string | null' is not assignable to type 'string'.
Type 'null' is not assignable to type 'string'.
Even though I'm positive the property isn't nullable here.
Q: How can I convince TS that the existing model conforms to the type based on type checks in the code flow?
As a workaround, I can cast the type (but that ignores the existing type safety):
needsVin(myVehicle as IVehicleNonNullVin);
Or build a new model (but this doesn't scale well with lots of properties):
const nonNullVehicle: IVehicleNonNullVin = {
model: myVehicle.model,
vin: myVehicle.vin
}
needsVin(nonNullVehicle);
You can use a type predicate to define a user-defined type guard like this:
const isNonNullVin = (vehicle: IVehicle): vehicle is IVehicleNonNullVin =>{
return vehicle.vin !== null
}
if (!isNonNullVin(myVehicle)) {
throw new Error('null');
} else {
needsVin(myVehicle);
}
TypeScript will narrow that variable to that specific type if the original type is compatible.
Demo in TS Fiddle
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With