These templates solve a specific problem: getting LLMs to return valid, schema-conforming JSON instead of markdown-wrapped text or hallucinated keys.
Getting an LLM to reliably output structured data is the difference between a prototype and a production feature. JSON Mode Prompts are the missing layer between "the model understands my request" and "my code can parse the response without a regex hack." I've tested these across Claude, GPT-4, Gemini, and DeepSeek, and the patterns below are what survived real-world parsing failures.
Why Generic Prompts Fail Here
The failure mode is almost never "the model didn't understand." It's that the model returns:
- Markdown code fences around the JSON
- Trailing commas or single-quoted keys
- Extra explanatory text before or after the JSON
- Nested objects that don't match your TypeScript interfaces
Generic prompts like "return JSON" produce all of these. The fix isn't more instruction — it's giving the model a schema anchor and explicit constraints that eliminate ambiguity before generation starts.
Template 1: The Schema-First Extractor
This works best for data extraction tasks where you have a known shape (user profiles, log parsing, form data).
You are a data extraction engine. Extract the requested fields from the input text and return ONLY a valid JSON object.
SCHEMA (must match exactly):
{
"name": "string",
"age": "integer or null if unknown",
"email": "string or null if not present",
"address": {
"street": "string or null",
"city": "string or null",
"zip": "string or null"
}
}
RULES:
1. Output ONLY the JSON object. No markdown, no code fences, no commentary.
2. Use null for missing fields. Never invent data.
3. Preserve exact key names from the SCHEMA. No synonyms.
INPUT TEXT:
{{user_input_here}}
Placeholder meaning: {{user_input_here}} is the raw text you're extracting from. The schema block is non-negotiable — if you change a key name here, the model will follow it, so keep it in sync with your TypeScript interface.
Template 2: The Constrained Generator
This one generates content (product descriptions, test data, email drafts) where you need structure but also creative latitude.
Generate a JSON object matching this exact schema. The content should be realistic and varied.
SCHEMA:
{
"product_name": "string",
"category": "electronics | clothing | groceries",
"price_usd": "number, 2 decimal places",
"in_stock": "boolean",
"features": ["array of 3-5 strings"],
"summary": "string, max 50 words"
}
CONSTRAINTS:
- The "category" field must be one of the three allowed values only.
- price_usd must be between 1.00 and 9999.00.
- features must be specific, not generic ("wireless charging" not "good quality").
- Return ONLY the JSON object. No preamble, no code fence.
CONTEXT for generation:
{{generation_context_here}}
The {{generation_context_here}} placeholder gives the model direction — like "a budget smartphone" or "a winter jacket for pets." Without it, the model defaults to generic output that fails validation.
Template 3: The Edge-Case Handler
This is for when your data is messy — partial records, conflicting information, or multi-entity extraction.
You are parsing unstructured text into structured records. The input may contain multiple entities, missing fields, or contradictory data.
SCHEMA:
{
"records": [
{
"id": "string (construct from context if absent)",
"entity_type": "person | company | location",
"name": "string",
"confidence": "number 0.0 to 1.0",
"notes": "string or null",
"conflicts": ["array of strings describing data conflicts"]
}
]
}
RULES:
1. Extract ALL distinct entities, not just the first one.
2. If two sources disagree, include both in "conflicts" and pick the most recent.
3. confidence reflects how certain you are — 0.9+ for explicit matches, 0.5 for inferred.
4. If no entities exist, return {"records": []} — never return null or an error.
5. Output ONLY the JSON object. No markdown, no code fences.
INPUT TEXT:
{{messy_input_here}}
This one is harder because it forces the model to make judgment calls. The "records": [] fallback is critical — it prevents parse failures on empty inputs, which is the #1 cause of runtime crashes in my experience.
How to Adapt These for Your Own Codebase
Three concrete moves that take these from "works in a playground" to "works in production":
- Generate the schema from your TypeScript types. Use
zodorio-tsto derive the JSON schema programmatically. Hardcoding schemas guarantees drift when your interfaces change. - Strip whitespace and validate before parsing. Even with these prompts, run the response through
.trim()and a strict JSON parser. If parsing fails, retry once with a shorter prompt — long prompts degrade JSON compliance. - Add a version field to your schema. When you change a schema, old cached responses become invalid. A
"schema_version": 1field makes migrations trivial.
Do These Prompts Work With Any LLM?
Mostly, but with caveats. Claude and GPT-4 follow the "no markdown" rule reliably. Gemini occasionally wraps output in code fences regardless — strip them server-side. DeepSeek handles the schema-first template well but struggles with the edge-case template's multi-record extraction. OpenAI's response_format: { type: "json_object" } parameter makes Template 1 nearly bulletproof, but the prompt still matters for schema compliance. For production, always validate against your schema — never trust the model's output blindly.
The one adjustment that improves these prompts the most: show a full example response in the prompt. A single worked example of the expected output eliminates more parsing errors than any rule you can write.