Back to Blog

Structured JSON Output from AI Models: A Complete Developer Guide

Apr 1, 20269 min read

Getting structured data out of a large language model is one of the most common challenges in AI application development. LLMs are trained to produce natural language, not machine-readable JSON — yet most real applications need to parse the model's output and feed it to downstream systems. This guide covers every technique for getting reliable, parseable JSON from AI models in 2026.

Why LLMs Struggle with JSON

Without constraints, a model might produce JSON with markdown code fences (```json ... ```), trailing commas, unquoted keys, incomplete output (if the response is cut off), extra explanation text before or after the JSON, or inconsistent field names. Any of these will break JSON.parse().

Problematic LLM output (without JSON mode)
Sure! Here's the user data you requested:

```json
{
  "name": "Alice",
  "age": 30,
  "active": true,  // currently active
}
```

Let me know if you need anything else!

Technique 1: JSON Mode (OpenAI / compatible APIs)

OpenAI's Chat Completions API supports a response_format parameter that forces the model to output valid JSON. When set to { type: "json_object" }, the model is guaranteed to return parseable JSON with no surrounding text.

JSON mode with the OpenAI SDK
import OpenAI from 'openai';
const client = new OpenAI();

const response = await client.chat.completions.create({
  model: 'gpt-4o',
  response_format: { type: 'json_object' },
  messages: [
    {
      role: 'system',
      content: 'You are a data extraction assistant. Always respond with valid JSON.'
    },
    {
      role: 'user',
      content: 'Extract the name, email, and plan from this text: "Alice ([email protected]) just upgraded to the Pro plan."'
    }
  ]
});

const data = JSON.parse(response.choices[0].message.content);
// { "name": "Alice", "email": "[email protected]", "plan": "Pro" }

Always mention JSON in the system prompt

When using JSON mode, your system prompt must mention JSON or the API will return an error. Say something like "Always respond with a JSON object" to satisfy this requirement.

Technique 2: Structured Outputs (JSON Schema enforcement)

Structured Outputs go further than JSON mode — you provide a JSON Schema and the model is guaranteed to return data that exactly matches that schema. This is the most reliable technique for production AI applications.

Structured outputs with JSON Schema
const response = await client.chat.completions.create({
  model: 'gpt-4o-2024-08-06',  // structured outputs require this model or newer
  response_format: {
    type: 'json_schema',
    json_schema: {
      name: 'user_extraction',
      strict: true,
      schema: {
        type: 'object',
        properties: {
          name: { type: 'string' },
          email: { type: 'string', format: 'email' },
          plan: { type: 'string', enum: ['free', 'pro', 'enterprise'] }
        },
        required: ['name', 'email', 'plan'],
        additionalProperties: false
      }
    }
  },
  messages: [...]
});

Technique 3: Function Calling (Tool Use)

Function calling (now called "tool use" in the OpenAI API) was the original structured output mechanism. You define a function with a JSON Schema for its parameters, and the model returns a structured call to that function when appropriate. This is ideal for agentic applications where the model needs to decide which action to take.

Function calling / tool use
const response = await client.chat.completions.create({
  model: 'gpt-4o',
  tools: [
    {
      type: 'function',
      function: {
        name: 'create_user',
        description: 'Create a new user in the system',
        parameters: {
          type: 'object',
          properties: {
            name: { type: 'string', description: 'Full name' },
            email: { type: 'string', description: 'Email address' },
            role: { type: 'string', enum: ['admin', 'editor', 'viewer'] }
          },
          required: ['name', 'email', 'role']
        }
      }
    }
  ],
  messages: [{ role: 'user', content: 'Create a viewer account for Bob at [email protected]' }]
});

const toolCall = response.choices[0].message.tool_calls?.[0];
if (toolCall?.function.name === 'create_user') {
  const args = JSON.parse(toolCall.function.arguments);
  await createUser(args);  // { name: "Bob", email: "[email protected]", role: "viewer" }
}

Technique 4: Prompt Engineering (model-agnostic)

When using models that don't support JSON mode or structured outputs, good prompting gets you 90% of the way there:

  • Show an example in the prompt: Include a JSON example of the exact output you want. Models follow examples much more reliably than abstract instructions.
  • Use a system prompt: Put JSON formatting instructions in the system message, not the user message.
  • Ask for only JSON: "Respond with only the JSON object, no explanation, no markdown code fences."
  • Specify the schema explicitly: List every field name and its type in the prompt.
  • Use low temperature: Set temperature to 0 or 0.1 for deterministic, structured output.
Effective system prompt for JSON output
You are a data extraction assistant.

Always respond with a single JSON object in this exact format:
{
  "name": string,
  "email": string,
  "plan": "free" | "pro" | "enterprise"
}

Do not include any explanation, markdown, or text outside the JSON object.

Build JSON prompts from a schema

Use our JSON to Prompt Template converter to automatically generate a prompt from any JSON structure. Great for quickly creating LLM instructions for data extraction tasks.

Parsing and Validating LLM JSON Responses

Repair malformed JSON

Even with best-effort prompting, LLM output can have minor JSON errors. The jsonrepair library handles common issues like trailing commas, unquoted keys, and truncated output.

Robust LLM JSON parsing
import { jsonrepair } from 'jsonrepair';

function parseLLMJson(raw) {
  // Strip markdown code fences if present
  const stripped = raw.replace(/^```(?:json)?
?/m, '').replace(/
?```$/m, '').trim();

  try {
    return JSON.parse(stripped);
  } catch {
    // Try to repair and parse again
    return JSON.parse(jsonrepair(stripped));
  }
}

Validate against a schema

Validate with Zod
import { z } from 'zod';

const UserSchema = z.object({
  name: z.string(),
  email: z.string().email(),
  plan: z.enum(['free', 'pro', 'enterprise'])
});

const result = UserSchema.safeParse(parsedJson);
if (!result.success) {
  console.error('LLM returned invalid schema:', result.error.format());
  // Retry with a corrective prompt or fall back to defaults
} else {
  const user = result.data;  // fully typed and validated
}

Comparison: Which Technique to Use

TechniqueReliabilityModel SupportBest For
Structured Outputs (JSON Schema)Highest — schema enforcedOpenAI gpt-4o+, Gemini 1.5+Production apps with strict schema
JSON ModeHigh — valid JSON guaranteedOpenAI, Groq, many othersAny structured data extraction
Function Calling / Tool UseHighMost major providersAgentic apps, action selection
Prompt EngineeringMedium — may need repairAny modelOpen-source models, quick prototypes

Tools for working with JSON in AI pipelines

Format, validate, and inspect the JSON your AI models produce. Build prompt templates from JSON schemas.