JSON Schema
On this page
Generating JSON Schemas
The JSONSchema.make
function allows you to generate a JSON Schema from a predefined schema.
Example
Here's an example where we define a schema for a "Person" with properties "name" (a string) and "age" (a number) and we generate the corresponding JSON Schema.
ts
import {JSONSchema ,Schema } from "@effect/schema"constPerson =Schema .Struct ({name :Schema .String ,age :Schema .Number })constjsonSchema =JSONSchema .make (Person )console .log (JSON .stringify (jsonSchema , null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "object","required": ["name","age"],"properties": {"name": {"type": "string"},"age": {"type": "number"}},"additionalProperties": false}*/
ts
import {JSONSchema ,Schema } from "@effect/schema"constPerson =Schema .Struct ({name :Schema .String ,age :Schema .Number })constjsonSchema =JSONSchema .make (Person )console .log (JSON .stringify (jsonSchema , null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "object","required": ["name","age"],"properties": {"name": {"type": "string"},"age": {"type": "number"}},"additionalProperties": false}*/
The JSONSchema.make
function aims to produce an optimal JSON Schema representing the input part of the decoding phase. It does this by traversing the schema from the most nested component, incorporating each refinement, and stops at the first transformation encountered.
Consider a modification to the schema of the age
field:
ts
import {JSONSchema ,Schema } from "@effect/schema"constPerson =Schema .Struct ({name :Schema .String ,age :Schema .Number .pipe (// refinement, included in the generated JSON SchemaSchema .int (),// transformation, excluded in the generated JSON SchemaSchema .clamp (1, 10))})constjsonSchema =JSONSchema .make (Person )console .log (JSON .stringify (jsonSchema , null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "object","required": ["name","age"],"properties": {"name": {"type": "string"},"age": {"type": "integer","description": "an integer","title": "integer"}},"additionalProperties": false}*/
ts
import {JSONSchema ,Schema } from "@effect/schema"constPerson =Schema .Struct ({name :Schema .String ,age :Schema .Number .pipe (// refinement, included in the generated JSON SchemaSchema .int (),// transformation, excluded in the generated JSON SchemaSchema .clamp (1, 10))})constjsonSchema =JSONSchema .make (Person )console .log (JSON .stringify (jsonSchema , null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "object","required": ["name","age"],"properties": {"name": {"type": "string"},"age": {"type": "integer","description": "an integer","title": "integer"}},"additionalProperties": false}*/
The new JSON Schema for the age
field shows it as type "integer"
, keeping the refinement of being an integer and excluding the transformation that clamps the value between 1
and 10
.
Specific Outputs for Schema Types
Literals
Literals are transformed into enum
types within JSON Schema:
Single literal
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Literal ("a")console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","enum": ["a"]}*/
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Literal ("a")console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","enum": ["a"]}*/
Union of literals
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Literal ("a", "b")console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","enum": ["a","b"]}*/
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Literal ("a", "b")console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","enum": ["a","b"]}*/
Void
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Void console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","$id": "/schemas/void","title": "void"}*/
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Void console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","$id": "/schemas/void","title": "void"}*/
Any
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Any console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","$id": "/schemas/any","title": "any"}*/
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Any console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","$id": "/schemas/any","title": "any"}*/
Unknown
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Unknown console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","$id": "/schemas/unknown","title": "unknown"}*/
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Unknown console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","$id": "/schemas/unknown","title": "unknown"}*/
Object
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Object console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","$id": "/schemas/object","anyOf": [{"type": "object"},{"type": "array"}],"description": "an object in the TypeScript meaning, i.e. the `object` type","title": "object"}*/
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Object console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","$id": "/schemas/object","anyOf": [{"type": "object"},{"type": "array"}],"description": "an object in the TypeScript meaning, i.e. the `object` type","title": "object"}*/
String
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .String console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "string"}*/
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .String console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "string"}*/
Number
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Number console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "number"}*/
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Number console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "number"}*/
Boolean
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Boolean console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "boolean"}*/
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Boolean console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "boolean"}*/
Tuples
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Tuple (Schema .String ,Schema .Number )console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "array","minItems": 2,"items": [{"type": "string"},{"type": "number"}],"additionalItems": false}*/
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Tuple (Schema .String ,Schema .Number )console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "array","minItems": 2,"items": [{"type": "string"},{"type": "number"}],"additionalItems": false}*/
Arrays
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Array (Schema .String )console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "array","items": {"type": "string"}}*/
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Array (Schema .String )console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "array","items": {"type": "string"}}*/
Non Empty Arrays
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .NonEmptyArray (Schema .String )console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "array","minItems": 1,"items": {"type": "string"}}*/
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .NonEmptyArray (Schema .String )console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "array","minItems": 1,"items": {"type": "string"}}*/
Structs
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Struct ({name :Schema .String ,age :Schema .Number })console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "object","required": ["name","age"],"properties": {"name": {"type": "string"},"age": {"type": "number"}},"additionalProperties": false}*/
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Struct ({name :Schema .String ,age :Schema .Number })console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "object","required": ["name","age"],"properties": {"name": {"type": "string"},"age": {"type": "number"}},"additionalProperties": false}*/
Records
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Record ({key :Schema .String ,value :Schema .Number })console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "object","required": [],"properties": {},"patternProperties": {"": {"type": "number"}}}*/
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Record ({key :Schema .String ,value :Schema .Number })console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "object","required": [],"properties": {},"patternProperties": {"": {"type": "number"}}}*/
Mixed Structs with Records
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Struct ({name :Schema .String ,age :Schema .Number },Schema .Record ({key :Schema .String ,value :Schema .Union (Schema .String ,Schema .Number )}))console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "object","required": ["name","age"],"properties": {"name": {"type": "string"},"age": {"type": "number"}},"patternProperties": {"": {"anyOf": [{"type": "string"},{"type": "number"}]}}}*/
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Struct ({name :Schema .String ,age :Schema .Number },Schema .Record ({key :Schema .String ,value :Schema .Union (Schema .String ,Schema .Number )}))console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "object","required": ["name","age"],"properties": {"name": {"type": "string"},"age": {"type": "number"}},"patternProperties": {"": {"anyOf": [{"type": "string"},{"type": "number"}]}}}*/
Enums
ts
import {JSONSchema ,Schema } from "@effect/schema"enumFruits {Apple ,Banana }constschema =Schema .Enums (Fruits )console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","$comment": "/schemas/enums","anyOf": [{"title": "Apple","enum": [0]},{"title": "Banana","enum": [1]}]}*/
ts
import {JSONSchema ,Schema } from "@effect/schema"enumFruits {Apple ,Banana }constschema =Schema .Enums (Fruits )console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","$comment": "/schemas/enums","anyOf": [{"title": "Apple","enum": [0]},{"title": "Banana","enum": [1]}]}*/
Template Literals
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .TemplateLiteral (Schema .Literal ("a"),Schema .Number )console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "string","description": "a template literal","pattern": "^a[+-]?\\d*\\.?\\d+(?:[Ee][+-]?\\d+)?$"}*/
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .TemplateLiteral (Schema .Literal ("a"),Schema .Number )console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "string","description": "a template literal","pattern": "^a[+-]?\\d*\\.?\\d+(?:[Ee][+-]?\\d+)?$"}*/
Unions
Unions are expressed using anyOf
or enum
, depending on the types involved:
Generic Union
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Union (Schema .String ,Schema .Number )console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","anyOf": [{"type": "string"},{"type": "number"}]}*/
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Union (Schema .String ,Schema .Number )console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","anyOf": [{"type": "string"},{"type": "number"}]}*/
Union of literals
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Literal ("a", "b")console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","enum": ["a","b"]}*/
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Literal ("a", "b")console .log (JSON .stringify (JSONSchema .make (schema ), null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","enum": ["a","b"]}*/
Identifier Annotations
You can augment your schemas with identifier
annotations to enhance their structure and maintainability.
When you utilize these annotations, your schemas are included within a "$defs" object property at the root of the JSON Schema and referenced from there, enabling better organization and readability.
ts
import {JSONSchema ,Schema } from "@effect/schema"constName =Schema .String .annotations ({identifier : "Name" })constAge =Schema .Number .annotations ({identifier : "Age" })constPerson =Schema .Struct ({name :Name ,age :Age })constjsonSchema =JSONSchema .make (Person )console .log (JSON .stringify (jsonSchema , null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "object","required": ["name","age"],"properties": {"name": {"$ref": "#/$defs/Name"},"age": {"$ref": "#/$defs/Age"}},"additionalProperties": false,"$defs": {"Name": {"type": "string","description": "a string","title": "string"},"Age": {"type": "number","description": "a number","title": "number"}}}*/
ts
import {JSONSchema ,Schema } from "@effect/schema"constName =Schema .String .annotations ({identifier : "Name" })constAge =Schema .Number .annotations ({identifier : "Age" })constPerson =Schema .Struct ({name :Name ,age :Age })constjsonSchema =JSONSchema .make (Person )console .log (JSON .stringify (jsonSchema , null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "object","required": ["name","age"],"properties": {"name": {"$ref": "#/$defs/Name"},"age": {"$ref": "#/$defs/Age"}},"additionalProperties": false,"$defs": {"Name": {"type": "string","description": "a string","title": "string"},"Age": {"type": "number","description": "a number","title": "number"}}}*/
By structuring your JSON Schema with identifier annotations, each annotated schema is clearly defined in a separate section, making the entire schema easier to navigate and maintain. This approach is especially useful for complex schemas that require clear documentation of each component.
Standard JSON Schema Annotations
Standard JSON Schema annotations such as title
, description
, default
, and examples
are supported.
These annotations allow you to enrich your schemas with metadata that can enhance readability and provide additional information about the data structure.
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .String .annotations ({description : "my custom description",title : "my custom title",default : "",examples : ["a", "b"]})constjsonSchema =JSONSchema .make (schema )console .log (JSON .stringify (jsonSchema , null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "string","description": "my custom description","title": "my custom title","examples": ["a","b"],"default": ""}*/
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .String .annotations ({description : "my custom description",title : "my custom title",default : "",examples : ["a", "b"]})constjsonSchema =JSONSchema .make (schema )console .log (JSON .stringify (jsonSchema , null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "string","description": "my custom description","title": "my custom title","examples": ["a","b"],"default": ""}*/
Adding annotations to Struct properties
To enhance the clarity of your JSON schemas, it's advisable to add annotations directly to the property signatures rather than to the type itself. This method is more semantically appropriate as it links descriptive titles and other metadata specifically to the properties they describe, rather than to the generic type.
ts
import {JSONSchema ,Schema } from "@effect/schema"constPerson =Schema .Struct ({firstName :Schema .propertySignature (Schema .String ).annotations ({title : "First name"}),lastName :Schema .propertySignature (Schema .String ).annotations ({title : "Last Name"})})constjsonSchema =JSONSchema .make (Person )console .log (JSON .stringify (jsonSchema , null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "object","required": ["firstName","lastName"],"properties": {"firstName": {"type": "string","title": "First name"},"lastName": {"type": "string","title": "Last Name"}},"additionalProperties": false}*/
ts
import {JSONSchema ,Schema } from "@effect/schema"constPerson =Schema .Struct ({firstName :Schema .propertySignature (Schema .String ).annotations ({title : "First name"}),lastName :Schema .propertySignature (Schema .String ).annotations ({title : "Last Name"})})constjsonSchema =JSONSchema .make (Person )console .log (JSON .stringify (jsonSchema , null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "object","required": ["firstName","lastName"],"properties": {"firstName": {"type": "string","title": "First name"},"lastName": {"type": "string","title": "Last Name"}},"additionalProperties": false}*/
Recursive and Mutually Recursive Schemas
Recursive and mutually recursive schemas are supported, however it's mandatory to use identifier annotations for these types of schemas to ensure correct references and definitions within the generated JSON Schema.
ts
import {JSONSchema ,Schema } from "@effect/schema"interfaceCategory {readonlyname : stringreadonlycategories :ReadonlyArray <Category >}// Define a recursive schema with a required identifier annotationconstCategory =Schema .Struct ({name :Schema .String ,categories :Schema .Array (Schema .suspend (():Schema .Schema <Category > =>Category ))}).annotations ({identifier : "Category" })constjsonSchema =JSONSchema .make (Category )console .log (JSON .stringify (jsonSchema , null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","$ref": "#/$defs/Category","$defs": {"Category": {"type": "object","required": ["name","categories"],"properties": {"name": {"type": "string"},"categories": {"type": "array","items": {"$ref": "#/$defs/Category"}}},"additionalProperties": false}}}*/
ts
import {JSONSchema ,Schema } from "@effect/schema"interfaceCategory {readonlyname : stringreadonlycategories :ReadonlyArray <Category >}// Define a recursive schema with a required identifier annotationconstCategory =Schema .Struct ({name :Schema .String ,categories :Schema .Array (Schema .suspend (():Schema .Schema <Category > =>Category ))}).annotations ({identifier : "Category" })constjsonSchema =JSONSchema .make (Category )console .log (JSON .stringify (jsonSchema , null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","$ref": "#/$defs/Category","$defs": {"Category": {"type": "object","required": ["name","categories"],"properties": {"name": {"type": "string"},"categories": {"type": "array","items": {"$ref": "#/$defs/Category"}}},"additionalProperties": false}}}*/
In this example, the Category
schema refers to itself, making it necessary to use an identifier annotation to facilitate the reference.
Custom JSON Schema Annotations
When working with JSON Schema certain data types, such as bigint
, lack a direct representation because JSON Schema does not natively support them.
This absence typically leads to an error when the schema is generated:
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Struct ({a_bigint_field :Schema .BigIntFromSelf })constjsonSchema =JSONSchema .make (schema )console .log (JSON .stringify (jsonSchema , null, 2))/*throws:Error: Missing annotationat path: ["a_bigint_field"]details: Generating a JSON Schema for this schema requires a "jsonSchema" annotationschema (BigIntKeyword): bigint*/
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Struct ({a_bigint_field :Schema .BigIntFromSelf })constjsonSchema =JSONSchema .make (schema )console .log (JSON .stringify (jsonSchema , null, 2))/*throws:Error: Missing annotationat path: ["a_bigint_field"]details: Generating a JSON Schema for this schema requires a "jsonSchema" annotationschema (BigIntKeyword): bigint*/
To address this, you can enhance the schema with a custom annotation, defining how you intend to represent such types in JSON Schema:
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Struct ({a_bigint_field :Schema .BigIntFromSelf .annotations ({jsonSchema : {type : "some custom way to represent a bigint in JSON Schema"}})})constjsonSchema =JSONSchema .make (schema )console .log (JSON .stringify (jsonSchema , null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "object","required": ["a_bigint_field"],"properties": {"a_bigint_field": {"type": "some custom way to represent a bigint in JSON Schema"}},"additionalProperties": false}*/
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Struct ({a_bigint_field :Schema .BigIntFromSelf .annotations ({jsonSchema : {type : "some custom way to represent a bigint in JSON Schema"}})})constjsonSchema =JSONSchema .make (schema )console .log (JSON .stringify (jsonSchema , null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "object","required": ["a_bigint_field"],"properties": {"a_bigint_field": {"type": "some custom way to represent a bigint in JSON Schema"}},"additionalProperties": false}*/
Refinements
When defining a refinement (e.g., through the filter
function), you can attach a JSON Schema annotation to your schema containing a JSON Schema "fragment" related to this particular refinement.
This fragment will be used to generate the corresponding JSON Schema.
Note that if the schema consists of more than one refinement, the corresponding annotations will be merged.
ts
import {JSONSchema ,Schema } from "@effect/schema"constPositive =Schema .Number .pipe (Schema .filter ((n ) =>n > 0, {jsonSchema : {minimum : 0 }}))constschema =Positive .pipe (Schema .filter ((n ) =>n <= 10, {jsonSchema : {maximum : 10 }}))constjsonSchema =JSONSchema .make (schema )console .log (JSON .stringify (jsonSchema , null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "number","minimum": 0,"maximum": 10}*/
ts
import {JSONSchema ,Schema } from "@effect/schema"constPositive =Schema .Number .pipe (Schema .filter ((n ) =>n > 0, {jsonSchema : {minimum : 0 }}))constschema =Positive .pipe (Schema .filter ((n ) =>n <= 10, {jsonSchema : {maximum : 10 }}))constjsonSchema =JSONSchema .make (schema )console .log (JSON .stringify (jsonSchema , null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "number","minimum": 0,"maximum": 10}*/
The jsonSchema
annotation is intentionally defined as a generic object
. This allows it to describe non-standard extensions.
As a result, the responsibility of enforcing type constraints is left to you, the user.
If you prefer stricter type enforcement or need to support non-standard extensions, you can introduce a satisfies
constraint on the object literal. This constraint should be used in conjunction with the typing library of your choice.
Example
In the following example, we've used the @types/json-schema
package to provide TypeScript definitions for JSON Schema. This approach not only ensures type correctness but also enables autocomplete suggestions in your IDE.
ts
import {JSONSchema ,Schema } from "@effect/schema"import type {JSONSchema7 } from "json-schema"constPositive =Schema .Number .pipe (Schema .filter ((n ) =>n > 0, {jsonSchema : {minimum : 0 } // `jsonSchema` is a generic object; you can add any key-value pair without type errors or autocomplete suggestions.}))constschema =Positive .pipe (Schema .filter ((n ) =>n <= 10, {jsonSchema : {maximum : 10 } satisfiesJSONSchema7 // Now `jsonSchema` is constrained to fulfill the JSONSchema7 type; incorrect properties will trigger type errors, and you'll get autocomplete suggestions.}))constjsonSchema =JSONSchema .make (schema )console .log (JSON .stringify (jsonSchema , null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "number","minimum": 0,"maximum": 10}*/
ts
import {JSONSchema ,Schema } from "@effect/schema"import type {JSONSchema7 } from "json-schema"constPositive =Schema .Number .pipe (Schema .filter ((n ) =>n > 0, {jsonSchema : {minimum : 0 } // `jsonSchema` is a generic object; you can add any key-value pair without type errors or autocomplete suggestions.}))constschema =Positive .pipe (Schema .filter ((n ) =>n <= 10, {jsonSchema : {maximum : 10 } satisfiesJSONSchema7 // Now `jsonSchema` is constrained to fulfill the JSONSchema7 type; incorrect properties will trigger type errors, and you'll get autocomplete suggestions.}))constjsonSchema =JSONSchema .make (schema )console .log (JSON .stringify (jsonSchema , null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "number","minimum": 0,"maximum": 10}*/
For all other types of schema that are not refinements, the content of the annotation is used and overrides anything the system would have generated by default:
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Struct ({foo :Schema .String }).annotations ({jsonSchema : {type : "object" }})constjsonSchema =JSONSchema .make (schema )console .log (JSON .stringify (jsonSchema , null, 2))/*Output{"$schema": "http://json-schema.org/draft-07/schema#","type": "object"}the default would be:{"$schema": "http://json-schema.org/draft-07/schema#","type": "object","required": ["foo"],"properties": {"foo": {"type": "string"}},"additionalProperties": false}*/
ts
import {JSONSchema ,Schema } from "@effect/schema"constschema =Schema .Struct ({foo :Schema .String }).annotations ({jsonSchema : {type : "object" }})constjsonSchema =JSONSchema .make (schema )console .log (JSON .stringify (jsonSchema , null, 2))/*Output{"$schema": "http://json-schema.org/draft-07/schema#","type": "object"}the default would be:{"$schema": "http://json-schema.org/draft-07/schema#","type": "object","required": ["foo"],"properties": {"foo": {"type": "string"}},"additionalProperties": false}*/
Specialized JSON Schema Generation with Schema.parseJson
When utilizing Schema.parseJson
, JSON Schema generation follows a specialized approach. Instead of merely generating a JSON Schema for a string—which would be the default output representing the "from" side of the transformation defined by Schema.parseJson
—it specifically generates the JSON Schema for the actual schema provided as an argument.
Example
ts
import {JSONSchema ,Schema } from "@effect/schema"// Define a schema that parses a JSON string into a structured objectconstschema =Schema .parseJson (Schema .Struct ({a :Schema .parseJson (Schema .NumberFromString ) // Nested parsing from JSON string to number}))constjsonSchema =JSONSchema .make (schema )console .log (JSON .stringify (jsonSchema , null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "object","required": ["a"],"properties": {"a": {"type": "string"}},"additionalProperties": false}*/
ts
import {JSONSchema ,Schema } from "@effect/schema"// Define a schema that parses a JSON string into a structured objectconstschema =Schema .parseJson (Schema .Struct ({a :Schema .parseJson (Schema .NumberFromString ) // Nested parsing from JSON string to number}))constjsonSchema =JSONSchema .make (schema )console .log (JSON .stringify (jsonSchema , null, 2))/*Output:{"$schema": "http://json-schema.org/draft-07/schema#","type": "object","required": ["a"],"properties": {"a": {"type": "string"}},"additionalProperties": false}*/