Part of Python for Beginners

Parse, Create & Manage JSON Data | Python for Beginners #18

Sandy LaneSandy Lane

Video: Parse, Create & Manage JSON Data | Python for Beginners #18 by Taught by Celeste AI - AI Coding Coach

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

Python JSON: dumps, loads, dump, load

json.dumps(obj) → string. json.loads(s) → Python object. json.dump(obj, f) → write to file. json.load(f) → read from file. Type mapping: dict ↔ object, list ↔ array, True/False/None ↔ true/false/null.

JSON is the lingua franca of web APIs and config files. Python's json module makes round-tripping trivial.

What JSON looks like

{
  "name": "Alice",
  "age": 25,
  "active": true,
  "hobbies": ["reading", "coding"],
  "address": null
}

Key/value pairs in {}, lists in [], strings in "", numbers, true/false/null. That's all of JSON.

Python ↔ JSON type mapping

Python JSON
dict object {}
list, tuple array []
str string
int, float number
True true
False false
None null

Note tuples become arrays — and come back as lists. JSON has no tuple, set, or datetime types; you have to convert manually.

Python → JSON string: dumps

import json

user = {
  "name": "Alice",
  "age": 25,
  "hobbies": ["reading", "coding"],
  "active": True,
  "score": None
}

s = json.dumps(user)
print(s)
# {"name": "Alice", "age": 25, "hobbies": ["reading", "coding"], "active": true, "score": null}

json.dumps(obj) returns a JSON string. The s is for "string."

Note that True becomes true, None becomes null — Python casing → JSON casing automatically.

Pretty printing

pretty = json.dumps(user, indent=2)
print(pretty)

Output:

{
  "name": "Alice",
  "age": 25,
  "hobbies": [
    "reading",
    "coding"
  ],
  "active": true,
  "score": null
}

indent=2 (or 4) adds line breaks and indentation. Useful for human-readable output.

Other options:

  • sort_keys=True — alphabetize keys.
  • separators=(",", ":") — minimal whitespace (smaller output).
  • ensure_ascii=False — keep Unicode characters as-is (default escapes them).

JSON string → Python: loads

text = '{"name": "Bob", "age": 30, "active": false}'
parsed = json.loads(text)

print(parsed["name"])     # Bob
print(parsed["age"])      # 30
print(parsed["active"])   # False  (Python bool)

json.loads(s) parses a JSON string and returns Python objects.

If the string is malformed:

try:
  json.loads("{not valid json}")
except json.JSONDecodeError as e:
  print(f"Bad JSON: {e}")

JSONDecodeError includes msg, doc, pos — useful for debugging.

File I/O: dump and load

students = [
  {"name": "Alice", "grade": "A", "score": 95},
  {"name": "Bob", "grade": "B", "score": 82},
]

# Write
with open("students.json", "w") as f:
  json.dump(students, f, indent=2)

# Read
with open("students.json", "r") as f:
  loaded = json.load(f)

print(f"Loaded {len(loaded)} students")

Note: dumps/loads for strings, dump/load for files. The trailing s is the only difference.

For UTF-8 files (which all JSON should be):

with open("data.json", "w", encoding="utf-8") as f:
  json.dump(data, f, ensure_ascii=False, indent=2)

Nested structures

order = {
  "id": "ORD-001",
  "customer": {
    "name": "Alice",
    "email": "alice@example.com"
  },
  "items": [
    {"product": "Widget", "qty": 3, "price": 9.99},
    {"product": "Gadget", "qty": 1, "price": 24.99}
  ],
  "total": 54.96
}

s = json.dumps(order, indent=2)
restored = json.loads(s)

print(restored["customer"]["name"])      # Alice
print(restored["items"][0]["product"])    # Widget

Nested dicts and lists work transparently. Access nested data with chained [...].

Encoding non-JSON types

from datetime import datetime

data = {"timestamp": datetime.now()}
json.dumps(data)
# TypeError: Object of type datetime is not JSON serializable

Datetimes, sets, custom objects don't map to JSON. Three options:

1. Pre-convert manually:

data = {"timestamp": datetime.now().isoformat()}
json.dumps(data)

2. Custom encoder:

class DateTimeEncoder(json.JSONEncoder):
  def default(self, obj):
    if isinstance(obj, datetime):
      return obj.isoformat()
    return super().default(obj)

json.dumps({"ts": datetime.now()}, cls=DateTimeEncoder)

3. default= callback:

def convert(obj):
  if isinstance(obj, datetime):
    return obj.isoformat()
  raise TypeError(f"Cannot encode {type(obj)}")

json.dumps({"ts": datetime.now()}, default=convert)

Decoding into custom types

from datetime import datetime

def parse_dates(d):
  for k, v in d.items():
    if isinstance(v, str) and v.endswith("Z"):
      try:
        d[k] = datetime.fromisoformat(v.rstrip("Z"))
      except ValueError:
        pass
  return d

raw = '{"name": "event", "when": "2026-01-15T10:00:00Z"}'
data = json.loads(raw, object_hook=parse_dates)
print(type(data["when"]))   # datetime

object_hook receives each parsed dict; return the version you want stored.

For complex schemas, use pydantic or dataclasses-json — proper schema validation.

Compact and minified

data = {"a": 1, "b": [2, 3]}

# Default (with spaces)
json.dumps(data)
# '{"a": 1, "b": [2, 3]}'

# Compact
json.dumps(data, separators=(",", ":"))
# '{"a":1,"b":[2,3]}'

Compact output saves bytes for network transmission.

JSON Lines (NDJSON)

# One JSON object per line — common for logs and streaming
records = [{"id": 1}, {"id": 2}, {"id": 3}]

with open("data.jsonl", "w") as f:
  for r in records:
    f.write(json.dumps(r) + "\n")

# Read line by line
with open("data.jsonl", "r") as f:
  for line in f:
    record = json.loads(line)
    print(record)

Easy to append, easy to stream. Don't try to parse this with json.load — that expects one document.

A real-world flow: load, modify, save

# Read existing
with open("students.json") as f:
  students = json.load(f)

# Modify
students.append({"name": "Diana", "grade": "A", "score": 96})
for s in students:
  s["honors"] = s["score"] >= 90

# Write back
with open("students.json", "w") as f:
  json.dump(students, f, indent=2)

Read into Python objects, work in Python, write back. JSON is the boundary; in between, you have full Python.

Common stumbles

Single quotes. JSON requires double quotes. '{"name": "Alice"}' is fine in Python but the inner JSON must use ".

Trailing commas. JSON doesn't allow [1, 2, 3,] — Python does. Be careful when hand-writing JSON.

True vs true. Inside JSON, lowercase. The library handles this automatically when serializing/parsing.

Unicode escaping. By default, json.dumps escapes non-ASCII ("héllo""héllo"). Pass ensure_ascii=False to keep it as-is.

json.load on a string. loads for strings, load for file objects. Easy to mix up.

Loading huge files all at once. Each json.load parses the whole file. For multi-GB files, use a streaming parser (ijson package) or JSON Lines.

Treating tuples as JSON arrays. Tuples serialize as arrays — but come back as lists. If you need tuples, convert after parsing.

What's next

Lesson 19: classes and objects. class, __init__, self, attributes, methods.

Recap

json.dumps(obj) / loads(s) for strings; json.dump(obj, f) / load(f) for files. Type mapping: dict↔object, list↔array, True/False/None↔true/false/null. Use indent= for pretty output, ensure_ascii=False for Unicode-as-is. For non-JSON types (datetimes, sets), supply a custom encoder or pre-convert. Use JSON Lines for streamable record-per-line storage.

Next lesson: classes and objects.

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.