skip to content

How do you shape a JSON schema so a model actually complies with it?

level: middleimportance: must knowfreq 54%

answer

  1. the schema is part of the prompt
  2. flat beats deeply nested
  3. finite value sets become enums
  4. unions are where models fail hardest
  5. discriminator field instead of oneOf branches

basics

~20 s

Prefer flat objects over deep nesting, enums over free text, and a single shape with a discriminator field over unions of alternative shapes. Give fields self-describing names and descriptions that state units and format. Compliance is a property of the schema's design, not only of the model.

solid answer

~50 s

Compliance rates vary enormously with schema shape, so the schema is a design artifact you tune, not a fixed requirement you hand over. Four rules carry most of the benefit. **Flatten**: deeply nested objects invite the model to lose its place and misplace fields; two or three levels is a good ceiling. **Enumerate**: any field with a finite value set gets an `enum` rather than a free-text description, which removes synonym drift entirely. **Avoid alternative-shape unions**: `oneOf`/`anyOf` across three similar shapes is the highest-failure construct in practice, because the model mixes fields between branches — replace it with one object carrying a `mode` discriminator enum, and enforce the branch-specific required fields in your business-rule layer. **Name and describe well**: field descriptions are read as instructions, so state units, formats and examples there. As of mid-2026 most frontier providers offer a strict schema mode, but coverage across self-hosted and smaller models is uneven — schema shape is the portable lever.

code

json · 39 lines
json
{
  "type": "object",
  "additionalProperties": false,
  "required": ["mode", "consignee_name", "gross_weight_kg", "commodities"],
  "properties": {
    "mode": {
      "type": "string",
      "enum": ["air", "ocean", "road"],
      "description": "Transport mode for the whole shipment."
    },
    "consignee_name": { "type": "string" },
    "gross_weight_kg": { "type": "number", "minimum": 0 },
    "air_waybill": {
      "type": ["string", "null"],
      "description": "Required when mode is air; null otherwise."
    },
    "container_number": {
      "type": ["string", "null"],
      "description": "Required when mode is ocean; null otherwise."
    },
    "commodities": {
      "type": "array",
      "maxItems": 50,
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": ["hs_code", "description"],
        "properties": {
          "hs_code": {
            "type": "string",
            "pattern": "^[0-9]{6}$",
            "description": "Six-digit HS heading, digits only, no separators."
          },
          "description": { "type": "string" }
        }
      }
    }
  }
}

go deeper

for a junior

Know that how you write the schema changes how often the model gets it right. Be able to name two concrete choices — use enums for fixed value sets, and keep the object shallow rather than deeply nested.

for a middle

Explain why unions of alternative shapes fail and how a discriminator enum plus optional fields replaces them. Be ready to discuss required-versus-nullable and why marking everything required encourages invented values.

for a senior

Show that you measure. Talk about logging failures by field path, spotting the one field driving most repair traffic, and treating that as a schema defect to fix rather than retry volume to absorb. Mention the token cost of large schemas on every call.

for a principal

Own the schema as a versioned contract between the extraction layer and every downstream consumer. Be ready to argue where validation should live — schema versus rules layer — and how you decompose an extraction that has outgrown a single call without fragmenting ownership.

## The schema is a prompt A schema handed to a model is not only a validation contract; it is part of the instruction the model conditions on. Field names, ordering, descriptions and structural complexity all shape what comes back. Two schemas that accept exactly the same set of documents can produce very different compliance rates. Treating the schema as a tunable design artifact — measured, revised, versioned — is what separates a pipeline at 99% first-pass validity from one at 80% that leans on a repair loop to cover the gap. ## Flat beats nested Deep nesting is the most common self-inflicted wound. Every level of nesting is another place to close a brace early, misattribute a field to the wrong parent, or drift on a repeated sub-object. Practical guidance: keep the structure to about two or three levels, and let arrays of small flat objects carry repetition rather than object trees. If a schema needs a fourth level of nesting, it is usually two schemas — decompose the extraction into separate calls rather than asking one generation to hold the whole tree. There is a secondary cost too: a large nested schema occupies a meaningful slice of the input window on every single call, and long schemas compete for attention with the actual task. ## Enums beat free text Any field whose values come from a finite set should be declared as an `enum`. Without one, a model asked for an incoterm will return `"DDP"` on one call and `"delivered duty paid"` on another, and your code inherits a normalization problem forever. With one, the value set is part of the contract, so mismatches are caught mechanically and — where the provider constrains generation against the schema — often prevented outright. When the value set is large (hundreds of tariff codes, say), an enum becomes impractical: it bloats the schema and dilutes attention. The pattern there is a typed string with a `pattern` constraint on the format, plus a lookup against your own reference data as a business-rule check. Format in the schema, membership in the rules layer. ## Discriminated flat objects beat unions This is the highest-leverage rule and the one candidates most often miss. Suppose a freight forwarder handles air, ocean and road shipments, each with genuinely different fields — flight number and airway bill for air, vessel and container number for ocean, trailer and carrier for road. The textbook modelling instinct is a `oneOf` across three object shapes. In practice this is where models fail hardest. Branch selection is implicit, and the model routinely produces a hybrid: an air shipment carrying a container number, or a record satisfying no branch cleanly. The error a validator emits for a failed `oneOf` is also unhelpfully vague — it typically reports that the value matched zero (or more than one) branch, without naming which field caused it, which makes the repair turn much weaker. The fix is to **flatten the union into one object with a discriminator enum**: a single `mode` field taking `air`, `ocean` or `road`, with the mode-specific fields present as optional properties on the one flat object. The schema then accepts a slightly wider set of documents than you strictly want, so you close the gap in the business-rule layer: given `mode == "ocean"`, assert the vessel and container fields are populated and the air-only fields are absent. You trade a little structural precision for a large jump in compliance and a far more actionable error message. ## Required, optional and null Be deliberate about `required`. Marking every field required pushes the model to invent a value rather than leave a genuine unknown out — the schema effectively forbids honesty. Marking almost nothing required means your consuming code faces a combinatorial space of absent fields. The useful middle: require exactly the fields the downstream consumer cannot function without, and for optional-but-known-shape fields prefer an explicit nullable type over silent absence, so "the model looked and found nothing" is distinguishable from "the model never addressed this". Some strict provider modes require every property to be listed as required, in which case nullable types become the way to express optionality. ## Names and descriptions are instructions Descriptive `snake_case` names outperform terse codes: `gross_weight_kg` carries the unit, the concept and the type expectation in one token sequence, where `gw` carries none of them. Use each field's `description` to state the format precisely, including one example where format is unusual — `"six-digit HS heading, digits only, no separators"` prevents the very common `8471.30.01` failure against a six-digit pattern. ## Closing the shape and bounding the size Set `additionalProperties: false` so invented keys are rejected rather than silently ignored, and bound arrays with `maxItems` where a real ceiling exists. Both convert a class of quiet weirdness into a named validation error. ## Measure, then revise The closing discipline: log validation failures by field path, aggregate them, and treat a field that fails disproportionately as a schema-design defect rather than a fact of nature. A single field driving most of your repair traffic is almost always fixable by an enum, a clearer description or a flattening — and fixing it removes an entire generation's worth of cost and latency from every affected request.

  • You flattened a three-shape union into one object with a mode enum. What did you lose, and how do you get it back?
    You lost structural exclusivity: the schema now accepts an air shipment that also carries a container number, because every mode-specific field is optional on the one flat object. You get it back in the business-rule layer — a conditional check keyed on the discriminator that asserts the required fields for that mode are present and the fields belonging to other modes are absent. The validation is just as strict; it moved from the schema to code, where its error messages are also far more actionable.
  • How would you decide whether a field should be an enum or a pattern-constrained string?
    By the size and stability of the value set. A handful to a few dozen stable values — transport modes, incoterms, statuses — belongs in an enum: it is self-documenting, prevents synonym drift, and can be enforced during generation. Hundreds or thousands of values, or a set that changes without a schema deploy, should be a pattern-constrained string validated against your own reference data. Cramming a large enum into a schema bloats every request and dilutes attention across the rest of the fields.
  • When does a schema get too big, and what do you do about it?
    When it starts costing real attention — many dozens of fields, four or more levels of nesting, or long enums — you typically see compliance degrade on the fields furthest from the top. The remedy is decomposition: split one extraction into two or three focused calls with small schemas, each covering a coherent slice, and assemble the result in code. You pay extra calls and buy back accuracy plus much sharper error attribution when something fails.

saying these in an interview costs you the question

  • Treats compliance as purely a model property, not a schema-design one
  • Models alternative shapes as oneOf branches and expects reliable selection
  • Marks every field required, pushing the model to invent values
  • Uses free-text fields where a finite enum would do
  • Assumes deeply nested schemas cost nothing in accuracy or tokens

context