isvalid is an asynchronous node.js library for validating and error correcting JSON. In contrary to JSON Schema it uses a very simple schema model - inspired by the Mongoose schemas.
- How to Use
- How it Works
- License
isvalid uses a simple schema modal to specify how the data should be formatted. It supports generic validators for all types and type specific validators.
Usage: isvalid(dataToValidate, validationSchema, callback)
Here's a simple example on how to use the validator.
const isvalid = require('isvalid');
isvalid(inputData, {
'user': { type: String, required: true },
'pass': { type: String, required: true }
}).then((data) => {
// Data was validated and valid data is available.
}).catch((err) => {
// A validation error occured.
});
– or using await
/async
.
const isvalid = require('isvalid');
try {
let data = await isvalid(inputData, {
'user': { type: String, required: true },
'pass': { type: String, required: true }
};
// Data is validated.
} catch(err) {
// A validation error occured.
}
Connect and Express middleware is build in.
Usage: isvalid.validate.body(schema)
validates req.body
.
Usage: isvalid.validate.query(schema)
validates req.query
.
Usage: isvalid.validate.param(schema)
validates req.param
.
var validate = require('isvalid').validate;
app.param('myparam', validate.param(Number));
app.post('/mypath/:myparam',
validate.query({
'filter': String
}),
validate.body({
'mykey': { type: String, required: true }
}),
function(req, res) {
// req.param.myparam, req.body and req.query are now validated.
// - any default values - or type conversion - has been applied.
}
);
Remark: If validation fails
isvalid
will unset the validated content (eg.req.body
will becomenull
if body validation fails). This is to ensure that routes does not get called with invalid data, in case a validation error isn't correctly handled.
isvalid is a comprehensive validation library - build for ease of use. Both as Connect or Express middleware - where it comes in really handy - or as stand alone.
In order to be a complete schema, schemas must have at least the type
and/or post
validator. But, as you will notice throughout this document, many of the examples lots of validators with neither. Instead they just use the type shortcuts.
This is because isvalid supports type shortcuts for all its supported types, and you are - if you want to help yourself - going to use them a lot. You can read more about type shortcuts in the designated section at the near-bottom of this document.
Errors in function parameters or schemas are thrown - as they are developer errors - and validation errors are passed to the callback.
- Wrong parameters throw
Error
instances. - Schema errors throw
SchemaError
instances. - Validation errors are passed to the callback as
ValidationError
instances.
The SchemaError
contains the schema
property which contains the actual schema in which there is an error. It also has the message
property with the description of the schema error.
The ValidationError
contains three properties besides the message
.
- `keyPath` is an array indicating the key path in the data where the error occured.
- `schema` is the schema that failed to validate.
- `validator` is the name of the validator that failed.
These types are supported by the validator:
Object
Array
String
Number
Boolean
Date
- and
post
validators.
There are some validators that are common to all types, and some types have specific validators.
You specify the type like this:
{ type: String }
or if type
is your only validator, you can also do this:
String
In the above example the input must be of type String
.
All schemas must have at least a type
, post
or equal
validator - or a combination of any of them.
These validators are supported by all types.
Defaults data to specific value if data is not present in input. It takes a specific value or it can call a post function to retrieve the value.
Type: Any value or a function.
Example:
{
"email": { type: String, default: "email@not.set" }
}
This tells the validator, that an email
key is expected, and if it is not found, it should just assign it with whatever is specified as default
.
This works with all supported types - below with a boolean type:
{
"receive-newsletter": { type: Boolean, default: false }
}
Now if the receive-newsletter
field is absent in the data the validator will default it to false
.
default
also supports functions that returns a value or a promise (async functions).
An asynchronous default function works using a callback.
{
"created": {
type: String,
default: function(fn) {
fn('This is my default value');
}
}
}
A synchronous default function works the same way, except you leave out the callback - and return the value instead.
{
"created": {
type: String,
default: function() {
return 'This is my default value';
}
}
}
Values: true
, false
or 'implicit'
.
required
works a little like default. Except if the value is absent a validation error is send to the callback.
{ type: String, required: true }
The above specifies that the data must be present and be of type String
.
Example:
{
type: Object,
required: 'implicit',
schema: {
'user': { type: String, required: true }
'email': String
}
}
The above example is to illustrate what 'implicit'
does. Because the key user
in the subschema is required, the parent object inherently also becomes required. If none of the subschemas are required, the parent object is also not required.
This enables you to specify that some portion of the data is optional, but if it is present - it's content should have some required fields.
See the example below.
{
type: Object,
required: false,
schema: {
'user': { type: String, required: true }
'email': String
}
}
In the above example the data will validate if the object is not present the input, even though user
is required - because the parent object is explicitly not required. If the object - on the other hand - is present it must have the user
key and it must be of type String
.
Remark: If
required
is not specified, thenObject
andArray
types are by default'implicit'
. All other types are by default non-required. Alsorequired
is ignored ifdefault
is specified.
Type: Boolean
This validator allows for null
-values to pass through - even if the input is required.
{ type: String, required: true, allowNull: true }
In the above example input must be of type String
, it is required, but null
is allowed.
Remark: By default
null
is not allowed.
Type: Any
This validator allows for a static value. If this is provided the data must match the value of this validator.
This works with any type (also Object
and Array
) and a deep comparison is performed.
Type: Object
Errors are really not a validator - it allows you to customize the errors emitted by the validators. All validators have default error messages, but these can be customized in order to make user and context friendly error messages.
An example below.
{
'username': {
type: String,
required: true,
match: /^[^ @]+$/,
errors: {
type: 'Username must be a string.',
required: 'Username is required.',
match: 'Username cannot contain any white spaces.'
}
}
}
Now in case any of the validators fail, they will emit the error message specified - instead of the default built-in error message. The message
property of ValidationError
will contain the message on validation failure.
The schema
validator of Object
and Array
types specifies the schema of their children. Objects have keys and values - arrays only have a single schema.
An example below of an object schema with a user
key.
{
type: Object,
schema: {
'username': String
}
}
And an example below of an array of strings.
{
type: Array,
schema: String
}
The Object
type has only one specific validator - besides the common validators (it has two, though, but the one is deprecated in favour of the other.)
Type String
of values: 'allow'
, 'deny'
or 'remove'
This validator is used to control how unknown keys in objects are handled.
The validator has three options:
allow
Pass any unknown key onto the validated object.deny
Come back with error if object has unknown key.remove
Remove the unknown key from the validated object.
An example below.
{
type: Object,
unknownKeys: 'remove',
schema: {
awesome: Boolean
}
}
In the above example, if you validated the following object, the why
key would be absent from the validated data.
{
awesome: true,
why: 'It is!'
}
Default is
deny
.
Type: Boolean
This validator has been deprecated in favour of unknownKeys
, and will be removed in the next major version. Please update your schemas.
The Array
type has two specific validator - besides the common validators.
Type: Number
or String
This ensures that an array has a specific number of items. This can be either a number or a range. The validator sends an error to the callback if the array length is outside the bounds of the specified range(s).
An array that should have exactly 2 items:
{
type: Array,
len: 2,
schema: { … }
}
An array that should have at least 2 items:
{
type: Array,
len: '2-',
schema: { … }
}
An array that should have a maximum of 2 items:
{
type: Array,
len: '-2',
schema: { … }
}
An array that should have at least 2 items and a maximum of 5 items:
{
type: Array,
len: '2-5',
schema: { … }
}
An array that should have at two or less items, exactly 5 items or 8 or more items:
{
type: Array,
len: '-2,5,8-',
schema: { … }
}
Type: Boolean
This ensures that all elements in the array are unique - basically ensuring the array is a set. If two or more elements are the same, the validator sends an error to the callback.
Example:
{
type: Array,
unique: true,
schema: { … }
}
The
unique
validator does a deep comparison on objects and arrays.
Type: Boolean
If the provided data is not an array - but it matches the subschema - this will wrap the data in an array before actual validation.
Example:
{
type: Array,
autowrap: true,
schema: { … }
}
If autowrap
is set to true
and autowrap fails (the subschema cannot validate the data), then the type
validator will emit a 'Must be of type Array.'
error.
Default is
false
.
The String
type has three specific validator - besides the common validators.
Type: Boolean
This does not do any actual validation. Instead it trims the input in both ends - before any other validators are checked. Use this if you want to remove any unforeseen white spaces added by the user.
Type: RegExp
This ensures that a string can be matched against a regular expression. The validator sends an error to the callback if the string does not match the pattern.
This example shows a string that must contain a string of at least one character of latin letters or decimal numbers:
{ type: String, match: /^[a-zA-Z0-9]+$/ }
Type: Array
This is complimentary to match
- as this could easily be achieved with match
- but it is simpler and easier to read. The validator ensures that the string can be matched against a set of values. If it does not, it sends an error to the callback.
{ type: String, enum: ['none','some','all'] }
In the above example the string can only have the values of none
, some
or all
.
Remark that
enum
is case sensitive.
The Number
type has only one specific validator - besides the common validators.
Type: Number
or String
This ensures that the number is within a certain range. If not the validator sends an error to the callback.
The
range
validator uses the same formatting as the array'slen
validator described above.
post
are for usage when the possibilities of the validation schema falls short. post
basically outsources validation to a post function.
type
becomes optional when usingpost
. You can completely leave out any validation and just use apost
validator.
{
type: Object,
schema: {
'low': Number,
'high': Number
}
'post': async (data, schema) => {
if (data.low > data.high) {
throw new Error('low must be lower than high');
}
return data;
}
}
In the above example we have specified an object with two keys - low
and high
. The validator will first make sure, that the object validates to the schema. If it does it will then call the post validator - which in this example calls the callback with an error if low is bigger than high.
post
functions works both by returning promises (async functions) and returning a value.- If no value is returned the data does not change.
- Thrown errors are caught and converted to a
ValidationError
internally.
If you need to pass any options to your custom validator, you can do so by using the options
property of the schema.
An example below.
{
'myKey': {
options: {
myCustomOptions: 'here'
},
post: function(data, schema, fn) {
// schema.options will now contain whatever options you supplied in the schema.
// In this example schema.options == { myCustomOptions: 'here'}.
}
}
}
The post
validator also support an array of validators. Instead of providing a function, provide an array of functions. Synchronous and asynchronous can be mixed and matched as necessary.
An example.
{
post: [
function(data, schema, fn) {
data(null, myValidatedData);
},
function(data, schema) {
return mySecondValidatedData
}
]
}
If, though, any of the post validator functions returns an error (either using the callback in an asynchronous function, or by throwing an error in a synchronous one), none of the rest of the post validators in the post validator chain will get called, and isvalid will return an error.
The post validator functions are called in order.
pre
does the exact same thing as post
described above, except it is called and validated before any other validators are validated.
Some types can be specified using shortcuts. Instead of specifying the type, you simply just use the type. This works with Object
and Array
types.
In this document we've been using them extensively on Object
examples, and the first example of this document should have looked like this, if it hadn't been used.
isvalid(inputData, {
type: Object,
schema: {
'user': { type: String, required: true },
'pass': { type: String, required: true }
}
}, function(err, validData) {
/*
err: Error describing invalid data.
validData: The validated data.
*/
});
Object shortcuts are used like this:
{
"user": String
}
and is in fact the same as this:
{
type: Object,
schema: {
"user": { type: String }
}
}
Which means that data should be an object with a user
key of the type String
.
Remark: Internally the library tests for object shortcuts by examining the absent of the
type
andpost
validators. So if you need objects schemas with validators for keys with those names, you must explicitly format the object usingtype
andschema
- hence the shortcut cannot be used.
The same goes for arrays:
[String]
is essentially the same as:
{
type: Array,
schema: { type: String }
}
Which means the data must be an array of strings.
The others are a bit different. They are - in essence - a shortcut for the validator type
. Instead of writing type
you just specify the type directly. Available types are all the supported types of isvalid, namely Object
, Array
, String
, Number
, Boolean
and Date
.
An example below.
{
"favorite_number": Number
}
The above example is really an example of two shortcuts in one - the Object
and the Number
type shortcut. The above example would look like the one below, if shortcuts had not been used.
{
type: Object,
schema: {
"favorite_number": { type: Number }
}
}
If the schema has type Number
and the input holds a String
containing numbers (or/and a point), the validator will automatically convert the string into a number.
Likewise will schemas of type Boolean
be automatically converted into a Boolean
if a String
with the value of true
or false
is in the data.
If the schema is of type Date
and a String
containing an ISO-8601 formatted date is supplied, it will automatically be parsed and converted into a Date
.
ISO-8601 is the date format that JSON.stringify(...)
convert Date
instances into, so this allows you to just serialize an object to JSON on - as an example - the client side, and then isvalid will automatically convert that into a Date
instance when validating on the server side.
MIT