validate-structure
v2.0.0
Published
Check that an object matches the expected structure.
Downloads
8
Maintainers
Readme
validate-structure
Check that an object matches the expected structure.
Installation
npm i validate-structure
Note: Node 14+ is required.
Usage
Functions
validateStructure()
Signature: validateStructure(val: any, structure: Structure, strict?: boolean, customTypes?: TypeDefs): ValidationError[]
Arguments:
val
The value to validate.structure
The expected structure ofval
. This must be a Structure.- (optional)
strict
Whether or not extra keys should be treated as a failure (defaults totrue
) - (optional)
customTypes
Any custom types that you want to refer to multiple times can be placed here for convenience. This should be an object where each key is the name of the type and the value is a Structure.
matchesStructure()
Signature: matchesStructure(val: any, structure: Structure, strict?: boolean, customTypes?: TypeDefs): boolean
This is a simple wrapper around validateStructure()
that returns true
if and
only if there were no errors returned by validateStructure()
. It can be used
if you only care that it doesn't match, but if you need to know what didn't
match you should use validateStructure()
directly.
Structure
validate-structure
is built around a robust type system, which means it can
handle any data structure you throw at it.
Basic Structure
The most basic Structure is a single string representing a native type. The
following typeof
s are supported out-of-the-box:
"any"
(matches anything exceptnull
andundefined
)"null"
"undefined"
(note that this still requires the key to be present)"boolean"
"number"
"bigint"
"string"
"symbol"
"function"
There are also some non-typeof
checks provided as well:
"int"
Matches any integer"array"
Matches any array"object"
Matches any object that is not an array ornull
Operators
Sometimes you might find that just the basic types aren't enough. That's why
validate-structure
has a range of operators you can use to form complex
Structures.
"<type>?"
Optional: matches"<type>"
ornull
orundefined
."<type>[]"
Array: matches an array of"<type>"
.- To ensure that an array's length is valid, use
<type>[min...max]
- If you need to enforce a minimum length you can leave off max:
<type>[min...]
- Likewise for a maximum length:
<type>[...max]
- If your array has a fixed length (
min === max
) you can use the shorthand form<type>[N]
- If the array can't be empty, you can either use
<type>[1...]
or the shorthand form<type>[.]
- If null values are allowed inside the array, put a
?
between the<type>
and the[
(like so:int?[]
).
- To ensure that an array's length is valid, use
"[<type 1>, ..., <type n>]"
Tuple: matches an array of"<type>"
s with strict length. For example, if you have an image size stored as a pair of integers you can validate that with"[int, int]"
.<type 1> | ... | <type n>
Union: matches at least one of the"<type>"
s.(<type>)
Group: used to group types together. For instance the structure(string | int)[]
will match an array where each item is either a string or an integer.
Objects
If your data can't be represented by one of the basic types you can use an object structure. It should exactly match the expected structure with a few exceptions:
- If a value is optional you can append a
'?'
to the key. For example:{ 'optional?': 'string' }
. - If a value should be an array you can use the array syntax above (
[]
,[min...max]
,[min...]
,[...max]
,[.]
, and[N]
). For example:{ 'arr[]': 'string' }
. - If a value should be a tuple, you can use an array of values. For example:
{ size: ["int", "int"] }
.
Custom types
If you have a complex type that you would like to reuse in multiple places you can specify it as a custom type. It also allows for recursion.
const types = {
Person: {
name: "Name",
"parents[]?": "Person",
"spouse?": "Person",
"children[]?": "Person",
},
Name: { first: "string", last: "string" },
};
validateStructure(person, "Person", true, types);
Functions
If you have a use case that is not satisfied by the aforementioned methods, you can write a custom validator function. These come in two flavors and can be used anywhere a Structure can.
Matcher (
type MatcherFn = (val: any) => boolean
). This is the simpler of the two. It takes in a value and should returntrue
if the value matches andfalse
if it doesn't.Validator (
type ValidatorFn = (val: any, path: string, types: TypeValidators, strict: boolean) => ValidationError[]
). This is more complicated, but gives much more control. The arguments are as follows:val
The value to validate.path
The keypath of the value in dot notation, used for error messages.types
A key:value map of otherValidatorFn
s. For example,types.array
is theValidatorFn
that matches any array. This includes matchers for any custom types you have defined.strict
Whether or not the validation is running in strict mode. SeevalidateStructure()
for details.
The return type is an array of
ValidationError
objects. EachValidationError
is an object consisting of three keys:msg
,path
, andtype
.msg
is a string explaining what the error is,path
is the path to the invalid item, andtype
is the type of the error. The default types are"key"
,"val-start"
and"val-end"
, depending on where the mismatch was, but any string may be used. Here is an example error:{ msg: 'array must not be empty', path: ['arr'], type: "val-start" }
.Here is an example to check if a value is a string that starts with a
$
:import type { ValidatorFn } from "validate-structure"; import { validateStructure, buildError } from "validate-structure"; const dollarString: ValidatorFn = (val, path, types, strict) => { // Check if the value is a string const errors = types.string(val, path, types, strict); if (errors.length > 0) return errors; // The value is fine, return no errors. if (val.startsWith("$")) return []; // The value is invalid, return an error return buildError(`'${val}' does not start with a '$'`, path); }; validateStructure("$12", dollarString); // -> [] validateStructure("12", dollarString); // -> [{msg: "'12' does not start with a '$'", path: ""}] validateStructure({ price: "12" }, { price: dollarString }); // -> [{msg: "'12' does not start with a '$'", path: "price"}]
Examples
import { matchesStructure } from "validate-structure";
// A single string
matchesStructure("hello world", "string"); // -> true
// A single integer
matchesStructure(14, "int"); // -> true
matchesStructure(14.2, "int"); // -> false
// An array of numbers
matchesStructure([], "number[]"); // -> true
matchesStructure([14], "number[]"); // -> true
matchesStructure([1, 2, 3, 4, 5], "number[]"); // -> true
// A tuple of 2 numbers
const sizeTuple = "[number, number]"; // This could also be written "number[2]"
matchesStructure([1, 2], sizeTuple); // -> true
matchesStructure([1], sizeTuple); // -> false
matchesStructure([1, 2, 3], sizeTuple); // -> false
// A tuple of a string and an int
const ruleTuple = "[string, int]";
matchesStructure(["tabLength", 2], sizeTuple); // -> true
matchesStructure([14, 2], sizeTuple); // -> false
// A custom object structure
const structure = { id: "int", name: "string" };
matchesStructure({ id: 14, name: "John" }, structure); // -> true
// Strict mode is on by default
matchesStructure({ id: 14, name: "John" }, { id: "int" }); // -> false
matchesStructure({ id: 14, name: "John" }, { id: "int" }, false); // -> true
// Complex structures
const structure = {
name: "string",
tabs: {
name: "string",
"color?": "string",
"contents[.]": {
"title?": "string",
body: "string",
},
},
};
matchesStructure(
{
name: "Home",
tabs: {
name: "About",
contents: [
{ title: "About Us", body: "Lorem ipsum dolor sit amet" },
{ body: "consectetur adipiscing elit" },
],
},
},
structure
); // -> true
matchesStructure(
{
name: "Home",
tabs: {
name: "About",
contents: [],
},
},
structure
); // -> false (tabs.contents must be non-empty)
Contributing
If you want to help out, please read the CONTRIBUTING.md.