LLM JSON Schema Compatibility: Which Features Work in OpenAI, Anthropic, and Google (2026)
Each LLM provider honors a different subset of JSON Schema. These gaps silently break production pipelines. Here's what actually works in OpenAI, Anthropic Claude, and Google Gemini.
Have broken JSON right now? Fix it free in under 1 second — no signup.
Fix My JSON →LLM JSON Schema Compatibility: Which Features Work in OpenAI, Anthropic, and Google (2026)
As of 2026, OpenAI, Anthropic, and Google Gemini all support native structured output with JSON Schema enforcement. Grammar-constrained decoding means the model's token sampling is constrained to schema-valid output — in theory, you get clean JSON every time.
In practice, each provider honors a different subset of JSON Schema, and the differences are large enough to silently break a shared Zod or Pydantic schema that works perfectly on one provider and fails on another.
This guide documents the specific compatibility gaps as of August 2026, with working code examples for each provider.
The Core Problem: One Schema, Three Interpreters
When you write a JSON Schema and send it to three providers, you're sending it to three different schema compilers. Each compiles your schema into a finite state machine (FSM) that constrains token sampling. If your schema uses a feature the FSM can't handle, the provider either:
- Rejects the request with a schema compilation error (safe — you know immediately)
- Silently accepts partial enforcement (dangerous — you discover it at runtime)
- Strips the unsupported keyword and enforces what remains (subtle — validation passes, constraints don't)
The focus has shifted from "will my LLM produce valid JSON?" to "which slice of JSON Schema does each vendor actually honor?" The differences hide exactly where production pipelines break.
Provider Comparison: JSON Schema Support
additionalProperties
Controls whether extra keys are allowed in objects.
| Provider | Support | Behavior |
|---|---|---|
| OpenAI | Required | Returns schema error if omitted |
| Anthropic | Required | Returns schema compilation error if omitted |
| Google Gemini | Optional | Some SDK versions silently allow extra fields; others block |
additionalProperties: false. It's required on OpenAI and Anthropic, and the Gemini behavior is inconsistent enough to treat it as required.
{
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"}
},
"required": ["name", "age"],
"additionalProperties": false
}
anyOf / oneOf / allOf
Discriminated unions and composition keywords.
| Provider | anyOf | oneOf | allOf |
|---|---|---|---|
| OpenAI | ✅ with caveats | ⚠️ limited | ✅ |
| Anthropic | ✅ | ✅ | ✅ |
| Google Gemini | ⚠️ limited | ❌ not supported | ✅ |
OpenAI requires discriminator objects for anyOf to work reliably in structured mode. Gemini does not support oneOf. Use anyOf with explicit discriminators if you need cross-provider compatibility.
{
"anyOf": [
{
"type": "object",
"properties": {
"type": {"type": "string", "const": "user"},
"name": {"type": "string"}
},
"required": ["type", "name"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"type": {"type": "string", "const": "bot"},
"model": {"type": "string"}
},
"required": ["type", "model"],
"additionalProperties": false
}
]
}
$ref and $defs
Schema references and definitions for reuse.
| Provider | $ref support | $defs / recursive | Notes |
|---|---|---|---|
| OpenAI | ✅ | ✅ with depth limit | Recursive schemas supported but capped |
| Anthropic | ✅ | ✅ | Full support as of GA release |
| Google Gemini | ⚠️ | ❌ no recursion | Inline expansion required |
For Gemini, expand all $refs inline before sending. Don't rely on $defs for reuse when targeting Gemini.
pattern (regex constraints)
String constraints using regular expressions.
| Provider | Support | Notes |
|---|---|---|
| OpenAI | ⚠️ limited | Simple patterns work; complex regex may be ignored |
| Anthropic | ⚠️ limited | Best-effort enforcement; complex patterns stripped |
| Google Gemini | ❌ | Not supported in structured output mode |
pattern for data validation in structured output. Use it in your JSON Schema for documentation, but validate the pattern yourself after receiving the response.
enum Values
Constrained string values.
| Provider | Support | Notes |
|---|---|---|
| OpenAI | ✅ | Full support |
| Anthropic | ✅ | Full support |
| Google Gemini | ✅ | Full support |
enum is the most universally supported constraint. Use it for categorical values instead of pattern when possible.
minimum / maximum (numeric bounds)
| Provider | Support | Notes |
|---|---|---|
| OpenAI | ⚠️ | Enforced at sampling level for integers; floats best-effort |
| Anthropic | ⚠️ | Best-effort; not guaranteed at token level |
| Google Gemini | ❌ | Not enforced |
Always validate numeric bounds after receiving the response. Don't trust the model to honor minimum: 0 for a price field.
default Values
| Provider | Behavior |
|---|---|
| OpenAI | Ignored |
| Anthropic | Ignored |
| Google Gemini | Ignored |
default in JSON Schema is metadata — it's never used by any LLM provider to fill in missing fields. Set required fields as required.
Working Code Examples
OpenAI Structured Output
from openai import OpenAI
from pydantic import BaseModel
from typing import Literal
client = OpenAI()
class Order(BaseModel):
order_id: str
status: Literal["pending", "shipped", "delivered"]
amount: float
# Note: additionalProperties=false is added automatically by .parse()
completion = client.beta.chat.completions.parse(
model="gpt-4o",
messages=[{"role": "user", "content": "Extract order info: Order #A123 for $45.00 is shipped"}],
response_format=Order,
)
order = completion.choices[0].message.parsed
print(order.status) # "shipped"
Anthropic Structured Output
Anthropic shipped structured output in November 2025 as a beta, then moved to GA in early 2026. The API shape changed:
# ❌ Old (beta) — no longer works:
# response_format={"type": "json_schema", ...}
# + header: "anthropic-beta: output-types-2025-05-02"
# ✅ New (GA 2026) — use output_config:
import anthropic
import json
client = anthropic.Anthropic()
schema = {
"type": "object",
"properties": {
"order_id": {"type": "string"},
"status": {"type": "string", "enum": ["pending", "shipped", "delivered"]},
"amount": {"type": "number"}
},
"required": ["order_id", "status", "amount"],
"additionalProperties": False
}
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
output_config={
"format": {
"type": "json_schema",
"json_schema": {
"name": "order",
"schema": schema
}
}
},
messages=[{"role": "user", "content": "Extract order info: Order #A123 for $45.00 is shipped"}]
)
order = json.loads(response.content[0].text)
print(order["status"]) # "shipped"
Note: The anthropic-beta header is no longer required for structured output in the GA release. Remove it from any code that still includes it.
Google Gemini Structured Output
import google.generativeai as genai
import json
genai.configure(api_key="YOUR_API_KEY")
model = genai.GenerativeModel("gemini-1.5-pro")
# Gemini uses response_schema with inline types
# Expand all $refs inline — no $defs support
response = model.generate_content(
"Extract order info: Order #A123 for $45.00 is shipped",
generation_config=genai.GenerationConfig(
response_mime_type="application/json",
response_schema={
"type": "OBJECT",
"properties": {
"order_id": {"type": "STRING"},
"status": {"type": "STRING", "enum": ["pending", "shipped", "delivered"]},
"amount": {"type": "NUMBER"}
},
"required": ["order_id", "status", "amount"]
# Note: additionalProperties not supported — add server-side validation
}
)
)
order = json.loads(response.text)
print(order["status"]) # "shipped"
Gemini-specific note: Use uppercase type names (STRING, NUMBER, BOOLEAN, OBJECT, ARRAY) in the response_schema. Lowercase string, number etc. also work in most versions, but uppercase is the documented format.
Building Cross-Provider Schemas
If your application routes requests to multiple providers (for cost, latency, or fallback), write schemas to the strictest common denominator:
def make_cross_provider_schema(properties: dict, required: list) -> dict:
"""
Build a JSON Schema compatible with OpenAI, Anthropic, and Gemini.
Rules:
- additionalProperties: false (required by OpenAI + Anthropic)
- No $ref / $defs (Gemini doesn't support)
- No pattern (not reliably enforced anywhere)
- No oneOf (Gemini doesn't support)
- Use enum instead of pattern for categorical values
- Use anyOf with explicit type field for unions
"""
return {
"type": "object",
"properties": properties,
"required": required,
"additionalProperties": False
}
# Usage
schema = make_cross_provider_schema(
properties={
"name": {"type": "string"},
"status": {"type": "string", "enum": ["active", "inactive"]},
"score": {"type": "integer"}
},
required=["name", "status", "score"]
)
Always Validate After Receiving
Even with grammar-constrained decoding, validate the response against your schema. Providers enforce schema compliance at different strictness levels, and some constraints (numeric bounds, patterns) are best-effort.
import jsonschema
import json
def parse_and_validate(response_text: str, schema: dict) -> dict:
"""Parse response JSON and validate against schema."""
try:
data = json.loads(response_text)
except json.JSONDecodeError:
# This shouldn't happen with structured output, but handle it
from json_repair import repair_json
data = json.loads(repair_json(response_text))
# Validate — catches pattern, minimum/maximum, and other unenforced constraints
jsonschema.validate(instance=data, schema=schema)
return data
Use AI JSONMedic's JSON Schema validator to test schemas before deploying, and the schema generator to build schemas from sample responses.
Compatibility Reference Table (August 2026)
| JSON Schema Keyword | OpenAI | Anthropic | Gemini |
|---|---|---|---|
type | ✅ | ✅ | ✅ |
properties | ✅ | ✅ | ✅ |
required | ✅ | ✅ | ✅ |
additionalProperties | ✅ required | ✅ required | ⚠️ inconsistent |
enum | ✅ | ✅ | ✅ |
anyOf | ✅ w/ discriminator | ✅ | ⚠️ limited |
oneOf | ⚠️ | ✅ | ❌ |
allOf | ✅ | ✅ | ✅ |
$ref / $defs | ✅ | ✅ | ⚠️ no recursion |
pattern | ⚠️ | ⚠️ | ❌ |
minimum / maximum | ⚠️ | ⚠️ | ❌ |
default | ❌ (ignored) | ❌ (ignored) | ❌ (ignored) |
✅ Full support · ⚠️ Partial/best-effort · ❌ Not supported
Frequently Asked Questions
Does grammar-constrained decoding guarantee valid JSON?
Yes — for syntax. All three providers enforce JSON syntax at the token-sampling level, producing <0.1% syntax failure rates in practice. The gaps are in JSON Schema keyword support: syntax is guaranteed, constraint enforcement varies by keyword and provider.
What happens when I use an unsupported schema keyword?
It depends on the provider. OpenAI and Anthropic tend to return explicit errors for clearly unsupported constructs. Gemini tends to accept the schema but silently ignore unsupported keywords — meaning your constraint is there in the schema but not enforced in the output. Always validate responses server-side.
Why did my schema work on OpenAI but fail on Anthropic?
The most common cause is additionalProperties. OpenAI returns a clear error when it's missing; Anthropic returns a schema compilation error with a different message. Other causes: nested $refs, oneOf (not fully supported everywhere), and recursive schemas.
Do I still need to validate structured output responses?
Yes. Grammar-constrained decoding ensures the output is syntactically valid JSON that matches the schema structure. It does not enforce semantic constraints like minimum, maximum, pattern, or cross-field relationships. Always run jsonschema.validate() or equivalent after parsing.
How do I test my schema across providers before deploying?
Use AI JSONMedic's schema validator to test that your schema itself is valid JSON Schema. Then test each provider's response against the schema programmatically — the compatibility gaps above are your checklist for what to verify.
What's the best schema format for multi-provider compatibility?
Use the strictest common denominator: type, properties, required, additionalProperties: false, and enum. Avoid pattern, oneOf, recursive $refs, and numeric bounds as enforcement constraints. For complex type unions, use anyOf with an explicit discriminator field.
Still dealing with broken JSON?
Paste it in and get it fixed in under 1 second — free, no signup, no install. Works with ChatGPT, Claude, n8n, and any AI output.
Fix My JSON Free →Related Articles