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.

Primitive Values

The simplest structs are ones that validate "primitive" values, like strings, numbers or booleans. For example:

import { assert, 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 calling assert or is 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 { assert, 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.

🤖 Check out the Types reference for all of the possible struct types.

Composed Values

In addition to simple, "flat" values, you can also compose structs into more complex shapes. The most common example of this is object structs:

import { assert, number, object, string } from 'superstruct'

const User = object({
  id: number(),
  email: string(),
  name: string(),
})

// passes
assert(
  {
    id: 1,
    email: 'jane@example.com',
    name: 'Jane',
  },
  User
)

// throws! (id is invalid)
assert(
  {
    id: '1',
    email: 'jane@example.com',
    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, assert, number } 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),
})

🤖 For modelling recursive structures you can use the lazy utility to prevent circular errors.

Optional Values

You can also model optional properties. For example, maybe an email address isn't strictly required for all your users, you could do:

import { number, object, optional, string } 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: 'jane@example.com',
}

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()),
})

🤖 Check out the Types reference for all of the possible struct types.

Custom Values

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!

🤖 Check out the Types reference for all of the possible struct types.

Last updated