6日目のゴール
6日目のテーマは
「JSONに“少しリッチなデータ構造”を持たせて、アプリの表現力を上げる」 ことです。
ここまでであなたはすでに、
JSONに複数データを保存できる
起動時に復元できる
追加・編集・削除・検索・フィルタ・ソートができる
コードを役割ごとに整理できる
というところまで来ています。
6日目では一歩進んで、
メモに「タグ」や「カテゴリ」を持たせる
JSONに“入れ子のデータ”を保存する
データの「形(構造)」を意識して設計する
という、「データ設計」の感覚を育てていきます。
今日の題材:メモを“ただの文字列”から“情報のかたまり”へ
メモにどんな情報を持たせるか考える
今までのメモは、こうでした。
{
"text": "牛乳を買う",
"created_at": "2025-05-05 10:30",
"important": False
}
これはこれでシンプルで良いのですが、
現実の「メモ」はもっと情報を持っています。
例えば、こんな項目が考えられます。
カテゴリ(仕事 / プライベート / 勉強 など)
タグ(複数可:買い物, アイデア, 読書 など)
完了フラグ(やり終えたタスク系メモかどうか)
これをPythonの辞書で表現すると、こうなります。
memo = {
"text": "牛乳を買う",
"created_at": "2025-05-05 10:30",
"important": False,
"category": "生活",
"tags": ["買い物", "食料品"],
"done": False
}
PythonJSONにすると、ほぼそのままです。
{
"text": "牛乳を買う",
"created_at": "2025-05-05 10:30",
"important": false,
"category": "生活",
"tags": ["買い物", "食料品"],
"done": false
}
ここで大事なのは、
「メモ=文字列」ではなく「メモ=情報のまとまり」
として扱い始めることです。
データ構造を変えるときの考え方
「既存のデータにどう影響するか」を意識する
メモの形を変えるときは、
次の二つを必ず意識します。
新しいメモを作るとき、どの項目を必須にするか
既存の JSON ファイルとの互換性をどうするか
例えば、すでに memos.json に
{
"text": "Pythonの勉強をする",
"created_at": "2025-05-04 21:00",
"important": true
}
のようなデータが入っている状態で、
新しく category や tags、done を追加すると、
古いデータにはそのキーがありません。
このときに大事なのは、
「キーがない場合はどう扱うか」を決めておくことです。
例えば、
category がなければ “未分類” とみなす
tags がなければ空リスト [] とみなす
done がなければ False とみなす
といったルールを決めておくと、
古いデータも壊さずに扱えます。
メモの新しい形をコードに落とし込む
メモ作成処理を拡張する
メモ追加関数を、
新しい構造に合わせて書き直します。
from datetime import datetime
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 category is None or 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 input_and_add_memo(memos):
text = input("メモ内容: ")
category = input("カテゴリ(未入力なら未分類): ")
tags_text = input("タグ(カンマ区切りで複数指定可。例: 買い物, 食料品): ")
if tags_text.strip() == "":
tags = []
else:
tags = [t.strip() for t in tags_text.split(",")]
memo = create_memo(text, category, tags)
if memo is None:
print("空のメモは追加しません。")
return
memos.append(memo)
print("メモを追加しました。")
Pythonここで深掘りしたいポイントは三つあります。
一つ目は、「入力値をそのまま信じない」ことです。
カテゴリが空なら "未分類" に置き換え、
タグは空文字を取り除いています。
これは「データの一貫性」を守るための大事な考え方です。
二つ目は、「tags を必ずリストにしている」ことです。
JSONでは配列、Pythonではリストとして扱うことで、
後で「タグで検索」「タグでフィルタ」がしやすくなります。
三つ目は、「メモの形をここで統一している」ことです。
アプリのどこからメモを見ても、
必ず text / created_at / important / category / tags / done が存在する、
という前提で話ができるようになります。
表示を“情報量のある形”に変える
カテゴリ・タグ・完了状態を見せる
メモの構造がリッチになったので、
表示もそれに合わせて変えます。
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ここでの重要ポイントを丁寧に見ていきます。
memo.get("important") や memo.get("done") を使っているのは、
古い JSON にそのキーがない場合でもエラーにしないためです。get は、キーがなければ None を返します。
important_mark と done_mark で、
「重要かどうか」「完了しているかどうか」を
一目で分かる記号に変えています。
カテゴリは <仕事> のように表示し、
タグは [買い物 / 食料品] のように並べています。
このように、
「内部のデータ構造」と「画面に見せる形」を分ける
というのが、とても大事な考え方です。
タグでフィルタする
「タグを含むメモだけ」を取り出す
タグを持たせたら、
「特定のタグを持つメモだけ見たい」という欲が出てきます。
そのためのフィルタ関数を作ります。
def filter_memos_by_tag(memos, tag):
result = []
for memo in memos:
tags = memo.get("tags", [])
if tag in tags:
result.append(memo)
return result
Pythonユーザー入力と組み合わせると、こうなります。
def input_and_show_by_tag(memos):
tag = input("表示したいタグを入力してください: ")
if tag == "":
print("タグが空です。")
return
filtered = filter_memos_by_tag(memos, tag)
if not filtered:
print(f"タグ「{tag}」を持つメモはありません。")
return
print(f"=== タグ「{tag}」のメモ ===")
show_memos(filtered)
Pythonここでのポイントは、
タグは「1対多」の関係(メモ1件にタグ複数)なので、tags をリストとして扱い、if tag in tags: で判定していることです。
カテゴリでグループ表示する
「カテゴリごとにまとめて表示する」感覚
カテゴリを持たせたら、
「カテゴリごとにメモを眺めたい」というニーズも出てきます。
まずは、カテゴリ一覧を集めます。
def collect_categories(memos):
categories = set()
for memo in memos:
category = memo.get("category", "未分類")
categories.add(category)
return sorted(categories)
Python次に、カテゴリごとにメモを表示します。
def show_memos_grouped_by_category(memos):
if not memos:
print("メモはまだありません。")
return
categories = collect_categories(memos)
for category in categories:
print(f"\n=== カテゴリ: {category} ===")
group = [m for m in memos if m.get("category", "未分類") == category]
show_memos(group)
Pythonここでの重要ポイントは、
「カテゴリ一覧を先に集めてから、カテゴリごとにフィルタする」
という流れです。
これは、データベースや集計処理でもよく使うパターンで、
「グルーピング」の基本的な考え方です。
完了フラグをタスク風に使う
メモを「やることリスト」としても使えるようにする
done フラグを持たせたので、
メモを「タスクっぽく」扱うこともできます。
完了フラグを切り替えるロジックは、重要フラグとほぼ同じです。
def toggle_done(memos, index):
if index < 0 or index >= len(memos):
return False
memo = memos[index]
memo["done"] = not memo.get("done", False)
return True
Pythonユーザー入力と組み合わせると、こうなります。
def input_and_toggle_done(memos):
if not memos:
print("完了フラグを変更できるメモがありません。")
return
show_memos(memos)
num_text = input("完了フラグを切り替えるメモの番号を入力してください: ")
if not num_text.isdigit():
print("数字を入力してください。")
return
num = int(num_text)
index = num - 1
if toggle_done(memos, index):
print("完了フラグを切り替えました。")
else:
print("その番号のメモはありません。")
Pythonさらに、「未完了のメモだけ」「完了済みのメモだけ」を表示することもできます。
def filter_done_memos(memos, done=True):
result = []
for memo in memos:
if memo.get("done", False) == done:
result.append(memo)
return result
Pythondef show_done_memos(memos):
done_memos = filter_done_memos(memos, done=True)
if not done_memos:
print("完了したメモはありません。")
return
show_memos(done_memos)
def show_undone_memos(memos):
undone_memos = filter_done_memos(memos, done=False)
if not undone_memos:
print("未完了のメモはありません。")
return
show_memos(undone_memos)
Python6日目のミニアプリ:タグ・カテゴリ・完了付きメモJSONアプリ
全体像をまとめて眺める
ここまでの内容を組み合わせると、
6日目のアプリはこんな形になります(主要部分のみ)。
import json
import os
from datetime import datetime
FILENAME = "memos.json"
def load_memos():
if not os.path.exists(FILENAME):
return []
with open(FILENAME, "r", encoding="utf-8") as f:
return json.load(f)
def save_memos(memos):
with open(FILENAME, "w", encoding="utf-8") as f:
json.dump(memos, f, ensure_ascii=False, indent=2)
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 category is None or 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
def input_and_add_memo(memos):
text = input("メモ内容: ")
category = input("カテゴリ(未入力なら未分類): ")
tags_text = input("タグ(カンマ区切り。例: 買い物, 食料品): ")
if tags_text.strip() == "":
tags = []
else:
tags = [t.strip() for t in tags_text.split(",")]
memo = create_memo(text, category, tags)
if memo is None:
print("空のメモは追加しません。")
return
memos.append(memo)
print("メモを追加しました。")
def toggle_important(memos, index):
if index < 0 or index >= len(memos):
return False
memo = memos[index]
memo["important"] = not memo.get("important", False)
return True
def toggle_done(memos, index):
if index < 0 or index >= len(memos):
return False
memo = memos[index]
memo["done"] = not memo.get("done", False)
return True
def filter_memos_by_tag(memos, tag):
result = []
for memo in memos:
tags = memo.get("tags", [])
if tag in tags:
result.append(memo)
return result
def collect_categories(memos):
categories = set()
for memo in memos:
category = memo.get("category", "未分類")
categories.add(category)
return sorted(categories)
def show_memos_grouped_by_category(memos):
if not memos:
print("メモはまだありません。")
return
categories = collect_categories(memos)
for category in categories:
print(f"\n=== カテゴリ: {category} ===")
group = [m for m in memos if m.get("category", "未分類") == category]
show_memos(group)
def filter_done_memos(memos, done=True):
result = []
for memo in memos:
if memo.get("done", False) == done:
result.append(memo)
return result
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メニュー側で、
タグで絞る
カテゴリごとに表示する
完了・未完了だけ表示する
といった選択肢を用意すれば、
かなり“使えるメモアプリ”になってきます。
6日目で絶対に押さえてほしい本質
「JSONは、ただの保存形式ではなく“データ設計のキャンバス”」
今日の一番大事なポイントは、
メモにどんな情報を持たせるか
その情報をどう表現するか(文字列・リスト・フラグなど)
古いデータとの整合性をどう保つか
といった「データの形」を意識し始めたことです。
JSONは、
Pythonの dict / list をそのまま外に出したものです。
だからこそ、
構造を工夫すればするほど、
アプリの表現力も一緒に上がっていきます。
もし「サブメモを持たせたい」「チェックリストを入れたい」など
さらに複雑な構造を試したくなったら、
それはもう“データモデリング”の世界に足を踏み入れています。
7日目は、その集大成として
「完成形アプリ」と「データの一生」をまとめていきましょう。


