チュートリアル
Gemini API で JSON を出力するには?Structured Output と JSON Schema の実践ガイド
モデルに「JSON だけ返して」と頼むのはやめましょう。Structured Output で形を固定し、JSON Schema でフィールドを縛れば、下流のコードが安定して解析できます。
Gemini に JSON を頼むと、前置き文、欠けたカンマ、「わかりやすい」フィールド名が混ざります。プロンプトの「JSON のみ」は確率を下げるだけで契約ではありません。Structured Output は契約をデコード側に移します。MIME を宣言し Schema を付ければ、その形で token が出ます。読み終われば JSON モードと Schema モードを選べ、動くリクエストを書け、それでもローカル検証が必要なことがわかります。
なぜ構造化出力が必要か
下流が JSON.parse した瞬間、失敗は文言の問題ではなくパイプライン停止です。分類は固定 enum、抽出は安定キー、ツール呼び出しはパラメータオブジェクトが要ります。自由記述のコストは高く、壊れた JSON 一回で再試行・ログ・ユーザーの再クリックになります。
より静かな失敗は「parse できるが形が違う」ことです。items が配列であるべきなのにオブジェクトが来たり、score が数値であるべきなのに "0.9" が来たりします。コードは undefined を読み、バグはずっと後で爆発します。Structured Output が直すのは形であり正誤ではありません。合法で Schema に沿った JSON は取れても、分類が正しい保証はありません。本番では業務ルール検証が残ります。静かになるのはパース層です。
公式の能力説明は Gemini Structured output ドキュメント を参照してください。標準 JSON Schema のキーワードとキー順の強化は Structured Outputs の更新 にあります。書く前に Understanding JSON Schema を見て、「仕様上書ける語」と「今のモデルが実際に縛る語」を混同しないでください。
JSON モードと Schema モード
スイッチは二段です。一段目は「JSON として parse できる」ことだけ。二段目は「宣言した Schema に合う JSON」です。名前付きフィールドを読むなら二段目。探索的抽出でキー自体もモデルに任せるときだけ一段目を検討します。
| 段階 | 設定するもの | 実際の保証 |
|---|---|---|
| JSON モード | responseMimeType: application/json |
多くの場合は合法 JSON。名前と入れ子はまだモデル任せ |
| Schema モード | MIME タイプ + responseSchemaまたはresponseJsonSchema |
形・型・必須・enum を Schema が拘束 |
JSON モードだけのとき、公式も「強いヒント」であり不正出力の小さなリスクを残すと注意します。オブジェクトとして必ず parse したいなら Schema も送ります。Schema は入力 token に入るので、同じ説明を prompt に再掲しないでください。重複は品質とクォータを削ります。
responseSchema か responseJsonSchema か
responseSchema は OpenAPI 3.0 風のサブセットです。REST の型名は OBJECT、STRING のように大文字が多いです。フラットなオブジェクト、enum 分類、propertyOrdering でキー順を固定する用途に向きます。$ref / $defs は解釈しないため、再帰ツリーや定義の再利用はインライン化しかなく、すぐ膨らみます。
responseJsonSchema は Gemini 2.5 以降向けで、より標準に近い JSON Schema を扱い、anyOf、$ref、minimum / maximum、additionalProperties、type: null、prefixItems などをカバーします。Pydantic や Zod から生成して渡すと摩擦が減ります。新しいモデルは宣言順のキーを保つので、ログ diff とゴールデンテストに向きます。
選び方は三つ。フラットな分類・抽出ならどちらでもよい。再帰・共有定義・ユニオンなら responseJsonSchema を優先。キー順を固定するなら、今のエンドポイントが propertyOrdering をまだ尊重するか確認し、全 API が同じだと思い込まないこと。
Schema の書き方
本当に読むオブジェクトだけ書いてください。世界の完全モデルは不要です。任意フィールドを増やすほど、誤記入の隙が増えます。必須は required、enum は enum、数値範囲は minimum / maximum。プロパティの description は prompt で再説明より安定しがちです。拘束がデコードと一緒に動くからです。
次の Schema はチケット分類です。カテゴリは三値、優先度は整数、要約は文字列。Schema モードが持つべきなのは形であり、「業務上ほんとうに緊急か」ではありません。
{
"type": "object",
"properties": {
"category": {
"type": "string",
"enum": ["billing", "bug", "feature"],
"description": "Ticket category"
},
"priority": {
"type": "integer",
"minimum": 1,
"maximum": 5
},
"summary": {
"type": "string"
}
},
"required": ["category", "priority", "summary"],
"additionalProperties": false
}
配列の要素は items で書きます。タプルに近い固定長なら JSON Schema 経路の prefixItems を見てください。required に入れないことだけでは nullable になりません。null を明示しないと、モデルがキーを省略し、コードは obj.field が必ず在ると仮定したままになります。
入れ子はインラインで十分です。同じ構造が三度目、または木が自分を参照するときだけ $defs + $ref を使います。早すぎる抽象化は拒否理由を読みにくくします。サーバが Schema を拒むと、展開後の巨大オブジェクトと向き合うことになります。
Python と JavaScript の実装
公式 SDK のよくある書き方です。モデル名はアカウントで使える 2.5 / 新しめの ID に置き換え、サンプル名を本番固定だと思わないでください。
Python:MIME タイプ + JSON Schema
from google import genai
client = genai.Client()
schema = {
"type": "object",
"properties": {
"category": {"type": "string", "enum": ["billing", "bug", "feature"]},
"priority": {"type": "integer"},
"summary": {"type": "string"},
},
"required": ["category", "priority", "summary"],
}
response = client.models.generate_content(
model="gemini-2.5-flash",
contents="Classify this ticket: invoice PDF cannot be downloaded.",
config={
"response_mime_type": "application/json",
"response_json_schema": schema,
},
)
print(response.text)
すでに Pydantic なら Model.model_json_schema() を response_json_schema に渡し、model_validate_json(response.text) でローカル再検証します。第一層は API の形、第二層は「合法に見えて意味がおかしい値」(優先度 99 など)を型で拒否することです。
JavaScript:generationConfig
const response = await ai.models.generateContent({
model: "gemini-2.5-flash",
contents: "Classify this ticket: invoice PDF cannot be downloaded.",
config: {
responseMimeType: "application/json",
responseJsonSchema: {
type: "object",
properties: {
category: { type: "string", enum: ["billing", "bug", "feature"] },
priority: { type: "integer" },
summary: { type: "string" },
},
required: ["category", "priority", "summary"],
},
},
});
const data = JSON.parse(response.text);
REST なら同じフィールドを generationConfig に置きます。OpenAPI 風の responseSchema は一部エンドポイントで型名が大文字のままです。JSON Schema の小文字 object と混ぜないでください。応答はすぐ JSON.parse。正規表現でコードブロックを削らないこと。JSON MIME を宣言したなら、全体を JSON として扱います。
よくある落とし穴
- 構造を prompt と Schema の両方に書くと、モデルが二つの説明の間で揺れ、品質が落ちます。
- 巨大 Schema:深い入れ子、深すぎる
$ref、広すぎるanyOfは拒否されたり拘束が弱まったりします。小さなオブジェクトで通してから足してください。 - 真実性を Schema に丸投げしないでください。enum は集合を限るだけで、誤選択は止められません。重要な経路は抜き取りやルールを。
- ローカル型と Schema のドリフト。Pydantic / Zod を変えてリクエスト側 Schema が古いと、本番でキーが増減しても静かです。
- ハッピーパスだけでは足りません。空配列、nullable、長い文字列、不正 enum(ブロックされるべき)を足してください。
もう一つの運用問題:ログに response.text だけ残さないこと。モデル、Schema ハッシュ、prompt 版を一緒に。壊れの多くは「既定が変わった」か「Schema の微修正が拒否された」です。この三点がないと「昨日まで動いた」になります。
本番:検証・比較・やり直し
API の本文は信用できないバイト列です。parse し、同じ Schema で再検証し、内部型へ写します。失敗時は原文を記録(秘匿はマスク)し、再試行か縮退かを決めます。まったく別の Schema で再試行すると、モデル不安定なのか契約変更なのか切り分けできません。
デバッグではサンプルを本サイトのツールに貼るのが早いです。JSON フォーマット で入れ子を見、JSON 検証 で構文を取り、契約を JSON Schema に入れてインスタンスが通るか見ます。フィールドが増減したら JSON Diff で二回の応答を比べてください。目視のログより速いです。
抽出パイプラインを設計するなら、まず「理想出力」を手で書き、Schema ツールで型を逆算し、その Schema を Gemini リクエストに戻します。契約の源は一箇所——チャットの口約束ではなく、テストできる文書——になります。
よくある質問 FAQ
application/json だけで足りますか?
探索段階なら可。固定フィールドを読むなら Schema も出してください。でなければ JSON に似た散文であり、API ではありません。
Structured Output は関数呼び出しの代わりになりますか?
なりません。関数呼び出しはツール選択と引数埋め。Structured Output は今回の回答の形です。コード実行や外部 API ならツール。型付きデータ一塊なら Structured Output で往復を減らせます。
なぜ Schema が拒否されますか?
そのエンドポイントが未対応のキーワード、再帰が深すぎる、responseSchema と responseJsonSchema の方言混在が典型です。オブジェクト一つ・フィールド三つまで減らして通してから足してください。
出力は必ず正しいですか?
いいえ。形が合法でも事実は誤り得ます。金額、メール、チケット分類はルールか抜き取りが要ります。
まとめと次の一歩
安定した JSON は、より長い「JSON だけ出して」ではなく MIME と Schema です。フラットな仕事なら responseSchemaまたはresponseJsonSchema のどちらでもよく、$ref、ユニオン、Pydantic/Zod 生成なら後者です。応答後もローカル検証し、失敗例を整形・Schema・Diff で回帰テストにしてください。
次は、いちばん壊れやすいエンドポイントを「一つの Schema、一回のリクエスト、一回のローカル validate」にしてください。そこを静かにしてから横展開します。