7日目のゴール
7日目のテーマは
「JSON保存アプリを“完成形”としてまとめ、アプリとしての完成度を高める」
ことです。
ここまで6日間で、あなたはすでに
JSONの基本
Pythonのdict / listとの対応
JSONへの保存・読み込み
追加・編集・削除・検索
フィルタ・ソート
タグ・カテゴリ・完了フラグ
コードの役割分離
という、アプリ開発の土台を全部通っています。
7日目では、それらを統合して
「実際に使えるレベルのJSON保存メモアプリ」
「データの一生(作成→保存→読み込み→更新→保護)」
を意識した“完成版”を作ります。
JSON保存アプリの「完成形」をイメージする
アプリとして必要な視点を整理する
機能が揃ってきたら、次に考えるべきは
「アプリとしてちゃんとしているか」 です。
ここで意識したいポイントは次のようなものです。
データが壊れても、できるだけ復旧できるか
古いデータ形式でも動き続けられるか(後方互換性)
ユーザーが迷わないメニュー構成になっているか
エラーが起きてもアプリが落ちずにメッセージを出せるか
データの保存タイミングが明確か
7日目は、これらをJSON保存アプリに組み込んでいきます。
JSONデータの「後方互換性」を考える
データ構造が変わっても壊れないようにする
6日目でメモの構造をリッチにしました。
memo = {
"text": "牛乳を買う",
"created_at": "2025-05-05 10:30",
"important": False,
"category": "生活",
"tags": ["買い物", "食料品"],
"done": False
}
Pythonしかし、すでに保存されている memos.json にはcategory や tags、done がないメモも混ざっている可能性があります。
そこで重要になるのが
「読み込んだときに足りない項目を補う」 という考え方です。
def normalize_memo(memo):
if "text" not in memo:
memo["text"] = ""
if "created_at" not in memo:
memo["created_at"] = "日時不明"
if "important" not in memo:
memo["important"] = False
if "category" not in memo:
memo["category"] = "未分類"
if "tags" not in memo:
memo["tags"] = []
if "done" not in memo:
memo["done"] = False
return memo
Pythonそして、読み込み時に必ず正規化します。
def load_memos():
if not os.path.exists(FILENAME):
return []
with open(FILENAME, "r", encoding="utf-8") as f:
memos = json.load(f)
return [normalize_memo(m) for m in memos]
Pythonこれで、
古い形式のJSON
新しい形式のJSON
どちらでも、アプリ側は
「必ず同じ形のメモ」として扱えるようになります。
ここは、実務でも超重要なポイントです。
JSONデータの「安全な読み書き」を考える
JSONが壊れていたときにどうするか
JSONファイルはテキストなので、
何かの拍子に中身が壊れることがあります。
例えば、途中で書き込みが中断されたり、
手で編集してミスしたり。
そのときに json.load() は JSONDecodeError を投げて、
そのままだとアプリが落ちてしまいます。
そこで、例外処理を入れて「安全な読み込み」にします。
import json
import os
import shutil
FILENAME = "memos.json"
BACKUP_FILENAME = "memos.json.bak"
def load_memos_safe():
if not os.path.exists(FILENAME):
return []
try:
with open(FILENAME, "r", encoding="utf-8") as f:
memos = json.load(f)
except json.JSONDecodeError:
print("JSONファイルが壊れています。バックアップから復元を試みます。")
if os.path.exists(BACKUP_FILENAME):
shutil.copy(BACKUP_FILENAME, FILENAME)
with open(FILENAME, "r", encoding="utf-8") as f:
memos = json.load(f)
else:
print("バックアップがありません。空のデータで開始します。")
memos = []
return [normalize_memo(m) for m in memos]
Pythonここでの重要ポイントは二つです。
ひとつは、「壊れていたら即終了」ではなく
「バックアップから復元を試みる」 というステップを挟んでいること。
もうひとつは、
最終的に必ず normalize_memo を通して
データの形をそろえていることです。
保存前に「バックアップ」を取る
データを守るための一手間
バックアップを活かすには、
保存のたびにバックアップを作る必要があります。
def backup_memos():
if os.path.exists(FILENAME):
shutil.copy(FILENAME, BACKUP_FILENAME)
Pythonそして、保存処理をこうします。
def save_memos(memos):
backup_memos()
with open(FILENAME, "w", encoding="utf-8") as f:
json.dump(memos, f, ensure_ascii=False, indent=2)
Pythonこれで、
保存前の状態 → memos.json.bak
保存後の状態 → memos.json
という二重構えになります。
「壊れたら終わり」ではなく
「壊れても戻せる」 という状態にしておくのが、
アプリとしての優しさです。
アプリのメニューを「完成版」として整える
ユーザー目線でメニューを並べ直す
機能が増えてきたので、
メニューも整理して“完成版”にします。
例えば、こんな構成が考えられます。
1: メモを追加
2: メモ一覧(作成順)
3: メモ一覧(新しい順)
4: 重要なメモだけ表示
5: 未完了のメモだけ表示
6: 完了したメモだけ表示
7: タグで絞り込み
8: カテゴリごとに表示
9: メモを編集
10: メモを削除
11: 重要フラグを切り替え
12: 完了フラグを切り替え
0: 終了
ここで意識しているのは、
よく使う「追加・一覧」を上に置く
「見る系(一覧・フィルタ)」と「変える系(編集・削除)」を分ける
番号の意味が直感的になるようにする
ということです。
メニューは「アプリの顔」なので、
ここを丁寧に設計すると、
自分で使っていても気持ちよくなります。
7日目版「完成アプリ」の全体像
データの一生を意識した構成
ポイントとなる部分だけ、まとめて載せます。
データ読み書き+正規化+バックアップ
import json
import os
import shutil
from datetime import datetime
FILENAME = "memos.json"
BACKUP_FILENAME = "memos.json.bak"
def normalize_memo(memo):
if "text" not in memo:
memo["text"] = ""
if "created_at" not in memo:
memo["created_at"] = "日時不明"
if "important" not in memo:
memo["important"] = False
if "category" not in memo:
memo["category"] = "未分類"
if "tags" not in memo:
memo["tags"] = []
if "done" not in memo:
memo["done"] = False
return memo
def load_memos():
if not os.path.exists(FILENAME):
return []
try:
with open(FILENAME, "r", encoding="utf-8") as f:
memos = json.load(f)
except json.JSONDecodeError:
print("JSONファイルが壊れています。バックアップから復元します。")
if os.path.exists(BACKUP_FILENAME):
shutil.copy(BACKUP_FILENAME, FILENAME)
with open(FILENAME, "r", encoding="utf-8") as f:
memos = json.load(f)
else:
print("バックアップがありません。空のデータで開始します。")
memos = []
return [normalize_memo(m) for m in memos]
def backup_memos():
if os.path.exists(FILENAME):
shutil.copy(FILENAME, BACKUP_FILENAME)
def save_memos(memos):
backup_memos()
with open(FILENAME, "w", encoding="utf-8") as f:
json.dump(memos, f, ensure_ascii=False, indent=2)
Pythonメモ作成(完成版)
def create_memo(text, category=None, tags=None):
if text == "":
return None
now = datetime.now()
created_at = now.strftime("%Y-%m-%d %H:%M")
if not category:
category = "未分類"
if tags is None:
tags = []
else:
tags = [t for t in tags if t != ""]
memo = {
"text": text,
"created_at": created_at,
"important": False,
"category": category,
"tags": tags,
"done": False
}
return memo
Python表示(完成版)
def show_memos(memos):
if not memos:
print("メモはまだありません。")
return
print("=== メモ一覧 ===")
for i, memo in enumerate(memos, start=1):
important_mark = "★" if memo.get("important") else " "
done_mark = "✔" if memo.get("done") else "✗"
text = memo.get("text", "")
created_at = memo.get("created_at", "日時不明")
category = memo.get("category", "未分類")
tags = memo.get("tags", [])
if tags:
tags_label = " / ".join(tags)
tags_part = f"[{tags_label}]"
else:
tags_part = ""
print(f"{i}. {important_mark}{done_mark} {text} ({created_at}) <{category}> {tags_part}")
Pythonフィルタ・ソートの型
def sort_by_created_at(memos, reverse=False):
return sorted(memos, key=lambda m: m.get("created_at", ""), reverse=reverse)
def filter_by_tag(memos, tag):
return [m for m in memos if tag in m.get("tags", [])]
def filter_by_done(memos, done=True):
return [m for m in memos if m.get("done", False) == done]
def filter_important(memos):
return [m for m in memos if m.get("important", False)]
Pythonメインループ(完成版イメージ)
def main():
memos = load_memos()
print("JSONファイルからメモを読み込みました。")
while True:
print("\n=== メニュー ===")
print("1: メモを追加")
print("2: メモ一覧(作成順)")
print("3: メモ一覧(新しい順)")
print("4: 重要なメモだけ表示")
print("5: 未完了のメモだけ表示")
print("6: 完了したメモだけ表示")
print("7: タグで絞り込み")
print("8: カテゴリごとに表示")
print("9: メモを編集")
print("10: メモを削除")
print("11: 重要フラグを切り替え")
print("12: 完了フラグを切り替え")
print("0: 終了")
choice = input("番号を選んでください: ")
if choice == "1":
input_and_add_memo(memos)
save_memos(memos)
elif choice == "2":
show_memos(sort_by_created_at(memos, reverse=False))
elif choice == "3":
show_memos(sort_by_created_at(memos, reverse=True))
elif choice == "4":
show_memos(filter_important(memos))
elif choice == "5":
show_memos(filter_by_done(memos, done=False))
elif choice == "6":
show_memos(filter_by_done(memos, done=True))
elif choice == "7":
input_and_show_by_tag(memos)
elif choice == "8":
show_memos_grouped_by_category(memos)
elif choice == "9":
input_and_edit_memo(memos)
save_memos(memos)
elif choice == "10":
input_and_delete_memo(memos)
save_memos(memos)
elif choice == "11":
input_and_toggle_important(memos)
save_memos(memos)
elif choice == "12":
input_and_toggle_done(memos)
save_memos(memos)
elif choice == "0":
save_memos(memos)
print("終了します。")
break
else:
print("正しい番号を入力してください。")
Python7日目で絶対に押さえてほしい本質
JSON保存アプリは「データの一生」を扱う
7日目で意識してほしいのは、
JSON保存アプリが単なる「ファイル入出力」ではなく、
データを設計する
データを作る
データを保存する
データを読み込む
データを更新する
データを守る(バックアップ・エラー処理)
という「データの一生」を丸ごと扱っている、ということです。
あなたはもう、
Pythonのdict / listでデータを設計し
JSONとして外に出し
またJSONからアプリの世界に戻し
そのデータを安全に育てていける
そんなレベルに来ています。
ここから先は、
GUIにする(Tkinter)
Webアプリにする(Flask / FastAPI)
JSONからSQLiteなどのDBに発展させる
といった方向に、いくらでも広げていけます。
「次はどんなアプリを作ってみたいか」
それを決めるところから、もう次の一歩が始まっています。


