Home > Enterprise >  Narrow enum type to a single member
Narrow enum type to a single member

Time:08-18

enum Unit {
    Kilometer,
    Meter,
}

interface Measurement {
    distanceUnit: Unit
    distance: number
}

interface MetersMeasurement {
    distanceUnit: Unit.Meter
}

function someCalculation(item: MetersMeasurement){
    return;
}

function main(){
    const item : Measurement = {
        distance: 100,
        distanceUnit: Unit.Meter
    }

    if(item.distanceUnit !== Unit.Meter) return;
    
    someCalculation(item)
                 // ^^^^ Types of property 'distanceUnit' are incompatible.     
                 //      Type 'Unit' is not assignable to type 'Unit.Meter'.
}

Playground

  1. If MetersMeasurement and Measurement had the same fields, we could use a type predicate to narrow it down. But my problem requires that MetersMeasurement will only have a subset of the fields of Measurement. Therefore a type predicate wouldn't work.

  2. We could also change the type of distanceUnit in the interface MetersMeasurement to Unit. And then move the if check for meters inside someCalculation function. But let's also say I don't want to do that. I don't want to give the function this responsibility.

  3. We could create a new object of type MetersMeasurement based on the original object and then pass that to someCalculation() instead. But this feels like a workaround (and extra code) because we couldn't narrow down the type [playground].

Is there a way to properly narrow down the type without breaking the rules? (no any or casting)

CodePudding user response:

You can use a type guard to check whether or not the item is MetersMeasurement or Measurement

enum Unit {
    Kilometer,
    Meter,
}

interface Measurement {
    distanceUnit: Unit
    distance: number
}

interface MetersMeasurement {
    distanceUnit: Unit.Meter
}

function someCalculation(item: MetersMeasurement){
    return;
}

function isMeasurement(obj: Measurement): obj is Measurement {
  return (obj as Measurement).distance !== undefined;
}

function main(){
    const item = {
        distance: 100,
        distanceUnit: Unit.Meter
    }

    if(isMeasurement(item)) return;
    
    someCalculation(item)
}

CodePudding user response:

Try this:

enum Unit {
    Kilometer,
    Meter,
}

type Distance = {
    distance: number;
}

type MetersMeasurement = {
    distanceUnit: Unit.Meter;
} & Distance

type KilometersMeasurement = {
    distanceUnit: Unit.Kilometer;
} & Distance

type Measurement = MetersMeasurement | KilometersMeasurement;


/*
 * return km
 */
function meterCalculation(item: MetersMeasurement) {
    return item.distance * .001;
}

function main(){
    const item : Measurement = {
        distance: 100,
        distanceUnit: Unit.Meter
    }

    if (item.distanceUnit == Unit.Meter) {
        meterCalculation(item);
    }
}

Playground

  • Related