Learn/Language Integrations

JSON in Python — requests, pandas, FastAPI & Data Pipelines

Python is the second most popular language for working with JSON — from API calls with requests to data analysis with pandas to building APIs with FastAPI. This guide covers every pattern you need, from basic file I/O to production-grade validation with Pydantic.

Python's json Module — The Basics

Python's built-in json module handles serialization (Python → JSON) and deserialization (JSON → Python):

Python ↔ JSON Type Mapping

loads() and dumps() — String Operations

Parse and serialize JSON stringspython
1import json
2
3# Parse a JSON string → Python dict
4json_string = '{"name": "Alice", "age": 30, "active": true}'
5data = json.loads(json_string)
6print(data["name"]) # "Alice"
7print(type(data)) # <class 'dict'>
8
9# Serialize Python dict → JSON string
10user = {"name": "Bob", "scores": [95, 87, 92], "verified": False}
11json_output = json.dumps(user, indent=2)
12print(json_output)
13# {
14# "name": "Bob",
15# "scores": [95, 87, 92],
16# "verified": false ← Python False becomes JSON false
17# }

load() and dump() — File Operations

Read and write JSON filespython
1import json
2from pathlib import Path
3
4# Read JSON from file
5with open("config.json", "r", encoding="utf-8") as f:
6 config = json.load(f)
7
8# Write JSON to file
9data = {"users": [{"id": 1, "name": "Alice"}]}
10with open("output.json", "w", encoding="utf-8") as f:
11 json.dump(data, f, indent=2, ensure_ascii=False)
12
13# Using pathlib (Python 3.5+)
14config = json.loads(Path("config.json").read_text(encoding="utf-8"))

Formatting Options

ParameterPurposeExample
indent=2Pretty-print with indentationjson.dumps(data, indent=2)
sort_keys=TrueAlphabetical key orderjson.dumps(data, sort_keys=True)
ensure_ascii=FalsePreserve Unicode (é, ñ, 中)json.dumps(data, ensure_ascii=False)
separators=(",", ":")Compact/minified outputjson.dumps(data, separators=(",", ":"))
default=strSerialize non-standard typesjson.dumps(data, default=str)

Handling Custom Types

Python's json module only handles basic types. For datetime, Decimal, UUID, and other types, use a custom serializer:

Custom JSON encoderpython
1import json
2from datetime import datetime, date
3from decimal import Decimal
4from uuid import UUID
5
6class CustomEncoder(json.JSONEncoder):
7 def default(self, obj):
8 if isinstance(obj, (datetime, date)):
9 return obj.isoformat()
10 if isinstance(obj, Decimal):
11 return float(obj)
12 if isinstance(obj, UUID):
13 return str(obj)
14 if isinstance(obj, set):
15 return list(obj)
16 return super().default(obj)
17
18order = {
19 "id": UUID("12345678-1234-5678-1234-567812345678"),
20 "total": Decimal("49.99"),
21 "placed_at": datetime(2026, 4, 2, 14, 30),
22 "tags": {"express", "priority"},
23}
24
25print(json.dumps(order, cls=CustomEncoder, indent=2))
26# {
27# "id": "12345678-1234-5678-1234-567812345678",
28# "total": 49.99,
29# "placed_at": "2026-04-02T14:30:00",
30# "tags": ["express", "priority"]
31# }

HTTP APIs with requests

Making API callspython
1import requests
2
3# GET — parse JSON response
4response = requests.get("https://api.github.com/users/octocat")
5response.raise_for_status()
6user = response.json() # Automatically parses JSON
7print(user["login"]) # "octocat"
8
9# POST — send JSON body
10new_post = {"title": "Hello", "body": "World", "userId": 1}
11response = requests.post(
12 "https://jsonplaceholder.typicode.com/posts",
13 json=new_post, # Sets Content-Type: application/json automatically
14)
15created = response.json()
16print(created["id"]) # 101

Tip

Always use the json= parameter in requests, not data=json.dumps(). The json= parameter automatically sets the Content-Type header and handles serialization.
Error handling for API responsespython
1import requests
2
3def fetch_user(user_id: int) -> dict:
4 try:
5 response = requests.get(
6 f"https://api.example.com/users/{user_id}",
7 timeout=10,
8 )
9 response.raise_for_status()
10 return response.json()
11 except requests.exceptions.HTTPError as e:
12 print(f"HTTP error: {e.response.status_code}")
13 raise
14 except requests.exceptions.JSONDecodeError:
15 print("Response was not valid JSON")
16 raise
17 except requests.exceptions.ConnectionError:
18 print("Could not connect to API")
19 raise

pandas & JSON

Reading JSON into DataFrames

Load JSON data into pandaspython
1import pandas as pd
2
3# From a JSON file
4df = pd.read_json("users.json")
5
6# From a JSON string
7json_str = '[{"name": "Alice", "score": 95}, {"name": "Bob", "score": 87}]'
8df = pd.read_json(json_str)
9
10# From an API response
11df = pd.read_json("https://api.example.com/data")
12
13# From nested JSON — normalize nested objects
14data = [
15 {"name": "Alice", "address": {"city": "NYC", "zip": "10001"}},
16 {"name": "Bob", "address": {"city": "LA", "zip": "90001"}},
17]
18df = pd.json_normalize(data)
19print(df.columns)
20# Index(['name', 'address.city', 'address.zip'], dtype='object')

Exporting DataFrames to JSON

DataFrame to JSONpython
1import pandas as pd
2
3df = pd.DataFrame({
4 "name": ["Alice", "Bob"],
5 "score": [95, 87],
6 "passed": [True, True],
7})
8
9# List of objects (most common for APIs)
10print(df.to_json(orient="records", indent=2))
11# [
12# {"name": "Alice", "score": 95, "passed": true},
13# {"name": "Bob", "score": 87, "passed": true}
14# ]
15
16# To a file
17df.to_json("output.json", orient="records", indent=2)
orientOutput ShapeBest For
"records"[{...}, {...}]API responses, general use
"split"{"columns": [...], "data": [...]}Preserving column order + index
"index"{"0": {...}, "1": {...}}Row-keyed lookups
"columns"{"col1": {...}, "col2": {...}}Column-oriented analysis
"values"[[...], [...]]Compact arrays, charts

FastAPI & Pydantic

FastAPI uses Pydantic models for automatic JSON validation, serialization, and documentation:

FastAPI with Pydantic modelspython
1from fastapi import FastAPI, HTTPException
2from pydantic import BaseModel, EmailStr, Field
3from datetime import datetime
4
5app = FastAPI()
6
7class UserCreate(BaseModel):
8 name: str = Field(min_length=1, max_length=100)
9 email: EmailStr
10 age: int = Field(ge=0, le=150)
11 role: str = Field(default="viewer", pattern="^(admin|editor|viewer)$")
12
13class UserResponse(BaseModel):
14 id: int
15 name: str
16 email: str
17 role: str
18 created_at: datetime
19
20 class Config:
21 from_attributes = True
22
23@app.post("/users", response_model=UserResponse, status_code=201)
24async def create_user(user: UserCreate):
25 # Pydantic already validated the JSON body
26 db_user = await db.create(user.model_dump())
27 return UserResponse.model_validate(db_user)
28
29@app.get("/users/{user_id}", response_model=UserResponse)
30async def get_user(user_id: int):
31 user = await db.get(user_id)
32 if not user:
33 raise HTTPException(status_code=404, detail="User not found")
34 return UserResponse.model_validate(user)

Tip

Pydantic models automatically generate JSON Schema. FastAPI uses this to build interactive API documentation at /docs (Swagger UI) and /redoc.

Pydantic for Standalone Validation

Validate JSON without FastAPIpython
1from pydantic import BaseModel, ValidationError
2
3class Config(BaseModel):
4 host: str
5 port: int = 8000
6 debug: bool = False
7 allowed_origins: list[str] = []
8
9# Validate from dict/JSON
10try:
11 config = Config.model_validate_json('{"host": "localhost", "port": 3000}')
12 print(config.host) # "localhost"
13 print(config.debug) # False (default)
14except ValidationError as e:
15 print(e.errors())
16 # [{"type": "missing", "loc": ["host"], "msg": "Field required"}]

Command-Line JSON Processing

Python as a JSON CLI toolbash
1# Pretty-print a JSON file
2python -m json.tool data.json
3
4# Pretty-print from stdin (piped from curl)
5curl -s https://api.github.com/users/octocat | python -m json.tool
6
7# Compact/minify JSON
8python -c "import json,sys; json.dump(json.load(sys.stdin), sys.stdout, separators=(',',':'))" < data.json
9
10# Extract a field with Python one-liner
11echo '{"name":"Alice","age":30}' | python -c "import json,sys; print(json.load(sys.stdin)['name'])"

Common Mistakes

1. Single Quotes vs Double Quotes

1# WRONG — Python dict repr uses single quotes, which is not valid JSON
2data = {'name': 'Alice'}
3print(data) # {'name': 'Alice'} — this is NOT valid JSON
4
5# CORRECT — use json.dumps() to get valid JSON
6import json
7print(json.dumps(data)) # {"name": "Alice"} — valid JSON

2. Not Handling Encoding

1# WRONG — may fail on non-ASCII characters
2with open("data.json") as f:
3 data = json.load(f)
4
5# CORRECT — always specify encoding
6with open("data.json", encoding="utf-8") as f:
7 data = json.load(f)

3. Using eval() Instead of json.loads()

1# NEVER DO THIS — eval() executes arbitrary code
2data = eval('{"name": "Alice"}') # Security vulnerability!
3
4# ALWAYS use json.loads()
5data = json.loads('{"name": "Alice"}') # Safe

Warning

Using eval() on untrusted JSON is a critical security vulnerability. An attacker could inject __import__('os').system('rm -rf /') as a value. Always use json.loads().

Try It — Validate Python-Style JSON

Try It Yourself

Valid JSON that maps to a Python dict — experiment with types

Frequently Asked Questions

What is the difference between json.load() and json.loads() in Python?
json.load() reads from a file object (e.g., an opened file handle). json.loads() parses a JSON string directly. Similarly, json.dump() writes to a file and json.dumps() returns a string. The "s" stands for "string".
How do I pretty-print JSON in Python?
Use json.dumps(data, indent=2) to get a formatted string with 2-space indentation. Add sort_keys=True to sort keys alphabetically. For terminal output, you can also use python -m json.tool < file.json from the command line.
Can Python dictionaries be directly used as JSON?
Python dictionaries are very similar to JSON objects, but they are not the same. Python allows non-string keys, tuple values, and single quotes. JSON requires double-quoted string keys. Use json.dumps() to convert a Python dict to valid JSON, and json.loads() to convert JSON back to a dict.
How do I handle dates in JSON with Python?
JSON has no date type. Python's json module raises TypeError for datetime objects. Use a custom serializer: json.dumps(data, default=str) converts datetimes to ISO format strings. For more control, pass a custom function: default=lambda o: o.isoformat() if isinstance(o, datetime) else str(o).
How do I convert a pandas DataFrame to JSON?
Use df.to_json(orient="records") for a list of objects (most common for APIs). Other orient options: "split" (separate columns/data/index), "index" (dict keyed by index), "columns" (dict keyed by column). Add indent=2 for readable output.
What is Pydantic and why use it for JSON?
Pydantic is a Python library for data validation using type annotations. It validates JSON data, converts types automatically, and generates JSON Schema. FastAPI uses Pydantic models for request/response validation. It is the Python equivalent of Zod in TypeScript.