JSON schema -> llguidance converter
February 18, 2026 ยท View on GitHub
This sub-module converts JSON schema to llguidance grammar.
It aims to either produce a grammar conformant to the JSON schema semantics (Draft 2020-12), or give an error, but see below for some known differences.
There are various limits on the size of the input schema and the resulting grammar. However, we've successfully processed schemas up to 4 MB in size.
For features that are not fully supported, we provide percentages of support among the 11k maskbench schemas.
Supported JSON schema features
Following JSON schema features are supported.
Core features:
anyOfoneOf(68%) - converted toanyOfonly when provably equivalentallOf(98%) - intersection of certain schemas is not supported right now$ref- external/remote refs unsupportedconstenumtype- both single type and array of types- sibling keys - when schema has keywords in addition to
anyOf,allOf,$ref, the result is intersection
Array features:
itemsprefixItemsminItemsmaxItems
Object features:
properties- order of properties is fixed to the order in schemaadditionalPropertiespatternProperties(98%) - they have to be disjointrequiredminPropertiesandmaxProperties(90%) - only supported when everything defined inpropertiesisrequired(i.e., it only limitsadditionalPropertiesorpatternProperties) - this covers case of an object used as a map with upper/lower bounds on the number of keys; there is also some special handling for either/both being0or1- mostly for the case of at-least-one-property-required
String features:
minLengthmaxLengthpattern(99%) - lookarounds not supportedformat(74%), with the following formats:date-time,time,date,duration,email,hostname,ipv4,ipv6,uuid,uri
Number features (for both integer and number):
minimummaximumexclusiveMinimumexclusiveMaximummultipleOf
Departures from JSON schema semantics
- order of object properties is fixed, see below
- string
formatis enforced by default, with unrecognized or unimplemented formats returning errors - for properties specified with
additionalPropertiesorpatternProperties, the grammar does not enforce unique keys; the ones listed inpropertiesare enforced uniquely, in given order
Whitespace handling
By default any whitespace is allowed inside of the JSON object.
Whitespace is not allowed before the first { or after the last }.
You can modify your grammar easily to allow initial or trailing whitespace.
You can set top-level "x-guidance" key to control this.
Following keys are available inside of it:
item_separator, defaults to","- a regex pattern for the separator between array items or object propertieskey_separator, defaults to":"- a regex pattern for the separator between object keys and valueswhitespace_flexible, defaults totrue; set tofalseto enforce compact JSON representationwhitespace_pattern, optional string, overrideswhitespace_flexible;whitespace_flexible: trueis equivalent towhitespace_pattern: r"[\x20\x0A\x0D\x09]+"coerce_one_of, defaults tofalse; when set totrue, the"oneOf"will be treated as"anyOf"json_allowed_escapes, optional string, defaults to"nrbtf\\\"u"; controls which escape sequences are allowed after\in JSON strings. Each character in the string enables the corresponding escape (for example,nenables\n,uenables\uXXXX). Set to"nrbtf\\\""to disallow\uXXXXescapes. Note that removingumeans control characters without named escapes (for example, U+0000, U+001E) become unrepresentable.lenient, defaults tofalse; when set totrue, the unsupported keywords and formats will be ignored; impliescoerce_one_of: true
For example:
{
"x-guidance": {
"whitespace_flexible": false
},
"type": "object",
"properties": {
"a": {
"type": "string"
}
}
}
The separators are treated as regex patterns, allowing for flexible formatting:
{
"x-guidance": {
"item_separator": "\\s{0,2},\\s{0,2}",
"key_separator": "\\s{0,2}:\\s{0,2}",
"whitespace_flexible": false
},
"type": "object",
"properties": {
"a": { "type": "number" },
"b": { "type": "number" }
}
}
This will match JSON like {"a":1,"b":2}, {"a": 1, "b": 2}, or {"a" : 1 , "b":2},
but not {"a":1, "b":2} (too much whitespace after comma).
The "x-guidance" key is only recognized at the top level of the schema.
Property order
TL;DR
Properties follow order in properties map.
When schemas are merged with allOf etc., the properties maps are merged in order.
Any additionalProperties or patternProperties come in any order, but after properties and required.
Easiest way to override this, is to include "my_property": true in appropriate position in "properties",
before anyOf/allOf/oneOf/$ref.
Details, best ignored
While this algorithm may not be the easiest to implement, we judge it to be the least surprising to the user. Basically, the schema is processed line-by-line, left-to-right, and property order is fixed as we go.
The enforced property order during generation is as follows:
- Each property in the
"properties"object, in order of appearance - Each property in
"required", in order of appearance (if not already in"properties"they are constrained with"additionalProperties")
When two schemas are joined (more than two is defined inductively), the resulting "properties" object will have order given by:
- Each property in the left schema, in order of appearance
- Each property in the right schema, in order of appearance (if not already in the left schema)
- Recursive cases:
- If the left schema defines a property that is also found in the right schema, the schema of the resulting merged property will be the result of merging the two properties with the same left,right precedence
- If the left schema defines a property NOT found in the right schema, it will be merged on the right by the right schema's
additionalProperties - If the right schema defines a property NOT found in the left schema, it will be merged on the left by the left schema's
additionalProperties
When two schemas are joined, the resulting "required" array will have order given by:
- Each property in the left
"required", in order of appearance - Each property in the right
"required", in order of appearance (if not in the left array)
Note: precedence in "properties" and "required" are tracked separately as schemas are merged, with the order imposed by the final schema prioritizing "properties" over "required".
When a schema is built from multiple applicators, the applicators are processed in order of appearance.
E.g., if a schema contains both "properties" and "allOf" (which has another "properties" definition nested inside), the resulting constraints will depend on which of these keys appears first. The one that appears first will have precedence.
These semantics extend even to applicators that violate the typical keyword independence semantics of JSON keywords.
E.g., even though the behavior of additionalProperties is defined in terms of "properties" and "patternProperties" its position in a schema determines its precedence independently of the location of "properties". If it applies to a property defined in a subschema of "allOf" or "anyOf", whether it applies before or after said definition is determined by its position relative to the "allOf" or "anyOf" keyword.