/json-mode-patterns
Structured JSON output from Claude: tool-use-as-JSON, schema, parsing, partial recovery. Triggers: JSON mode, structured output, schema validation, JSON parsing.
$ npx -y skills add softspark/ai-toolkit --skill json-mode-patterns --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/json-mode-patterns
Context preview
The summary Claude sees to decide when to auto-load this skill.
Structured JSON output from Claude: tool-use-as-JSON, schema, parsing, partial recovery. Triggers: JSON mode, structured output, schema validation, JSON parsing.
SKILL.md
json-mode-patterns.SKILL.mdname: json-mode-patterns
description: "Structured JSON output from Claude: tool-use-as-JSON, schema, parsing, partial recovery. Triggers: JSON mode, structured output, schema validation, JSON parsing."
effort: medium
user-invocable: false
allowed-tools: Read
JSON Mode Patterns
Claude does not have a dedicated `response_format: json` parameter like some other APIs. The idiomatic way to get guaranteed JSON is **tool use with a forced function call**. This skill documents that pattern plus fallbacks.
Preferred Pattern: Tool-as-Schema
Define a tool whose input schema IS the JSON shape you want, then force the model to call it.
tools = [{
"name": "record_analysis",
"description": "Return the analysis as structured data",
"input_schema": {
"type": "object",
"properties": {
"sentiment": {"type": "string", "enum": ["positive", "neutral", "negative"]},
"confidence": {"type": "number", "minimum": 0, "maximum": 1},
"themes": {"type": "array", "items": {"type": "string"}}
},
"required": ["sentiment", "confidence", "themes"]
}
}]
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
tools=tools,
tool_choice={"type": "tool", "name": "record_analysis"},
messages=[{"role": "user", "content": text_to_analyze}]
)
# The structured result is in response.content
for block in response.content:
if block.type == "tool_use" and block.name == "record_analysis":
result = block.input # already a Python dict, schema-validated
breakWhy this wins:
- Schema is enforced at the API level
- No regex or parsing from model text
- Enums, min/max, required fields actually constrain the output
Fallback: Prompted JSON + Strict Parse
When tool use is unavailable (some SDKs/proxies strip it):
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
system="You return ONLY valid JSON. No prose, no markdown fences.",
messages=[{
"role": "user",
"content": f"Extract as JSON matching this schema: {schema_str}\n\nInput: {text}"
}]
)
import json
try:
result = json.loads(response.content[0].text)
except json.JSONDecodeError:
# Claude sometimes wraps in ```json ... ```
result = json.loads(strip_markdown_fence(response.content[0].text))Add a regex fallback that extracts the first `{...}` block if the model added a preface.
Schema Design Rules
- **Favor enums** over free-form strings when values are known
- **Mark required fields** aggressively — default-optional produces flaky output
- **Use arrays of objects**, not parallel arrays (`[{name, value}]` over `{names: [], values: []}`)
- **Shallow beats nested** — 2 levels of nesting max unless necessary
- **Document each field** in the tool's `description` as well as the schema
Partial Output Recovery
Model hits `max_tokens` mid-JSON. Strategies:
1. **Increase `max_tokens`** if the schema is genuinely large (most common cause). 2. **Split the schema** — generate one field per call, merge. 3. **Use streaming** and close unclosed braces if stop reason is `max_tokens`.
if response.stop_reason == "max_tokens":
# Either retry with higher budget or gracefully degrade
raise IncompleteOutputError(...)Validation After Parse
Even with tool schema enforcement, business rules aren't enforced by JSON Schema. Add a Pydantic/Zod layer:
from pydantic import BaseModel, Field
class Analysis(BaseModel):
sentiment: Literal["positive", "neutral", "negative"]
confidence: float = Field(ge=0, le=1)
themes: list[str] = Field(min_length=1, max_length=10)
parsed = Analysis(**result) # raises on violationGotchas
- **Tool call tokens count toward output budget** — a huge schema eats max_tokens fast
- **`stop_reason == "tool_use"`** is success, not an error
- **Streaming with tool use** requires handling `content_block_delta` events with `input_json_delta` deltas
- **Model picks a different tool** than you expected if `tool_choice` is `"auto"` — always force the specific tool for JSON mode
Related
- `claude-api` skill — Anthropic SDK essentials
- Anthropic docs: https://docs.claude.com/en/docs/build-with-claude/structured-outputs
Read more
name: json-mode-patterns description: "Structured JSON output from Claude: tool-use-as-JSON, schema, parsing, partial recovery. Triggers: JSON mode, structured output, schema validation, JSON parsing." effort: medium user-invocable: false allowed-tools: Read
JSON Mode Patterns
Claude does not have a dedicated `response_format: json` parameter like some other APIs. The idiomatic way to get guaranteed JSON is **tool use with a forced function call**. This skill documents that pattern plus fallbacks.
Preferred Pattern: Tool-as-Schema
Define a tool whose input schema IS the JSON shape you want, then force the model to call it.
tools = [{
"name": "record_analysis",
"description": "Return the analysis as structured data",
"input_schema": {
"type": "object",
"properties": {
"sentiment": {"type": "string", "enum": ["positive", "neutral", "negative"]},
"confidence": {"type": "number", "minimum": 0, "maximum": 1},
"themes": {"type": "array", "items": {"type": "string"}}
},
"required": ["sentiment", "confidence", "themes"]
}
}]
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
tools=tools,
tool_choice={"type": "tool", "name": "record_analysis"},
messages=[{"role": "user", "content": text_to_analyze}]
)
# The structured result is in response.content
for block in response.content:
if block.type == "tool_use" and block.name == "record_analysis":
result = block.input # already a Python dict, schema-validated
breakWhy this wins:
- Schema is enforced at the API level
- No regex or parsing from model text
- Enums, min/max, required fields actually constrain the output
Fallback: Prompted JSON + Strict Parse
When tool use is unavailable (some SDKs/proxies strip it):
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
system="You return ONLY valid JSON. No prose, no markdown fences.",
messages=[{
"role": "user",
"content": f"Extract as JSON matching this schema: {schema_str}\n\nInput: {text}"
}]
)
import json
try:
result = json.loads(response.content[0].text)
except json.JSONDecodeError:
# Claude sometimes wraps in ```json ... ```
result = json.loads(strip_markdown_fence(response.content[0].text))Add a regex fallback that extracts the first `{...}` block if the model added a preface.
Schema Design Rules
- **Favor enums** over free-form strings when values are known
- **Mark required fields** aggressively — default-optional produces flaky output
- **Use arrays of objects**, not parallel arrays (`[{name, value}]` over `{names: [], values: []}`)
- **Shallow beats nested** — 2 levels of nesting max unless necessary
- **Document each field** in the tool's `description` as well as the schema
Partial Output Recovery
Model hits `max_tokens` mid-JSON. Strategies:
1. **Increase `max_tokens`** if the schema is genuinely large (most common cause). 2. **Split the schema** — generate one field per call, merge. 3. **Use streaming** and close unclosed braces if stop reason is `max_tokens`.
if response.stop_reason == "max_tokens":
# Either retry with higher budget or gracefully degrade
raise IncompleteOutputError(...)Validation After Parse
Even with tool schema enforcement, business rules aren't enforced by JSON Schema. Add a Pydantic/Zod layer:
from pydantic import BaseModel, Field
class Analysis(BaseModel):
sentiment: Literal["positive", "neutral", "negative"]
confidence: float = Field(ge=0, le=1)
themes: list[str] = Field(min_length=1, max_length=10)
parsed = Analysis(**result) # raises on violationGotchas
- **Tool call tokens count toward output budget** — a huge schema eats max_tokens fast
- **`stop_reason == "tool_use"`** is success, not an error
- **Streaming with tool use** requires handling `content_block_delta` events with `input_json_delta` deltas
- **Model picks a different tool** than you expected if `tool_choice` is `"auto"` — always force the specific tool for JSON mode
Related
- `claude-api` skill — Anthropic SDK essentials
- Anthropic docs: https://docs.claude.com/en/docs/build-with-claude/structured-outputs
Professional-grade AI coding toolkit with multi-platform support. Machine-enforced safety, 109 skills, 44 agents, expanded lifecycle hooks, persona presets, experimental opt-in plugin packs, and benchmark tooling — works with Claude Code, Claude Chat/Cowork,
Repo: softspark/ai-toolkit
Other skills on ai-toolkit.
- /ai-toolkit-rules
Mandatory engineering, security, testing, git, performance, quality, and response rules. Claude MUST load this skill for every technical, coding, debugging, review, architecture, DevOps, data, or file-editing task in Chat or Cowork.
Open skill - /mem-search
Search past coding sessions using natural language. Finds relevant observations, decisions, and context from previous work.
Open skill - /a11y-validate
Accessibility validator: WCAG 2.1 AA, EN 301 549, EAA. Triggers: a11y, accessibility, WCAG, EAA, ARIA, contrast, keyboard, screen reader.
Open skill - /agent-creator
Creates new specialized agents with frontmatter, tools, delegation. Triggers: new agent, create agent, agent scaffold, specialized agent.
Open skill - /analyze
Analyzes code quality, complexity, patterns across codebase. Triggers: quality report, hotspot scan, code analysis, architecture signal.
Open skill - /api-patterns
REST/GraphQL API design: naming, versioning, pagination, idempotency, OpenAPI. Triggers: API design, REST, GraphQL, OpenAPI, Swagger, idempotency, rate limit.
Open skill

