EXERCISE 21: Add progress tracking with JSON
- Dictionaries
- List of dictionaries
- What is JSON
- Reading and writing JSON
- Defining and calling functions (revisited)
Task: Convert your progress tracking from text format to JSON for richer data.
Rather than saving progress as rows of text in progress.txt, you will save a list of dictionaries to a file called progress.json.
At the moment you are just saving two variables to track progress for a single user. This would become unwieldy if you wanted to store many variables, complex data structures, or data for multiple users. A dictionary allows you to structure your data and access values more directly. This kind of structured dictionary format can be saved to a file format called JSON; this is called serialization, while loading the content of a JSON file as a dictionary is called deserialization.
Start by specifying (just on pen and paper) the JSON dictionary structure you would use to capture the two variables you are tracking.
{
"num_cards_practiced": an_int,
"score": a_float,
}
In the code, this progress history data will be represented by a list of dictionaries, one for each session. E.g.
[
{
"num_cards_practiced": 10,
"score": 0.8,
},
{
"num_cards_practiced": 10,
"score": 0.6,
},
{
"num_cards_practiced": 10,
"score": 0.5,
}
]
-
Modify your function
write_score_info()which writes progress information toprogress.txtto instead write toprogress.json. Unlike previously, you will not be able to simply append the current session data to the file. Instead, you need to first load existing data fromprogress.jsonor create an empty list (if it is the first session). After the dictionary for the current session is appended to the list of previous sessions, it can be written back out toprogress.json -
Modify your
get_progress_history()so it fetches the list of dictionaries with progress information from theprogress.jsonfile. If the file is not found or there is no progress information yet available, an empty list is returned.
Bonus Task : Extend your dictionary structure to capture some other information from each session. You might also like to amend your code for displaying history to make use of this additional data or change the display message itself to expose it directly to the user.
Run and check: Run your code in the terminal to make sure it works with the command
python flashcards_app`
- Practice flashcards
- Check your
progress.jsonfile to see if the values have been saved correctly. - You should be able to see your progress history as you could previously with
progress.txt
Read through and add comments: Add any comments in your code that will help you understand it when you come back to it later.
Save your progress: Commit with message “EXERCISE 21: Add progress tracking with JSON” and save your work to Github with the standard Git workflow.
(Re)read the guides:
- Dictionaries
- List of dictionaries
- What is JSON
- Reading and writing JSON
- Defining and calling functions (revisited)
Example solution
flashcards_app.py
# Created by: Alex Ubuntu
# Date: 01.01.2026
# Purpose: A personal flashcard trainer to help with learning
# Import the random module
import random
# Import json module
import json
### LOAD FLASHCARDS
# Set flashcards list from file
# Initialize empty list
flashcards_list = []
file_separator = ','
# Open and read the file
flashcard_file = "flashcards.txt"
try:
with open(flashcard_file, 'r') as file:
lines = file.readlines()
except:
# Handle scenario where file is not found
# and display comprehensible message to user.
print(f"The file {flashcard_file} is missing. Please add it to initialise your flashcards.")
exit()
# Process each line
# Each line has: question,answer
for line in lines:
# Remove whitespace/newlines
line = line.strip()
# Extract question and answer
# with pattern matching assuming two comma
# separated values in a row:
question, answer = line.split(file_separator)
# Add to list
flashcards_list.append((question, answer))
# Confirm loaded
print(f"{len(flashcards_list)} flashcards loaded!")
# Set flashcards dictionary from file
# Initialize empty dictionary
flashcards = {}
file_separator = ','
### INITIAL INTERACTION WITH USER
# Welcome message
print("Welcome to your personal flashcard trainer!")
# Absolute maximum number of cards so that the user can't ask for too many
ABSOLUTE_MAX_CARDS = 100
DEFAULT_MAX_CARDS = 20
# Fetch from user and save to variable
name = input("What is your name? ")
max_cards = DEFAULT_MAX_CARDS # Set a default value for max_cards
# Confirm name
print(f"\nMy name is {name}")
# Card and score variables
num_cards_completed = 0
num_cards_correct = 0
score = 0
## FUNCTIONS
# Function to calculate and display score information
def display_score_info():
# Handle case where no cards have been completed yet.
if num_cards_completed <=0:
print("You need to practice before you can get a score.")
else:
# Calculate score
score = (num_cards_correct/num_cards_completed) * 100
print(f"\nYou have answered {num_cards_correct} out of {num_cards_completed} correctly. Your score is {score}%.")
# Function to write score information to file
def write_score_info():
# Calculate score
score = (num_cards_correct/num_cards_completed) * 100
data_to_append = {
"num_cards_practiced": num_cards_completed,
"score": score,
}
# Initialise user_data as empty dictionary
user_data = {}
# Initialise score_data as empty dictionary
score_data = []
# Try opening the json file
try:
with open('progress.json', 'r') as file:
# Load file contents if found
score_data = json.load(file)
except FileNotFoundError:
# Otherwise do nothing so score_data remains an empty list
pass
# Append latest session data
score_data.append(data_to_append)
# Write out score_data with the latest session data appended
# to json file
with open('progress.json', 'w') as file:
json.dump(score_data, file, indent=4)
# Fetches progress history from json file
# Empty list returned if history or file not found
def get_progress_history():
score_data = []
# Open and read the file
try:
with open(user_filename, 'r') as file:
# Load file contents if found
user_data = json.load(file)
# Use safe extraction to get the progress_history
# containing the list of score data for different sessions.
# If it isn't there, set to empty list.
return user_data.get("progress_history", [])
except FileNotFoundError:
return []
### APP MENU LOOP
while True:
print("\nSelect an option by entering a number")
print("1: Set the number of cards you wish to practice")
print("2: Start flashcards")
print("3: Show the current score")
print("4: View progress history") # Option to view progress history
print("5: Exit")
choice = input("\nChoose an option: ")
if choice == "1":
while True:
# More stringent validation for input for maximum number of cards
# Fetch input from user but don't attempt to convert the input string
# to int until certain it will work
entered_max_cards = input("\nHow many cards would you like to practice each session? ")
# Check if input string represents an integer
if entered_max_cards.isdigit():
# Convert to integer data type
entered_max_cards = int(entered_max_cards)
# Check if value is within the value range
# (between 1 and the absolute maximum number of cards)
if entered_max_cards > 0 and entered_max_cards < ABSOLUTE_MAX_CARDS:
# Set the max_cards variable with the user's preference
max_cards = entered_max_cards
# Confirm number maximum number of cards per session
print(f"\nI want to practice at most {max_cards} cards per session")
break
else:
## Let the user know what the range should be (Bonus task).
print(f"\nPlease enter a valid number bewteen 1 and {ABSOLUTE_MAX_CARDS}.")
else:
print(f"\nPlease enter a whole number number over 0.")
elif choice == "2":
# Reset the number of cards completed for each session
num_cards_completed = 0
num_cards_correct = 0
# Iterate through flashcards list of tuples
# Each tuple element of the list contains a question, answer pair
# Using a for loop means that the number of flashcards displayed
# is limited by the number of items in flashcards so the user
# may end up practicing fewer cards than they specified
for q, a in flashcards_list:
# Ask user question and save response into variable
user_answer = input(f"\nQuestion: {q}\n")
# Increment the count of cards completed
num_cards_completed += 1
# Display user's answer and correct answer
print(f"Your answer: {user_answer}, Correct answer: {a}")
# Response deemed to be correct even if given in different case
if user_answer.lower() == a.lower():
# Increment count of cards answered correctly
num_cards_correct += 1
print("Correct")
else:
print("Incorrect")
# Check if number of cards completed has hit the user's
# preferred maximum number of cards
if num_cards_completed >= max_cards:
break
print("\nWell done on completing your practice session!")
# Write score info out with each session completed
write_score_info()
# Display score info at the end of a session
display_score_info()
elif choice == "3":
display_score_info()
elif choice == "4":
#Session 4: 10 cards completed, score 60%
# Open and read the file
with open('progress.txt', 'r') as file:
lines = file.readlines()
print("How many sessions would you like to view?")
num_sessions = int(input("Enter the number or 0 to show all: "))
print("What order would you like to see them in?")
print("1: Oldest")
print("2: Most recent")
order = input("Choose an option by entering 1 or 2: ")
# If the user wants to see all sessions, set num_sessions
# to the number of sessions available
if num_sessions==0:
num_sessions = len(lines)
selected_lines = lines
if order =="2":
# Need to reverse order of list if
# most recent first is selected
selected_lines = selected_lines[::-1]
selected_lines = selected_lines[:num_sessions]
#TODO: Add handling of invalid inputs
# Process each line
# Each line has: num_cards_completed,num_cards_correct,score
# Enumerate to get the session number - assumes the
# row number corresponds to the session number
for i, line in enumerate(selected_lines):
# Remove whitespace/newlines
line = line.strip()
# Split by ',' separator
parts = line.split(file_separator)
# Extract num_cards_completed,num_cards_correct,score
# Name these variables something different
# (e.g. saved_cards_completed, saved_cards_correct, saved_score)
# so they don't write over those being used by the rest of the program
saved_cards_completed = parts[0]
saved_cards_correct = parts[1]
saved_score = parts[2]
print(f"Session {i}: {saved_cards_completed} cards completed, score {saved_score}%")
elif choice == "5":
print(f"We hope you enjoyed your practice session today, {name}.")
display_score_info()
# Display feedback message based on score
if score > 90 and score <= 100:
print("Excellent work!")
elif score > 70 and score <= 90:
print("Good job!")
elif score > 50 and score <= 70:
print("Keep practicing!")
elif score > 0 and score <= 50:
print("Need more study time!")
print("Look forward to seeing you again soon!")
break
else:
# Clarify instruction to get valid input
print("\nInvalid value entered. Please make sure you enter just a single digit (no other words): 1, 2, 3 or 4, to select an option.")
Take a break: 🌳
