In TypeScript, JSON.parse
returns type any
, which isn't useful if you want type safety - if you're using TypeScript, you probably do.
typed-validation
lets you build build validators that TypeScript can understand, meaning that TypeScript can check that your validator aligns with the type it's supposed to be validating - no need to force types using type assertion!
$ npm install --save typed-validation
Note: When using this module in a TypeScript project, 0.8.1 and later versions of this module require TypeScript 2.8 or above.
Example: check that a value of type any
(perhaps from an untrusted source, such as a file) is an object that conforms to an interface called Employee
:
// 1) Define the interface
interface Employee {
name: string;
roleCode: number;
completedTraining?: boolean;
addressPostcode: string;
}
// 2) Define the validator
const employeeValidator: Validator<Employee> = {
name: isString(minLength(1)),
roleCode: isNumber(min(1, max(10))),
completedTraining: optional(isBoolean()),
addressPostcode: isString(matches(/^[a-z]{2}\d{1,2}\s+\d{1,2}[a-z]{2}$/i))
};
// 3) Validate
const unsafeObject: any = {
name: 'Bob Smith',
roleCode: 7,
completedTraining: true,
addressPostcode: 'AB1 2CD'
};
const result = validate(unsafeObject, conformsTo(employeeValidator));
if (result.success) {
const bob = result.value;
const name = bob.name;
}
// Handle errors
const unsafeObject2: any = {
name: 'Name',
roleCode: 4,
completedTraining: 'false',
addressPostcode: 'WRONG'
};
const result2 = validate(unsafeObject2, conformsTo(employeeValidator));
if (!result2.success) {
console.log(result2.toString());
}
// Outputs:
// 2 validation errors:
// $.completedTraining: Expected boolean, got string
// $.addressPostcode: Failed regular expression /^[a-z]{2}\d{1,2}\s+\d{1,2}[a-z]{2}$/i
Validators are built by combining simple assertions using function composition and higher-order functions. For example, the isString()
assertion returns a function which accepts a single argument of type any
and returns either a SuccessResult<string>
or an ErrorResult
. It will return SuccessResult<string>
if and only if the argument is a string, or an ErrorResult
otherwise. This module provides a number of assertions, described below.
An assertion may take another assertion as its last argument; if assertion check passes, it calls the next assertion. For example, isString(minLength(1, maxLength(10)))
first checks if the value is a string, then checks if its length is at least 1, and then checks that its length is no more than 10. If isString
fails, minLength
isn't run. Chaining assertions in this way allows for complex validation of types and values.
Some assertions require other assertions to come before it. For example, minLength
can't be used by itself because it needs another assertion to check that the value has the length
property - so something like isString(minLength(1))
or isArray(minLength(1))
.
Jump to section:
Assertions:
- conformsTo
- optional
- nullable
- defaultsTo
- onErrorDefaultsTo
- isBoolean
- isNumber
- min
- max
- isString
- matches
- minLength
- maxLength
- lengthIs
- isArray
- eachItem
- isObject
- equals
- isMap
- eachValue
- either
The validate
function takes two arguments; the first is the argument to validate, the second is an assertion. It returns a result object; if result.success
is true
, then result.value
contains the validated value. If result.success
is false, then result.errors
contains the list of errors, which can be formatted by calling result.toString()
.
const result = vaildate(argumentToValidate, isString(minLength(1)));
if (result.success) {
const validated: string = result.value;
console.log(`The validated result is: ${validated}`);
} else {
console.log(`Validation failed: ${result.toString()}`);
}
When validatin fails, validate
will always return an ErrorResult
object, which has a property errors: ValidationError[]
.
The ValidationError
type has a number of useful properties:
errorCode
: A string which is one of a set of error codes, e.g.NOT_STRING
. Useful for producing custom error messages or triggering certain error logic.message
: A human-readable error message, with more information as to why the validation failed.path
: An array of objects that describe the path to the value that caused the validation to fail. Each object is either anArrayIndexPathNode
(which has anindex
property) orKeyPathNode
(which has akey
property).
The ErrorResult.toString()
method prints this information in a human-readable format. The name of the root object defaults to $
, but this can be changed by passing a string, e.g. err.toString('this')
.
Validator
is a type that enables TypeScript to validate that the validator correctly aligns to the interface it is supposed to validate.
The keys of the validator align with the keys of the interface. The values of the validator are a chain of assertions.
interface IFoo {
bar: string;
baz: number;
}
// A valid validator
const fooValidator: Validator<IFoo> = {
bar: isString(),
baz: isNumber()
};
// All of these are invalid, and will result in an error from the TypeScript compiler
const fooValidator: Validator<IFoo> = {
bar: isString()
}; // Missing 'baz'
const fooValidator: Validator<IFoo> = {
bar: isNumber(), // Wrong type
baz: isNumber()
};
const fooValidator: Validator<IFoo> = {
bar: isString(),
baz: isNumber(),
blah: isBoolean() // Unexpected property
};
Takes two validators and returns a new validator that validates the intersection type of T
and U
- useful for extending existing validators to prevent repetition.
Example:
interface IFoo {
abc: number;
}
const fooValidator: Validator<IFoo> = {
abc: isNumber()
};
interface IBar extends IFoo {
xyz: string;
}
const barValidator: Validator<IBar> = extendValidator(fooValidator, {
xyz: isString()
});
Returns an error if the value does not conform to the given validator.
interface IFoo {
bar: number;
}
const fooValidator: Validator<IFoo> = {
bar: isNumber()
};
// Returns a success result
validate({bar: 123}, conformsTo(fooValidator));
// Returns an error result
validate({bar: 'example'}, conformsTo(fooValidator));
The third argument is an optional object of options:
allowAdditionalProperties: boolean
- If false, returns an error if there are any properties in addition to the ones defined in the validator. Defaults totrue
, which removes additional properties from the result.
const result1 = validate({foo: 'abc', bar: 123}, conformsTo(fooValidator));
if (result1.success) {
console.log(result1.value); // {bar: 123}
}
const result2 = validate({foo: 'abc', bar: 123}, conformsTo(fooValidator, {allowAdditionalProperties: false}));
if (!result2.success) {
console.log(result2.toString())
// 1 validation error:
// $: Unexpected additional properties: foo
}
Used when the properties may not present on the object, or its value is undefined. Example:
interface IFoo {
bar?: string;
// You can also use 'undefined' in a union type
baz: number | undefined;
}
const fooValidator: Validator<IFoo> = {
bar: optional(isString()),
baz: optional(isNumber())
};
// Both of these are acceptable
validate({}, conformsTo(fooValidator));
validate({bar: undefined}, conformsTo(fooValidator));
Used when the value could be null (e.g. prop: T | null
).
interface IFoo {
bar: string | null;
}
const fooValidator: Validator<IFoo> = {
bar: nullable(isString()),
};
If the property on the object being validated is undefined, then return the given default value instead.
interface IFoo {
bar: string;
}
const fooValidator: Validator<IFoo> = {
bar: defaultsTo('baz', isString()),
};
const result = validate({}, conformsTo(fooValidator));
if (result.success) {
console.log(result.value);
}
// Output:
// {bar: 'baz'}
Note: the default value will get passed through the assertion chain.
If the following assertion chain fails, then return the given value instead.
interface IFoo {
bar: string;
}
const fooValidator: Validator<IFoo> = {
bar: onErrorDefaultsTo('baz', isString()),
};
const result = validate({bar: 123}, conformsTo(fooValidator));
if (result.success) {
console.log(result.value);
}
// Output:
// {bar: 'baz'}
Returns an error if the value is not a boolean.
Returns an error if the value is not a number.
Returns an error if the value is less than the given minimum.
interface IFoo {
bar: number;
}
const fooValidator: Validator<IFoo> = {
bar: isNuber(min(0))
};
// Returns an error result
const result = validate({bar: -1}, conformsTo(fooValidator));
Returns an error if the value is greater than the given maximum - see min.
Returns an error if the value is not a string.
Returns an error if the string value does not match the given regex.
interface IFoo {
bar: string;
}
const fooValidator: Validator<IFoo> = {
bar: isStirng(matches(/^[a-z]+$/))
};
// Returns an error result
const result = validate({bar: '123abc'}, conformsTo(fooValidator));
Returns an error if length of the value (e.g. a string or an array) is less than the given minimum.
interface IFoo {
bar: string;
}
const fooValidator: Validator<IFoo> = {
bar: isStirng(minLength(1))
};
// Returns an error result
const result = validate({bar: ''}, conformsTo(fooValidator));
Returns an error if length of the value (e.g. a string or an array) is less than the given maximum - see minLength.
Returns an error if length of the value (e.g. a string or an array) is not equal to the given length - see minLength.
Returns an error if the value is not an array. If no other assertions are given, then the type defaults to any[]
.
interface IFoo {
bar: any[];
}
const fooValidator: Validator<IFoo> = {
bar: isArray()
};
// This is valid
validate({
bar: ['abc', 123, true, null]
}, conformsTo(fooValidator));
// Returns an error
validate({
bar: 'baz'
}, conformsTo(fooValidator));
Returns an error if any value of an array does not match the following assertion chain.
interface IFoo {
bar: number[];
}
const fooValidator: Validator<IFoo> = {
bar: isArray(eachItem(isNumber()))
};
// This is valid
validate({
bar: [1, 2, 3]
}, conformsTo(fooValidator));
// Returns an error
validate({
bar: ['abc', 123, true, null]
}, conformsTo(fooValidator));
Returns an error if the value is not an object.
interface IFoo {
bar: any;
}
const fooValidator: Validator<IFoo> = {
bar: isObject()
};
// This is valid, but it wouldn't be safe to access properties on foo.bar
const foo = validate({
bar: {baz: 123}
}, conformsTo(fooValidator));
// Returns an error
validate({
bar: 'object'
}, conformsTo(fooValidator));
Returns an error if the value does not equal one of the given values.
type Bar = 'A' | 'B' | 'C';
interface IFoo {
bar: Bar;
}
const fooValidator: Validator<IFoo> = {
bar: equals<Bar>('A', 'B', 'C')
};
// Returns an error
validate({
bar: 'D'
}, conformsTo(fooValidator));
Validates that the result is a map of string
onto any
.
interface IFoo {
map: {[key: string]: any};
}
const fooValidator: Validator<IFoo> = {
map: isMap()
};
// This is valid
validate({
map: {
bar: 'ABC',
baz: {x: 123, y: 456},
blah: null
}
}, conformsTo(fooValidator));
// This is also valid
validate({
map: { }
}, conformsTo(fooValidator));
// Returns an error - not an object
validate({
map: 'abc'
}, conformsTo(fooValidator));
// Returns an error - one of the keys is not a string
validate({
map: {
bar: 'ABC',
baz: {x: 123, y: 456},
0: true
}
}, conformsTo(fooValidator));
Returns an error if any value of a map does not match the following assertion chain.
interface IFoo {
map: {[key: string]: string};
}
const fooValidator: Validator<IFoo> = {
map: isMap(eachValue(isString(minLength(1))))
};
// This is valid
validate({
map: {
bar: 'ABC',
'a very long key with spaces': 'DEF'
}
}, conformsTo(fooValidator));
// Returns an error result
validate({
map: {
bar: true, // Not a string
baz: '' // Too short
}
}, conformsTo(fooValidator));
Checks against multiple assertions until either one is valid, or they all fail. Useful for complex union types. Assertions are checked in the order given.
When no match is found, all the validation errors for each type are printed.
interface IFoo {
bar: string | string[];
}
const fooValidator: Validator<IFoo> = {
bar: either(
is('a letter', isString(lengthIs(1))),
is('an array of letters', isArray(eachItem(isString(lengthIs(1)))))
)
};
// These are valid
validate({bar: 'A'}, conformsTo(fooValidator));
validate({bar: ['A', 'B', 'C']}, conformsTo(fooValidator));
// An invalid example
const result = validate({bar: ['A', 'BC', 'D']}, conformsTo(fooValidator));
if (!result.success) {
console.log(result.toString());
// 1 validation error:
// $.bar: No match found - the following assertions failed:
// Not a letter, due to 1 validation error:
// $: Expected string, got array
// Not an array of letters, due to 1 validation error:
// $[1]: Length 2 is not equal to 1
}
Note: Due to limitations with generics, currently up to 20 assertions are supported by TypeScript.