json-schema-declared
v1.6.2
Published
`json-schema-declared` is a set of utility type querying JSON Schema metaschemas expressed as Typescript `const` declarations. Those declaration are generated from metaschemas collected at [the official JSON Schema website specification links](https://jso
Downloads
911
Readme
Json Schemas Declared
json-schema-declared is a set of utility type querying JSON Schema metaschemas expressed as Typescript const declarations. Those declaration are generated from metaschemas collected at the official JSON Schema website specification links, dereferenced and accessible via Typescript helper types.
import 'json-schema-declared'
/*
Query metaschema by identifiers: version name or id.
*/
let m: Metaschema<'2020-12'>
// declare const {
// readonly $schema: "https://json-schema.org/draft/2019-09/schema",
// properties: { ... },
// ...
// }
let m: MetaschemaByID<'http://json-schema.org/draft-04/schema#'>
let m: MetaschemaByVersion<'draft-00'>
/*
Analyze metaschema content.
*/
let a: SimpleTypes<'draft-02'> // "string" | "integer" | ... | "any"
let a: Keywords<'draft-07'> // "$schema" | ... | "properties" | ...
/*
Type your schemas
Note: feature is a work in progress, but is well usable now.
*/
let s: JsonSchema<'2020-12'> = { $schema: 'https://json-schema.org/draft/2019-09/schema' }
// ERROR: Types of property $schema are incompatible (ts 2322)
/*
Metaschema identifiers
*/
let i: MetaschemaVersion // e.g. "draft-06"
let m: MetaschemaId // e.g. 'http://json-schema.org/draft-07/schema#'
let m: AllMetaschemaId // together with dependencies, e.g. 'https://json-schema.org/draft/2019-09/meta/core'
let m: MetaschemaIdentifier // Version and ID together.
/*
Convert metaschema identifiers.
*/
let c: Id2Version<'http://json-schema.org/draft-02/schema#'> // 'draft-02'
let c: Version2Id<'2019-09'> // 'https://json-schema.org/draft/2019-09/schema'Instalation
npm install -D "json-schema-declared"
pnpm install -D "json-schema-declared"Building
The package distributed here is already built. If you want to run building script yourself, to:
pnpm build-declarationsRationale
Implementers of JSON Schema tools use various utilities to align their software against JSON Schema specification. One such utility is the official JSON Schema Test Suite containing unit tests for various JSON Schema metaschema versions.
How then to check if our tool covers all the keywords of any official metaschema ("draft") available? The meaning and possible subschemas of the keywords change over time. Custom parsers are often not transparent which JSON schema versions they use, tending to neglect the support of the oldest and newest JSON Schema spec. Their source code often contains whole chunks of unspecified metaschemas rewritten by hand into e.g. Typescript types.
It is thus sometimes the case we would like to have some kind of validation already on typing level using metaschemas (i.e. schemas describing schemas). For example, if we need to create a factory function that validates our schema depending on schema version, we may use various patterns of exhaustion to check whether our implementation covers all the cases coming from metaschema versioning.
All metaschemas brought together
The main focus of this package is to bring all the metaschema declarations into one easily accessible place. We believe it is important to work as close to the original schema file as possible. Using Typescript types, we stay within the domain of "dev dependencies" not increasing your app's "dist" size.
json-schema-declared helps at explicitly informing our project which schemas we do support.
import { MetaschemaVersion } from "json-schema-declared"
/* "2020-12" | "2019-09" | "draft-07" | ... | "draft-00 */
type SupportedMetaschemas = Except<MetaschemaVersion, `draft-0${0|1|2}`|'2020-12'>
type MyUtility<V extends SupportedMetaschemas> = ...
const MyJsonSchemaUtilities: { [M in SupportedMetaschemas]: Validator<M> } = {
/*
Implementation of utilities for supported schemas
from draft-03 to draft 2019-09
*/
}
const utility: MyUtility<"wrong-version"> // TS error! (ts 2344)The list comes from the official metaschema list, stored at the official JSON Schema website.
Metaschemas as declared constant types
Luckily, JSON metaschemas are expressed (obviously) as JS object notation, allowing us to use powerful Typescript as const directly on the metaschema file.
This library uses declarations autogenerated from JSON Schema metaschema files and adheres to them at all times. A source file called metaschemas.json contains a list of all the links to official JSON Schema metaschemas. This file is thought as the Single Source of Truth (SSOT) for the rest of the tooling [^1].
[^1]: There are some cases when additional data must be injected, for example for drafts-03 and older, when "simple types" where declared in the specification document rather than draft's metaschema. Those cases are clearly stated in the comments and specification is referenced.
During build stage, metaschemas listed in SSOT are being downloaded, dereferenced and saved as declared constants exported variables (in declarations.d.ts). All the utility tools refer directly to those declarations as a whole. Additional exhaustion guards and tests keep in check if all the declared schemas are covered. This distribution is already built and ready to use.
// From `src/declarations.d.ts`
export declare const JSON_SCHEMA_ORG_DRAFT_07_SCHEMA: {
$schema: "http://json-schema.org/draft-07/schema#",
$id: "http://json-schema.org/draft-07/schema#",
title: "Core schema meta-schema",
definitions: { ... }
properties: { ... }
}Using the package
The user can interface with metaschema declarations using our utility types. All the types take form of generics, whom you need to provide schema version name. The names are spelled according to json-schema.org and are all part of the MetaschemaVersion string literal (defined itself as a reference to all declarations within declarations.d.ts).
import { MetaschemaVersion, Metaschema } from "json-schema-declared"
// MetaschemaVersion: "2020-12" | "2019-09" | ... | "draft-00"
type Schema = Metaschema<"2020-12">
// Result's in inferred type:
// type Schema = {
// $schema: "https://json-schema.org/draft/2020-12/schema";
// $id: "https://json-schema.org/draft/2020-12/schema";
// $vocabulary: { ... },
// ...
// }You can then browse metaschemas as objects:
type Subschemas = Schema['allOf']['length'] // 7The JSON Pointer references are being resolved by creating references to the already declared Typescript constants, pointing the object nodes back to the metaschema declarations:
type Subschemas = Schema['allOf'][number]['title']
/* "Applicator vocabulary meta-schema" | ... */You can access the raw metaschema by setting an additional Raw parameter to true:
type RawSchema = Metaschema<'draft-06', true>['properties']['not']
// -> { $ref: "#"; }Further metaschemas' content analysis is possible using SimpleTypes and Keywords type utilities:
import { Keywords } from "json-schema-declared"
type Draft2020 = Keywords<"2020-12">
/* ... "readOnly" | ...21 more... | "$recursiveRef" */
type Draft00 = Keywords<"draft-00">
/* ..."maximumCanEqual" | "maxDecimal" | "disallow" */
import { SimpleTypes } from "json-schema-declared"
type Types2020 = SimpleTypes<"2020-12">
type Types00 = SimpleTypes<"draft-00">
// Get legacy types.
type LegacyTypes = Exclude<Types00, Types2020> // "any"Version vs $id
Within JSON Schemas, metaschemas are referred by their $id (e.g. http://json-schema.org/draft-04/schema#). However, json-schema.org specifications provide additional naming convention using version "names". Version names are validated against MetaschemaVersion string literal union containing all the version published so far by json-schema.org.
We define two types containing allthe metaschema $id, coming in two flavours:
// All $ids (including dependent schemas, e.g. 2020-12's `meta/core`:
type AllMetaschemaId = ... | ...
// All $ids *without* dependencies:
type MetaschemaId = ... | ... When building your own JSON Schema metaschema-compliant software, it is adviced to stick to the version names instead of $id. The main benefit is that they express the whole specification rather than a singular schema.
Beware that metaschemas can be expressed via many interconnected schemas, all bearing their own $id. You are thus encouraged to use the version names, leaving dependent schemas for internal use of referencing mechanisms.
There are however situations when it is easier to find metaschema by it's $id, for example when consuming a schema. Then, we may well use it's $schema, containing a metaschema's URI, to reference one of stored metaschemas.
import { Keywords } from "json-schema-declared"
const someSchema = { "$schema": "http://json-schema.org/draft-04/schema#", ... } as const
type KeywordsNeeded = Keywords<typeof someSchema.$schema]>json-schema-declared utilities often allow for using metaschema version name and $id interchangeably. However, when you implement a various metaschema-compliant code, you may want to stick to version name.
import { MetaschemaVersion } from "json-schema-declared"
const someSchema: {"$schema": ... }
class Parser<V extends MetaschemaVersion> { public version?: V }
/* Question: how to get version name from `someSchema`? */You can move from $id to version name using Id2Version utility type. Notice that unlike MetaschemaByID, you cannot access version names of dependent schemas (because they have none):
import { Id2Version } from "json-schema-declared"
type MyVersion = Id2Version<"https://json-schema.org/draft-04/schema">
// -> 'draft-04'
type MyVersion = Id2Version<"https://json-schema.org/draft/2019-09/schema">
// -> '2019-09'
/* The `meta/core` is a dependent schema of draft 2019-09 */
type NoVersion = Id2Version<"https://json-schema.org/draft/2019-09/meta/core">
// -> neverInternally, many json-schema-declared utilities use either MetaschemaByID or Id2Version, depending on context.
import { Keywords, SimpleTypes } from "json-schema-declared"
/* A dependent schema defines keys! */
type YesKeys = Keywords<"https://json-schema.org/draft/2019-09/meta/core">
// -> ... | ... | ...
/*
But it is the metaschema as a whole which defines simple types.
Thus, a dependend schema cannot be looked for them.
*/
type NoTypes = SimpleTypes<"https://json-schema.org/draft/2019-09/meta/core">
// -> neverYou may as well convert a version to Id. The query is done using metaschema itself:
type ID = Version2Id<"2019-09">
// -> "https://json-schema.org/draft/2019-09/schema"Finally, if you don't know whether to use version or id, you can use MetaschemaIdentifier utility.
JsonSchema as type
[!Warning] This feature is experimental. For now, it's types are too generic.
You can type a JSON object as a JSON Schema instance using JsonSchema type:
import { JsonSchema } from "json-schema-declared"
const mySchema: JsonSchema<"2019-19"> = {}See also
Other projects that focus on Json Schema interoperability:
