Search code examples
typescriptclass-validator

Dynamic class validation based on external (non class) property


I'm trying to use class-validator to create a dynamic validation of person data based on user's selected fields.

E.g:

// I have the following class/type
class PersonType {
  name = ''
  email = ''
  phone = ''
  motherName = ''
  ... etc
}

....

// And the user selected these fields as required fields:
['name', 'motherName'] 

Reading class-validator docs I've found conditional validation but this only shows how to use class's props as validation parameter:

import { ValidateIf, IsNotEmpty } from 'class-validator'

class PersonValidator {
  @ValidateIf(o => o.CLASS_PROP .... condition )
  @IsNotEmpty()
  name: string
  
  .... rest
}

My validation function is this right now:

import { validate } from 'class-validator'
import { plainToInstance } from 'class-transformer'

async function isClassValid(C: any, O: any) {
  const object = plainToInstance(C, O)
  const errors = await validate( object )

  if( errors.length > 0 ) throw Error('Class not valid')
  return true
}

...

app.post('/person', async (req: Request, res: Response) => {
  const {body} = req
  
 const requiredFields = await getFromDatabase(...) // returns ['name', 'motherName'] 
  try {
    await isClassValid( PersonValidator, body )
    // continue
  } catch( error ) {
    // handle error
  }
})

How can I validate class's fields based on requiredFields fields array?

What I want to achieve is:

class PersonValidator {
  @ValidateIf(() => requiredFields.includes[ 'name' ])
  @IsNotEmpty()
  name: string
  
  .... rest
}


Solution

  • You can return validator class with decorators with a function:

    function getPersonValidator(
      requiredFields: RequiredFieldsType
    ): typeof PersonType {
    
      class PersonValidator {
        @( requiredFields.includes[ 'name' ] ? IsNotEmpty : IsOptional )()
        name: string
      
        @( requiredFields.includes[ 'email' ] ? IsEmail : IsOptional )()
        email: string
    
        etc ...
      }
    
      return PersonValidator
    }
    

    Then call it in your route:

    const requiredFields = ['name', 'motherName'] 
    try {
      await isClassValid( getPersonValidator( requiredFields ), body )
      // continue
    } catch( error ) {
      // handle error
    }
    

    Hope this helps :)