@bscotch/schema-builder
)
SchemaBuilder (SchemaBuilder is built on top of TypeBox to make it easier to build self-contained, complex schemas and reusable schema libraries, with complete Typescript support along the way.
⚡ Quick Start
See the TypeBox docs for the core concepts and features, since SchemaBuilder builds upon those. In particular, this package provides a SchemaBuilder
class which extends the TypeBuilder
class from TypeBox (the Type
export from TypeBox is a TypeBuilder
instance.)
Install: npm install @bscotch/schema-builder
Then build your schema:
import {
SchemaBuilder,
Static,
StaticDefs
} from '@bscotch/schema-builder';
// SchemaBuilder makes heavy use of chaining, and of using callback
// functions to provide Schemas, so that your `this` type stays
// up to date as you change your SchemaBuilder instance.
// Create a definitions library for re-usability. Related or cross-
// referencing definitions can be collected by a single `SchemaBuilder`
// instance for convenience.
const lib = new SchemaBuilder().addDefinition('aString', function () {
// By using a callback function, `this` refers to the current
// SchemaBuilder instance.
return this.String();
});
// Create a complex schema, starting by importing the definitions
// stored by the `lib` SchemaBuilder instance we just made.
const mySchema = new SchemaBuilder({ lib })
.use(function () {
// Using a callback via the `.use()` method lets
// you use the `this` keyword to refer to the current SchemaBuilder
// instance, with full type support for any definitions or other
// changes made to the instance.
return this.addDefinition('nums', this.LiteralUnion([1, 2, 3]))
.addDefinition('moreNums', this.Array(this.Number()))
.addDefinition('deeper', function () {
return this.Object({
deepArray: this.Array(this.DefRef('nums')),
libRef: this.Array(this.DefRef('aString')),
});
});
})
// Most function return an updated `this`, so that the return type
// includes changes made to the SchemaBuilder instance.
.setRoot('deeper');
// Get the root schema as a Typescript interface, which can
// be used for static validation and intellisense.
type MySchema = Static<typeof mySchema>;
// {
// deepArray: (1|2|3)[];
// libRef: string[];
// }
// Get the defs library as a Typescript interface
type MySchemaDefs = StaticDefs<typeof mySchema>;
// {
// aString: string;
// nums: 2 | 1 | 3;
// moreNums: StaticArray<TNumber>;
// deeper: {
// deepArray: StaticArray<TRef<TLiteralUnion<[1, 2, 3]>>>;
// libRef: StaticArray<...>;
// };
// }
// Validate data against the schema
const data: MySchema = {
deepArray: [2, 3],
libRef: ['hello']
};
mySchema.assertIsValid(data);
// Save the schema to disk
mySchema.writeSchemaSync('mySchema.json');
What is JSON Schema?
JSON Schema is a specification for describing data structures, and it is incredibly useful for solving several problems simultaneously:
- Data structure description (machine-readable, allows for use of generic solutions for specific data problems)
- Data description/documentation (human-readable)
- Data validation (machine-readable, reduces need for additional test-writing)
By solving this all at the same time, in one place, developers can dramatically reduce the amount of code they need to write and thus reduce errors and increase productivity.
SchemaBuilder
solve?
What problems does Schema authoring is hard and error-prone
JSON Schema is a pain to write since any given schema is itself a complex data structure, and JSON Schema has strict rules that schema authors must follow to create valid, useful schemas.
Various tools exist to help with this, including using YAML instead of JSON for better human-readability, intellisense in editors like Visual Studio Code, and various libraries in the JavaScript ecosystem for programmatically creating schemas with varying amounts of Typescript support.
Prior Art
Typescript + JSON Schema
Typescript tries to solve many of the same problems that JSON Schema does, at least for JSON-compatible data structures. There are a few libraries that try to use Typescript to infer Schemas or vice versa:
-
TypeBox: Create a Schema and corresponding Typescript types at the same time. This reduces duplicate effort, and is the basis for
SchemaBuilder
. - typescript-json-schema: Create a Schema from Typescript types.
- json-schema-to-typescript: Exactly the opposite! Create a Typescript interface from a JSON Schema.
Validators
The primary use case for JSON Schema is in building data validators. As such, there are many libraries/modules providing related functionality:
- ajv: Easily the most popular validator. Well tested, with plenty of extended functionality on top of canonical JSON Schema.
- jsonschema: A popular validator that still offers significant flexibility for feature extension, including mutation of data during parsing, but is less feature-rich than ajv.
- z-schema: Robust and popular validator. Appears to be restricted to JSON Schema spec.
- json-schema-faker: Generates fake data based on a schema. Useful for testing.
Parsers & Serializers
JSON Schemas describe data, and so can be used to programmatically parse and serialize corresponding data.
-
fast-json-stringify: A JSON stringifier that makes use of a corresponding JSON Schema to precompile a stringifier that is much faster than the general
JSON.stringify
function.
Misc. Schema tools
JSON Schemas can be difficult to create and understand, and there are various tools out there for simplifying their use:
- json-schema-traverse: Call functions on each subschema of a JSON Schema.
-
json-schema-ref-parser: Utilities for de-referencing
$ref
s in JSON Schemas, including the ability to convert such a schema into one with internal references.