kingwei
kingwei

Reputation: 773

class-validator validate discriminated union type

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?

Upvotes: 23

Views: 15179

Answers (2)

Javi Marzán
Javi Marzán

Reputation: 1340

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.

Upvotes: 13

kingwei
kingwei

Reputation: 773

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

Upvotes: 26

Related Questions