Command Palette

Search for a command to run...

Hectal
PHASE 4Intermediate ~19 min· topic 4 of 8

Topic 4.4

Describing the Shape with JSON Schema

In one line

JSON Schema is a standard way to write down the shape of JSON: which keys exist, which are required, what type each has, and which values are allowed. The same schema can steer the model, drive a provider's constrained decoding and check the reply.

Think of it like this

The printed rules on a form. "Name: required, text. Age: whole number. Plan: tick one of Free, Pro, Team. Notes: optional." A clerk, a customer and an inspector can all read the same rules. A JSON Schema is those rules written so that programs can read them.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

JSON Schema
A standard format for describing the shape of JSON data.
properties
In a schema for an object: the list of keys and the schema of each value.
required
The list of keys that must appear in the object.
enum
A list of the only values a field may have.
items
In a schema for an array: the schema every element must follow.
additionalProperties
Whether keys that are not listed in properties are allowed. false forbids them.
nullable
A field that is present but may hold null, meaning "no value".

Step by step

01Step 1: write the contract in words

Say it plainly: a customer has a name and an age (required), a plan that is free, pro or team (required), and an optional note. Every rule in the schema should come from a sentence like that.

02Step 2: build the schema from a field list

The first example keeps the rules as plain data, a list of (name, type, required) entries, and a function that turns them into a schema. Printing the result shows the exact JSON you would send to a provider. Notice the order: type, properties, required, additionalProperties.

main.pywhole filepython
{"type": "object",
 "properties": {"plan": {"type": "string", "enum": ["free", "pro", "team"]}},
 "required": ["plan"],
 "additionalProperties": False}

03Step 3: read a nested schema

Real contracts nest: a ticket has a customer object, a priority enum, a list of tags and a list of attachment objects. The second example walks such a schema and prints an outline: one line per field with its type and whether it is required. Reading outlines is how you review a schema quickly.

Step 3: read a nested schemadiagram
Rendering diagram…

04Step 4: close the schema

Add additionalProperties: false at each object level where typos should fail. Strict mode on Groq requires it: without it the API answers HTTP 400 with additionalProperties:false must be set on every object, and it also demands every key in required ("The following properties must be listed in required: age"). Other providers have similar rules; read the error body, it names the exact problem.

05Step 5: generate instead of hand-write

Hand-written schemas drift from the types in your code. In Python, a Pydantic model gives you its schema with model_json_schema(). In JavaScript, Zod 4 has z.toJSONSchema(schema). In Java you write the record or class and either generate the schema with a library or keep a small hand-written schema next to it with a test. Topic 4.5 shows the libraries.

Try it yourself

  1. 1

    Add a field

    Add ("email", "string", True) to the field list of the first example. Predict how the required line changes, then run.

  2. 2

    Make an array

    In the outline example, add a key scores that is an array of integers to the schema. Predict the outline line. Then give it items of type object with a key value and see the extra lines.

  3. 3

    Write one by hand

    Write a schema (as JSON) for {"city": "Pune", "days": 3} where days is an integer and both keys are required. Run it through the outline example to read it back.

Code & diagrams

Build a JSON Schema from a list of fields New tab

The schema is built as plain data, then printed in compact form. Java uses the course's Json helper.

Sign in to run this example in your browser.

Expected output

{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"integer"},"plan":{"type":"string","enum":["free","pro","team"]},"note":{"type":"string"}},"required":["name","age","plan"],"additionalProperties":false}
required: name, age, plan
Read a nested schema as an outline New tab

A recursive walk: objects go into their properties, arrays of objects into their items.

Sign in to run this example in your browser.

Expected output

customer: object (required)
customer.name: string
customer.email: string (required)
priority: one of low, normal, urgent (required)
tags: list of string
attachments: list of object
attachments[]: object
attachments[].url: string
attachments[].size: integer

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

A type name that doesn't exist

In the Python field-list example change ("age", "integer", True) to ("age", "int", True) and run it.

terminal
$ Run
── what you'll see ──
{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"int"},"plan":{"type":"string","enum":["free","pro","team"]},"note":{"type":"string"}},"required":["name","age","plan"],"additionalProperties":false}
required: name, age, plan

Myth vs fact

Myth

A schema is only for the model.

Fact

It is for everyone: the provider's decoder, your validator, your docs and your tests.

Myth

More detailed schemas always give better answers.

Fact

Each field costs tokens and a chance of error. Ask only for what the next step of the program needs.

Myth

Missing key and null are the same thing.

Fact

A missing key could be a model mistake. null is a deliberate "no value". Telling them apart is useful (Topic 4.7).

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Treat the schema as an API contract: version it, review changes, and keep a test that validates a few known good and known bad replies against it.

  • ▸

    Order properties the way you want the model to think. Models write keys in order, so a reason or evidence field before the label often improves the label.

  • ▸

    Large enums (hundreds of values) make prompts long and mistakes likely. Split into two steps (category, then item) or use retrieval (the coming RAG course) to offer only likely candidates.

Remember this

  1. 1

    Why this exists. Without a schema the shape lives in three places that drift apart: the prompt ("reply with name, age and plan"), the parsing code (data["plan"]), and the person who wrote them. One schema, written once, can be sent to the provider (Topic 4.3), used to validate replies (Topic 4.5), and shown to the next engineer.

  2. 2

    It is just JSON. A schema for an object has "type": "object", a "properties" map from key to the schema of its value, and a "required" list of keys that must be present. Scalar types are string, integer, number, boolean and null; containers are object and array. An array has "items", the schema of every element.

  3. 3

    Enums are the most useful rule for LLMs. {"type": "string", "enum": ["free", "pro", "team"]} says the value must be exactly one of those. Classification, routing and status fields should almost always be enums, so the rest of your program can use an ordinary switch instead of guessing what a sentence meant.

  4. 4

    Optional is not the same as nullable. A key left out of required may be missing from the reply. A type like ["string", "null"] allows the key to be present with the value null. Models and strict modes behave differently with each: some providers' strict modes want every key listed as required, and use null to mean "no value". That is also the safest way to tell "the model didn't find it" from "the model forgot the key" (Topic 4.7).

  5. 5

    **Nesting and additionalProperties.** Objects inside objects and arrays of objects are just schemas inside schemas. "additionalProperties": false rejects keys you didn't list, which catches typos like plann. Without it, a reply with an extra key still passes.

  6. 6

    Write the description for the model. A "description" text on a field is read by the model in schema mode and by humans reading your code. "The customer's plan at signup, not today" prevents a wrong answer better than a field called plan alone. Keep schemas small: every extra field adds tokens, chances to be wrong and work for your validator. You also rarely write schemas from scratch. Libraries generate one from a type (Pydantic's model_json_schema(), Zod's z.toJSONSchema), which is where Topic 4.5 ends up.

Explain it without notes

01

What are the main parts of a JSON Schema for an object?

02

What is the difference between a field that is optional and a field that is nullable?

03

Why are enums important for LLM output?

04

Why generate schemas from types instead of writing them by hand?

Practice

01

Write the schema (as a Python dictionary) for an object with a required string city and a required integer days, rejecting any other keys. Print it with json.dumps, compact.

02

Write required_names(schema) that returns the list of required keys of an object schema, or an empty list if there are none.

Trade-offs

  • ↔

    Small schemas are cheap and easy to satisfy; rich schemas capture more rules but cost tokens and may hit a provider's schema limits.

  • ↔

    Closed schemas (additionalProperties: false) catch typos and extra keys; open ones are more forgiving.

  • ↔

    Generated schemas stay in sync with code; hand-written ones are easier to read and tune.

Done when you can

  • Done when you can write a schema with types, required keys, an enum and a nested object.

  • Done when you can explain optional vs nullable.

  • Done when you can read a schema as an outline of fields.

  • Done when you can name a way to generate a schema from a type in your language.