typescripttypescript4.0

Convert interface with nullable string property to string property


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?

Demo in TS Playground


Workarounds

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);

Solution

  • 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