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
- What machine-readable output means and why JSON is common
- Providing a schema or worked example
- JSON mode, structured outputs, and response_format
- Validating output and retrying on parse failure
- Why 'return ONLY JSON' plus a schema wins
- Turning loose prose into reliable, parseable data
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 yearCommon Mistakes (and the fix)
- Asking for data in prose. — Ask for JSON with named fields.
- No schema or example. — Show the exact keys and types.
- Letting commentary slip in. — Say “return ONLY JSON”.
- Trusting output without checking. — Validate, then retry on failure.
⏱ Test Yourself — Timed Quiz
10 quick questions, 12 seconds each. Instant feedback — beat the clock!
🎉 Lesson Complete
- ✅ Structured output is a predictable, machine-readable shape like JSON
- ✅ Provide a schema or worked example of the fields and types
- ✅ Use JSON mode / structured outputs / response_format when available
- ✅ Validate the result and retry on parse failure
- ✅ “Return ONLY JSON” plus a schema beats freeform
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
- Previous: System Prompts & Setting Behavior
- Next: Tool & Function Calling — Let the model call your functions and APIs in a call-execute-observe loop
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.