Back to KB
Difficulty
Intermediate
Read Time
6 min

Guaranteed JSON Every Time: Using Claude's Structured Outputs with JSON Schema

By Codcompass TeamΒ·Β·6 min read

If you've ever tried to get structured data out of an LLM with a prompt like "Please respond in valid JSON with the following fields...", you already know the story. It works 95% of the time. The other 5% it returns prose, fenced markdown, a trailing apology, or β€” my personal favorite β€” { "name": "Alice", "age": 30, } with a trailing comma that explodes your parser at 3 AM.

This guide walks through a technique that makes Claude return JSON that conforms to your schema on every single call, with no regex parsing and no retry loops. It works because we're not really asking Claude for JSON β€” we're tricking it into a code path that produces JSON as a side effect.

About this guide. I'm one of the maintainers of claudeapi.com, a third-party Claude API gateway. All code below targets the official Anthropic endpoint (api.anthropic.com); a comparison of alternative gateways for developers in restricted regions appears near the end. Skip it if you're on the official endpoint and it works fine for you.

The trick: hijack tool use

Claude's tool_use feature was designed so the model can call your functions. Under the hood, when you define a tool, you give Claude a JSON Schema for its arguments. The model is fine-tuned to produce arguments that match that schema, validated server-side before being returned to you.

So the trick is: define a fake tool whose input schema is the structure you want, then force Claude to call it. You never actually execute the "tool" β€” you just read its arguments. That's your guaranteed JSON.

Example 1: basic product extraction

import anthropic
import json

client = anthropic.Anthropic()  # reads ANTHROPIC_API_KEY from env

product_schema = {
    "type": "object",
    "properties": {
        "name": {"type": "string"},
        "price_usd": {"type": "number"},
        "in_stock": {"type": "boolean"},
        "tags": {"type": "array", "items": {"type": "string"}},
    },
    "required": ["name", "price_usd", "in_stock"],
}

response = client.messages.create(
    model="claude-opus-4-7",
    max_tokens=1024,
    tools=[{
        "name": "extract_product",
        "description": "Extract structured product information from text.",
        "input_schema": product_schema,
    }],
    tool_choice={"type": "tool", "name": "extract_product"},  # forces the call
    messages=[{
        "role": "user",
        "content": "The new SoundCore Mini 3 is $39.99, currently in stock, available in black and red.",
    }],
)

# The structured data is in the tool_use block's input field
for block in response.content:
    if block.type == "tool_use":
        data = block.input
        print

πŸŽ‰ Mid-Year Sale β€” Unlock Full Article

Base plan from just $4.99/mo or $49/yr

Sign in to read the full article and unlock all 635+ tutorials.

Sign In / Register β€” Start Free Trial

7-day free trial Β· Cancel anytime Β· 30-day money-back