Claude Structured Output Python: Pydantic & JSON Schema

Extract structured data from Claude in Python using Pydantic models, JSON schema via tool_use, and TypedDict. Working code examples with validation. Updated for Claude Sonnet 4.6 and claude-haiku-4-5.

💥 50p impulse-buy: Power Prompts PDF (first 10 buyers) 30 battle-tested Claude Code prompts · 8-page PDF · paste into CLAUDE.md and never re-type a prompt again · 50p impulse-buy, no commitment

Claude doesn't have a JSON mode flag, but the tool_use pattern with forced tool choice gives you guaranteed structured output with full schema validation.

Pattern 1: Pydantic model → JSON Schema → Claude tool

import anthropic
from pydantic import BaseModel

client = anthropic.Anthropic()

class ProductInfo(BaseModel):
    name: str
    price_usd: float
    category: str
    in_stock: bool
    tags: list[str]

# Get JSON schema from Pydantic
schema = ProductInfo.model_json_schema()

# Force Claude to populate the schema
response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=512,
    tools=[{
        "name": "extract_product",
        "description": "Extract product information from text.",
        "input_schema": schema
    }],
    tool_choice={"type": "tool", "name": "extract_product"},
    messages=[{
        "role": "user",
        "content": "Parse: 'Ergonomic Mesh Chair – $249.99, Furniture, currently available. Tags: office, ergonomic, adjustable'"
    }]
)

for block in response.content:
    if block.type == "tool_use":
        product = ProductInfo(**block.input)
        print(product)
# ProductInfo(name='Ergonomic Mesh Chair', price_usd=249.99, category='Furniture', in_stock=True, tags=['office', 'ergonomic', 'adjustable'])

Pattern 2: Manual JSON Schema (no Pydantic)

schema = {
    "type": "object",
    "properties": {
        "sentiment": {"type": "string", "enum": ["positive", "negative", "neutral"]},
        "confidence": {"type": "number", "minimum": 0, "maximum": 1},
        "key_phrases": {"type": "array", "items": {"type": "string"}},
        "summary": {"type": "string"}
    },
    "required": ["sentiment", "confidence", "key_phrases", "summary"]
}

response = client.messages.create(
    model="claude-haiku-4-5-20251001",  # Haiku is fast + cheap for classification
    max_tokens=256,
    tools=[{"name": "analyze_sentiment", "description": "Analyze sentiment.", "input_schema": schema}],
    tool_choice={"type": "tool", "name": "analyze_sentiment"},
    messages=[{"role": "user", "content": "The product arrived damaged and support took 3 weeks to respond. Terrible experience."}]
)

for block in response.content:
    if block.type == "tool_use":
        print(block.input)
# {'sentiment': 'negative', 'confidence': 0.97, 'key_phrases': ['arrived damaged', 'support took 3 weeks'], 'summary': 'Customer received damaged product with slow support response.'}

Pattern 3: Nested objects

schema = {
    "type": "object",
    "properties": {
        "company": {"type": "string"},
        "founded": {"type": "integer"},
        "headquarters": {
            "type": "object",
            "properties": {
                "city": {"type": "string"},
                "country": {"type": "string"}
            },
            "required": ["city", "country"]
        },
        "executives": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "name": {"type": "string"},
                    "title": {"type": "string"}
                },
                "required": ["name", "title"]
            }
        }
    },
    "required": ["company", "founded", "headquarters", "executives"]
}

response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=512,
    tools=[{"name": "extract_company", "description": "Extract company info.", "input_schema": schema}],
    tool_choice={"type": "tool", "name": "extract_company"},
    messages=[{
        "role": "user",
        "content": "Anthropic was founded in 2021 and is headquartered in San Francisco, USA. Key leaders: Dario Amodei (CEO) and Daniela Amodei (President)."
    }]
)

Batch structured extraction

from concurrent.futures import ThreadPoolExecutor

texts = [
    "Great product, fast shipping! Love the quality.",
    "Broke after one week. Cheap materials.",
    "OK for the price. Nothing special.",
]

def classify(text):
    r = client.messages.create(
        model="claude-haiku-4-5-20251001",
        max_tokens=128,
        tools=[{"name": "classify", "description": "Classify sentiment.", "input_schema": schema}],
        tool_choice={"type": "tool", "name": "classify"},
        messages=[{"role": "user", "content": text}]
    )
    for b in r.content:
        if b.type == "tool_use":
            return b.input
    return None

with ThreadPoolExecutor(max_workers=5) as pool:
    results = list(pool.map(classify, texts))

for text, result in zip(texts, results):
    print(f"{result['sentiment']:8s} | {text[:50]}")

For cost estimates on high-volume structured extraction jobs, use the Claude API Cost Calculator. For the full tool_use reference, see the tool use deep-dive. For batch processing large datasets, see Claude Batch API.

Frequently asked questions

Does Claude have a JSON mode like GPT-4?
Claude doesn't have a dedicated 'JSON mode' toggle, but you can guarantee structured output by defining a tool with a JSON schema and setting tool_choice to force Claude to use it. This is more flexible than a JSON mode because you validate shape, required fields, and types all at once.
How do I use Pydantic with Claude?
Define your Pydantic model, call `.model_json_schema()` to get the JSON schema, pass it as the `input_schema` of a tool, and set `tool_choice` to force that tool. Claude populates the schema fields; parse the result back into your Pydantic model with `MyModel(**block.input)`.
What's the most reliable way to get structured output from Claude?
The tool_use pattern with forced tool choice (`tool_choice: {type: 'tool', name: 'your_tool'}`) is the most reliable approach. It guarantees Claude fills the exact schema, including required fields, types, and nesting. Plain prompt instructions to 'respond in JSON' work ~90% of the time but can drift.
Can I get nested objects and arrays from Claude?
Yes. JSON Schema supports nested objects, arrays, enums, and optional fields natively. Define `properties` as nested `object` types or `array` types with an `items` schema. Claude reliably populates deep nesting when forced via tool_choice.

Free tools

Cost Calculator → API Cookbook → Diff Summarizer → Skills Browser →

More examples

Claude API Python QuickstartClaude API Node.js / TypeScript QuickstartClaude API Streaming in PythonClaude API Streaming in Node.js / TypeScriptClaude API Tool Use in PythonClaude API Tool Use in Node.js / TypeScript