Custom Validator of Map Keys #2064
              
                Unanswered
              
          
                  
                    
                      dieeisenefaust
                    
                  
                
                  asked this question in
                Questions & Answers
              
            Replies: 1 comment
-
| Updated the logic to to be more generic, so users can feed it the enum type they wish to validate against along with logic to verify the constraint parameter was provided. @ValidatorConstraint({ name: "customMapKeyValidator", async: false })
export class customMapKeyValidator implements ValidatorConstraintInterface {
  validate(map: Map<string, string>, args: ValidationArguments) {
    let validationAnswer = true;
    if (args.constraints && args.constraints[0] instanceof Object) {
      map.forEach((value, key) => {
        if (!(key in args.constraints[0])) {
          validationAnswer = false;
        }
      });
    } else {
      validationAnswer = false;
    }
    //console.log(validationAnswer);
    return validationAnswer; // for async validations you must return a Promise<boolean> here
  }
  defaultMessage(args: ValidationArguments) {
    const invalidKeys: Array<string> = [];
    const map: Map<string, string> = args.value;
    if (args.constraints && args.constraints[0] instanceof Object) {
      map.forEach((value, key) => {
        if (!(key in args.constraints[0])) {
          invalidKeys.push(key);
        }
      });
    } else {
      return "Constraint parameter not provided or was not an Enum.";
    }
    if (invalidKeys.length === 1) {
      return `Key [${invalidKeys}] is not an accepted value.`;
    } else {
      return `Keys [${invalidKeys}] are not accepted values.`;
    }
  }
}Usage:   //...see code in previous post for PhoneType enum
   export enum Roles {
     Manager = "Manager",
     Employee = "Employee",
   }
   class User {
   ...
  @IsOptional({ groups: ["create"] })
  @MaxLength(12, { each: true, groups: ["create"] })
  @Validate(customMapKeyValidator, [PhoneType], { groups: ["create"] })
  @Type(() => String)
  @Transform(({ value }) => new Map(value), { toClassOnly: true })
  @Transform(({ value }) => JSON.stringify(Array.from(value.entries())), {
    toPlainOnly: true,
  })
  public phoneNumbers: Map<PhoneType, string>;
  @IsOptional({ groups: ["create"] })
  @Validate(customMapKeyValidator, [Roles], { groups: ["create"] })
  @Type(() => String)
  @Transform(({ value }) => new Map(value), { toClassOnly: true })
  @Transform(({ value }) => JSON.stringify(Array.from(value.entries())), {
    toPlainOnly: true,
  })
  public roles: Map<Roles, string>;
}Output: [
  ValidationError {
    target: User {
      username: 'example1',
      firstName: 'Kevin',
      lastName: 'Smith',
      password: '[***]',
      phoneNumbers: Map(3) {
        'Home' => '123-456-7890',
        'Cell' => '098-765-4321',
        'TV' => '098-765-4321'
      },
      roles: Map(2) {
        'Manager' => 'ref1',
        'Temp' => 'ref1'
      }
    },
    value: {},
    property: 'phoneNumbers',
    children: [],
    constraints: {
      customMapKeyValidator: 'Keys [Cell,TV] are not accepted values.'
    }
  },
  ValidationError {
    target: {
      username: 'example1',
      firstName: 'Kevin',
      lastName: 'Smith',
      password: 'thisisatest',
      phoneNumbers: Map(3) {
        'Home' => '123-456-7890',
        'Cell' => '098-765-4321',
        'TV' => '098-765-4321'
      },
      roles: Map(2) {
        'Manager' => 'ref1',
        'Temp' => 'ref1'
      }
    },
    value: {},
    property: 'roles',
    children: [],
    constraints: {
      customMapKeyValidator: 'Key [Temp] is not an accepted value.'
    }
  }
] | 
Beta Was this translation helpful? Give feedback.
                  
                    0 replies
                  
                
            
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment
  
        
    
Uh oh!
There was an error while loading. Please reload this page.
-
I have not found any other documentation on this and the one closed issue I found simply stated that class-validator did not validate keys.
So I decided to post this solution for validating map keys which are of type Enum and post an oustanding question I have about custom validators as well.
Maps obviously allow for dynamic key names, but canned validators look at the values, not the keys.
This means, if you want to use a canned validator, you have to use something like an Array or Object and store a 'type' and 'value' key/value pairs separately.
Using phone numbers as an example, you might have to have something like:
Whereas, using a Map, you could do
Not only is your code shorter, but your JSON/Object will be as well:
Using subclass:
Using Map:
Using the custom validator constraint logic, you are able to access to several args including the entire object and the specific property being evaluated. Therefore, validating the key comes down to casting the value parameter as a
Map<>and then looping through it to ensure each key matches a value in your chosen Enum and returning false if one or more do not.The same for the defaultMessage method; loop through the phoneNumber entries in the phoneNumber Map and build an array of keys that are invalid which you can then use in your custom message.
This leads me to the outstanding question I have: Can anyone think of a better way to share the invalid keys from the
validatemethod with thedefaultMessagemethod? As it stands right now, I have to iterate over the Map in each method separately instead of building the list of invalid keys only once in thevalidatemethod and then accessing it in thedefaultMessagemethod. The class is only instantiated once, so you can't have some private array as it will be shared by/written to by every call to the validator. Just seems inefficient.I haven't build a custom decorator for it yet, but using this on the
phoneNumbersparameter in theUserclass looks like this:And the example output looks like:
Validation failed:
Validation Successful allows User to be created:
Beta Was this translation helpful? Give feedback.
All reactions