Error database

json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

The string you parsed is not JSON at all — often an LLM reply wrapped in markdown fences, or an error page where JSON was expected. Log the raw string, then use structured output instead of hoping.

The message you saw
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

By Updated

The error

Output
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

What it means

json.loads looked at position 0 of the string and did not find the beginning of any JSON value. Line 1, column 1 means the very first character is already wrong — the string starts with something other than {, [, a quote, a number or a literal. In practice that means the string is markdown, prose, HTML, or empty.

Why it happens

For AI developers there are two big sources.

LLM output. You asked the model to "respond with JSON", and it responded with almost-JSON: wrapped in ``json ` fences, prefixed with "Here is the JSON you requested:", or truncated mid-object by a token limit. Models follow instructions probabilistically; parsers do not.

HTTP responses. You called .json() on a response that was actually an HTML error page, an empty body, or a plain-text proxy message. The status code was never checked, so the failure surfaced at the parser instead of the request.

How to fix it

1. Look at the actual string before anything else.

python
print(repr(raw[:200]))
Output
'```json\n{"city": "Pune", "score": 8}\n```'

repr exposes fences, whitespace and emptiness instantly. Diagnosis done.

2. For LLM output, stop parsing prose — use the structured output features. OpenAI's JSON schema mode and Anthropic's tool calling make the model return machine-validated JSON:

python
resp = client.messages.create(
    model=MODEL, max_tokens=1024,
    tools=[{"name": "record_score", "input_schema": schema}],
    tool_choice={"type": "tool", "name": "record_score"},
    messages=messages,
)
data = next(b.input for b in resp.content if b.type == "tool_use")

The provider enforces the shape; the fence problem disappears by construction.

3. If you must parse free text, strip fences defensively and validate.

python
import json, re

def parse_llm_json(raw: str) -> dict:
    m = re.search(r"\{.*\}", raw, re.DOTALL)
    if not m:
        raise ValueError(f"no JSON object found in: {raw[:200]!r}")
    return json.loads(m.group())

Pair it with a Pydantic model so wrong-but-parseable output also fails loudly, and retry the LLM call once on failure.

4. Check for truncation. JSON cut off mid-object means the reply hit the output-token cap — raise max_tokens and check the stop reason rather than patching the string.

5. For HTTP responses, gate on status and content type first.

python
r = requests.get(url, timeout=30)
r.raise_for_status()
data = r.json()

raise_for_status() converts the hidden 502-with-HTML-body into a visible HTTP error at the right layer.

How to prevent it

Never feed a parser something you have not logged. For every LLM-to-JSON boundary in a real system, choose schema-enforced output over prompt-and-pray; keep the regex fallback only for models and modes without structured support.