README
validation.ts
Validation for TypeScript
This module helps validating incoming JSON, url params, etc in a type safe manner.
validate
Every validator has a validate
function which returns a Result (either a {type: 'ok', value} or a {type: 'error', errors})
A validated value can be transformed at any point during the validation process (e.g. isoDate
).
Errors are accumulated.
import { errorDebugString, isOk } from 'validation.ts'
const myValidator = ...
const result = myValidator.validate(myJson)
if (isOk(result)) {
console.log(result.value)
}
else {
console.error(errorDebugString(result.errors))
}
In case of errors, The Result
contains an Array of { message: string, context: string }
where message
is a debug error message for developers and context
is the path where the error occured (e.g root / data / 0 / name
)
errorDebugString
will give you a complete debug string of all errors, e.g.
At [root / c] Error validating the key. "c" is not a key of {
"a": true,
"b": true
}
At [root / c] Error validating the value. Type error: expected number but got string
primitives
import * as v from 'validation.ts'
v.string
v.number
v.boolean
v.null
v.undefined
v.isoDate
tagged primitive
Sometimes, a string
or a number
is not just any string or number but carries extra meaning, e.g: email
, uuid
, userId
, KiloGram
, etc.
Tagging such a primitive as it's being validated can help make the downstream code more robust.
type UserId = string & { __tag: 'UserId' } // Note: You can use any naming convention for the tag.
const userIdValidator = v.string.tagged<UserId>()
literal
import { literal } from 'validation.ts'
// The only value that can ever pass this validation is the 'X' string literal
const validator = literal('X')
array
import { array, string } from 'validation.ts'
const validator = array(string)
tuple
import { tuple, string, number } from 'validation.ts'
const validator = tuple(string, number) // Tuple2
object, literal union, optional
import { string, object, union, optional } from 'validation.ts'
const person = object({
id: string,
prefs: object({
csvSeparator: optional(union(',', ';', '|'))
})
})
Note: For bigger unions of strings, consider using the keyof
validator instead.
dictionary
A dictionary is an object where all keys and all values share a common type.
import { dictionary, string, number } from 'validation.ts'
const validator = dictionary(string, number)
keyof
import Set from 'space-lift/object/set'
import { keyof } from 'validation.ts'
const keys = Set('aa', 'bb', 'cc').value()
const keyValidator = keyof(keys)
keyValidator.validate('bb') // Ok<'aa' | 'bb' | 'cc'> = Ok('bb')
// keyof typeof keys === typeof keyValidator.T === 'aa' | 'bb' | 'cc'
map, filter, flatMap
import { string, Ok } from 'validation.ts'
const validator = string
.filter(str => str.length > 3)
.map(str => `${str}...`)
.flatMap(str => Ok(str.toLowerCase()))
recursion
import { recursion, string, array, object }
type Category = { name: string, categories: Category[] }
const category = recursion<Category>(self => object({
name: string,
categories: array(self)
}))
Deriving the typescript type from the validator type
Note: this can be used with any combination of validators except ones using recursion
.
Instead of using the derived type as your sole interface, use it to compare its compatibility with your handcrafted interfaces which will always be more readable in IDE's tooltips.
import { object, string, number } from 'validation.ts'
const person = object({
name: string,
age: number
})
type PersonFromValidator = typeof person.T
type Person = {
name: string
age: number
}
Configuration
A Configuration object can be passed to modify the default behavior of the validators:
Configuration.transformObjectKeys
Transforms every keys of every objects before validating.
const burger = v.object({
options: v.object({
doubleBacon: v.boolean
})
})
const ok = burger.validate({
options: {
'double_bacon': true
}
}, { transformObjectKeys: v.snakeCaseTransformation })
Thanks
To gcanti
and his io-ts
library which provided great inspiration.