Created
October 4, 2014 21:47
-
-
Save kerrishotts/3ffe4c5c704b23e8d22f to your computer and use it in GitHub Desktop.
Simple JS Object Validation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Validates a source against the specified rules. `source` can look like this: | |
* | |
* { aString: "hi", aNumber: { hi: 294.12 }, anInteger: 1944.32 } | |
* | |
* `rules` can look like this: | |
* | |
* { | |
* "a-string": { | |
* title: "A String", -- optional; if not supplied, key is used | |
* key: "aString", -- optional: if not supplied the name of this rule is used as the key | |
* required: true, -- optional: if not supplied, value is not required | |
* type: "string", -- string, number, integer, array, date, boolean, object, *(any) | |
* minLength: 1, -- optional: minimum length (string, array) | |
* maxLength: 255 -- optional: maximum length (string, array) | |
* }, | |
* "a-number": { | |
* title: "A Number", | |
* key: "aNumber.hi", -- keys can have . and [] to reference properties within objects | |
* required: false, | |
* type: "number", | |
* min: 0, -- if specified, number/integer can't be smaller than this number | |
* max: 100 -- if specified, number/integer can't be larger than this number | |
* }, | |
* "an-integer": { | |
* title: "An Integer", | |
* key: "anInteger", | |
* required: true, | |
* type: "integer", | |
* enum: [1, 2, 4, 8] -- if specified, the value must be a part of the array | |
* -- may also be specified as an array of objects with title/value properties | |
* } | |
* } | |
* | |
* @param {*} source source to validate | |
* @param {*} rules validation rules | |
* @returns {*} an object with two fields: `validates: true|false` and `message: validation message` | |
* | |
* LICENSE: MIT | |
* Copyright Kerri Shotts, 2014 | |
*/ | |
function validate( source, rules ) { | |
"use strict"; | |
var r = { | |
validates: true, | |
message: "" | |
}; | |
if ( !( rules instanceof Object ) ) { | |
return r; | |
} | |
// go over each rule in `rules` | |
Object.keys( rules ) | |
.forEach( function ( prop ) { | |
if ( r.validates ) { | |
// get the rule | |
var rule = rules[ prop ], | |
v = source, | |
// and get the value in source | |
k = ( rule.key !== undefined ) ? rule.key : prop, | |
title = ( rule.title !== undefined ) ? rule.title : prop; | |
k = k.replace( "[", "." ) | |
.replace( "]", "" ) | |
.replace( "\"", "" ); | |
k.split( "." ) | |
.forEach( function ( keyPart ) { | |
try { | |
v = v[ keyPart ]; | |
} catch ( err ) { | |
v = undefined; | |
} | |
} ); | |
// is it required? | |
if ( ( ( rule.required !== undefined ) ? rule.required : false ) && v === undefined ) { | |
r.validates = false; | |
r.message = "Missing required value " + title; | |
return; | |
} | |
// can it be null? | |
if ( !( ( rule.nullable !== undefined ) ? rule.nullable : false ) && v === null ) { | |
r.validates = false; | |
r.message = "Unexpected null in " + title; | |
return; | |
} | |
// is it of the right type? | |
r.message = "Type Mismatch; expected " + rule.type + " not " + ( typeof v ) + " in " + title; | |
switch ( rule.type ) { | |
case "number": | |
if ( v !== undefined ) { | |
if ( isNaN( parseFloat( v ) ) ) { | |
r.validates = false; | |
return; | |
} | |
if ( v != parseFloat( v ) ) { | |
r.validates = false; | |
return; | |
} | |
} | |
break; | |
case "integer": | |
if ( v !== undefined ) { | |
if ( isNaN( parseInt( v, 10 ) ) ) { | |
r.validates = false; | |
return; | |
} | |
if ( v != parseInt( v, 10 ) ) { | |
r.validates = false; | |
return; | |
} | |
} | |
break; | |
case "array": | |
if ( v !== undefined && !( v instanceof Array ) ) { | |
r.validates = false; | |
return; | |
} | |
break; | |
case "date": | |
if ( v instanceof Object ) { | |
if ( !( v instanceof Date ) ) { | |
r.validates = false; | |
return; | |
} else if ( v instanceof Date && isNaN( v.getTime() ) ) { | |
r.validates = false; | |
r.message = "Invalid date in " + title; | |
return; | |
} | |
} else if ( typeof v === "string" ) { | |
if ( isNaN( ( new Date( v ) ) | |
.getTime() ) ) { | |
r.validates = false; | |
r.message = "Invalid date in " + title; | |
return; | |
} | |
} else if ( !( v instanceof "object" ) && v !== undefined ) { | |
r.validates = false; | |
return; | |
} | |
break; | |
case "object": | |
if ( !( v instanceof Object ) && v !== undefined ) { | |
r.validates = false; | |
return; | |
} | |
break; | |
case "*": | |
break; | |
default: | |
if ( !( typeof v === rule.type || v === undefined ) ) { | |
r.validates = false; | |
return; | |
} | |
} | |
r.message = ""; | |
// if we're still here, types are good. Now check length, range, and enum | |
// check range | |
r.message = "Value out of range " + v + " in " + title; | |
if ( typeof rule.min === "number" && v < rule.min ) { | |
r.validates = false; | |
return; | |
} | |
if ( typeof rule.max === "number" && v > rule.max ) { | |
r.validates = false; | |
return; | |
} | |
r.message = ""; | |
// check length | |
if ( ( typeof rule.minLength === "number" && v !== undefined && v.length !== undefined && v.length < rule.minLength ) || | |
( typeof rule.maxLength === "number" && v !== undefined && v.length !== undefined && v.length > rule.maxLength ) ) { | |
r.message = "" + title + " out of length range"; | |
r.validates = false; | |
return; | |
} | |
// check enum | |
if ( rule.enum instanceof Object && v !== undefined ) { | |
if ( rule.enum.filter( function ( e ) { | |
if ( e.value !== undefined ) { | |
return e.value == v; | |
} else { | |
return e == v; | |
} | |
} ) | |
.length === 0 ) { | |
r.message = "" + title + " contains unexpected value " + v + " in " + title; | |
r.validates = false; | |
return; | |
} | |
} | |
// check pattern | |
if ( rule.pattern instanceof Object && v !== undefined ) { | |
if ( v.match( rule.pattern ) === null ) { | |
r.message = "" + title + " doesn't match pattern in " + title; | |
r.validates = false; | |
return; | |
} | |
} | |
} | |
} ); | |
return r; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment