Typescript class validator that validates based on varying data types

Currently, I am utilizing TypeORM and seeking ways to dynamically set the validation fields based on the value of another field. Let me illustrate this using my DTO model:

import { IsString, IsOptional, IsNumber, IsEnum, IsObject, IsBoolean, ValidateNested } from 'class-validator';

export enum AttributeTypes {
    DATE = 'DATE',
    TIME = 'TIME',
    NUMBERS = 'NUMBERS',
}

export class BaseValidation {
    @IsOptional()
    @IsBoolean()
    required: boolean;
}

export class modelCreate {
    @IsOptional()
    @IsNumber()
    id: number;

    @IsOptional()
    @IsString()
    label: string;

    @IsOptional()
    @IsEnum(AttributeTypes)
    type: AttributeTypes;

    @IsOptional()
    @IsObject()
    @ValidateNested()
    validation: BaseValidation;
}

An issue arises with the validation field within modelCreate as it can vary in structure and properties when stored in the database:

validation: {
   required: true,
   text: 2
}

or may appear like this:

   validation: {
       required: false,
       number: 1,
       maxNumber: 10
    }

This variability is dependent on the type property within modelCreate. For example, if the type is 'TIME', the desired validation would be:

BaseValidation {
    @IsBoolean()
     required: true,
    @IsString()
    text: 2
}

Conversely, if the type is 'NUMBERS', the expected validation format changes to:

  BaseValidation {
           @IsBoolean()
           required: boolean,
           @IsNumber()
           number: number,
           @IsNumber()
           maxNumber: number
        }

The main query revolves around how to switch between different classes in the validation field based on the value of the type field within class validator, and whether such a functionality is feasible.

Answer №1

To achieve this, it is necessary to create two separate classes for handling strings and numbers respectively. Next, update the type to

validation: StringBaseValidation | NumberBaseValidation;
. This will allow the class-validator to differentiate between the two cases.

If the data is being retrieved from a request, you should use

@Type((obj) => obj.writeConditionToDetectString ? StringBaseValidation : NumberBaseValidation)
to assist the library in identifying the correct type of data coming from the request.

Answer №2

Ensure you have a distinct field to facilitate the class-transformer in recognizing the type for casting and utilizing in validation. This can also be applied to nested array objects by using the "required" field.

For instance, if the value of the property required is true, then utilize class A; otherwise, if it is false, use class B.
In the discriminator property, define a field that distinguishes between different polymorphic forms of nested objects. The subTypes.[].value represents the class used in validation, while subTypes.[].name signifies the value expected by the discriminator field to match the corresponding class specified in subTypes.[].name.

import { Type } from 'class-transformer';
import { IsBoolean, IsNumber, IsString, ValidateNested } from 'class-validator';

class Discriminator {
  @IsBoolean()
  required: boolean;
}

class A extends Discriminator {  
  @IsString()
  text: string;
}

class B extends Discriminator {  
  @IsNumber()
  number: number;

  @IsNumber()
  maxNumber: number
}

export class C {

  // Other fields...

  @ValidateNested()
  @Type(() => Discriminator, {
    discriminator: {
      property: 'required',
      subTypes: [
        { value: A, name: true as unknown as string },
        { value: B, name: false  as unknown as string },
      ],
    },
    keepDiscriminatorProperty: true,
  })
  validation: A | B;
}

Update your controller to reflect these changes:

@Controller()
export class AppController {
  @Post()
  public example(
    @Body(new ValidationPipe({ transform: true }))
    body: C,
  ) {
    return body;
  }
}

You can now make requests as follows:

curl --location 'localhost:3000/' \
--header 'Content-Type: application/json' \
--data '{
  "validation": {
    "required": true,
    "text": "Some text..."
  } 
}'

If you adjust the second element of the required property to false, an error will occur:

{
- "required": true,
+ "required": false,
  "text": "Some text..."
}
{
  "statusCode": 400,
  "message": [
    "validation.number must be a number conforming to the specified constraints",
    "validation.maxNumber must be a number conforming to the specified constraints"
  ],
  "error": "Bad Request"
}

References: https://github.com/typestack/class-transformer#providing-more-than-one-type-option

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Challenges faced with Angular 4 TypeScript destructuring implementation

When using the syntax {this.firstName, this.lastName} = this.data;, destructuring does not work as expected. {this.firstName, this.lastName} = this.data; The value of this.data is: {firstName: 'joe', lastName: 'smith'} However, afte ...

Incorporating a CSS Module into a conditional statement

Consider the following HTML structure <div className={ `${style.cell} ${cell === Player.Black ? "black" : cell === Player.White ? "white" : ""}`} key={colIndex}/> Along with the associated CSS styles .cell { ...

Ionic 4 - Best practices for handling asynchronous data with multiple observables

I successfully accessed this route: http://localhost:8100/questions/question?id=3 However, I am facing a challenge in handling two subscribers simultaneously. The first subscriber is responsible for loading the questions array from an external service. ...

Is there a way to customize the language used for the months and days on the DatePicker

Is there a way to change the language of the DatePicker (months and days) in Material UI? I have attempted to implement localization through both the theme and LocalizationProvider, but neither method seems to work. Here are some resources I have looked a ...

Is it possible to escape the code within the setup block using the Vue-Composition API?

<script setup lang="ts"> import router from '@/router'; import { useMainStore } from '@/stores/main'; import { ref } from 'vue'; const mainStore = useMainStore(); const x = ref<object| undefined>(); if ...

What is the method to make a String bold when sending it through a messaging service?

Here is the structure of my service: import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root', }) export class MessageService { messages: string[] = []; add(message: string) { this.messages.push(message); ...

Defining the signature of an unnamed function in TypeScript

Within my Express code, I have an anonymous function set up like this: app.use((err, req, res, next) => { // ... }); I am looking to specify the type of the function as ErrorRequestHandler (not the return type). One way to achieve this is by defining ...

resolved after a new promise returned nothing (console.log will output undefined)

Here is my Promise Function that iterates through each blob in Azure BlobStorage and reads each blob. The console.log(download) displays the values as JSON. However, when trying to close the new Promise function, I want the resolve function to return the ...

What is the best method for transforming an object into an interface without prior knowledge of the keys

I am looking for a solution to convert a JSON into a TypeScript object. Here is an example of the JSON data: { "key1": { "a": "b" }, "key2": { "a": "c" } } The keys key1 and key2 a ...

Remove the array stored in the local storage of an Ionic 2 application

In my application, I store data in a string. To convert the data into arrays, I use JSON.parse. this.items = JSON.parse(todos); On the results page, I display my arrays as follows: Array1 Array2 Array3 However, I have noticed that the delete button aft ...

The 'void' data type must be equipped with a '[Symbol.iterator]()' function that produces an iterator

Whenever I execute the post() function below, it triggers an error message stating: Type 'void' must have a '[Symbol.iterator]()' method that returns an iterator. This is the code snippet causing the issue: static async post(options: ...

Setting key-value pairs in TypeScript objects explained

I encountered an issue with setting key/value pairs on a plain object. type getAObjectFn = <K extends string, V>(k: K, v: V) => Record<K, V> const getAObject: getAObjectFn = (k, v) => { return { [k]: v } } console.log(getAObject ...

Refreshing Angular 2 routes when parameters are updated

I am currently embarking on my Angular 2 Application development journey. I have created an OverviewComponent with a straightforward template structure as shown below: <div class="row"> <div class="col-lg-8"> <router-outlet></ro ...

Check if a string contains only special characters and no letters within them using regular expressions

My goal is to validate a string to ensure it contains letters only between two '#' symbols. For example: #one# + #two# - is considered a valid string #one# two - is not valid #one# + half + #two# - is also not a valid string (only #one# and # ...

Show the login form and accompanying controls in the center of the screen with Angular 6

Currently, I am working on developing a Reactive form using Angular 6. In my TypeScript file, I have successfully obtained the form instance along with form controls. The next step involves iterating through these form controls and displaying the user inpu ...

What is the reason for the distinction between {[x: string]: this} and Record<string, this> in TypeScript class member definitions?

Context Recently, I inquired about declaring a class member that is a function with covariant parameters related to the class, and it was suggested to utilize the polymorphic this type which proved to be a perfect solution. However, when implementing thi ...

Presenting information on the user interface

Recently, I have been working on an API endpoint that retrieves comments by ID, using the endpoint get/comments/:id. When I tested this endpoint using Postman, the response I received was as follows: { "id": 401478, "page": 1, "results": [ ...

Type of Data for Material UI's Selection Component

In my code, I am utilizing Material UI's Select component, which functions as a drop-down menu. Here is an example of how I am using it: const [criteria, setCriteria] = useState(''); ... let ShowUsers = () => { console.log('Wor ...

Transfer the export to a different file and update all the files that import it accordingly

Is there a way, particularly in Typescript, to seamlessly move an export statement like the one below to an existing file and automatically update all files that import it? export const MyVar = 3; In the case of refactoring using Right Click > Refactor ...

Angular fails to combine values within routerLink

The issue is straightforward - I have a component that retrieves the last searched items saved in sessionStorage as an array of ListItem objects: export class SearchlistComponent { results = JSON.parse(<string>sessionStorage.getItem("lastSear ...