JSON to Python: Parse, Convert, and Fix JSON in Python (2026 Complete Guide)
Parse JSON strings into Python dicts, convert Python objects back to JSON, handle every common error, and repair broken JSON from APIs and LLMs — complete Python JSON reference for 2026.
Have broken JSON right now? Fix it free in under 1 second — no signup.
Fix My JSON →Python's json module ships in the standard library and handles the common case cleanly. But real-world JSON — especially from LLMs, third-party APIs, and scraped sources — doesn't always parse on the first try. This guide covers the full Python JSON workflow: parsing, converting, validating with Pydantic, and repairing the broken inputs that json.loads() refuses to handle.
The Basics: json.loads() vs json.load()
Two functions do the heavy lifting:
| Function | Input | Use for |
|---|---|---|
json.loads(s) | String, bytes, bytearray | Parsing JSON from memory (API responses, variables) |
json.load(fp) | File-like object | Parsing JSON from files |
import json
# From a string
raw = '{"name": "Alice", "age": 30, "active": true}'
data = json.loads(raw)
print(data["name"]) # Alice
print(type(data)) # <class 'dict'>
# From a file
with open("config.json") as f:
config = json.load(f)
The return types follow a consistent mapping:
| JSON | Python |
|---|---|
object {} | dict |
array [] | list |
string "" | str |
number (integer) | int |
number (float) | float |
true / false | True / False |
null | None |
Converting Python Objects to JSON
Use json.dumps() for in-memory strings and json.dump() for writing to files:
import json
user = {
"name": "Alice",
"scores": [95, 87, 92],
"active": True,
"notes": None
}
# Pretty-print with indentation
print(json.dumps(user, indent=2))
# Write to file
with open("user.json", "w") as f:
json.dump(user, f, indent=2)
# Compact (no spaces — for APIs)
compact = json.dumps(user, separators=(",", ":"))
Useful dumps() parameters:
# Sort keys alphabetically
json.dumps(data, sort_keys=True)
# Handle non-ASCII characters without escaping
json.dumps({"city": "São Paulo"}, ensure_ascii=False)
# Custom indentation character
json.dumps(data, indent="\t")
Handling Common JSON Parse Errors
JSONDecodeError — the main exception
import json
try:
data = json.loads('{"name": "Alice", "age": }')
except json.JSONDecodeError as e:
print(f"Error: {e.msg}")
print(f"At position: {e.pos}, line: {e.lineno}, col: {e.colno}")
print(f"Document: {e.doc}")
json.JSONDecodeError inherits from ValueError, so except ValueError also catches it — but you lose the position information.
The most common error types
Trailing comma:# This fails
json.loads('{"a": 1, "b": 2,}')
# JSONDecodeError: Expecting property name enclosed in double quotes
# Fix: use jsonrepair or strip trailing commas
Single quotes instead of double quotes:
# This fails — JSON requires double quotes
json.loads("{'name': 'Alice'}")
# Comes up constantly with LLM outputs and Python repr()
Python booleans and None:
# Python repr() — not valid JSON
json.loads("{'active': True, 'data': None}")
# Fix: replace Python literals with JSON literals
import re
fixed = text.replace("True", "true").replace("False", "false").replace("None", "null")
# (Only safe for simple cases — use jsonrepair for production)
Unquoted keys:
# JavaScript-style object literal — not valid JSON
json.loads("{name: 'Alice'}")
For any of these broken inputs, parse them through AI JSONMedic's repair tool before passing to json.loads(), or use the jsonrepair library programmatically (see below).
Parsing Nested JSON
Python's json.loads() handles nested structures natively — no recursion needed:
import json
response = '''
{
"user": {
"id": 42,
"profile": {
"name": "Alice",
"tags": ["admin", "beta"]
}
},
"meta": {
"total": 1,
"page": 1
}
}
'''
data = json.loads(response)
# Access nested values
name = data["user"]["profile"]["name"]
first_tag = data["user"]["profile"]["tags"][0]
# Safe access with .get() (returns None instead of KeyError)
age = data["user"].get("age") # None
age = data["user"].get("age", 0) # 0 (default)
Iterate over a JSON array:
items_json = '[{"id": 1, "val": "a"}, {"id": 2, "val": "b"}]'
items = json.loads(items_json)
for item in items:
print(item["id"], item["val"])
Custom Object Parsing with object_hook
object_hook is called on every JSON object (dict) during parsing. Use it to transform dicts into custom Python classes:
import json
from datetime import datetime
class User:
def __init__(self, id, name, created_at):
self.id = id
self.name = name
self.created_at = created_at
def decode_user(d):
if "created_at" in d:
d["created_at"] = datetime.fromisoformat(d["created_at"])
return User(**d)
raw = '{"id": 1, "name": "Alice", "created_at": "2026-08-04T09:00:00"}'
user = json.loads(raw, object_hook=decode_user)
print(user.name) # Alice
print(type(user)) # <class 'User'>
print(user.created_at) # 2026-08-04 09:00:00
For more complex transformations, consider using a proper data class or Pydantic model instead.
Validating JSON Structure with Pydantic (2026 Best Practice)
json.loads() gives you a plain dict with no type guarantees. Pydantic models add runtime validation:
from pydantic import BaseModel, ValidationError, field_validator
from typing import Optional
import json
class UserProfile(BaseModel):
id: int
name: str
email: str
age: Optional[int] = None
@field_validator("email")
@classmethod
def validate_email(cls, v):
if "@" not in v:
raise ValueError("invalid email")
return v
raw = '{"id": 1, "name": "Alice", "email": "[email protected]"}'
try:
user = UserProfile.model_validate_json(raw)
print(user.name) # Alice
print(user.age) # None
except ValidationError as e:
print(e.json(indent=2))
model_validate_json() (Pydantic v2) handles both parsing and validation in one call. Use it over json.loads() + manual validation for any data that enters your application from external sources.
Fixing Broken JSON from APIs and LLMs
LLMs are the most common source of broken JSON in Python applications today. Common failure modes:
- Output wrapped in markdown code fences (
`json ...`) - Trailing commas after the last key
- Single-quoted strings
- Python
True/False/Noneliterals instead of JSONtrue/false/null - Truncated output (incomplete arrays or objects from context window limits)
Quick repair for simple cases
import re
import json
def quick_fix(text: str) -> dict:
# Strip markdown code fences
text = re.sub(r'^(?:json)?\s*', '', text.strip(), flags=re.MULTILINE)
text = re.sub(r'\s*```$', '', text.strip(), flags=re.MULTILINE)
# Replace Python literals
text = text.replace("True", "true").replace("False", "false").replace("None", "null")
return json.loads(text)
### Robust repair with jsonrepair
The `jsonrepair` npm package has a Python port that handles the full range of broken JSON:bash
pip install json-repair
python
from json_repair import repair_json
import json
broken = """
{
'name': 'Alice',
active: True,
scores: [95, 87,]
}
"""
repaired_str = repair_json(broken)
data = json.loads(repaired_str)
print(data["name"]) # Alice
print(data["active"]) # True
For one-off debugging, paste broken JSON into [AI JSONMedic](/) — it runs 14 repair layers client-side and shows you exactly what was fixed. For production pipelines, use `json_repair` programmatically.
Also see our guide on [fixing Python JSON boolean errors](/blog/llm-python-boolean-json-errors) for the full pattern of `True`/`False`/`None` handling.
## Working with Large JSON Files
For large files that don't fit comfortably in memory, avoid loading the entire structure at once:python
import json
Stream NDJSON (newline-delimited JSON) — one object per line
with open("large.ndjson") as f:
for line in f:
line = line.strip()
if line:
record = json.loads(line)
process(record) # process one record at a time
For true streaming of large JSON arrays (not NDJSON), use `ijson`:bash
pip install ijson
python
import ijson
with open("large.json", "rb") as f:
# Stream items from a JSON array at the top level
for item in ijson.items(f, "item"):
process(item)
## JSON Encoding for Custom Python Types
By default, `json.dumps()` raises `TypeError` for types it doesn't know how to handle (datetime, Decimal, custom objects, UUID, etc.). Fix this with a custom encoder:python
import json
import decimal
from datetime import datetime, date
from uuid import UUID
class ExtendedEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, (datetime, date)):
return obj.isoformat()
if isinstance(obj, decimal.Decimal):
return str(obj)
if isinstance(obj, UUID):
return str(obj)
if hasattr(obj, "__dict__"):
return obj.__dict__
return super().default(obj)
data = {
"created": datetime.now(),
"price": decimal.Decimal("19.99"),
"id": UUID("550e8400-e29b-41d4-a716-446655440000")
}
print(json.dumps(data, cls=ExtendedEncoder, indent=2))
## Python JSON Performance Reference
For applications that parse many JSON payloads per second, the standard library `json` module may become a bottleneck. Alternatives:
| Library | Speed vs stdlib | Notes |
|---|---|---|
| `json` (stdlib) | baseline | Always available, no install |
| `orjson` | 5–10x faster | Binary output, supports datetime/UUID natively |
| `ujson` | 3–6x faster | Drop-in replacement, simpler API than orjson |
| `msgspec` | 10–20x faster | Fastest overall, requires model definitions |bash
pip install orjson
python
import orjson
Returns bytes
raw_bytes = orjson.dumps({"name": "Alice"})
Parse bytes or string
data = orjson.loads(raw_bytes)
With options
raw = orjson.dumps(data, option=orjson.OPT_INDENT_2 | orjson.OPT_SORT_KEYS)
```
orjson is the most popular choice for high-throughput API services. It handles datetime, UUID, numpy arrays, and dataclasses natively without a custom encoder.
Frequently Asked Questions
Q: What's the difference between json.loads() and json.load()?A: json.loads() parses a JSON string (or bytes) that's already in memory — the s stands for "string". json.load() reads from a file-like object (an open file, a network socket, any object with a read() method). Use loads for API responses stored in variables; use load when reading from disk.
A: Python's json module does not accept single quotes — JSON requires double quotes per RFC 8259. For data with single quotes, use ast.literal_eval() if the data is a Python literal, or run it through a repair library like json_repair which converts single quotes to double quotes automatically. See our post on fixing JSON single quotes for full patterns.
A: The most common culprit is invisible characters: a BOM (byte order mark) at the start of the string, non-breaking spaces, or Unicode control characters embedded in values. Try json.loads(response.encode("utf-8").decode("utf-8-sig")) to strip the BOM, or inspect the raw bytes with print(repr(response[:50])) to see what's actually in the string.
A: json.loads() handles top-level arrays directly — it returns a Python list. There's no difference in usage: data = json.loads('[1, 2, 3]') returns [1, 2, 3].
A: Use Pydantic's model_validate_json() — it parses and validates in one call, gives you typed attributes, and produces clear error messages. For schema-based validation without models, use jsonschema.validate() with a JSON Schema document. For quick structural validation without a schema, json.loads() alone confirms the JSON is parseable; Pydantic or jsonschema is needed for field-level validation.
Still dealing with broken JSON?
Paste it in and get it fixed in under 1 second — free, no signup, no install. Works with ChatGPT, Claude, n8n, and any AI output.
Fix My JSON Free →Related Articles