Bidirectional JSON Schema (Draft 7 / 2020-12) interop — convert existing JSON Schemas to fully-typed @cleverbrush/schema builders and back.
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.
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.
@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',
});| Keyword | Builder equivalent |
|---|---|
type: 'string' | string() |
type: 'number' | number() |
type: 'integer' | number() with integer flag |
type: 'boolean' | boolean() |
type: 'array' + items | array(itemBuilder) |
type: 'object' + properties | object({ … }) |
required: […] | required / optional per property |
additionalProperties: true | .acceptUnknownProps() |
const | literal builder |
enum | union(…) of const builders |
anyOf / allOf | union(…) / fallback |
minLength / maxLength | .minLength() / .maxLength() |
pattern | .matches(regex) |
minimum / maximum | .min() / .max() |
exclusiveMinimum / exclusiveMaximum | .min() / .max() exclusive |
multipleOf | number().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' });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>| Limitation | Notes |
|---|---|
Custom validators (addValidator) | Not representable in JSON Schema — omitted in toJsonSchema output; not recoverable by fromJsonSchema |
$ref / $defs | Not supported in fromJsonSchema |
if / then / else, not | Not supported |
allOf in fromJsonSchema | Falls 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 |