17

I have a mongoose discriminator schema, which mean the data will be different according to one of the attributes.

class Feature {
  name: string
  option: ColorFeature|SizeFeature
}

class ColorFeature {
  kind: 'color'
  color: string
}

class SizeFeature {
  kind: 'size'
  size: number
}

What is the correct way to validate the Feature class so that it only accepts 2 different kinds?

kingwei
  • 643
  • 6
  • 14

2 Answers2

19

it can be achieved by using validateNested() together with class-transformer discriminator

class BaseFeature {
  kind: 'size' | 'color'
}

class ColorFeature extends BaseFeature {
  kind: 'color'
  color: string
}

class SizeFeature extends BaseFeature {
  kind: 'size'
  size: number
}


class Feature {
  name: string

  @ValidateNested()
  @Type(() => BaseFeature, {
    keepDiscriminatorProperty: true,
    discriminator: {
      property: 'kind',
      subTypes: [
        { value: SizeFeature, name: 'size' },
        { value: ColorFeature, name: 'color' },
      ],
    },
  })
  option: SizeFeature | ColorFeature;
}
kingwei
  • 643
  • 6
  • 14
10

I spent a lot of time with this, and I finally found a cleaner way of doing it than the current answer.

Basically, the decorator @Type gives us some helper options if we want to use them, such us.. the object! So, you can return one type or the other conditionally, so the validation is done over one of the two types:

class Feature {
  name: string

  @ValidateNested()
  @IsDefined()
  @Type(({ object }) => {
    if(object.option?.kind === 'color') return ColorFeature;
    else if(object.option?.kind === 'size') return SizeFeature;
    // Handle edge case where the previous ifs are not fullfiled
  })
  option: ColorFeature | SizeFeature
}

You can even use a switch case or some Record for cleanliness sake in case you have more types:

  @ValidateNested()
  @IsDefined()
  @Type(({ object }) => {
    switch(object.option?.kind){
      case 'color':
        return ColorFeature;
      case 'size':
        return SizeFeature;
      case 'shape':
        return ShapeFeature;
      default:
        // Manage edge cases
    }
  })
  option: ColorFeature | SizeFeature | ShapeFeature

Then, you also have to use validation decorators in the extended classes, so that they are correctly validated.

Javi Marzán
  • 1,121
  • 16
  • 21
  • ` Property 'object' does not exist on type 'TypeHelpOptions | undefined'.` Is it stil working? class-validator@^0.14.0 – mike927 Feb 24 '23 at 15:26