Python | 1 日 120 分 × 7 日アプリ学習:JSON保存アプリ(中級編)

Web APP Python
スポンサーリンク

6日目のゴール

6日目のテーマは
「JSON に“少しリッチなデータ構造”を持たせて、アプリの表現力を上げる」 ことです。

ここまでであなたはすでに、

JSON に複数データを保存できる
検索・更新・削除・フィルタ・ソートができる
コードを役割ごとに整理できる

というところまで来ています。

6日目では一歩進んで、

タスクに「優先度」や「締切日」を持たせる
JSON に“入れ子のデータ”を保存する
データの「形(構造)」を意識して設計する

という、「データ設計」の感覚を育てていきます。


今日の題材:ToDo を“ただの文字列”から“情報のかたまり”へ

タスクにどんな情報を持たせるか考える

今までのタスクは、こうでした。

{"title": "本を30分読む", "done": False}
Python

これはこれでシンプルで良いのですが、
現実の「やること」は、もっと情報を持っています。

例えば、こんな項目が考えられます。

タイトル(何をするか)
優先度(高・中・低)
締切日(いつまでにやるか)
メモ(補足情報)
完了したかどうか

これを Python の辞書で表現すると、こうなります。

task = {
    "title": "本を30分読む",
    "priority": "high",
    "due": "2025-05-10",
    "memo": "技術書を中心に読む",
    "done": False
}
Python

JSON にすると、ほぼそのままです。

{
  "title": "本を30分読む",
  "priority": "high",
  "due": "2025-05-10",
  "memo": "技術書を中心に読む",
  "done": false
}

ここで大事なのは、
「タスク=文字列」ではなく「タスク=情報のまとまり」
として扱い始めることです。


データ構造を変えるときの考え方

「既存のアプリにどう影響するか」を意識する

タスクの形を変えるときは、
次の二つを必ず意識します。

新しいタスクを作るとき、どの項目を必須にするか
既存の JSON ファイルとの互換性をどうするか

例えば、すでに tasks.json

{"title": "メールを整理する", "done": true}
Python

のようなデータが入っている状態で、
新しく prioritydue を追加すると、
古いデータにはそのキーがありません。

このときに大事なのは、

「キーがない場合はどう扱うか」を決めておくことです。

例えば、

priority がなければ “normal” とみなす
due がなければ “未設定” と表示する

といったルールを決めておくと、
古いデータも壊さずに扱えます。


新しいタスクの形をコードに落とし込む

add_task を拡張する

タスク追加関数を、
新しい構造に合わせて書き直します。

def add_task(tasks):
    title = input("タスク名: ")
    priority = input("優先度(high / normal / low): ")
    due = input("締切日(例: 2025-05-10。未設定なら空のまま): ")
    memo = input("メモ(任意): ")

    if priority not in ("high", "normal", "low"):
        print("優先度が不正です。normal として扱います。")
        priority = "normal"

    if due == "":
        due = None

    if memo == "":
        memo = None

    task = {
        "title": title,
        "priority": priority,
        "due": due,
        "memo": memo,
        "done": False
    }

    tasks.append(task)
    print("タスクを追加しました。")
Python

ここで深掘りしたいポイントは三つあります。

一つ目は、「入力値をそのまま信じない」ことです。
priority"high" / "normal" / "low" 以外だったら、
自動的に "normal" にしてしまっています。
これは「データの一貫性」を守るための大事な考え方です。

二つ目は、「空文字を None に変換している」ことです。
duememo が空のままだと、
後で「未設定なのか、本当に空文字なのか」が分かりにくくなります。
None にしておくと、「値がない」という意味がはっきりします。

三つ目は、「task の形をここで統一している」ことです。
アプリのどこからタスクを見ても、
必ず title / priority / due / memo / done が存在する、
という前提で話ができるようになります。


表示を“情報量のある形”に変える

show_tasks をリッチ表示にする

タスクの構造がリッチになったので、
表示もそれに合わせて変えます。

def show_tasks(tasks):
    if not tasks:
        print("タスクはありません。")
        return

    print("=== タスク一覧 ===")
    for task in tasks:
        mark = "✔" if task["done"] else "✗"

        if task.get("priority") == "high":
            priority_label = "[高]"
        elif task.get("priority") == "low":
            priority_label = "[低]"
        else:
            priority_label = "[中]"

        due = task.get("due")
        if due is None:
            due_label = "(締切: なし)"
        else:
            due_label = f"(締切: {due})"

        memo = task.get("memo")
        if memo:
            memo_label = f" - メモ: {memo}"
        else:
            memo_label = ""

        print(f"{mark} {priority_label} {task['title']} {due_label}{memo_label}")
Python

ここでの重要ポイントを丁寧に見ていきます。

task.get("priority") を使っているのは、
古い JSON に priority がない場合でもエラーにしないためです。
get は、キーがなければ None を返します。

priority_label では、
"high" → [高]
"low" → [低]
それ以外 → [中]
というふうに、人間に分かりやすいラベルに変換しています。

dueNone のときは「締切なし」と表示し、
値があるときはその日付を表示します。

memo があるときだけ、メモを表示します。
ないときは何も表示しません。

このように、
「内部のデータ構造」と「画面に見せる形」を分ける
というのが、とても大事な考え方です。


優先度で並び替える

priority をソートキーに使う

優先度を持たせたら、
「高いものを上に表示したい」という欲が出てきます。

そのためのソート関数を作ります。

def sort_tasks_by_priority(tasks):
    def priority_value(task):
        p = task.get("priority", "normal")
        if p == "high":
            return 0
        elif p == "normal":
            return 1
        else:
            return 2

    return sorted(tasks, key=priority_value)
Python

ここでのポイントは、

priority をそのままソートに使うのではなく、
"high" → 0, "normal" → 1, "low" → 2
という「数値」に変換していることです。

ソートは「小さいものが先に来る」ので、
0 → 1 → 2 の順、つまり
「高 → 中 → 低」の順に並びます。

task.get("priority", "normal") としているのは、
古いデータで priority がない場合に "normal" とみなすためです。


締切日で並び替える

文字列の日付でも「YYYY-MM-DD」ならそのままソートできる

締切日 due"2025-05-10" のような文字列で持っています。

この形式(YYYY-MM-DD)なら、
実は文字列のままソートしても日付順になります。

def sort_tasks_by_due(tasks):
    def due_value(task):
        due = task.get("due")
        if due is None:
            return "9999-12-31"
        return due

    return sorted(tasks, key=due_value)
Python

ここでの工夫は、
締切がないタスクに "9999-12-31" を割り当てていることです。

これにより、

締切があるタスクが先
締切なしのタスクが後

という並びになります。

本格的にやるなら datetime モジュールを使いますが、
中級編の段階では「文字列の日付をうまく使う」という発想で十分です。


6日目のミニアプリ:リッチタスク JSON アプリ

全体像をまとめて見る

ここまでの内容を組み合わせると、
6日目のアプリはこんな形になります。

import json
import os

FILENAME = "tasks.json"

def load_tasks():
    if not os.path.exists(FILENAME):
        return []
    with open(FILENAME, "r", encoding="utf-8") as f:
        return json.load(f)

def save_tasks(tasks):
    with open(FILENAME, "w", encoding="utf-8") as f:
        json.dump(tasks, f, ensure_ascii=False, indent=2)

def add_task(tasks):
    title = input("タスク名: ")
    priority = input("優先度(high / normal / low): ")
    due = input("締切日(例: 2025-05-10。未設定なら空のまま): ")
    memo = input("メモ(任意): ")

    if priority not in ("high", "normal", "low"):
        print("優先度が不正です。normal として扱います。")
        priority = "normal"

    if due == "":
        due = None
    if memo == "":
        memo = None

    task = {
        "title": title,
        "priority": priority,
        "due": due,
        "memo": memo,
        "done": False
    }

    tasks.append(task)
    print("タスクを追加しました。")

def find_task(tasks, title):
    for task in tasks:
        if task["title"] == title:
            return task
    return None

def update_task_done(tasks):
    title = input("完了状態を変更するタスク名: ")
    task = find_task(tasks, title)
    if task is None:
        print("タスクが見つかりません。")
        return

    done_text = input("完了なら1、未完了なら0: ")
    task["done"] = (done_text == "1")
    print("完了状態を更新しました。")

def show_tasks(tasks):
    if not tasks:
        print("タスクはありません。")
        return

    print("=== タスク一覧 ===")
    for task in tasks:
        mark = "✔" if task["done"] else "✗"

        p = task.get("priority")
        if p == "high":
            priority_label = "[高]"
        elif p == "low":
            priority_label = "[低]"
        else:
            priority_label = "[中]"

        due = task.get("due")
        if due is None:
            due_label = "(締切: なし)"
        else:
            due_label = f"(締切: {due})"

        memo = task.get("memo")
        if memo:
            memo_label = f" - メモ: {memo}"
        else:
            memo_label = ""

        print(f"{mark} {priority_label} {task['title']} {due_label}{memo_label}")

def sort_tasks_by_priority(tasks):
    def priority_value(task):
        p = task.get("priority", "normal")
        if p == "high":
            return 0
        elif p == "normal":
            return 1
        else:
            return 2
    return sorted(tasks, key=priority_value)

def sort_tasks_by_due(tasks):
    def due_value(task):
        due = task.get("due")
        if due is None:
            return "9999-12-31"
        return due
    return sorted(tasks, key=due_value)

def main():
    tasks = load_tasks()
    print("JSONファイルから読み込みました。")

    while True:
        print("\n=== メニュー ===")
        print("1: タスク追加")
        print("2: 全タスク表示")
        print("3: 優先度順で表示")
        print("4: 締切日順で表示")
        print("5: 完了状態を変更")
        print("0: 終了")

        choice = input("番号を選んでください: ")

        if choice == "1":
            add_task(tasks)
            save_tasks(tasks)

        elif choice == "2":
            show_tasks(tasks)

        elif choice == "3":
            sorted_tasks = sort_tasks_by_priority(tasks)
            show_tasks(sorted_tasks)

        elif choice == "4":
            sorted_tasks = sort_tasks_by_due(tasks)
            show_tasks(sorted_tasks)

        elif choice == "5":
            update_task_done(tasks)
            save_tasks(tasks)

        elif choice == "0":
            save_tasks(tasks)
            print("終了します。")
            break

        else:
            print("正しい番号を入力してください。")

if __name__ == "__main__":
    main()
Python

6日目で絶対に押さえてほしい本質

「JSONは、ただの保存形式ではなく“データ設計のキャンバス”」

今日の一番大事なポイントは、

タスクにどんな情報を持たせるか
その情報をどう表現するか(文字列・None・ラベルなど)
古いデータとの整合性をどう保つか

といった「データの形」を意識し始めたことです。

JSON は、
Python の dict / list をそのまま外に出したものです。
だからこそ、

構造を工夫すればするほど、
アプリの表現力も一緒に上がっていきます。

もし「タグをつけたい」「サブタスクを持たせたい」など
さらに複雑な構造を試したくなったら、
それはもう“データモデリング”の世界に足を踏み入れています。
7日目は、その集大成としてまとめていきましょう。

タイトルとURLをコピーしました