Structured Output: JSON & Schemas

Reviewed & published by Brayan K

Structured output means asking the AI to answer in a predictable, machine-readable shape, most often JSON, instead of a chatty paragraph. When the shape is fixed, your code can read each field reliably.

Part of the free Prompt Engineering course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

The trick is to ask for JSON, provide a schema or example, and validate what comes back. This lesson shows you how to get clean, parseable output every time.

What You'll Learn in This Lesson

1 Ask for a machine-readable shape

Freeform prose is hard for a program to read. A fixed JSON shape maps straight to fields. Same goal, two outputs:

Chatty prose, no reliable structure.

Clean keys and values your code can read.

2 Give a schema or example

Show the model the exact fields and types you expect. A worked example is the simplest schema:

Many AI tools also offer a JSON mode, structured outputs, or a response_format option that constrains the model to valid JSON automatically, which is even more reliable than prose alone.

3 Validate, then retry on failure

Never trust output blindly. Check it matches your schema first. If it does not parse, ask the model to fix it:

Try it for real

Section 3 said validate and retry; this is what that looks like in code. The six strings below are hand-written test cases shaped like the replies you get back — clean, wrapped in chatter, missing a field, wrong type, broken, not JSON at all. No model is called: the parser and validator are the point, and the retry message is built from the problems they find.

# The parser is where "return ONLY JSON" actually gets enforced. No model calls:
# the six replies below are hand-written to look like the kinds of strings that
# come back, so the VALIDATION logic is what you study and change.

import json

SCHEMA = {"name": str, "email": str, "company": str, "wants_demo": bool}

def extract_json(raw):
    """Cut away chatter and ```json fences: keep the first { to the last }."""
    start, end = raw.find("{"), raw.rfind("}")
    if start == -1 or end == -1:
        raise ValueError("no JSON object found")
    return raw[start:end + 1]

def validate(data, schema):
    """Return a list of problems. An empty list means the data fits the schema."""
    problems = []
    for key, expected in schema.items():
        if key not in data:
            problems.append(f"missing field '{key}'")
        elif not isinstance(data[key], expected):
            problems.append(f"'{key}' should be {expected.__name__}, got {type(data[key]).__name__}")
    for key in data:
        if key not in schema:
            problems.append(f"unexpected field '{key}'")
    return problems

def parse_reply(raw):
    try:
        data = json.loads(extract_json(raw))
    except ValueError as e:                  # json.JSONDecodeError is a ValueError too
        return None, [f"not valid JSON: {e}"]
    return data, validate(data, SCHEMA)

replies = {
    "clean":        '{"name": "Ada", "email": "ada@example.com", "company": "Analytical", "wants_demo": true}',
    "with chatter": 'Sure! Here you go:\n```json\n{"name": "Ada", "email": "ada@example.com", "company": "Analytical", "wants_demo": true}\n```',
    "missing key":  '{"name": "Ada", "email": "ada@example.com", "wants_demo": true}',
    "wrong type":   '{"name": "Ada", "email": "ada@example.com", "company": "Analytical", "wants_demo": "yes"}',
    "broken json":  '{"name": "Ada", "email": "ada@example.com" "company": "Analytical", "wants_demo": true}',
    "not json":     'Ada from Analytical wants a demo, her email is ada@example.com',
}

for label, raw in replies.items():
    data, problems = parse_reply(raw)
    status = "OK  " if not problems else "FAIL"
    print(f"{status} {label:13} {problems if problems else data}")

# The retry message is built FROM the problems list. That is the validate-and-
# retry loop from section 3, with the model call left out.
_, problems = parse_reply(replies["wrong type"])
print()
print("--- retry prompt you would send back ---")
print("Your last reply did not match the schema:")
for p in problems:
    print(f"- {p}")
print("Return ONLY a corrected JSON object, no commentary.")

# Run it. The first lines you should see are below; the rest follows.

# ✅ Expected output:
# OK   clean         {'name': 'Ada', 'email': 'ada@example.com', 'company': 'Analytical', 'wants_demo': True}
# OK   with chatter  {'name': 'Ada', 'email': 'ada@example.com', 'company': 'Analytical', 'wants_demo': True}
# FAIL missing key   ["missing field 'company'"]
# FAIL wrong type    ["'wants_demo' should be bool, got str"]
# FAIL broken json   ["not valid JSON: Expecting ',' delimiter: line 1 column 44 (char 43)"]
# FAIL not json      ['not valid JSON: no JSON object found']
# 🎯 YOUR TURN — finish the validator so a bad reply is caught before your code uses it.

import json

SCHEMA = {"title": str, "year": int, "in_stock": bool}

def validate(data, schema):
    problems = []
    for key, expected in schema.items():
        # 1) A required key that is not in the data is a problem.
        if key ___ data:
            # 👉 replace ___ with  not in
            problems.append("missing " + key)
        # 2) A key that IS present must have the type the schema says.
        elif not isinstance(data[key], ___):
            # 👉 replace ___ with  expected
            problems.append("wrong type for " + key)
    return problems

replies = [
    '{"title": "Dune", "year": 1965, "in_stock": true}',
    '{"title": "Dune", "year": "1965", "in_stock": true}',
    '{"title": "Dune", "in_stock": false}',
]

for raw in replies:
    # 3) Turn the JSON text into a Python dict before validating it.
    data = json.___(raw)
    # 👉 replace ___ with  loads
    problems = validate(data, SCHEMA)
    print("OK" if not problems else "FAIL " + ", ".join(problems))

# ✅ Expected output:
# OK
# FAIL wrong type for year
# FAIL missing year

Common Mistakes (and the fix)

⏱ Test Yourself — Timed Quiz

10 quick questions, 12 seconds each. Instant feedback — beat the clock!

🎉 Lesson Complete

Practice quiz

What is 'structured output' from an AI?

  • A reply formatted in a predictable, machine-readable shape like JSON
  • A reply with lots of emojis
  • A reply that is always one word
  • A spoken answer

Answer: A reply formatted in a predictable, machine-readable shape like JSON. Structured output means a predictable, parseable shape such as JSON, not freeform prose.

Why ask for JSON instead of freeform text?

  • It looks prettier
  • It is always shorter
  • Your code can reliably parse it into fields and values
  • It avoids all errors forever

Answer: Your code can reliably parse it into fields and values. JSON gives a predictable structure your program can read field by field.

What does providing a schema or example do?

  • Hides the output
  • Shows the AI exactly which fields and types you expect, improving reliability
  • Nothing useful
  • Makes the AI slower

Answer: Shows the AI exactly which fields and types you expect, improving reliability. A schema or worked example pins down the exact shape you want back.

A common phrase to get clean JSON is…

  • 'use big words'
  • 'write a poem'
  • 'be creative'
  • 'Return ONLY valid JSON, no extra text'

Answer: 'Return ONLY valid JSON, no extra text'. Telling the model to return only JSON with no commentary reduces parsing failures.

What is 'JSON mode' or 'response_format' / 'structured outputs'?

  • A feature that constrains the model to emit valid JSON matching your schema
  • A typing game
  • A way to delete data
  • A slower model

Answer: A feature that constrains the model to emit valid JSON matching your schema. These features make the API enforce a JSON shape rather than relying on prose instructions alone.

If parsing the AI's JSON fails, a good strategy is…

  • Give up entirely
  • Validate the output and retry, asking it to fix the JSON
  • Ignore the error
  • Restart your computer

Answer: Validate the output and retry, asking it to fix the JSON. Validate, and on failure retry with a request to correct the malformed JSON.

Why does 'return ONLY JSON' plus a schema beat freeform?

  • It does not
  • It uses fewer letters
  • It is more polite
  • It removes commentary AND pins the exact fields, so parsing is reliable

Answer: It removes commentary AND pins the exact fields, so parsing is reliable. Combining a no-prose instruction with an explicit schema gives the most reliable parse.

Which output is easiest for a program to consume?

  • An audio clip
  • A chatty paragraph describing the user
  • { "name": "Ada", "age": 36 }
  • A hand-drawn diagram

Answer: { "name": "Ada", "age": 36 }. A clean JSON object maps directly to fields your code can read.

Including a worked example in your prompt mainly helps because…

  • It changes the model
  • The model can copy the exact shape, keys, and types you want
  • It is required by law
  • It wastes space

Answer: The model can copy the exact shape, keys, and types you want. A concrete example demonstrates the precise structure you expect.

Validation in this context means…

  • Checking the returned data actually matches the expected schema before you use it
  • Complimenting the AI
  • Translating to another language
  • Deleting the response

Answer: Checking the returned data actually matches the expected schema before you use it. Validation confirms the output has the right fields and types before your code trusts it.

Continue this course

Frequently asked questions

What is structured output in simple terms?

It is when you ask the AI to answer in a fixed, machine-readable shape instead of a chatty paragraph. The most common shape is JSON, a format of keys and values like {"name": "Ada", "age": 36}. Because the shape is predictable, your program can pull out each field reliably instead of trying to parse loose prose.

How do I actually get reliable JSON back?

Two things together work best: tell the model to 'return ONLY valid JSON with no extra text,' and give it the exact schema or a worked example of the fields and types you want. Many AI tools also offer a JSON mode, structured outputs, or a response_format setting that constrains the model to valid JSON automatically, which is even more reliable than prose instructions alone.

What if the JSON comes back broken?

Always validate the output against your expected schema before using it. If it fails to parse or is missing fields, retry, asking the model to fix the JSON. A validate-and-retry loop turns the occasional malformed response into a clean one without crashing your program.

Why does adding a schema beat just asking nicely?

Freeform requests leave the model guessing your field names, types, and structure, so results drift. A schema or example removes that guesswork by showing exactly what you want. Pairing it with 'return ONLY JSON' strips out the explanatory chatter that would otherwise break a parser. Together they make the output dependable.