Force Claude to return valid JSON using tool use or system prompts. Python and Node.js examples for structured output, schemas, and parsing. Works with all Claude 4 models.
Claude does not have a built-in "JSON mode" like OpenAI, but you can reliably get structured JSON output using two approaches: tool use (recommended) or a well-crafted system prompt. Tool use is more reliable because Claude is specifically trained to populate tool arguments as valid JSON.
Define a tool whose input_schema matches the JSON structure you want. Force Claude to call it with tool_choice.
import anthropic, json
client = anthropic.Anthropic()
tool = {
"name": "extract_info",
"description": "Extract structured information from text.",
"input_schema": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "Person's full name"},
"email": {"type": "string", "description": "Email address"},
"company": {"type": "string", "description": "Company name"},
"role": {"type": "string", "description": "Job title"}
},
"required": ["name", "email"]
}
}
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=512,
tools=[tool],
tool_choice={"type": "tool", "name": "extract_info"}, # force this tool
messages=[
{"role": "user", "content": "From this email signature: John Doe, CTO at Acme Corp, john@acme.com — extract the contact info."}
]
)
# Tool use blocks are in message.content
tool_block = next(b for b in message.content if b.type == "tool_use")
data = tool_block.input # already a dict — no json.loads needed
print(data)
# {'name': 'John Doe', 'email': 'john@acme.com', 'company': 'Acme Corp', 'role': 'CTO'}
Works well for simple schemas. Less reliable for complex nested structures — use tool use for those.
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=256,
system='Respond ONLY with valid JSON. No explanation, no markdown, no code fences. Use the schema: {"sentiment": "positive|negative|neutral", "confidence": 0.0-1.0, "topics": [string]}',
messages=[
{"role": "user", "content": "The new Claude 4 models are incredibly fast and cheap. Highly recommend!"}
]
)
import json
result = json.loads(message.content[0].text)
print(result)
# {'sentiment': 'positive', 'confidence': 0.95, 'topics': ['Claude 4', 'speed', 'pricing']}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const response = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 512,
tools: [{
name: "record_product",
description: "Record product details as structured data.",
input_schema: {
type: "object",
properties: {
name: { type: "string" },
price: { type: "number" },
category: { type: "string" },
in_stock: { type: "boolean" }
},
required: ["name", "price", "category", "in_stock"]
}
}],
tool_choice: { type: "tool", name: "record_product" },
messages: [{ role: "user", content: "Apple AirPods Pro 2 cost $249, they're in the audio category, and yes they're available." }]
});
const toolBlock = response.content.find(b => b.type === "tool_use");
console.log(toolBlock.input);
// { name: 'Apple AirPods Pro 2', price: 249, category: 'audio', in_stock: true }
# Use the Message Batches API to extract JSON from 100s of documents cheaply
# (50% cost reduction vs standard API)
batch = client.messages.batches.create(
requests=[
{
"custom_id": f"doc-{i}",
"params": {
"model": "claude-haiku-4-5",
"max_tokens": 256,
"tools": [tool],
"tool_choice": {"type": "tool", "name": "extract_info"},
"messages": [{"role": "user", "content": doc_text}]
}
}
for i, doc_text in enumerate(documents)
]
)
print(f"Batch ID: {batch.id}") # poll until complete
See Claude Cost Calculator to estimate batch extraction costs. Use Prompt-Pricing Recommender to pick the right model for your schema complexity.