Home / LLM JSON Errors / JSONDecodeError: Extra data

Fix JSONDecodeError: Extra data in LLM Streaming

You're streaming from an LLM API and suddenly hit json.decoder.JSONDecodeError: Extra data: line 1 column X (char Y). This guide covers the three root causes and working Python fixes for each.

The error you're seeing
Traceback (most recent call last):
  File "app.py", line 42, in process_stream
    data = json.loads(content)
           ^^^^^^^^^^^^^^^^^^^
json.decoder.JSONDecodeError: Extra data: line 1 column 27 (char 26)

Three root causes

  1. 1. SSE framing — missing double newlines between events (most common)
  2. 2. Concatenating streaming chunks before parsing
  3. 3. Model returned multiple top-level JSON values
1

SSE framing — missing double newlines

Server-Sent Events (SSE) separate each event with a blank line (\n\n). When a proxy or middleware strips these blank lines, consecutive data: lines merge in the buffer. The OpenAI Python SDK tries to json.loads() the combined result and hits "Extra data".

Broken SSE stream (blank lines stripped)
data: {"id":"chatcmpl-1","choices":[{"delta":{"content":"{"}}]}
data: {"id":"chatcmpl-1","choices":[{"delta":{"content":"name"}}]}
data: {"id":"chatcmpl-1","choices":[{"delta":{"content":":"}}]}
# ↑ No blank lines → SDK concatenates → json.loads fails
Correct SSE stream (with blank lines)
data: {"id":"chatcmpl-1","choices":[{"delta":{"content":"{"}}]}
↵ (blank line)
data: {"id":"chatcmpl-1","choices":[{"delta":{"content":"name"}}]}
↵ (blank line)

This happens most often when you're running a custom proxy or middleware. The fix is to ensure each yielded SSE chunk ends with \n\n:

Python fix — SSE framing helper
def sse_frame(chunk: str) -> str:
    """Ensure every SSE chunk ends with the required double newline."""
    if chunk.endswith('\n\n'):
        return chunk
    return chunk.rstrip('\n') + '\n\n'

# In your async generator:
async def stream_proxy(upstream_response):
    async for chunk in upstream_response.aiter_text():
        yield sse_frame(chunk)  # ← not just `yield chunk`
Real-world note: This is the exact bug we found in StreamFix's own streaming pipeline. We were stripping blank lines in our upstream_generator() for logging purposes — causing the exact error above for all users of the OpenAI Python SDK. The sse_frame() helper fixed it.
2

Concatenating streaming chunks before parsing

If you manually iterate a streaming response and join all chunks, you end up with the raw SSE protocol text — not JSON. Calling json.loads() on that gives "Extra data" because of the data: prefixes between events.

❌ Wrong — parsing raw SSE text
import httpx, json

resp = httpx.post(url, headers=headers, json=body)
raw = resp.text  # SSE protocol text, not JSON!

# raw looks like:
# data: {"choices":[{"delta":{"content":"{"}}]}
# 
# data: {"choices":[{"delta":{"content":"name"}}]}
# ...

data = json.loads(raw)  # 💥 JSONDecodeError: Extra data
✅ Correct — parse each event individually
import httpx, json

chunks = []
with httpx.stream("POST", url, headers=headers, json=body) as resp:
    for line in resp.iter_lines():
        if not line.startswith("data: "):
            continue
        payload = line[6:]  # strip "data: " prefix
        if payload == "[DONE]":
            break
        event = json.loads(payload)  # parse each event
        delta = event["choices"][0]["delta"].get("content", "")
        if delta:
            chunks.append(delta)

full_content = "".join(chunks)
data = json.loads(full_content)  # ✅ parse the assembled content

Or simply use the OpenAI Python SDK which handles SSE parsing internally — no manual chunk handling needed.

3

Model returned multiple top-level JSON values

json.loads() only accepts a single JSON value. If the model output contains additional content after the closing brace — a newline, an explanation, or a second object — you get "Extra data".

❌ Model output with trailing content
# Model said:
{"name": "Alice", "age": 30}
Note: I've extracted the name and age from the text.

# json.loads() sees:  {...}\nNote: I've extracted...
json.loads(content)  # 💥 JSONDecodeError: Extra data at 'Note: I'…'
✅ Fix — extract first complete JSON object
import re, json

def extract_json(text: str) -> dict:
    # Strip markdown fences first
    text = re.sub(r'```(?:json)?\s*', '', text).strip()
    
    # Find first { ... } block
    start = text.find('{')
    if start == -1:
        raise ValueError("No JSON found")
    
    depth = 0
    for i, ch in enumerate(text[start:], start):
        if ch == '{': depth += 1
        elif ch == '}': depth -= 1
        if depth == 0:
            return json.loads(text[start:i+1])
    
    raise ValueError("Incomplete JSON")

data = extract_json(content)  # ✅ ignores trailing prose

Skip all of this — StreamFix handles it automatically

StreamFix is a drop-in OpenAI-compatible proxy that fixes all three causes above — SSE framing, concatenation bugs, trailing content, markdown fences — in real-time. One base_url change.

from openai import OpenAI

client = OpenAI(
    api_key="sk_YOUR_STREAMFIX_KEY",
    base_url="https://streamfix.up.railway.app/v1",  # ← that's it
)

# Streaming now works reliably — no JSONDecodeError
stream = client.chat.completions.create(
    model="openai/gpt-4o-mini",
    messages=[{"role": "user", "content": "Return JSON: {name, age}"}],
    stream=True,
)
Get Free API Key →

Related errors