• Stars
    star
    487
  • Rank 90,352 (Top 2 %)
  • Language
    TypeScript
  • Created almost 5 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Typesafe JSON (Schema) validator

npm version downloads build status coverage status Node.JS version

Suretype is a JSON validator targeting TypeScript and JSON Schema. It is ridiculously type safe when used in TypeScript, which is good for accuraccy, but also for aiding IDE auto-complete.

It's as easy as Joi, but ~70x faster.

It's (at least) as typesafe as Superstruct, but ~100x faster. ~2500x faster than Zod and ~1600x faster than ow.

These are x (times) not %

Benchmark results.

❯ yarn benchmark
Joi x 123,593 ops/sec ±0.60% (94 runs sampled)
Superstruct x 87,898 ops/sec ±0.33% (92 runs sampled)
Zod x 3,498 ops/sec ±1.15% (91 runs sampled)
ow x 5,533 ops/sec ±0.93% (85 runs sampled)
SureType x 8,982,429 ops/sec ±0.53% (91 runs sampled)
-----
73x faster than Joi
102x faster than Superstruct
2568x faster than Zod
1623x faster than ow

It supports most (if not all) of JSON schema, and nothing beyond that, so that the validator schemas written in TypeScript (or JavaScript) can be ensured to be convertible into JSON schema. This also prevents suretype from becoming feature bloated - it has a small and extremely simple API.

Errors are prettified using awesome-ajv-errors.

From a validator schema defined with suretype, you can trivially:

  • Compile a validator function (using the very fast Ajv)
  • Extract the corresponding JSON Schema
  • Deduce a TypeScript type corresponding to the validator schema (at compile-time!)
  • Using typeconv:
    • Export (convert) the validator schema into JSON Schema, Open API, TypeScript types or GraphQL, or;
    • The opposite (!); convert JSON Schema, Open API, TypeScript types or GraphQL into suretype validators! 🎉

The above makes it ideal in TypeScript environments. When used in RESTful applications, the exported schema can be used to document the APIs using OpenAPI. When used in libraries / clients, the TypeScript interfaces can be extracted to well-documented standalone files (including JSDoc comments).

Versions

  • Since version 3;
    • This is a pure ESM package. It requires at least Node 14.13.1, and cannot be used from CommonJS.
    • This package can be used in browsers without special hacks. It will not pretty-print codeframes or use colors if the bundling setup doesn't support it, but will to try to load support for it.
    • You can control colorized/stylized output globally or per validator

Minimal example

The following is a validator schema using suretype:

import { v } from "suretype"

const userSchema = v.object( {
    firstName: v.string( ).required( ),
    lastName: v.string( ),
    age: v.number( ).gte( 21 ),
} );

This schema object can be compiled into validator functions, and it can be used to deduce the corresponding TypeScript type:

import type { TypeOf } from "suretype"

type User = TypeOf< typeof userSchema >;

This type is compile-time constructed (or deduced), and is semantically identical to:

interface User {
    firstName: string;
    lastName?: string;
    age?: number;
}

Note the ? for the optional properties, i.e. those that aren't followed by required().

There are three ways of compiling a validator function; choose the one that best fits your application and situation. Given:

import { compile } from "suretype"

const data = ... // get data from somewhere, e.g. as a TypeScript unknown

Standard validator

The default behaviour of compile is to return a validator function returning extended Ajv output.

const userValidator = compile( userSchema );
userValidator( data );
// { ok: true } or
// { ok: false, errors: [Ajv errors...], explanation: string }

The explanation is a pretty-printed error.

Type-guarded validator

Use the second optional argument to specify simple mode. The return is a boolean, type guarded.

const isUser = compile( userSchema, { simple: true } );
isUser( data ); // true | false, usable as guard:

// Realistic usage:

if ( isUser( data ) ) {
    // Valid TypeScript, <data> is now typed(!) as the User type above
    data.firstName;
} else {
     // TypeScript compile error(!), <data> is unknown
    data.firstName;
}

Type-ensured validator

Specify ensure mode to get a validator function which returns the exact same output as the input (referentially equal), but with a deduced type. This is often the most practical mode.

const ensureUser = compile( userSchema, { ensure: true } );
ensureUser( data ); // returns data or throws an error if the data isn't valid.

// Realistic usage:

const user = ensureUser( data );
// <user> is ensured to be valid, *and* is of type User (as above)
user.firstName; // string
user.foo; // TypeScript compile-time error, there is no `foo` in User

On validation failure, the error thrown will be of the class ValidationError, which has both the raw Ajv errors as an errors property, and the pretty explanation in the property explanation.

Note: The returned ensurer function can optionally take a type parameter as long as it is equal to or compatible with the deduced type. This means that if the type is exported from suretype to decorated TypeScript declaration files (with annotations), those types can be used as a type parameter, and the returned type will be that type. Example:

import type { User } from './generated/user'
const user = ensureUser< User >( data );
// user is now of type User

Validate or ensure without compiling

Instead of creating a validator from compile, you can use the shorthands validate, isValid and ensure. They correspond to compiling without options, compiling in simple-mode and in ensure-mode.

import { validate, isValid, ensure } from 'suretype'

const validation = validate( userSchema, data ); // -> Validation object
const isUser = isValid( userSchema, data );      // -> Type-guarded boolean
const user = ensure( userSchema, data );         // -> user is data of type userSchema

Raw JSON Schema validator

Sometimes it's handy to not describe the validator schema programmatically, but rather use a raw JSON Schema. There will be no type deduction, so the corresponding interface must be provided explicitly. Only use this if you know the JSON Schema maps to the interface! raw works just like the v.* functions and returns a validator schema. It can also be annotated.

import { raw, compile } from 'suretype'

type User = ...; // Get this type from somewhere
const userSchema = raw< User >( { type: 'object', properties: { /* ... */ } } );

// Compile as usual
const ensureUser = compile( userSchema, { ensure: true } );

Configure

You can configure colorization and styling, instead of relying on support detection.

Either globally:

import { setSuretypeOptions } from 'suretype'

setSuretypeOptions( {
    colors: true | false,
    location: true | false,
    bigNumbers: true | false,
} );

and/or per validator, e.g.:

import { compile } from 'suretype'

const ensureThing = compile(
    schemaThing,
    { ensure: true, color: true, location: false }
);

Annotating schemas

You can annotate a validator schema using suretype() or annotate(). The return value is still a validator schema, but when exporting it, the annotations will be included.

The difference between suretype() and annotate() is that suretype() requires the name property, where as it's optional in annotate(). Use suretype() to annotate top-level schemas so that they have proper names in the corresponding JSON Schema.

Annotations are useful when exporting the schema to other formats (e.g. JSON Schema or pretty TypeScript interfaces).

import { suretype, annotate, v } from "suretype"

const cartItemSchema = suretype(
    // Annotations
    { name: "CartItem" },
    // The validator schema
    v.object( {
        productId: annotate( { title: "The product id string" }, v.string( ) ),
        // ...
    } )
);

The interface (i.e. the fields you can use) is called Annotations:

interface Annotations {
	name: string;
	title?: string;
	description?: string;
	examples?: Array< string >;
}

where only the name is required.

Thorough example

The following are two types, one using (or depending on) the other. They are named, which will be reflected in the JSON schema, shown below.

The userSchema is the same as in the above example, although it's wrapped in suretype() which annotates it with a name and other attributes.

Given these validation schemas:

import { suretype, v } from "suretype"

const userSchema = suretype(
    {
        name: "V1User",
        title: "User type, version 1",
        description: `
            A User object must have a firstName property,
            all other properties are optional.
        `,
        examples: [
            {
                firstName: "John",
                lastName: "Doe",
            }
        ],
    },
    v.object( {
        firstName: v.string( ).required( ),
        lastName: v.string( ),
        age: v.number( ).gte( 21 ),
    } )
);

const messageSchema = suretype(
    {
        name: "V1Message",
        title: "A message from a certain user",
    },
    v.object( {
        user: userSchema.required( ),
        line: v.string( ).required( ),
    } )
);

The JSON schema for these can be extracted, either each type by itself:

import { extractSingleJsonSchema } from "suretype"

// The JSON schema for User
const { schema: jsonSchema } = extractSingleJsonSchema( userSchema );

or as all types at once, into one big JSON schema. In this case, all validation schemas provided must be wrapped with suretype(), as they will become JSON schema "definitions" and therefore must have at least a name.

import { extractJsonSchema } from "suretype"

const { schema: jsonSchema, lookup, schemaRefName } =
    extractJsonSchema( [ userSchema, messageSchema ], { /* opts... */ } );

An optional second argument can be provided on the form:

interface ExtractJsonSchemaOptions {
    refMethod?: ExportRefMethod;
    onTopLevelNameConflict?: OnTopLevelNameConflict;
    onNonSuretypeValidator?: OnNonSuretypeValidator;
}

The ExportRefMethod type is a string union defined as:

    | 'no-refs'  // Don't ref anything. Inline all types to monolith types.
    | 'provided' // Reference types that are explicitly provided.
    | 'ref-all'  // Ref all provided types and those with names, suretype()'d.

The OnTopLevelNameConflict type is a string union defined as:

    | 'error'  // Fail the operation
    | 'rename' // Rename the validators to a unique name

The OnNonSuretypeValidator type is a string union defined as:

    | 'error'       // Fail the operation
    | 'ignore'      // Ignore, don't export
    | 'create-name' // Create a name 'Unknown'
    | 'lookup'      // Provide in lookup table

If lookup is specified, it allows unnamed validators. They won't exist in the resulting schema, but in a lookup table next to it. This lookup table will always exist, using this setting will simply allow unnamed validators.

The result is an object on the form:

interface ExtractedJsonSchema {
    schema: SchemaWithDefinitions; // Contains a 'definitions' property
    lookup: Map< CoreValidator< unknown >, any >;
    schemaRefName: Map< any, string >;
}

The lookup is useful to lookup the json schema for a certain validator object reference, especially unnamed ones which are not included in the schema.

The schemaRefName contains a lookup map from (top-level) schema object to its name as used when referring to it, not necessarily the same as what it is internally named, if there where naming conflicts and OnTopLevelNameConflict is rename.

In the example above, the jsonSchema object (which can be JSON.stringify'd) will be something like:

JSON Schema

{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "definitions": {
        "V1User": { // <-- This corresponds to the "name" property in suretype()
            "title": "User type, version 1",
            "description": "A User object must have a firstName property,\nall other properties are optional.",
            "examples": [
                {
                    "firstName": "John",
                    "lastName": "Doe"
                }
            ],
            "type": "object",
            "properties": {
                "firstName": { "type": "string" },
                "lastName": { "type": "string" },
                "age": { "type": "number", "minimum": 13 }
            },
            "required": [ "firstName" ]
        },
        "V1Message": {
            "title": "A message from a certain user",
            "type": "object",
            "properties": {
                "user": { "$ref": "#/definitions/V1User" }, // <-- Proper references
                "line": { "type": "string" }
            },
            "required": [ "user", "line" ]
        }
    }
}

Exporting using typeconv

A better (well, often much more practical) way of converting suretype validator schemas into JSON Schema is by using typeconv npm version.

You can convert from suretype validator schemas to:

  • TypeScript interfaces (pretty printed with JSDoc comments)
  • JSON Schema
  • Open API
  • GraphQL

When converting from suretype, typeconv will convert all exported validator schemas from the source files.

Example from SureType to TypeScript; $ npx typeconv -f st -t ts -o generated 'src/validators/**/*.ts'

You can also convert from any of these formats into suretype validators!

Example from Open API to SureType; $ npx typeconv -f oapi -t st -o generated 'schemas/**/*.yml'

More Repositories

1

awesome-phonenumber

Google's libphonenumber pre-compiled with the closure compiler
JavaScript
632
star
2

typeconv

Convert between JSON Schema, TypeScript, GraphQL, Open API and SureType
TypeScript
389
star
3

fetch-h2

HTTP/1+2 Fetch API client for Node.js
TypeScript
334
star
4

q

A platform-independent promise library for C++, implementing asynchronous continuations.
C++
191
star
5

u2f-api

U2F API for browsers
JavaScript
86
star
6

ts-to-openapi

Convert TypeScript types to OpenAPI schema components
TypeScript
55
star
7

meta-types

TypeScript meta functions for (especially variadic) meta programming
TypeScript
31
star
8

trace-unhandled

Much better tracing of unhandled promise rejections in JavaScript
JavaScript
28
star
9

awesome-ajv-errors

Prettified AJV errors
TypeScript
24
star
10

already

Utility functions for promises; finally, map, filter, etc
TypeScript
21
star
11

yaml-diff-patch

Apply a JSON diff/patch to YAML while preserving whitespace, comments and overall structure
TypeScript
19
star
12

core-types

Generic type declarations for e.g. TypeScript and JSON Schema
TypeScript
16
star
13

stream-mime-type

Get the mime type of a stream
TypeScript
10
star
14

graph-cycles

Analyze a graph to find cyclic loops
TypeScript
9
star
15

openapi-json-schema

Minimalistic OpenAPI 3 ⬌ JSON Schema conversion
TypeScript
9
star
16

core-types-ts

core-types ⬌ TypeScript interface conversion
TypeScript
8
star
17

jsonpos

Get the textual position to a property in a JSON text
TypeScript
7
star
18

haxec

Wrap a Node.js spawn() or exec() with before/after handlers
TypeScript
6
star
19

react-tree-reconciler

Simpler API for React reconcilers
TypeScript
6
star
20

oppa

😌 Super easy typesafe options parser for Node.js
TypeScript
5
star
21

json-schema-cycles

Analyize recursive (cyclic) JSON Schema types
TypeScript
4
star
22

compd

Run a command under a docker-compose setup
TypeScript
4
star
23

react-ancestry

Get the component ancestry in React
TypeScript
4
star
24

edit-json

Edit JSON text in-place for a minimal diff
TypeScript
4
star
25

core-types-json-schema

core-types ⬌ JSON Schema conversion
TypeScript
4
star
26

list-open-files

lsof for Node.js
TypeScript
3
star
27

stream-head

Peek the first couple of bytes from a stream
TypeScript
3
star
28

next-chunk

Asynchronously returns the next chunk in a Node.js readable stream
TypeScript
3
star
29

use-reusable-state

Deep-equal caching version of React useState
TypeScript
2
star
30

json-cst

Parse JSON into CST (Concrete Syntax Tree)
TypeScript
2
star
31

fast-string-compare

A (much) faster String.prototype.localeCompare
TypeScript
2
star
32

fetch-h2-br

🥐 Brotli decoder to fetch-h2-br
TypeScript
2
star
33

core-types-suretype

core-types ⬌ SureType validator conversion
TypeScript
2
star
34

json-schema-some

Array.prototype.some for JSON Schema
TypeScript
2
star
35

instead

In-place merge-and-replace
TypeScript
1
star
36

binmap

Ordered Map (ES Map compatible)
TypeScript
1
star
37

libcors

Javascript CORS handling, 100% transportation agnostic
TypeScript
1
star
38

react-vscode

React for VSCode
TypeScript
1
star