Chapter 4 · Mid-level Developer

JSON persistence

JSON (JavaScript Object Notation) is the standard text format for structured data — it's what APIs speak, and it's a clean way to save a dict or list to disk. The json module converts between Python objects and JSON text.

There are two pairs, and mixing them up is the #1 trap. The ones with s work on strings; the ones without work on files:

  • json.dumps(obj) → returns a JSON string (s = string).
  • json.loads(text) → parses a JSON string back to Python.
  • json.dump(obj, f) → writes JSON to an open file f.
  • json.load(f) → reads JSON from an open file.

The type mapping is mostly what you'd expect, with a few renames: Python dict→object, list/tuple→array, str→string, True/Falsetrue/false, and importantly Nonenull. Pass indent=2 to dumps/dump for pretty, human-readable output.

Syntax

import json

json.dumps(obj)              # → JSON string
json.loads('{"a": 1}')       # → Python dict

with open(path, 'w') as f:
    json.dump(obj, f)           # write to file

with open(path) as f:
    data = json.load(f)         # read from file

json.dumps(obj, indent=2)    # pretty-printed

Worked examples

Save a dict to disk and load it back

import json

data = {'name': 'Riley', 'score': 99, 'tags': ['a', 'b']}

with open('/tmp/save.json', 'w') as f:
    json.dump(data, f)

with open('/tmp/save.json') as f:
    loaded = json.load(f)

print(loaded == data)   # True  (roundtrip preserved it)

String roundtrip with dumps / loads (no file)

import json

data = {'tasks': ['a', 'b'], 'done': False}
text = json.dumps(data)
print(text)
# Output:
# {"tasks": ["a", "b"], "done": false}

print(json.loads(text) == data)   # True

Type mapping + pretty-printing

import json

print(json.dumps({'on': True, 'off': False, 'missing': None}))
# Output:
# {"on": true, "off": false, "missing": null}

print(json.dumps({'a': 1, 'b': [2, 3]}, indent=2))
# Output:
# {
#   "a": 1,
#   "b": [
#     2,
#     3
#   ]
# }

Common mistakes & gotchas

dump/dumps — the s is everything

json.dump(data, f) writes to a file; json.dumps(data) returns a string. Swap them and you'll either pass a file where a string is expected, or try to .write() nothing. Memory hook: s = string.

None becomes null, and dict keys become strings

JSON has no None — it becomes null, and loads back as None. Also, all object keys are strings in JSON, so json.dumps({1: "a"}) gives {"1": "a"} and you get the *string* "1" back, not the int.

Not everything is JSON-serialisable

json.dumps handles dicts, lists, strings, numbers, bools and None. Hand it a set, a datetime, or a custom object and it raises TypeError: ... is not JSON serializable. Convert to a supported type first (e.g. a date to its .isoformat() string).

Why it matters

JSON is the lingua franca of the modern backend — every web API returns it, config files use it, and it's the simplest reliable way to persist structured data between runs. The `json` module is a tool you'll touch in almost every real project that talks to the outside world.