DEV Community

Mohammad Reza Rahimian Golkhandani
Mohammad Reza Rahimian Golkhandani

Posted on

Write your own validator in less than 100 line!

Writing a validator is easy!

Base of a validator function is a traverse through object's properties.
Then you need to check your value with some basic if/else to validate input data.

The final validation schema will look like this:

const Schema = {
    'Username': 'Required|Alphabet|Min:3|Max:10',
    'Password': 'Required|Alphabet|Min:3|Max:10',
    'Profile': {
        'Age': 'Required|Numeric|Gt:17|Lt:99',
        'Sex': 'Required|In:Male,Female',
    }
} 
Enter fullscreen mode Exit fullscreen mode

Less talk more code, let's start with traverse function:

enum SchemaType {
    string,
    object,
    array
}
.
.
.
class Validator {

   private MainValidator(Schema:any, Data:any,Path?:string) {
        for (const key in Schema) {
            const Rule = Schema[key];
            const Node = (Path ? Path + "." : "") + key;
            const RuleData = Data ? Data[key] : {};
            const InputSchemaType:SchemaType | undefined = this.GetSchemaType(Rule);
            switch (InputSchemaType) {
                case SchemaType.object:
                    this.MainValidator(Rule, RuleData,key);
                    break;
                case SchemaType.array:
                    this.ArrayValidator(Rule[0],RuleData,key);
                    break;
                default:
                    this.InnerValidator(Rule, RuleData,Node);
                    break;
            }
        }
    }
.
.
.
    private GetSchemaType(Rule:any):SchemaType | undefined {
        if (typeof Rule === "string") {
            return SchemaType.string;
        }
        else if (typeof Rule === "object" && !Array.isArray(Rule)) 
        {
            return SchemaType.object;
        }
        else if (typeof Rule === "object" && Array.isArray(Rule)) 
        {
            return SchemaType.array;
        }
        else {
            return;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

MainValidator method is the base of our validation then we need to define the branches of the tree, ArrayValidator and InnerValidator.

.
.
.
    private ArrayValidator(Rule:any, Data:any, key?:string) {
        let index = 0;
        do {
            this.MainValidator(Rule, Data[index] , key+"."+index);
            index++
        } while ((Data || []).length > index);

     }
.
.
.
    private InnerValidator(rules:any,value:any,Node:string) {
        const FnNameAndArgs = this.GetFnNameAndArgs(rules);
        for (let index = 0; index < FnNameAndArgs.length; index++) {
            const FnNameAndArg = FnNameAndArgs[index];
            const Result = this.Validators.get(FnNameAndArg.name)?.call(this,value,...FnNameAndArg.args)
            if(Result && !Result.isValid) {
                this.Errors.has(Node) ? 
                this.Errors.get(Node)?.push(Result.message) :
                this.Errors.set(Node,[Result.message]); 
            }
        }   
    }   

Enter fullscreen mode Exit fullscreen mode

When the branches reach to the leaf mean GetFnNameAndArgs method. You should have the real validator methods and a place to store them.

Also because we want to use string patterns in our schema we could have a Map of string -> function

.
.
.
    private Validators: Map<string,Function> = new Map();
    private GetFnNameAndArgs(rules: any) {
        return rules.split('|').map((item:any) => {
            const V = item.split(':');
            const name = V[0]; V.shift();
            return { name, args: V };
        });
    }
Enter fullscreen mode Exit fullscreen mode

And finally a place to store Validation Errors

.
.
.
    private Errors: Map<string,string[]> = new Map();
.
.
.
Enter fullscreen mode Exit fullscreen mode

To add any kind of validator function we simply use constructor

const Required = (value:any) => {
    if(value) {
        return {
            isValid: true,
        }
    } else {
        return {
            isValid: false,
            message:"Is Required"
        }
    }
}

class Validator {
    constructor() {
        this.Validators.set("Required", Required);
    }
...
}

Enter fullscreen mode Exit fullscreen mode

And finally add an entry point for your class to do validation for input objects.


class Validator {
...
    Validate(Schema:any, Data:any){
        this.Errors = new Map<string,string[]>();
        this.MainValidator(Schema, Data);
        return this.Errors;
    }
...
}

Enter fullscreen mode Exit fullscreen mode

Conclusion

Now you have a nice and clean validator, with some help of memoization we also can make it faster than many other validator in js world.

You can find the whole code here.

Top comments (0)