Validating Data
Superstruct is designed to let you validate any data, ensuring that it matches a specific schema. In this guide we'll show you some of the possibilities.
The simplest structs are ones that validate "primitive" values, like strings, numbers or booleans. For example:
import { string } from 'superstruct'
const Struct = string()
assert('a string', Struct) // passes
assert(42, Struct) // throws!
In this case,
assert
will throw an error if the input data
is not a a string. So on any line after the assertion we're guaranteed to be dealing with a string input.🤖 Note: Superstruct works well with TypeScript guards and assertions, so after callingassert
oris
you can access your data in a type-safe way!
But Superstruct has simple structs like these for more than the primitive types. It has support out of the box for many of the common types you might need to validate—dates, functions, regexps, etc.
import { date } from 'superstruct'
const Struct = date()
assert(new Date(), Struct) // passes
assert('a string', Struct) // throws!
Here we're ensuring that
data
is a valid Date
object.In addition to simple, "flat" values, you can also compose structs into more complex shapes. The most common example of this is
object
structs:const User = object({
id: number(),
email: string(),
name: string(),
})
// passes
assert(
{
id: 1,
email: '[email protected]',
name: 'Jane',
},
User
)
// throws! (id is invalid)
assert(
{
id: '1',
email: '[email protected]',
name: 'Jane',
},
User
)
// also throws! (email is missing)
assert(
{
id: 1,
name: 'Jane',
},
User
)
This
User
struct will ensure that input data is an object with specific shape of properties, and with property values that match structs.You could also define a struct which represents a list of values that all match a specific type, using the
array
factory. For example:import { array } from 'superstruct'
const Struct = array(number())
assert([1, 2, 3], Struct) // passes!
assert(false, Struct) // throws!
assert(['a', 'b', 'c'], Struct) // throws! (invalid element)
These are only two examples, but Superstruct supports many complex structs—maps, sets, records, tuples, etc.
You can also compose structs together, for cases where you have relationships between pieces of data. For example, a
User
and a Team
:const User = object({
id: number(),
email: string(),
name: string(),
})
const Team = object({
id: number(),
name: string(),
users: array(User),
})
You can also model optional properties. For example, maybe an
email
address isn't strictly required for all your users, you could do:import { optional } from 'superstruct'
const User = object({
id: number(),
name: string(),
email: optional(string()),
})
Wrapping a struct in
optional
means that the value can also be undefined
and it will still be considered valid.So now both of these pieces of data would be valid:
const jane = {
id: 43,
name: 'Jane Smith',
email: '[email protected]',
}
const jack = {
id: 44,
name: 'Jack Smith',
}
Similarly to
optional
, you can use nullable
for properties that can also be null
values. For example:const Article = object({
title: string(),
body: string(),
published_at: nullable(date()),
})
Next up, you might have been wondering about the
email
property. So far we've just defined it as a string, which means that any old string will pass validation.But we'd really like to validate that the email is a valid email address. You can do this by defining a custom validation struct:
import { define } from 'superstruct'
import isEmail from 'is-email'
const email = () => define('email', (value) => isEmail(value))
Now we can define structs know about the
email
type:const User = object({
id: number(),
name: string(),
email: email(),
is_admin: optional(boolean()),
})
Now if you pass in an email string that is invalid, it will throw:
const data = {
id: 43,
name: 'Jane Smith',
email: 'jane',
}
assert(data, User) // throws! (invalid email)
And there you have it!
Last modified 1yr ago