I built this because I hated the verboseness of existing schema systems but needed something.
With the addtion of the official standard Json Type Definitions in AJV, I'm depricating this since the security and features offered in AJV under JTD schemas, makes this irrelivant. AJV can use your schemas to publish API schemas, and compile schema specific JSON parsers/serializers that are even more fast and secure than JSON.parse() or JSON.stringify()
which is beyond what I could hope to do in this current package.
Mostly derived from eivindfjeldstad/validate; Valivar provides dynamic schemas for modern javascript and typescript validation and sanitization. In Valivar you can use catch-all keys $
and *
to define any array or object respectively, letting you define a single schema that can apply consistent rules for growing and changing datasets.
npm install valivar
import valivar from "https://deno.land/x/valivar/mod.ts"
<script type="text/javascript" src="dist/valivar.js"></script>
<script type="text/javascript" src="https://unpkg.com/valivar"></script>
For a full list see the examples directory (coming)
Define a schema and call .validate()
with the object you want to validate.
The .validate()
function returns an array of validation errors.
import { Schema } from 'valivar'
const user = new Schema({
username: {
type: String,
required: true,
length: { min: 3, max: 32 }
},
pets: [{
name: {
type: String
required: true
},
animal: {
type: String
enum: ['cat', 'dog', 'cow']
}
}],
address: {
street: {
type: String,
required: true
},
city: {
type: String,
required: true
}
zip: {
type: String,
match: /^[0-9]+$/,
required: true
}
}
})
const errors = user.validate(obj)
Each error has a .path
, describing the full path of the property that failed validation, and a .message
describing the error.
errors[0].path //=> 'address.street'
errors[0].message //=> 'address.street is required.'
You can override the default error messages by passing an object to Schema.message()
.
const post = new Schema({
title: { required: true }
})
post.message({
required: (path) => `${path} can not be empty.`
})
const [error] = post.validate({})
assert(error.message = 'title can not be empty.')
It is also possible to define messages for individual properties:
const post = new Schema({
title: {
required: true,
message: 'Title is required.'
}
})
And for individual validators:
const post = new Schema({
title: {
type: String,
required: true,
message: {
type: 'Title must be a string.',
required: 'Title is required.'
}
}
})
Objects and arrays can be nested as deep as you want:
const event = new Schema({
title: {
type: String,
required: true
},
participants: [{
name: String,
email: {
type: String,
required: true
},
things: [{
name: String,
amount: Number
}]
}]
})
Arrays can be defined implicitly, like in the above example, or explicitly:
const post = new Schema({
keywords: {
type: Array,
each: { type: String }
}
})
Array elements can also be defined individually:
const user = new Schema({
something: {
type: Array,
elements: [
{ type: Number },
{ type: String }
]
}
})
Nesting also works with schemas:
const user = new Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true
}
})
const post = new Schema({
title: {
type: String,
required: true
},
content: {
type: String,
required: true
},
author: user
})
If you think it should work, it probably works.
Validate will naively assume that a nested object where all property names are validators is not a nested object.
const schema = new Schema({
pet: {
type: {
required: true,
type: String,
enum: ['cat', 'dog']
}
}
});
In this example, the pet.type
property will be interpreted as a type
rule, and the validations will not work as intended. To work around this we could use the slightly more verbose properties
rule:
const schema = new Schema({
pet: {
properties: {
type: {
required: true,
type: String,
enum: ['cat', 'dog']
}
}
}
});
In this case the type
property of pets.properties
will be interpreted as a nested property, and the validations will work as intended.
Custom validators can be defined by passing an object with named validators to .use
:
const hexColor = val => /^#[0-9a-fA-F]$/.test(val)
const car = new Schema({
color: {
type: String,
use: { hexColor }
}
})
Define a custom error message for the validator:
car.message({
hexColor: path => `${path} must be a valid color.`
})
Pass a constructor to .type
to validate against a custom type:
class Car {}
const user = new Schema({
car: { type: Car }
})
If you want to avoid constructing large objects, you can add paths to a schema by using the chainable API:
const user = new Schema()
user
.path('username').type(String).required()
.path('address.zip').type(String).required()
Array elements can be defined by using $
as a placeholder for indices:
const user = new Schema()
user.path('pets.$').type(String)
This is equivalent to writing
const user = new Schema({ pets: [{ type: String }]})
Values can be automatically typecast before validation.
To enable typecasting, pass an options object to the Schema
constructor with typecast
set to true
.
const user = new Schema(definition, { typecast: true })
You can override this setting by passing an option to .validate()
.
user.validate(obj, { typecast: false })
To typecast custom types, you can register a typecaster:
class Car {}
const user = new Schema({
car: { type: Car }
})
user.typecaster({
Car: (val) => new Car(val)
})
By default, all values not defined in the schema will be stripped from the object.
Set .strip = false
on the options object to disable this behavior. This will likely be changed in a future version.
When strict mode is enabled, properties that are not defined in the schema will trigger a validation error. Set .strict = true
on the options object to enable strict mode.