Computing Foundations › APIs and the Web › Day 24
Hands-on lab — Day 24: JSON and Data Serialization
- ← Back to the Day 24 lesson
- Open the hands-on files on GitHub — clone or download them from the public labs repository
- Local path in your clone:
labs/sections/computing-foundations/day-024-json-and-data-serialization/
Commands
Setup
cd labs/sections/computing-foundations/day-024-json-and-data-serialization Run
bash examples/json_tools.sh
bash starter/json_tools.sh Test
bash tests/run_tests.sh File tree
examples/json_tools.sh examples/samples/broken.json examples/samples/config.json expected-output/FIELDS.md expected-output/json_tools.txt expected-output/run_tests.txt metadata.yml README.md requirements/README.md security.md starter/json_tools.sh starter/json-worksheet.md tests/run_tests.sh troubleshooting.md
Lab README
Day 024 lab — Parse and Build JSON
Lesson
- Lesson title: JSON and Data Serialization
- Day number: 24 of 365
- Lesson article: https://ai-roadmap-365.github.io/day-024-json-and-data-serialization
- Lab files: everything you need is in this directory — follow “How to run” below.
- Browse the course locally: from the repository root, this lab also appears in the course website at
/labs/day-024-json-and-data-serializationwhen the site is running.
Purpose
Day 24's lesson explains serialization and JSON in depth. This lab makes it
concrete and offline: you validate a valid nested JSON file, extract a nested
field, pretty-print, and watch a deliberately broken file get rejected with a
real parser error — using only python3, which is already on your machine.
This is the exact groundwork for Week 4's project, a weather command-line
dashboard that parses live JSON.
Learning objectives
- Validate a JSON file from the command line and read the pass/fail result.
- Extract a nested field and an array element from a JSON document.
- Pretty-print JSON so a human can read its structure.
- Recognize the error a trailing comma produces and explain why it is invalid.
- Compare the
python3andjqways of doing the same JSON task.
Prerequisites
- The Day 24 lesson (read it first — it explains every concept this lab uses).
- A terminal with
python3available (macOS and Linux ship it). - No programming experience required; every command is given and explained.
Supported operating systems
- macOS — fully supported (tested on macOS with Apple Silicon, Python 3.14).
- Linux — fully supported (any distribution with
python3andbash). - Windows — run the scripts inside WSL, or run the individual
python3commands from the lesson by hand with Python 3 for Windows.
Hardware requirements
Any computer made in roughly the last 15 years. The lab only reads two small text files; it needs no special RAM, disk, or GPU.
Required software
python3(3.x) — forpython3 -m json.tooland thejsonmodule.bash(3.2 or newer) — preinstalled on macOS and Linux.
Free and open-source options
Everything here is free and open source: python3, bash, and the optional
jq are all open source or ship with your OS. No account, API key, network,
or purchase is needed.
Installation
None. Clone the repository (or copy this directory) and you are ready:
cd labs/sections/computing-foundations/day-024-json-and-data-serialization
File structure
day-024-json-and-data-serialization/
├── README.md ← you are here
├── metadata.yml ← machine-readable lab metadata
├── starter/
│ ├── json_tools.sh ← YOUR working file (4 exercises)
│ └── json-worksheet.md ← record your findings here
├── examples/
│ ├── json_tools.sh ← completed reference implementation
│ └── samples/
│ ├── config.json ← a valid, nested JSON file
│ └── broken.json ← a deliberately broken JSON file
├── tests/
│ └── run_tests.sh ← automated checks (7 checks)
├── expected-output/
│ ├── json_tools.txt ← real captured run of the example
│ ├── run_tests.txt ← real captured test run
│ └── FIELDS.md ← what must hold on every platform
├── requirements/
│ └── README.md ← dependency statement (python3 only)
├── troubleshooting.md
└── security.md
How to run
From this directory:
## 1. See the finished result first
bash examples/json_tools.sh
## 2. Your task: work through the four exercises in the starter
bash starter/json_tools.sh
## 3. Check your work
bash tests/run_tests.sh
What the commands do
bash examples/json_tools.sh— the reference script. It validatesconfig.jsonwithpython3 -m json.tool, extractscoordinates.latandsensors[0]withpython3one-liners (showing thejqequivalent as a comment), counts the top-level keys, pretty-prints the file, and then provesbroken.jsonis rejected by capturing and printing its real parser error.bash starter/json_tools.sh— the same four skills as four numbered exercises for you to work through: validate, extract a nested field, pretty-print, and spot the error in the broken file.bash tests/run_tests.sh— runs seven checks with no network:python3is present, the valid sample parses, the broken sample is rejected,coordinates.latextracts to26.9124, the file has 7 top-level keys,sensors[0]istemperature, and the example script runs end to end.
Expected output
See expected-output/json_tools.txt — a
real captured run. The broken file is rejected with (on Python 3.14):
Illegal trailing comma before end of object: line 4 column 53 (char 97)
On Python 3.12 and older the wording is Expecting property name enclosed in double quotes instead — same mistake, same non-zero exit. See
expected-output/FIELDS.md for what must hold on
every platform.
Validation steps
- Run
bash examples/json_tools.sh— it must finish without an unexpected error and print the broken-file message under step 5. - Confirm the extraction prints
26.9124and nothing else. - Confirm the key count is
7. - Run the tests (next section) — all checks must pass.
Tests
bash tests/run_tests.sh
Expected final line: 7 checks, 0 failure(s). The command exits 0 on success
and non-zero on any failure, so it can run in CI. No network is used.
Cleanup
Nothing to clean up: the scripts only read the two sample files and write only
console output. To reset your starter work, restore it from git:
git checkout -- starter/json_tools.sh.
Troubleshooting
See troubleshooting.md for the full list (missing
python3, key errors, quoting the one-liner, version-dependent error wording,
optional jq).
Security notes
See security.md. Short version: the scripts run no network
calls, need no elevated privileges, and only read local sample files — and the
golden rule of the day is to parse untrusted JSON, never to eval it.
Extension exercises
- Serialize a value you build in Python and confirm the round trip:
python3 -c "import json; d={'a':1,'b':[True,None]}; s=json.dumps(d); print(s); print(json.loads(s)==d)". - Try to serialize a value JSON has no type for, such as a set
(
python3 -c "import json; print(json.dumps({1,2,3}))"), read the error, and note why sets and dates must be encoded as one of the six JSON types. - If you installed
jq, redo every extraction withjqand compare its output with thepython3version.
Navigation
- Previous day: Day 23 — REST Fundamentals: Resources and Verbs
(
labs/sections/computing-foundations/day-023-rest-fundamentals-resources-and-verbs/). - Next day: Day 25 — API Authentication: Keys, Tokens, and OAuth
(
labs/sections/computing-foundations/day-025-api-authentication-keys-tokens-and-oauth/).
Expected output
FIELDS.md
# Expected output — Day 024 lab
The two `.txt` files in this directory are real captured runs on the
authoring machine (macOS, Apple Silicon, Python 3.14.0, 2026-07-12):
- `json_tools.txt` — a full run of `bash examples/json_tools.sh`.
- `run_tests.txt` — a full run of `bash tests/run_tests.sh` (ends in
`7 checks, 0 failure(s).`).
## What must be true on every platform
Regardless of OS or Python version, a correct run shows:
1. `examples/samples/config.json` **validates cleanly** (exit 0) and
pretty-prints with 7 top-level keys.
2. Extracting `coordinates.lat` prints exactly `26.9124`.
3. Extracting `sensors[0]` prints exactly `temperature`.
4. `examples/samples/broken.json` **fails to validate** with a non-zero
exit status.
5. `bash tests/run_tests.sh` ends with `7 checks, 0 failure(s).` and
exits 0.
## The one platform-dependent line
The wording of the broken-file error depends on the Python version:
- **Python 3.13 and newer** (as captured here):
`Illegal trailing comma before end of object: line 4 column 53 (char 97)`
- **Python 3.12 and older**:
`Expecting property name enclosed in double quotes: line 5 column 1 (char ...)`
Both messages report the same underlying mistake — the trailing comma after
the `sensors` array — and both cause a non-zero exit, which is what the test
checks. The exact character offset is not asserted anywhere.
json_tools.txt
=== 1. Validate the good file ===
OK: examples/samples/config.json is valid JSON
=== 2. Extract a nested field (coordinates.lat) ===
26.9124
=== 3. Extract the first sensor (sensors[0]) and count top-level keys ===
temperature
7 top-level keys
=== 4. Pretty-print the good file ===
{
"station": "Nimbus-7",
"active": true,
"elevation_m": 512,
"coordinates": {
"lat": 26.9124,
"lon": 75.7873
},
"sensors": [
"temperature",
"humidity",
"pressure"
],
"calibration": {
"temperature": {
"offset": -0.4,
"unit": "celsius"
},
"last_checked": "2026-07-01"
},
"notes": null
}
=== 5. The broken file is rejected ===
As expected, examples/samples/broken.json is invalid JSON. Parser said:
Illegal trailing comma before end of object: line 4 column 53 (char 97)
Done. The good file round-trips; the broken file (a trailing comma) is refused.
run_tests.txt
ok: python3 is available
ok: valid sample parses cleanly
ok: broken sample is rejected
ok: coordinates.lat extracts to 26.9124
ok: sample has 7 top-level keys
ok: sensors[0] extracts to 'temperature'
ok: examples/json_tools.sh runs successfully
7 checks, 0 failure(s).
Source files
examples/json_tools.sh (2146 bytes)
#!/usr/bin/env bash
# Day 024 lab — completed reference: work with JSON using only python3.
#
# Demonstrates the four skills the starter asks you to practise:
# 1. validate a JSON file (python3 -m json.tool)
# 2. extract a nested field (python3 one-liner; jq equivalent shown)
# 3. pretty-print a JSON file (python3 -m json.tool)
# 4. show a broken file being rejected, with its real error
#
# Everything runs offline. Run from the lab directory:
# bash examples/json_tools.sh
set -euo pipefail
good="examples/samples/config.json"
broken="examples/samples/broken.json"
echo "=== 1. Validate the good file ==="
# `python3 -m json.tool` parses its input and reprints it; it fails loudly
# (non-zero exit) if the input is not valid JSON.
if python3 -m json.tool "${good}" > /dev/null; then
echo "OK: ${good} is valid JSON"
else
echo "UNEXPECTED: ${good} failed to parse" >&2
exit 1
fi
echo
echo "=== 2. Extract a nested field (coordinates.lat) ==="
# Python one-liner: load the file, walk into coordinates, print lat.
python3 -c "import json; print(json.load(open('${good}'))['coordinates']['lat'])"
# jq equivalent (if you have jq installed):
# jq '.coordinates.lat' examples/samples/config.json
echo
echo "=== 3. Extract the first sensor (sensors[0]) and count top-level keys ==="
python3 -c "import json; d=json.load(open('${good}')); print(d['sensors'][0]); print(len(d), 'top-level keys')"
# jq equivalent:
# jq '.sensors[0]' examples/samples/config.json
# jq 'keys | length' examples/samples/config.json
echo
echo "=== 4. Pretty-print the good file ==="
python3 -m json.tool "${good}"
echo
echo "=== 5. The broken file is rejected ==="
# We EXPECT this to fail. Capture the error and show it, then continue.
if python3 -m json.tool "${broken}" > /dev/null 2> /tmp/day024_err.txt; then
echo "UNEXPECTED: ${broken} parsed but should be invalid" >&2
exit 1
else
echo "As expected, ${broken} is invalid JSON. Parser said:"
sed 's/^/ /' /tmp/day024_err.txt
rm -f /tmp/day024_err.txt
fi
echo
echo "Done. The good file round-trips; the broken file (a trailing comma) is refused."
examples/samples/broken.json (101 bytes)
{
"station": "Nimbus-7",
"active": true,
"sensors": ["temperature", "humidity", "pressure"],
}
examples/samples/config.json (317 bytes)
{
"station": "Nimbus-7",
"active": true,
"elevation_m": 512,
"coordinates": {
"lat": 26.9124,
"lon": 75.7873
},
"sensors": ["temperature", "humidity", "pressure"],
"calibration": {
"temperature": { "offset": -0.4, "unit": "celsius" },
"last_checked": "2026-07-01"
},
"notes": null
}
metadata.yml (570 bytes)
lesson_id: D024
day: 24
kind: data-processing
languages: [bash]
setup_commands:
- cd labs/sections/computing-foundations/day-024-json-and-data-serialization
run_commands:
- bash examples/json_tools.sh
- bash starter/json_tools.sh
test_commands:
- bash tests/run_tests.sh
cleanup_commands:
- 'git checkout -- starter/json_tools.sh # optional: reset your work'
requires_network: false
requires_api_key: false
estimated_minutes: 30
last_executed: '2026-07-12'
executed_on: 'macOS (Apple Silicon), Python 3.14.0, bash tests/run_tests.sh → 7 checks, 0 failures'
requirements/README.md (1240 bytes)
# Dependencies — Day 024 lab
**Only `python3`, which is preinstalled on macOS and every mainstream Linux
distribution.** This lab is deliberately offline and dependency-light:
- `python3` (3.x) — provides `python3 -m json.tool` (validate and
pretty-print) and the `json` module used by the one-line extractors. No
third-party packages are installed.
- `bash` (3.2 or newer) — runs the lab scripts. Preinstalled on macOS and
Linux.
## Optional
- `jq` — a dedicated command-line JSON processor. The lab's scripts show the
`jq` equivalent of each `python3` step as a comment, so you can compare the
two styles, but **every required step uses `python3` only**. Install `jq`
from your package manager if you want to try the commented commands
(`brew install jq` on macOS, `apt install jq` on Debian/Ubuntu).
## Platform notes
- **macOS / Linux:** nothing to install.
- **Windows:** run the scripts inside WSL (Windows Subsystem for Linux) so
`bash` and `python3` behave as documented, or install Python 3 for Windows
and run the individual `python3` commands from the lesson by hand.
- If `python3` is not found but `python` is, and `python --version` reports
3.x, substitute `python` for `python3` throughout.
starter/json_tools.sh (2206 bytes)
#!/usr/bin/env bash
# Day 024 lab — YOUR working file. Complete the four exercises below.
#
# The completed reference version is in examples/json_tools.sh — try each
# exercise yourself first, then compare. Everything runs offline with
# python3 (preinstalled on macOS and Linux). Run from the lab directory:
# bash starter/json_tools.sh
#
# Each exercise names the exact command to use in its comment. The commands
# below are already filled in so the script runs; work through them one at a
# time, changing the keys and files to explore, and check your understanding
# against examples/json_tools.sh.
set -euo pipefail
good="examples/samples/config.json"
broken="examples/samples/broken.json"
echo "=== Exercise 1: validate the good file ==="
# Use: python3 -m json.tool "${good}"
# It reprints the file if valid and fails with a non-zero exit if not.
# --- your command below ---
python3 -m json.tool "${good}"
echo
echo "=== Exercise 2: extract a nested field (coordinates.lat) ==="
# Use a python3 one-liner that loads the file and prints coordinates -> lat.
# Template (fill in the two keys):
# python3 -c "import json; print(json.load(open('${good}'))['coordinates']['lat'])"
# jq equivalent (optional): jq '.coordinates.lat' examples/samples/config.json
# --- your command below ---
python3 -c "import json; print(json.load(open('${good}'))['coordinates']['lat'])"
echo
echo "=== Exercise 3: pretty-print the good file ==="
# json.tool already pretty-prints. Print it indented to the screen.
# Use: python3 -m json.tool "${good}"
# --- your command below ---
python3 -m json.tool "${good}"
echo
echo "=== Exercise 4: spot the error in the broken file ==="
# Validate the BROKEN file. It SHOULD fail — read the error it prints.
# Use: python3 -m json.tool "${broken}"
# (We wrap it so the script does not stop on the expected failure.)
# --- your command below ---
if python3 -m json.tool "${broken}" > /dev/null 2>&1; then
echo "Hmm — broken.json parsed, but it should be invalid. Check the file."
else
echo "Correct: broken.json is invalid. Full error message:"
python3 -m json.tool "${broken}" || true
fi
echo
echo "Now record your findings in starter/json-worksheet.md."
starter/json-worksheet.md (1496 bytes)
# JSON worksheet — Day 024
Fill this in from your own runs of `starter/json_tools.sh` and the commands
in the lesson's hands-on section. Keep it — Week 4's project (a weather
command-line dashboard) parses live JSON and builds on this groundwork.
## The sample file (`examples/samples/config.json`)
| Question | Your answer | Command you used |
| --- | --- | --- |
| How many top-level keys does it have? | | |
| One nested value, and the path to it | | |
| What value type is `active`? | | (read it — no command needed) |
| What value type is `sensors`? | | |
| What value type is `notes`? | | |
Hint for the key count: `python3 -c "import json; print(len(json.load(open('examples/samples/config.json'))))"`
Hint for a nested value: `python3 -c "import json; print(json.load(open('examples/samples/config.json'))['coordinates']['lat'])"`
## The broken file (`examples/samples/broken.json`)
Run `python3 -m json.tool examples/samples/broken.json` and record the exact
error message it prints:
```text
(paste the error line here)
```
**Which single character makes the document invalid?**
(one sentence)
**Why does that one character make the whole document invalid?**
(two or three sentences, in your own words)
## The six JSON value types
List all six value types JSON allows, and next to each write which key in
`config.json` is an example of it (some types appear more than once).
1. object —
2. array —
3. string —
4. number —
5. boolean —
6. null —
tests/run_tests.sh (2638 bytes)
#!/usr/bin/env bash
# Tests for the Day 024 lab. Run from the lab directory:
# bash tests/run_tests.sh
#
# Verifies real behavior with no network:
# - the valid sample parses cleanly (exit 0)
# - the broken sample fails to parse (non-zero exit)
# - field extraction returns the known value (coordinates.lat == 26.9124)
# - the sample has the expected number of top-level keys (7)
# - the completed example script runs end to end
set -u
lab_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "${lab_dir}"
good="examples/samples/config.json"
broken="examples/samples/broken.json"
failures=0
checks=0
check() {
local label="$1" ok="$2"
checks=$((checks + 1))
if [ "${ok}" = "yes" ]; then
echo " ok: ${label}"
else
echo " FAIL: ${label}"
failures=$((failures + 1))
fi
}
# python3 must be available (the whole lab depends on it).
if command -v python3 > /dev/null 2>&1; then
check "python3 is available" "yes"
else
check "python3 is available" "no"
echo
echo "${checks} checks, ${failures} failure(s)."
exit 1
fi
# 1. Valid sample parses (exit 0).
if python3 -m json.tool "${good}" > /dev/null 2>&1; then
check "valid sample parses cleanly" "yes"
else
check "valid sample parses cleanly" "no"
fi
# 2. Broken sample fails to parse (non-zero exit).
if python3 -m json.tool "${broken}" > /dev/null 2>&1; then
check "broken sample is rejected" "no"
else
check "broken sample is rejected" "yes"
fi
# 3. Field extraction returns the known nested value.
lat="$(python3 -c "import json; print(json.load(open('${good}'))['coordinates']['lat'])" 2>/dev/null)"
if [ "${lat}" = "26.9124" ]; then
check "coordinates.lat extracts to 26.9124" "yes"
else
check "coordinates.lat extracts to 26.9124 (got '${lat}')" "no"
fi
# 4. Known top-level key count.
keys="$(python3 -c "import json; print(len(json.load(open('${good}'))))" 2>/dev/null)"
if [ "${keys}" = "7" ]; then
check "sample has 7 top-level keys" "yes"
else
check "sample has 7 top-level keys (got '${keys}')" "no"
fi
# 5. Known first array element.
first="$(python3 -c "import json; print(json.load(open('${good}'))['sensors'][0])" 2>/dev/null)"
if [ "${first}" = "temperature" ]; then
check "sensors[0] extracts to 'temperature'" "yes"
else
check "sensors[0] extracts to 'temperature' (got '${first}')" "no"
fi
# 6. The completed example script runs end to end.
if bash examples/json_tools.sh > /dev/null 2>&1; then
check "examples/json_tools.sh runs successfully" "yes"
else
check "examples/json_tools.sh runs successfully" "no"
fi
echo
echo "${checks} checks, ${failures} failure(s)."
[ "${failures}" -eq 0 ]
Troubleshooting
Troubleshooting — Day 024 lab
python3: command not found
Your system may expose Python 3 as python. Check with python --version;
if it reports 3.x, substitute python for python3 in every command. On
Windows, run the lab inside WSL, or install Python 3 from python.org.
bash examples/json_tools.sh stops immediately with a JSON error
If it fails on config.json, that file has probably been edited by accident.
Restore it from version control (git checkout -- examples/samples/config.json)
and re-run. The script is designed so that only broken.json should fail.
The broken-file error wording is different from the README
That is expected. Python 3.13 and newer say Illegal trailing comma before end of object; older versions say Expecting property name enclosed in double quotes. Both report the same trailing-comma mistake and both exit
non-zero, which is all the test checks. See expected-output/FIELDS.md.
KeyError when extracting a field
You asked for a key that is not in the file, or spelled one wrong. Keys are
case-sensitive: use lat, not Lat, and coordinates, not Coordinates.
Pretty-print the file first (python3 -m json.tool examples/samples/config.json)
to see the exact key names.
The Python one-liner errors about quotes
Keep the outer double quotes around the -c program and single quotes for the
filename inside, exactly as shown:
python3 -c "import json; print(json.load(open('examples/samples/config.json'))['coordinates']['lat'])".
Mixing them up makes the shell mangle the program before Python sees it.
jq: command not found
jq is optional. Every required step uses python3; the jq lines in the
scripts are comments for comparison only. Install jq (brew install jq or
apt install jq) only if you want to try them.
Permission denied when running a script
Run it through bash explicitly: bash starter/json_tools.sh. You do not need
to mark the file executable.
Security notes
Security notes — Day 024 lab
- What the scripts do: read two small local JSON files
(
examples/samples/config.jsonandexamples/samples/broken.json) and print or validate them withpython3. They make no network connections, write no files outside their own console output, and change no settings. - Never
evaluntrusted JSON. Because JSON's syntax overlaps with JavaScript's, it is tempting to turn a JSON string into values by running it as code — this is dangerous: a malicious payload could then execute arbitrary instructions. Always use a real parser (python3 -m json.tool, Python'sjson.load, orjq), which treats the input strictly as data and can only ever produce the six JSON value types, never executable behavior. - This lab only reads local sample files that ship with it, so there is no untrusted input here — but the habit matters for every real service you will call later, where the JSON comes from outside and must be parsed, not evaluated, and then validated before you trust its fields.
- Privileges: everything runs as your normal user. Nothing needs
sudo. - Reading before running: the scripts are short and commented — read them first. Running unread scripts is one of the most common ways developers get compromised.