CLAUDE LABEN
FORK — Claude Code 2.1.212で/forkの挙動が変わりました。会話を新しいバックグラウンドセッションへ複製し、作業を続けたまま並走できます。従来のセッション内サブエージェントは/subtaskに移りましたLIMITS — WebSearchの呼び出しがセッション単位で既定200回に制限されました。サブエージェントの起動も既定200回が上限で、暴走した検索・委譲のループを止められますMCPBG — 2分を超えるMCPツール呼び出しは自動的にバックグラウンドへ移り、セッションが固まらなくなりました。しきい値はCLAUDE_CODE_MCP_AUTO_BACKGROUND_MSで調整できますPLANFIX — プランモードがtouchやrmといったファイルを変更するBashコマンドを、許可プロンプトもcanUseToolコールバックも通さずに実行してしまう不具合が修正されましたSONNET5 — Claude Sonnet 5は導入価格として入力100万トークンあたり2ドル、出力10ドルで提供中です。8月31日を過ぎると3ドルと15ドルに戻りますIPO — Anthropicが早ければ10月の株式公開を視野に、引受銀行が投資家との面談を組み始めたと報じられていますFORK — Claude Code 2.1.212で/forkの挙動が変わりました。会話を新しいバックグラウンドセッションへ複製し、作業を続けたまま並走できます。従来のセッション内サブエージェントは/subtaskに移りましたLIMITS — WebSearchの呼び出しがセッション単位で既定200回に制限されました。サブエージェントの起動も既定200回が上限で、暴走した検索・委譲のループを止められますMCPBG — 2分を超えるMCPツール呼び出しは自動的にバックグラウンドへ移り、セッションが固まらなくなりました。しきい値はCLAUDE_CODE_MCP_AUTO_BACKGROUND_MSで調整できますPLANFIX — プランモードがtouchやrmといったファイルを変更するBashコマンドを、許可プロンプトもcanUseToolコールバックも通さずに実行してしまう不具合が修正されましたSONNET5 — Claude Sonnet 5は導入価格として入力100万トークンあたり2ドル、出力10ドルで提供中です。8月31日を過ぎると3ドルと15ドルに戻りますIPO — Anthropicが早ければ10月の株式公開を視野に、引受銀行が投資家との面談を組み始めたと報じられています
記事一覧/API & SDK
API & SDK/2026-04-16中級

Claude APIのJSON出力がパースできない——よくある5つの原因と即効コード

Claude APIからのJSON出力がパースできない問題を解決します。マークダウンラップ・途中切れ・補足コメント混入・Unicode問題・ストリーミング失敗の5パターンと、コピペで使えるFixコードを紹介します。

api38json3troubleshooting61python22structured-output5

本番デプロイ前日の深夜、JSONDecodeError: Expecting value: line 1 column 1 というエラーが出て冷や汗をかいた——そんな経験をした開発者は意外と多いのではないでしょうか。私自身も、Claude API で JSON を返させるコードをちゃんと書いたはずなのに、特定の入力でだけパースが失敗する、という問題に数時間費やした記憶があります。

原因を調べてみると、問題はコードではなくモデルの返し方にあることが多いです。Claude API の JSON 出力でよく起きる失敗パターン5つと、それぞれに対する即効 Fix を順番にご紹介します。

まず症状を「生テキスト」で確認する

パースエラーが出たら、最初にやるべきことは repr() でレスポンスの中身を確認することです。

import anthropic
 
client = anthropic.Anthropic()
 
message = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    messages=[
        {"role": "user", "content": "name と age を含む JSON を返してください"}
    ]
)
 
raw = message.content[0].text
print(repr(raw))  # 改行・制御文字・エスケープをそのまま確認できる

repr() を使うと、肉眼では分からない制御文字や二重エスケープもすべて見えます。以下の5パターンのうちどれに当てはまるかは、ここで一目瞭然になるはずです。

原因1: マークダウンのコードブロックに囲まれる

最も遭遇頻度が高いパターンです。「JSON で返してください」と書くだけでは、Claude がこう返してくることがあります:

```json
{"name": "田中", "age": 30}

人間向けのフォーマットとして丁寧な対応なのですが、機械処理には邪魔です。

**Fix — 正規表現で除去する**

```python
import re
import json

def extract_json_text(text: str) -> str:
    """コードブロックが混入していても JSON 文字列を取り出す"""
    # ```json ... ``` または ``` ... ``` を除去
    code_block = re.search(r"```(?:json)?\s*([\s\S]*?)```", text, re.DOTALL)
    if code_block:
        return code_block.group(1).strip()
    return text.strip()

raw = message.content[0].text
data = json.loads(extract_json_text(raw))

ただし、これはあくまで後処理での対策です。根本的にはプロンプトを改善するほうが確実です。

# プロンプトに明示的な制約を加える
content = (
    "以下のデータを JSON オブジェクトのみで返してください。\n"
    "コードブロック・説明・コメントは一切不要です。\n"
    "出力の最初の文字は '{' でなければなりません。\n\n"
    f"データ: {input_data}"
)

原因2: レスポンスが途中で切れる

max_tokens が小さすぎると、JSON の途中でレスポンスが終了します:

{"items": [{"id": 1, "name": "商品A"}, {"id": 2, "name": "商品

これは明確に不完全な JSON なので json.loads() は必ず失敗します。

Fix — stop_reason を必ず確認する

message = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=2048,  # 出力サイズを見積もって余裕を持たせる
    messages=messages
)
 
# ← これを毎回確認する習慣をつける
if message.stop_reason == "max_tokens":
    raise ValueError(
        f"レスポンスが max_tokens で打ち切られました。"
        f"現在の設定: max_tokens={message.usage.output_tokens}"
    )
 
data = json.loads(message.content[0].text)

配列や深くネストされた構造を生成させる場合は特に要注意です。生成するデータ量を事前に見積もって、出力トークン数に余裕を持たせてください。

原因3: モデルが補足コメントを混入させる

プロンプトの書き方によっては、Claude が JSON に前置きや後置きのテキストを添えることがあります:

以下が JSON です:

{"name": "田中", "age": 30}

name と age を含むオブジェクトを生成しました。

前後のテキストが邪魔でパースできません。

Fix — prefill(アシスタントの先頭を固定)

私が一番信頼している方法です。アシスタントの返答の先頭を { で固定すると、物理的に前置きテキストが挿入できなくなります。

messages = [
    {
        "role": "user",
        "content": "以下の情報を JSON オブジェクトに変換してください: 名前=田中, 年齢=30"
    },
    {
        "role": "assistant",
        "content": "{"  # ← JSON の開始文字を先頭に固定
    }
]
 
message = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=2048,
    messages=messages
)
 
# prefill した "{" を先頭に戻してからパース
raw = "{" + message.content[0].text
data = json.loads(raw)

この prefill テクニックは、後処理に頼らず確実に純粋な JSON を取得したい場合に特に有効です。配列を返させたい場合は [ を prefill してください。

原因4: Unicode エスケープの二重処理

日本語を含むデータで \u306f\u3058\u3081\u307e\u3057\u3066 のような Unicode エスケープが混入しているように見えることがあります。

実は json.loads() は Unicode エスケープを正しく処理できるので、これ自体は問題ではありません:

# これは問題なく動作する
raw = r'{"message": "\u306f\u3058\u3081\u307e\u3057\u3066"}'
data = json.loads(raw)
print(data["message"])  # → はじめまして(正しく展開される)

問題が起きるのは、エスケープが二重になっているときです:

# NG: \\u のように二重エスケープされている場合
raw = '{"message": "\\\\u306f\\\\u3058\\\\u3081\\\\u307e"}'
# json.loads は "\u306f" という文字列リテラルとして解釈してしまう
 
# repr() で確認するとすぐ分かる
print(repr(raw))  # → '{"message": "\\\\u306f\\\\u3058..."}'

repr()\\u が含まれていたら二重エスケープが原因です。文字列の構築方法(f-string の使い方、API からの取得方法)を見直してください。

原因5: ストリーミング中に逐次パースしようとする

ストリーミングで受け取ったチャンクを1つずつ json.loads() しようとすると、当然ながら途中の断片は不完全な JSON です:

# NG: ストリーミング中に毎チャンクパースするとエラー
with client.messages.stream(...) as stream:
    for text_chunk in stream.text_stream:
        data = json.loads(text_chunk)  # ← 毎回 JSONDecodeError

Fix — 完了後にまとめてパースする

# OK: 全テキストを結合してからパース
with client.messages.stream(
    model="claude-sonnet-4-6",
    max_tokens=2048,
    messages=messages
) as stream:
    full_text = stream.get_final_text()  # ← 完了まで待ってから取得
 
data = json.loads(extract_json_text(full_text))

UI のリアルタイム更新とバックエンドの JSON 処理を両立したい場合は、表示用にはストリーミングを使いつつ、処理用には完了後のテキストを使う設計にするのがおすすめです。

コピペで使えるユーティリティ関数

これまでの対策をすべて組み込んだ汎用関数です。新しいプロジェクトではここから始めてみてください:

import re
import json
import anthropic
 
def extract_json_text(text: str) -> str:
    """コードブロック・前後のテキストを除去して JSON 文字列を返す"""
    # コードブロックがある場合はその中身を取得
    code_block = re.search(r"```(?:json)?\s*([\s\S]*?)```", text, re.DOTALL)
    if code_block:
        return code_block.group(1).strip()
    # JSON 部分({ })を抽出
    json_obj = re.search(r"(\{[\s\S]*\})", text)
    if json_obj:
        return json_obj.group(1).strip()
    # JSON 配列([ ])を抽出
    json_arr = re.search(r"(\[[\s\S]*\])", text)
    if json_arr:
        return json_arr.group(1).strip()
    return text.strip()
 
def call_with_json(
    prompt: str,
    model: str = "claude-sonnet-4-6",
    max_tokens: int = 4096,
    as_array: bool = False
) -> dict | list:
    """JSON レスポンスを確実に取得するラッパー関数"""
    client = anthropic.Anthropic()
    
    prefix = "[" if as_array else "{"
    
    messages = [
        {
            "role": "user",
            "content": (
                f"以下の指示に対して {prefix} で始まる JSON のみで回答してください。\n"
                "コードブロック・説明・コメントは一切不要です。\n\n"
                f"{prompt}"
            )
        },
        {"role": "assistant", "content": prefix}  # prefill
    ]
    
    message = client.messages.create(
        model=model,
        max_tokens=max_tokens,
        messages=messages
    )
    
    # 途中切れチェック
    if message.stop_reason == "max_tokens":
        raise ValueError(
            f"レスポンスが途中で切れました(max_tokens={max_tokens})。"
            "max_tokens を増やして再試行してください。"
        )
    
    raw = prefix + message.content[0].text
    return json.loads(raw)
 
# 使用例
result = call_with_json(
    "名前: 佐藤, 年齢: 28, 職業: デザイナー をオブジェクトに変換して"
)
print(result)
# → {"name": "佐藤", "age": 28, "occupation": "デザイナー"}
 
items = call_with_json(
    "Python, JavaScript, Go の各言語の特徴を配列で返して。各要素は {lang, strength} を持つこと",
    as_array=True
)
print(items[0])
# → {"lang": "Python", "strength": "データ処理・機械学習"}

パースエラーに悩んでいる方は、まず repr() でレスポンスを確認して原因を特定する、というシンプルな一歩から始めてみてください。原因さえ分かれば、対策は上記のどれかで必ず解決できます。

Claude API の構造化出力についてより体系的に学びたい方には、Claude API 構造化出力 完全マスターガイドが参考になります。API エラーハンドリング全般については Claude API エラー完全ガイド もあわせてご覧ください。

シェア

お読みいただきありがとうございます

Claude Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

API & SDK2026-05-16
Claude API の Tool Use でスキーマ定義ミスが出たとき:実際に遭遇した3つのパターンと診断手順
Claude API の Tool Use でよく起きるスキーマ定義ミス・invalid_tool_use・Claude がツールを呼ばない問題を、実際のアプリ開発で遭遇したケースをもとに診断手順と修正パターンで解説します。
API & SDK2026-04-26
Claude APIストリーミングが途中で切れる — 症状別の診断と修正パターン
Claude APIのストリーミングレスポンスが途中で止まる原因をタイプ別に診断し、タイムアウト・レート制限・max_tokens・クライアント実装バグそれぞれの修正コードを解説します。
API & SDK2026-06-27
Claude API のストリーミングが「エラーも出さずに」止まるとき — 沈黙する停止を検知して途中から続ける運用メモ
Claude API のストリーミングが例外も出さずに無言で止まる「沈黙停止」を、トークン間隔のウォッチドッグで検知し、受信済みテキストを引き継いで途中から再開する実装と、長時間の自動運用で崩れないためのタイムアウト予算の設計をまとめます。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →