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.