@cleverbrush/schema-json

Bidirectional JSON Schema (Draft 7 / 2020-12) interop — convert existing JSON Schemas to fully-typed @cleverbrush/schema builders and back.

# @cleverbrush/schema-json
$npm install @cleverbrush/schema-json
# peer dependency
$npm install @cleverbrush/schema

💡 What it does

JSON Schema → typed builder

Pass any JSON Schema literal to fromJsonSchema() and get back a @cleverbrush/schema builder whose TypeScript type exactly mirrors the schema structure — required vs optional properties, literal types from const and enum, nested objects and arrays, all inferred at compile time. No code generation, no extra build step.

Builder → JSON Schema

Pass any @cleverbrush/schema builder to toJsonSchema() and get a plain JSON Schema object. Use it in OpenAPI specs, form generators, or any JSON Schema consumer. All declarative constraints (formats, min/max, patterns, enums) round-trip cleanly.

When to use this library

  • Consuming external APIs — you have a JSON Schema from an OpenAPI spec or third-party service and want to validate data with full TypeScript inference.
  • OpenAPI round-trip — generate JSON Schemas from your validators to embed in API specs.
  • Incremental migration — convert an existing JSON Schema catalogue to @cleverbrush/schema builders one step at a time.

fromJsonSchema(schema)

Converts a JSON Schema literal to a @cleverbrush/schema builder. Pass the schema with as const — this is required for TypeScript to infer precise builder and value types.

import { fromJsonSchema } from '@cleverbrush/schema-json';
import type { InferFromJsonSchema } from '@cleverbrush/schema-json';

// Define the JSON Schema once — use as const for precise inference
const UserJsonSchema = {
  type: 'object',
  properties: {
    id:    { type: 'string', format: 'uuid' },
    name:  { type: 'string', minLength: 1, maxLength: 100 },
    email: { type: 'string', format: 'email' },
    age:   { type: 'integer', minimum: 0 },
    role:  { enum: ['admin', 'user', 'guest'] },
  },
  required: ['id', 'name', 'email'],
} as const;

// TypeScript infers the value type from the JSON Schema — no duplication!
type User = InferFromJsonSchema<typeof UserJsonSchema>;
// {
//   id: string;
//   name: string;
//   email: string;
//   age?: number;
//   role?: 'admin' | 'user' | 'guest';
// }

const UserSchema = fromJsonSchema(UserJsonSchema);

// Validate data — result.object is fully typed as User
const result = UserSchema.validate({
  id: 'a1b2c3d4-e5f6-1234-8abc-000000000001',
  name: 'Alice',
  email: 'alice@example.com',
  role: 'admin',
});

Supported JSON Schema keywords

KeywordBuilder equivalent
type: 'string'string()
type: 'number'number()
type: 'integer'number() with integer flag
type: 'boolean'boolean()
type: 'array' + itemsarray(itemBuilder)
type: 'object' + propertiesobject({ … })
required: […]required / optional per property
additionalProperties: true.acceptUnknownProps()
constliteral builder
enumunion(…) of const builders
anyOf / allOfunion(…) / fallback
minLength / maxLength.minLength() / .maxLength()
pattern.matches(regex)
minimum / maximum.min() / .max()
exclusiveMinimum / exclusiveMaximum.min() / .max() exclusive
multipleOfnumber().multipleOf(n)
minItems / maxItems.minLength() / .maxLength() on array
format: 'email'.email() extension
format: 'uuid'.uuid() extension
format: 'uri' / 'url'.url() extension
format: 'ipv4'.ip({ version: 'v4' })
format: 'ipv6'.ip({ version: 'v6' })
format: 'date-time'.matches(iso8601 regex)
readOnly: true.readonly()
description.describe(text)

toJsonSchema(schema, opts?)

Converts any @cleverbrush/schema builder to a JSON Schema object. Declarative constraints (formats, min/max, patterns, enum/const literals, required/optional per property) round-trip cleanly. Descriptions set via .describe(text) are emitted as the description field on the corresponding JSON Schema node.

import { toJsonSchema } from '@cleverbrush/schema-json';
import { object, string, number, array } from '@cleverbrush/schema';

const ProductSchema = object({
  sku:   string().uuid(),
  name:  string().minLength(1).maxLength(255),
  price: number().min(0),
  tags:  array(string()).optional(),
});

// Default: JSON Schema Draft 2020-12 with $schema header
const spec = toJsonSchema(ProductSchema);
// {
//   "$schema": "https://json-schema.org/draft/2020-12/schema",
//   "type": "object",
//   "properties": {
//     "sku":   { "type": "string", "format": "uuid" },
//     "name":  { "type": "string", "minLength": 1, "maxLength": 255 },
//     "price": { "type": "number", "minimum": 0 },
//     "tags":  { "type": "array", "items": { "type": "string" } }
//   },
//   "required": ["sku", "name", "price"]
// }

// Embed in an OpenAPI spec (suppress $schema header)
const openApiSchema = toJsonSchema(ProductSchema, { $schema: false });

// Use Draft 07 format
const draft7Schema = toJsonSchema(ProductSchema, { draft: '07' });

🔷 TypeScript inference utilities

InferFromJsonSchema<S>

Derives the TypeScript value type from a statically-known JSON Schema — purely at the type level, no runtime call needed.

import type { InferFromJsonSchema } from '@cleverbrush/schema-json';

const S = {
  type: 'object',
  properties: {
    id:   { type: 'integer' },
    name: { type: 'string' },
  },
  required: ['id'],
} as const;

type Item = InferFromJsonSchema<typeof S>;
// { id: number; name?: string }

JsonSchemaNodeToBuilder<S>

Maps a statically-known JSON Schema node to the exact @cleverbrush/schema builder type. Use this when you want the builder type without calling fromJsonSchema at runtime.

import type { JsonSchemaNodeToBuilder } from '@cleverbrush/schema-json';

const S = { type: 'string', format: 'email' } as const;
type B = JsonSchemaNodeToBuilder<typeof S>;
// StringSchemaBuilder<string, true>

Limitations

LimitationNotes
Custom validators (addValidator)Not representable in JSON Schema — omitted in toJsonSchema output; not recoverable by fromJsonSchema
$ref / $defsNot supported in fromJsonSchema
if / then / else, notNot supported
allOf in fromJsonSchemaFalls back to SchemaBuilder<unknown> (no deep schema merge)
Dual IP format (both v4 + v6)format omitted in toJsonSchema output; no single JSON Schema keyword covers both