baml.json.schema

Lowers a BAML type to ordinary, provider-neutral JSON Schema.

Reference version

Signature

function baml.json.schema(
t: reflect.Type,
naming: baml.json.Naming = …

Lowers a BAML type to ordinary, provider-neutral JSON Schema.

The type-level counterpart to the value conversions above: they move a value between BAML and json, while this describes the shape a value of t would take.

A field whose type admits null (T?, json) is omitted from required, as the decoder lets it be missing. Maps use schema-valued additionalProperties, and unions use anyOf. Enums lower to a string enum of their variant names. A nullable type becomes a ["T", "null"] type list where it can, and anyOf otherwise. A nullable enum also lists null in its enum, and a nullable literal uses anyOf, because enum and const restrict the value whatever type says.

An int carries its range as minimum and maximum. A bigint is a decimal string ("-12"), because a JSON number that large loses precision in most parsers; a bigint literal is that string as a const. A media value is the tagged object { kind, source, value, mime, name? }, where source is "url" or "base64". A map's key type must be string, or string literals, which become propertyNames.

Some types have values but no JSON form: uint8array, functions, futures, reflect.Type, a class that requires one of those, and a class whose every value is cyclic (JSON holds only finite values). Others have no values at all: never, an enum with no variants, and a class that requires one of those. Both kinds are dropped from a union, so uint8array | int | string lowers as int | string, and an optional field of either kind is only null. They differ inside a list or map:

never[]                 // only []: { "type": "array", "maxItems": 0 }
map<string, never>      // only {}
uint8array[]            // no JSON form: the list is dropped like its items
map<string, uint8array> // no JSON form

A list of a type with no values is still a list, the empty one. A list of uint8array is not reduced to []: that would describe only the lists the type can hold when they happen to be empty.

A type whose values all lack a JSON form, or that has no values, is rejected rather than lowered to a schema nothing can satisfy.

naming picks the names fields and variants go by, as it does for decode<T>, so a schema and the decoder given the same naming agree on every key. With Naming.Aliased, a @skip field holds its type's empty value, so a class with a @skip field whose type has none (media, a function, a class that contains itself) has no JSON form.

Documentation becomes description, with the text the output format shows: a class's or a field's @description, then its /// docstring. A field's description sits beside the $ref when the field is a class. An enum's description holds its own documentation and one - <variant>: <documentation> line per documented variant, because JSON Schema has no description per enum value.

The root type is emitted inline, and every class and named alias beneath it goes into $defs to be reached by $ref. That is what lets a recursive graph terminate: a recursive root is emitted both inline and into $defs, so the $ref back to itself resolves. Definitions are keyed by name; when two share one, the later gets a _2 (then _3, ...) suffix.

Throws

  • baml.errors.Unsupported when no value of t has a JSON form, or t has no values.
  • baml.errors.Unsupported for a type no schema describes exactly: unknown, an interface type, media that does not name its kind, a map whose key is not string or string literals, and a generic class instance.
  • baml.errors.Unsupported for a class, enum, or alias the reflection context cannot resolve.

Source:<builtin>/baml/ns_json/json.bamlbytes 21475–21628