Working with JSON
JSON (JavaScript Object Notation) is the de facto standard for exchanging structured data between systems — APIs return it, config files use it, and log pipelines are full of it. Python's built-in json module converts between JSON text and native Python objects (dicts, lists, strings, numbers, booleans, None) without needing any third-party library. Because it's part of the standard library, all you need is import json.
Serializing: `json.dumps()`
json.dumps() ("dump string") takes a Python object and returns a JSON-formatted str. This is what you'd use to build a request body, write JSON to a socket, or log structured data as text.
import json
user = {
"name": "Ada Lovelace",
"age": 36,
"active": True,
"roles": ["admin", "editor"],
"manager": None,
}
json_text = json.dumps(user)
print(json_text)
print(type(json_text)){"name": "Ada Lovelace", "age": 36, "active": true, "roles": ["admin", "editor"], "manager": null}
<class 'str'>Deserializing: `json.loads()`
json.loads() ("load string") does the reverse — it parses a JSON-formatted string and returns the equivalent Python object.
import json
payload = '{"id": 42, "tags": ["python", "json"], "score": 9.5}'
data = json.loads(payload)
print(data)
print(type(data))
print(data["tags"]){'id': 42, 'tags': ['python', 'json'], 'score': 9.5}
<class 'dict'>
['python', 'json']Reading and Writing Files: `json.load()` / `json.dump()`
When your JSON lives in a file rather than a string, use json.dump() and json.load() (no "s"). These work directly with an open file object, so you don't need to manually read the file into a string first.
import json
config = {"debug": False, "max_retries": 3, "hosts": ["a.example.com", "b.example.com"]}
# Write to a file
with open("config.json", "w") as f:
json.dump(config, f)
# Read it back
with open("config.json", "r") as f:
loaded = json.load(f)
print(loaded)
print(loaded == config){'debug': False, 'max_retries': 3, 'hosts': ['a.example.com', 'b.example.com']}
TrueType Mapping
JSON has a small, fixed set of types. Python's richer type system is mapped onto it as follows:
Python | JSON |
|---|---|
| object |
| array |
| string |
| number |
|
|
|
|
Pretty-Printing with `indent` and `sort_keys`
By default dumps()/dump() produce compact, single-line output — great for wire transfer, hard to read. Pass indent= (a number of spaces) to pretty-print, and sort_keys=True to get deterministic, alphabetically-ordered keys (handy for diffs and tests).
import json
record = {"name": "Ada", "id": 7, "roles": ["admin", "editor"]}
pretty = json.dumps(record, indent=2, sort_keys=True)
print(pretty){
"id": 7,
"name": "Ada",
"roles": [
"admin",
"editor"
]
}Serializing Objects That Aren't JSON-Native
json.dumps() only knows how to handle the types in the table above. Pass it something else — a datetime, a custom class instance, a set — and it raises a TypeError. The fix is the default= parameter: a function that's called for any object the encoder doesn't recognize, and returns something JSON-serializable in its place.
import json
from datetime import datetime
event = {
"name": "deploy",
"timestamp": datetime(2026, 7, 6, 14, 30),
}
def default(obj):
if isinstance(obj, datetime):
return obj.isoformat()
raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")
print(json.dumps(event, default=default)){"name": "deploy", "timestamp": "2026-07-06T14:30:00"}The same default= argument works with json.dump() when writing to a file. It's only ever called for values the encoder can't already handle natively.
Handling Malformed JSON
Parsing text that isn't valid JSON — a trailing comma, single quotes instead of double quotes, an unterminated string — raises json.JSONDecodeError, a subclass of ValueError. Any code that parses JSON from an external source (a network response, user input, a file you don't fully control) should expect this and handle it.
import json
bad_json = "{'name': 'Ada'}" # single quotes are not valid JSON
try:
data = json.loads(bad_json)
except json.JSONDecodeError as exc:
print(f"Failed to parse JSON: {exc}")Failed to parse JSON: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)
json.dumps()/json.loads()convert to and from JSON strings.json.dump()/json.load()read and write JSON directly to an open file.Use
default=to teach the encoder how to serialize custom or non-native types.Use
indent=for human-readable output andsort_keys=Truefor deterministic key ordering.Always wrap parsing of external JSON in a
try/except json.JSONDecodeErrorblock.