What is superstruct?
The superstruct npm package is a library for validating, coercing, and structuring data in JavaScript and TypeScript. It allows developers to define interfaces and run-time type checks for JavaScript data structures, ensuring that data conforms to specified schemas.
What are superstruct's main functionalities?
Validation
Superstruct can be used to validate data against a defined schema. The example shows how to define a User struct and validate an object against it.
{"User": "const User = struct({name: 'string', age: 'number'}); const data = {name: 'Alice', age: 25}; const [error, user] = User.validate(data); if (error) { throw error; }"}
Coercion
Superstruct can coerce data to match a schema by applying default values. In the example, a UserWithDefaults struct is defined with an optional isAdmin field that defaults to false.
{"UserWithDefaults": "const UserWithDefaults = struct({name: 'string', age: 'number', isAdmin: 'boolean?'}); const data = {name: 'Bob', age: 30}; const user = UserWithDefaults.create(data);"}
Partial Structs
Superstruct allows creating partial structs, which can validate data that may not include all fields of the struct. The example demonstrates how to define a partial struct based on the User struct.
{"PartialUser": "const User = struct({name: 'string', age: 'number'}); const PartialUser = struct.partial(User); const data = {name: 'Charlie'}; const [error, partialUser] = PartialUser.validate(data); if (error) { throw error; }"}
Dynamic Structs
Superstruct can define dynamic structs that change based on the input data. The example shows a DynamicUser struct that requires a permissions array only if the isAdmin property is true.
{"DynamicUser": "const DynamicUser = struct.dynamic((value, branch, path) => { if (value && value.isAdmin) { return struct({name: 'string', age: 'number', permissions: 'array'}); } else { return struct({name: 'string', age: 'number'}); } }); const data = {name: 'Dave', age: 40, isAdmin: true, permissions: ['read', 'write']}; const [error, dynamicUser] = DynamicUser.validate(data); if (error) { throw error; }"}
Other packages similar to superstruct
joi
Joi is a powerful schema description language and data validator for JavaScript. It offers a similar API to superstruct but with a more extensive set of features for describing and validating data structures, including custom validation functions.
yup
Yup is a JavaScript schema builder for value parsing and validation. It defines a schema with an expressive API and is often used with form libraries like Formik. Yup schemas are immutable and composable, and it provides a slightly different API compared to superstruct.
ajv
Ajv is a JSON schema validator that supports draft-06/07/2019-09 JSON Schema standards. It is known for its performance and is used to validate JSON data on the server-side and in the browser. Unlike superstruct, Ajv relies on JSON Schema, which is a declarative language for validating the structure of JSON data.
A simple and composable way
to validate data in Javascript.
Superstruct makes it easy to define interfaces and then validate Javascript data against them. Its type annotation API was inspired by Typescript, Flow and GraphQL. But Superstruct is designed for runtime data validations, for example when accepting input in a REST or GraphQL API.
Example •
Why? •
Principles •
Documentation
Example
Superstruct exports a struct
factory for creating functions that validate data against a specific schema:
import struct from 'superstruct'
const validate = struct({
id: 'number',
title: 'string',
is_published: 'boolean',
tags: ['string'],
author: {
id: 'number',
}
})
const data = {
id: 34,
title: 'Hello World',
is_published: true,
tags: ['announcements'],
author: {
id: 1,
}
}
validate(data)
The schema definition syntax was inspired by Typescript, Flow and GraphQL.
But you can also define your own types—specific to your application's requirements—by using the exported superstruct
function. For example:
import { superstruct } from 'superstruct'
import isUuid from 'is-uuid'
import isEmail from 'is-email'
const struct = superstruct({
types: {
uuid: v => isUuid.v5(v),
email: v => isEmail(v) && v.length < 256,
}
})
const validate = struct({
id: 'uuid',
email: 'email',
is_admin: 'boolean?',
})
const data = {
id: '5a2de30a-a736-5aea-8f7f-ad0f019cdc00',
email: 'jane@example.com',
}
validate(data)
Why?
There are lots of existing validation libraries. Some of them, like Joi, are fairly popular. But most of them exhibit one or many issues...
-
Not throwing the errors. Many validators simply return true/false
or return details about the error. This was nice back in the days of callbacks, where throwing was discouraged, but in modern Javascript using throw
leads to much simpler code.
-
Not making it easy to define custom types. Lots of the validators ship with built-in types like emails, URLs, UUIDs, etc. But they often don't take into account things like maximum lengths, or But once you need to define your own custom types—which any reasonably sized use case will require—the APIs are complex, and poorly supported.
-
Not having single sources of truth. Many of the existing APIs encourage re-defining data types like names, emails, or over and over, with the source of truth being spread out across many files. This leads to
-
Not treating errors as part of the API. For the validators that do throw errors, they often throw simple message-only errors without any extra information. This makes it hard to customize the error message to make them helpful for users.
-
Not compiling schemas on creation. Some validators allow you to define schemas as plain Javascript objects, which seems nice. But then they delegate the more complex parsing and compiling logic to validation time, instead of doing the work up front.
Of course, not every validation library suffers from all of these issues, but most of them exhibit at least one. If you've run into this problem before, you might like Superstruct.
Which brings me to how Superstruct solves these issues...
Principles
-
Customizable types. Superstruct's power is in making it easy to define an entire set of custom data types that are specific to your application, so that you have full control over exactly what you're checking for.
-
Unopinionated defaults. Superscript only ships with the native Javascript types by default, that you never have to fight to override decisions made by "core" that differ from your application's needs.
-
Composable interfaces. Superstruct interfaces are composable, so you can break down commonly-repeated pieces into smaller components, and compose them to make up the more complex.
-
Terse schemas. The schemas in Superstruct are designed to be extremely terse. This makes them very easy to read and write, so that you're encouraged to have full data validation coverage.
-
Familiar API. The Superstruct API was heavily inspired by Typescript, Flow and GraphQL. If you're familiar with any of those then its schema definition API will feel very natural to use, so you can get started quickly.
-
Useful errors. The errors that Superstruct throws contain all the information you need to convert them into your own application-specific errors easy, which means more helpful errors for your end users!
-
Compiled validators. Superstruct does the work of compiling its schemas up front, so that it doesn't have to spend lots of time performing expensive tasks for every call to the validation functions in your hot code paths.
Documentation
License
This package is MIT-licensed.