GEMINI LABJP
SHEETS — Fill with Gemini expands to 28 more languages, so you can build spreadsheets in your own language with natural promptsALPHAEVOLVE — Gemini Enterprise makes AlphaEvolve generally available, autonomously discovering optimized code solutionsSPARK — Gemini Spark, a personal AI agent for macOS, organizes folders and runs workflows across WorkspaceOMNI — Gemini Omni blends text, photos, and video for high-quality creation, plus custom AI avatars of youGEMINI35 — Gemini 3.5 Pro slips to July 17 for a full rebuild, introducing a 2M-token context and Deep ThinkDEEPTHINK — The 3.5 Pro rebuild targets better math reasoning, SVG scene generation, and image qualitySHEETS — Fill with Gemini expands to 28 more languages, so you can build spreadsheets in your own language with natural promptsALPHAEVOLVE — Gemini Enterprise makes AlphaEvolve generally available, autonomously discovering optimized code solutionsSPARK — Gemini Spark, a personal AI agent for macOS, organizes folders and runs workflows across WorkspaceOMNI — Gemini Omni blends text, photos, and video for high-quality creation, plus custom AI avatars of youGEMINI35 — Gemini 3.5 Pro slips to July 17 for a full rebuild, introducing a 2M-token context and Deep ThinkDEEPTHINK — The 3.5 Pro rebuild targets better math reasoning, SVG scene generation, and image quality
Articles/API / SDK
API / SDK/2026-07-13Advanced

When responseSchema Can't Do $ref: Handling Recursive Schemas in Production with responseJsonSchema

Gemini's responseSchema is an OpenAPI subset with no $ref or $defs, so it can't express shared definitions or recursion. Here's how I moved to responseJsonSchema to reuse localized fields and handle a recursive category tree in production.

gemini101gemini-api272structured-output22json-schema2python101

Premium Article

The first wall I hit trying to have Gemini classify wallpaper categories was that I couldn't express a category tree in the schema at all. Nature to Mountain to Snowy Mountain: a parent holds children, which hold their own children, and the depth varies by image. When I tried to write a self-reference in responseSchema, it simply wouldn't accept $ref. So I hand-inlined three levels and gave up on anything deeper.

What dissolved that compromise was responseJsonSchema. It's a separate field from responseSchema, and it understands JSON Schema's $ref and $defs. You can define something once and reuse it, and you can declare recursion through self-reference. As an indie developer dealing with multilingual, nested structured output, that difference maps directly onto how maintainable the whole pipeline is. This piece walks through the differences, the migration, and the traps I actually stepped on.

What responseSchema can't express

responseSchema accepts a subset of the OpenAPI 3.0 Schema object. You get type, properties, items, enum, nullable, propertyOrdering, and the later-added anyOf. But it does not interpret $ref (a reference to another definition) or $defs (a place for reusable definitions), which are at the heart of JSON Schema.

That gap bites in two situations.

First, when you want to reuse the same shape in several places. In my case, a LocalizedText object representing a Japanese/English pair is needed in three spots: the category name, the description, and the tags. With responseSchema you inline it three times, the schema grows vertically, and every time you tweak the required fields you have to fix all three without missing one.

Second, recursion. A structure like a category tree, where a node contains itself as a child, can't be expressed without references. Expanding to a fixed depth is a workaround, but with variable-depth data it breaks somewhere eventually.

AspectresponseSchemaresponseJsonSchema
Schema dialectSubset of OpenAPI 3.0JSON Schema ($ref/$defs)
Reusing shared definitionsNo (inline only)Yes, via $defs + $ref
Recursion / self-referenceNoYes
propertyOrderingSupportedNo such concept
Using bothMutually exclusive (one at a time)

Define the localized object once with $defs

Start with reuse. Using google-genai (the newer Python SDK), define LocalizedText once in $defs and reference it from several places with $ref.

from google import genai
from google.genai import types
 
client = genai.Client(api_key="YOUR_API_KEY")
 
# Define LocalizedText once, reference it from multiple places
schema = {
    "type": "object",
    "$defs": {
        "LocalizedText": {
            "type": "object",
            "properties": {
                "ja": {"type": "string"},
                "en": {"type": "string"},
            },
            "required": ["ja", "en"],
        }
    },
    "properties": {
        "name": {"$ref": "#/$defs/LocalizedText"},
        "description": {"$ref": "#/$defs/LocalizedText"},
        "tags": {
            "type": "array",
            "items": {"$ref": "#/$defs/LocalizedText"},
        },
    },
    "required": ["name", "description", "tags"],
}
 
resp = client.models.generate_content(
    model="gemini-2.5-flash",
    contents="Give a snowy mountain wallpaper a category name, description, and tags in Japanese and English.",
    config=types.GenerateContentConfig(
        response_mime_type="application/json",
        response_json_schema=schema,
    ),
)
print(resp.text)

The formatted output looks like this:

{
  "name": {"ja": "雪山", "en": "Snowy Mountain"},
  "description": {"ja": "雪をかぶった山の風景", "en": "A mountain landscape covered in snow"},
  "tags": [
    {"ja": "自然", "en": "Nature"},
    {"ja": "冬", "en": "Winter"}
  ]
}

There's a single definition of LocalizedText. If I later decide to require zh alongside ja/en, I edit exactly one block inside $defs. Back when I inlined it under responseSchema, I applied the same change by hand in three places. The more definitions you accumulate, the more that difference compounds.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
If you've been inlining the same localized object over and over because responseSchema can't do $ref, you'll learn to define it once in $defs and reuse it everywhere
You'll declare a category tree of arbitrary depth using a self-referencing $ref instead of the fixed-depth workaround, and receive it with working code
You'll be able to decide between responseSchema and responseJsonSchema per task, knowing the feature gaps and the traps in each
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

API / SDK2026-03-20
Build an AI Data Analysis Agent with Gemini API — Combining Code Execution, Function Calling, and Structured Output
Learn how to build a production-ready AI data analysis agent in Python that combines Gemini API's Code Execution, Function Calling, and Structured Output to automatically analyze CSV/Excel data, generate visualizations, and produce structured reports.
API / SDK2026-06-17
Moving My Automation Off the Gemini CLI Before the June 18 Shutdown
On June 18, the Gemini CLI stops responding for hosted plans. Here is how I moved unattended scripts that called gemini from the shell over to the google-genai SDK, with structured output, retries, and cost measurement built in.
API / SDK2026-05-18
Building Automatic Wallpaper Category Classification with Gemini Vision
An indie developer shares how they implemented automatic wallpaper image classification with the Gemini Vision API — including accuracy results, real pitfalls, structured-output tips, and a cost comparison with GPT-4o Vision.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →