Class-validator: How to describe validators for two possible types: array or string?

Created on 18 Jan 2018  路  12Comments  路  Source: typestack/class-validator

How to describe validators for two possible types: array or string?

Two decorators seems not working.

@IsBoolean()
@IsString()
private readonly foo: boolean | string;
discussion

Most helpful comment

How about a signature like:

@IsType(Array<(val: any) => boolean>)
@IsType([
 val => typeof val == 'string',
 val => typeof val == 'boolean',
])
private readonly foo: boolean | string;

All 12 comments

There is currently no way of doing this. What is your use-case? Why do you accept both string and boolean? Do you convert it to one of them after validating with class-validator?

Yes. It's this property
https://github.com/javascript-obfuscator/javascript-obfuscator/pull/180/files#diff-15b001f4b5c1e28b83dca39130e3531bR121

Identifiers prefix can be as string and then it will using as is, or as boolean and then it will disabled (false value) or random generated (true value)

I removed boolean type so it's not important for me now, but anyway nice feature to have.

How about a signature like:

@IsType(Array<(val: any) => boolean>)
@IsType([
 val => typeof val == 'string',
 val => typeof val == 'boolean',
])
private readonly foo: boolean | string;

It would be nice!

@NoNameProvided Personally i'm against complex logic in decorator definitions.
Imagine having repeat those val => typeof val == 'string' in every property you want to validate. It quickly gets messy. Especially when you will need to change the logic of validating - then you will need to look for all references and change them one by one.

I suggest you to implement similar solution to the one i posted here.
Or if it's common to validate multi-typed properties in your project, you can use custom decorator factory to specify which type you want to validate.

import { registerDecorator, ValidationArguments, ValidationOptions, Validator } from "class-validator";


const typeValidator = {
    "string": function (value: any, args: ValidationArguments) {
        const validator = new Validator();
        return validator.isString(value);
    },
    "int": function (value: any, args: ValidationArguments) {
        const validator = new Validator();
        return validator.isInt(value);
    }
// Add more here
};

export function IsType(types: (keyof (typeof typeValidator))[], validationOptions?: ValidationOptions) {
    return function (object: Object, propertyName: string) {
        registerDecorator({
            name: "wrongType",
            target: object.constructor,
            propertyName: propertyName,
            options: validationOptions,
            validator: {
                validate(value: any, args: ValidationArguments) {
                    return types.some(v => typeValidator[v](value, args));
                },
                defaultMessage(validationArguments?: ValidationArguments) {
                    const lastType = types.pop();
                    if (types.length == 0)
                        return `Has to be ${lastType}`;
                    return `Can only be ${types.join(", ")} or ${lastType}.`;
                }
            }
        });
    };
}

And then usage is very simple and clean:

class example {
    @IsType(["string", "int"])
    public someValue: number | string;
}

@ZBAGI How would you replicate IsType for nested objects? @IsType([TypeA, TypeB])?

It should work exactly like example shows. If you meant an array then ValidationOptions.each set to true should do the work.

class example {
    @IsType(["string", "int"], { each: true })
    public someValue: (number | string)[];
}

@ZBAGI Thanks! I meant to ask if it could work with custom defined types? If I defined ClassA and ClassB, would this method work?

You can add whatever type checking function you wish
Here is example that will look for string in property typeName

const typeValidator = {
    "typeOne": function (value: any, args: ValidationArguments) {
        if(typeof value === "object")
                return false; // if its not an object it cannot be 'typeOne' object
        return value['typeName'] == "typeOne"; // Check typeName property and if it is typeOne then it is typeOne 'type'.
    },
};

And here is the usage:

class example {
    @IsType(["typeOne"])
    public someValue: object;
}

So many duplicated topics about it and we still have to implement our version ?

You could try this:

@IsArray()
@IsString({each: true})
Was this page helpful?
0 / 5 - 0 ratings