@alxandr/validated
v2.0.0
Published
JSON configuration utilities
Downloads
3
Readme
validated
Validate your configurations with precise error messages:
Define schema with validators which are agnostic to the actual representation of data, be it a JSON string, object in memory or any other format.
Use schema with runners specific for formats (object and JSON5 runners are included). Get error messages with precise info (line and column numbers for example).
Get the result of a validation as an object: either a plain JSON or some domain specific classes if schema is defined in that way.
Table of Contents
Installation
% npm install validated
Usage
Schema
Schema is defined with validators which are agnostic to the actual representation of data, be it a JSON string or an object in memory:
import {
mapping, arrayOf, object, partialObject, oneOf, maybe, enumeration, recur,
any, string, number, boolean
} from 'validated/schema'
There's schema validator for JSON objects in memory:
import {
validate as validateObject
} from 'validated/object'
And schema validator for strings with JSON/JSON5 encoded data:
import {
validate as validateJSON5
} from 'validated/json5'
Let's define some schema first:
let person = object({
name: string,
age: number,
})
let pet = object({
nickName: string,
age: number,
})
let collection = arrayOf(oneOf(person, pet))
validateJSON5(collection, '[{name: "John", age: 26}, {nickName: "Tima", age: 3}]')
// => [ { name: 'John', age: 26 }, { nickName: 'Tima', age: 3 } ]
validateObject(collection, [{name: "John", age: 26}, {nickName: "Tima", age: 3}])
// => [ { name: 'John', age: 26 }, { nickName: 'Tima', age: 3 } ]
List of schema primitives
any
Validates any value but not undefined
or null
:
validateObject(any, 'ok')
// => 'ok'
validateObject(any, 42)
// => 42
validateObject(any, null)
// ValidationError: Expected a value but got null
validateObject(any, undefined)
// ValidationError: Expected a value but got undefined
If you want to validated any value and even an absence of one then wrap it in
maybe
:
validateObject(maybe(any), null)
// => null
validateObject(maybe(any), undefined)
// => undefined
string
, number
, boolean
Validate strings, numbers and booleans correspondingly.
validateObject(string, 'ok')
// => 'ok'
validateObject(number, 42)
// => 42
validateObject(boolean, true)
// => true
enumeration
Validate enumerations:
validateObject(enumeration('yes', 'no'), 'yes')
// => 'yes'
validateObject(enumeration('yes', 'no'), 'no')
// => 'no'
validateObject(enumeration('yes', 'no'), 'oops')
// ValidationError: Expected value to be one of "yes", "no" but got "oops"
mapping
Validate mappings from string keys to values.
Untyped values (value validator defaults to any
):
validateObject(mapping(), {})
// => {}
validateObject(mapping(), {a: 1, b: 'ok'})
// => { a: 1, b: 'ok' }
validateObject(mapping(), 'oops')
// ValidationError: Expected a mapping but got string
Typed value:
validateObject(mapping(number), {a: 1})
// => { a: 1 }
validateObject(mapping(number), {a: 1, b: 'ok'})
// ValidationError: Expected value of type number but got string
// While validating value at key "b"
arrayOf
Validate arrays.
Untyped values (value validator defaults to any
):
validateObject(arrayOf(any), [])
// => []
validateObject(arrayOf(any), [1, 2, 'ok'])
// => [ 1, 2, 'ok' ]
validateObject(arrayOf(any), 'oops')
// ValidationError: Expected an array but got string
Typed value:
validateObject(arrayOf(number), [1, 2])
// => [ 1, 2 ]
validateObject(arrayOf(number), [1, 2, 'ok'])
// ValidationError: Expected value of type number but got string
// While validating value at index 2
object
Validate objects, objects must specify validator for each of its keys:
let person = object({
name: string,
age: number,
})
validateObject(person, {name: 'john', age: 27})
// => { name: 'john', age: 27 }
validateObject(person, {name: 'john'})
// ValidationError: Expected value of type number but got undefined
// While validating missing value for key "age"
validateObject(person, {name: 'john', age: 'notok'})
// ValidationError: Expected value of type number but got string
// While validating value at key "age"
validateObject(person, {name: 'john', age: 42, extra: 'oops'})
// ValidationError: Unexpected key: "extra"
// While validating key "extra"
validateObject(person, {nam: 'john', age: 42})
// ValidationError: Unexpected key: "nam", did you mean "name"?
// While validating key "nam"
If some key is optional, wrap its validator in maybe
:
let person = object({
name: string,
age: number,
nickName: maybe(string),
})
validateObject(person, {name: 'john', age: 27})
// => { name: 'john', age: 27 }
validateObject(person, {name: 'john', age: 27, nickName: 'J'})
// => { name: 'john', age: 27, nickName: 'J' }
You can also specify default values for keys:
let person = object({
name: string,
age: number,
nickName: string,
}, {
nickName: 'John Doe'
})
validateObject(person, {name: 'john', age: 27})
// => { name: 'john', age: 27, nickName: 'John Doe' }
validateObject(person, {name: 'john', age: 27, nickName: 'J'})
// => { name: 'john', age: 27, nickName: 'J' }
partialObject
Validate a subset of the keys from the object, passing all extra keys through:
let person = partialObject({
name: string,
age: number,
})
validateObject(person, {name: 'john', age: 27})
// => { name: 'john', age: 27 }
validateObject(person, {name: 'john', age: 42, extra: 'ok'})
// => { name: 'john', age: 42, extra: 'ok' }
maybe
Validates null
and undefined
but passes through any other value to the
underlying validator:
validateObject(maybe(string), null)
// => null
validateObject(maybe(string), undefined)
// => undefined
validateObject(maybe(string), 'ok')
// => 'ok'
validateObject(maybe(string), 42)
// ValidationError: Expected value of type string but got number
oneOf
Tries a multiple validators and choose the one which succeeds first:
validateObject(oneOf(string, number), 'ok')
// => 'ok'
validateObject(oneOf(string, number), 42)
// => 42
validateObject(oneOf(string, number), true)
// ValidationError: Either:
//
// Expected value of type string but got boolean
//
// Expected value of type number but got boolean
//
recur
Allows to define recursive validators:
let tree = recur(tree =>
object({
value: any,
children: maybe(arrayOf(tree))
})
)
validateObject(tree, {value: 'ok'})
// => { value: 'ok' }
validateObject(tree, {value: 'ok', children: [{value: 'child'}]})
// => { value: 'ok', children: [ { value: 'child' } ] }
Refining validations
Example:
class Point {
constructor(x, y) {
this.x = x
this.y = y
}
}
let point = arrayOf(number).andThen((value, error) => {
if (value.length !== 2) {
throw error('Expected an array of length 2 but got: ' + value.length)
}
return new Point(value[0], value[1])
})
validateObject(point, [1, 2])
// => Point { x: 1, y: 2 }
validateJSON5(point, '[1, 2]')
// => Point { x: 1, y: 2 }
validateJSON5(point, '[1]')
// ValidationError: Expected an array of length 2 but got: 1 (line 1 column 1)
Defining new schema types
Example:
import {Node} from 'validated/schema'
class Point {
constructor(x, y) {
this.x = x
this.y = y
}
}
class PointNode extends Node {
validate(context) {
// prevalidate value with primitive validators
let prevalidator = arrayOf(number)
let {value, context: nextContext} = prevalidator.validate(context)
// perform additional validations
if (value.length !== 2) {
// just report an error, context information such as line/column
// numbers will be injected automatically
throw context.error('Expected an array of length 2 but got: ' + value.length)
}
// construct a Point object, do whatever you want here
let [x, y] = value
let point = new Point(x, y)
// return constructed value and the next context
return {value: point, context: nextContext}
}
}
validateObject(new PointNode(), [1, 2])
// => Point { x: 1, y: 2 }
validateJSON5(new PointNode(), '[1, 2]')
// => Point { x: 1, y: 2 }
validateJSON5(new PointNode(), '[1]')
// ValidationError: Expected an array of length 2 but got: 1 (line 1 column 1)
Integration with FlowType
Validated library uses FlowType extensively. Its API is defined in a way which automatically infers types for produced values:
import {object, string, number} from 'validated/schema'
import {validate} from 'validated/json5'
let personSchema = object({
name: string,
age: number,
})
let value: {name: string; age: number} = validate(
personSchema,
'{"name": "Andrey", age: 29}'
)
Note that the type annotation isn't needed — FlowType infers the type automatically based on a schema.