You need Python installed, and to know how to run a
.py file. If python3 --version in a
terminal prints a version number, you are ready. If it does not — or
if you have never opened a terminal — do
Getting Started with
Python first; it installs Python and runs your first program, and
takes about fifteen minutes. Nothing else is needed: no account, no
payment, no extra software.
What is JSON?
JSON (JavaScript Object Notation) is the most common data format for exchanging information between systems. APIs return JSON, config files use JSON, and databases store JSON. If you work with any external data, you'll work with JSON.
{
"name": "Alice",
"age": 25,
"is_admin": false,
"skills": ["Python", "Linux", "Security"],
"address": {
"city": "London",
"country": "UK"
}
}
JSON objects become Python dicts, JSON arrays become lists, true/false
become True/False, and null becomes None.
Parsing JSON Strings
import json
# JSON string → Python dict
json_string = '{"name": "Alice", "age": 25, "active": true}'
data = json.loads(json_string)
print(data["name"]) # Alice
print(data["age"]) # 25
print(type(data)) # <class 'dict'>
# Python dict → JSON string
user = {"name": "Bob", "age": 30, "active": True}
json_output = json.dumps(user)
print(json_output) # {"name": "Bob", "age": 30, "active": true}
# Pretty-print JSON
print(json.dumps(user, indent=2))
# {
# "name": "Bob",
# "age": 30,
# "active": true
# }
Reading JSON Files
import json
# Read a JSON file
with open("config.json", "r") as file:
config = json.load(file) # Note: load (not loads)
print(config["database"]["host"])
print(config["debug"])
Writing JSON Files
import json
data = {
"users": [
{"name": "Alice", "role": "admin"},
{"name": "Bob", "role": "user"}
],
"version": "1.0",
"last_updated": "2026-03-04"
}
# Write to file (pretty-printed)
with open("data.json", "w") as file:
json.dump(data, file, indent=2) # Note: dump (not dumps)
loads/dumps work with strings (the "s" stands for string).
load/dump work with files.
Handling JSON Errors
import json
# Invalid JSON will raise JSONDecodeError
bad_json = '{"name": "Alice", age: 25}' # Missing quotes around key
try:
data = json.loads(bad_json)
except json.JSONDecodeError as e:
print(f"Invalid JSON: {e}")
# Invalid JSON: Expecting property name enclosed in double quotes
Working with Nested JSON
import json
response = '''
{
"status": "success",
"data": {
"users": [
{"id": 1, "name": "Alice", "email": "alice@example.com"},
{"id": 2, "name": "Bob", "email": "bob@example.com"}
],
"total": 2
}
}
'''
data = json.loads(response)
# Access nested values
print(data["status"]) # success
print(data["data"]["total"]) # 2
# Loop through nested arrays
for user in data["data"]["users"]:
print(f"{user['name']}: {user['email']}")
# Safe access with .get() (avoids KeyError)
phone = data["data"]["users"][0].get("phone", "Not provided")
print(phone) # Not provided
Practical Examples
Config File Manager
import json
from pathlib import Path
class Config:
def __init__(self, filename):
self.filename = filename
self.data = {}
self._load()
def _load(self):
path = Path(self.filename)
if path.exists():
with open(self.filename, "r") as f:
self.data = json.load(f)
def save(self):
with open(self.filename, "w") as f:
json.dump(self.data, f, indent=2)
def get(self, key, default=None):
return self.data.get(key, default)
def set(self, key, value):
self.data[key] = value
self.save()
# Usage
config = Config("app_settings.json")
config.set("theme", "dark")
config.set("language", "en")
config.set("notifications", True)
print(config.get("theme")) # dark
Log Analyzer
import json
# Parse JSON log entries
log_entries = [
'{"timestamp": "2026-03-04T10:30:00", "level": "INFO", "message": "Server started"}',
'{"timestamp": "2026-03-04T10:30:05", "level": "ERROR", "message": "Database timeout"}',
'{"timestamp": "2026-03-04T10:30:10", "level": "INFO", "message": "Retry successful"}',
'{"timestamp": "2026-03-04T10:31:00", "level": "ERROR", "message": "Disk space low"}'
]
errors = []
for entry_str in log_entries:
entry = json.loads(entry_str)
if entry["level"] == "ERROR":
errors.append(entry)
print(f"Found {len(errors)} errors:")
for error in errors:
print(f" [{error['timestamp']}] {error['message']}")
Data Transformation
import json
# Transform JSON data between formats
api_response = '''
[
{"first_name": "Alice", "last_name": "Smith", "age": 25},
{"first_name": "Bob", "last_name": "Jones", "age": 30}
]
'''
users = json.loads(api_response)
# Transform to a different format
transformed = {
user["first_name"].lower(): {
"full_name": f"{user['first_name']} {user['last_name']}",
"age": user["age"]
}
for user in users
}
print(json.dumps(transformed, indent=2))
# {
# "alice": {"full_name": "Alice Smith", "age": 25},
# "bob": {"full_name": "Bob Jones", "age": 30}
# }
JSON Serialization Tips
import json
from datetime import datetime
# Problem: datetime is not JSON serializable
data = {"timestamp": datetime.now()}
# json.dumps(data) # TypeError!
# Solution: custom serializer
def json_serial(obj):
if isinstance(obj, datetime):
return obj.isoformat()
raise TypeError(f"Type {type(obj)} not serializable")
json_output = json.dumps(data, default=json_serial)
print(json_output) # {"timestamp": "2026-03-04T10:30:00.000000"}
# Other useful dumps options
json.dumps(data, default=json_serial,
indent=2, # Pretty print
sort_keys=True, # Alphabetical keys
ensure_ascii=False) # Allow unicode characters
Now Do It Yourself: Five Steps
Reference above, real task here: you will create a configuration file, read it, reach into the nested parts, break it on purpose so its error never puzzles you, and write your change back to disk. Every output and every error below is what Python actually printed.
Go: open a terminal, move to a folder you can write in with cd ~, and open a text editor.
Do: save these six lines as settings.json in that folder.
{
"site": "finkatana.com",
"owner": {"name": "Alice", "email": "alice@example.com"},
"features": ["search", "newsletter"],
"maxUploadMb": 8
}
You should see: running cat settings.json (or type settings.json on Windows) prints it straight back. Note the shapes already present: an object, a nested object, an array and a plain number.
If not: if your editor added a .txt ending, rename the file — .json must be the last part of the name. The extension is only a label to humans, but you will confuse yourself in thirty seconds without it.
Go: in the same folder, start Python with python3.
Do: run these four lines.
import json
d = json.load(open("settings.json"))
print(type(d))
print(d["site"], d["maxUploadMb"] + 2)
You should see: <class 'dict'> then finkatana.com 10. That addition is the proof that matters: 8 arrived as a real number, not the text "8", so JSON types survive the trip into Python.
If not: FileNotFoundError means the terminal is in a different folder from the file. Check with pwd, or pass the full path. Do not move the file — move the terminal, with cd.
Go: stay at the >>> prompt.
Do: run these three lines.
print(d["owner"]["email"])
print(d["features"][0])
print(len(d["features"]))
You should see: alice@example.com, search, 2. Nested objects are just dicts inside dicts, so you index them one key at a time; arrays are lists, so they use numeric positions starting at 0.
If not: KeyError: 'theme' — or any other key name — means that key simply is not in the file, and Python refuses to guess. Use d.get("theme") to get None instead of an error, or d.get("theme", "dark") to supply a fallback. That single habit prevents most config-reading crashes.
Go: leave the prompt with exit() and create a second file, broken.json.
Do: save exactly this — note the comma after the last value — then run python3 -c "import json; json.load(open('broken.json'))".
{
"site": "finkatana.com",
}
You should see: json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 3 column 1 (char 29). JSON forbids a trailing comma after the final entry, unlike Python. The message gives you the line and column — go there first, every time.
If not: if it loads without complaint, you removed the comma while typing. Put it back and run it again: meeting this error once, deliberately, saves you from a puzzling ten minutes later. The other frequent cause is single quotes — JSON requires double quotes around every key and every string, even though Python accepts either.
Go: create a file called update.py in the same folder.
Do: put these five lines in it and run python3 update.py.
import json
d = json.load(open("settings.json"))
d["features"].append("contact-form")
json.dump(d, open("settings.json", "w"), indent=2)
print(open("settings.json").read())
You should see: the whole file printed back, reformatted onto separate indented lines, with "contact-form" now third in the features array. indent=2 is what makes it readable; without it the file is written as one long line, which is valid but miserable to edit by hand.
If not: if the file comes back empty, you opened it with "w" before reading it — that mode empties the file the instant it opens. Always finish reading before you open for writing. Because this rewrites the original, keep a copy the first few times: cp settings.json settings.json.bak.
Without scrolling up: name two things JSON forbids that Python allows in a dictionary
literal. If you can, you already avoid the two most common causes of
JSONDecodeError. Answer: a trailing comma after the last entry, and
single quotes — JSON requires double quotes around every key and string.
Now do it without the page: add a "theme": "dark"
key to settings.json from Python and write it back, then read it out again
to prove it stuck. Steps 2 and 5, joined up.
Summary
json.loads(string)parses a JSON string into Python objectsjson.dumps(obj)converts Python objects to a JSON stringjson.load(file)reads JSON from a filejson.dump(obj, file)writes JSON to a file- Use
indent=2for human-readable output - Use
.get(key, default)for safe access to nested data - Handle
json.JSONDecodeErrorfor invalid JSON input
You can now exchange data with APIs, save configurations, and process structured data. Next up: regular expressions — powerful pattern matching for text processing.