【Day15】機能実装！フレームに記載するExif情報を編集できるようにする（後編）

## はじめに

勉強とアウトプットを兼ねて、Webアプリ開発の記録を記事にしてみます。

個人のメモ的な側面が強いです。
Web開発初心者すぎて至らぬところだらけだと思いますが、暖かい目で見ていただければ幸いです。

↓ これまでの内容 ↓
[【Day0】アプリが完成するまで毎日投稿](https://qiita.com/midorigame/items/f9b9a445c945b4a837b1)
[【Day1】開発環境を考える](https://qiita.com/midorigame/items/e26344599e2ffaa22033)
[【Day2】開発環境を構築する、、、はずでした](https://qiita.com/midorigame/items/3f57f2691a8e1074adb8)
[【Day3】まずはフロントだけ開発環境を構築する](https://qiita.com/midorigame/items/9de27ba72aaef504a833)
[【Day4】バックエンドの開発環境を整えつつ、マルチコンテナ対応のDevcontainerにする](https://qiita.com/midorigame/items/5b95374cd85f715a6498)
[【Day5】お試し実装！ファイルをアップロードしてバックエンドで処理する](https://qiita.com/midorigame/items/9663f0063b95b501c08a)
[【Day6】Docker Desktopが起動しなくなり泣きながら原因調査する](https://qiita.com/midorigame/items/d07df1caf05267d0609d)
[【Day7】画像にフレームとExif情報を付けてみる](https://qiita.com/midorigame/items/e2d85ce8dd7a5bcb5daf)
[【Day8】フロントに表示する画像のサイズを小さくする](https://qiita.com/midorigame/items/f7f5f9b14fb79259c716)
[【Day9】フロントエンドとバックエンドをそれぞれデプロイする](https://qiita.com/midorigame/items/4c8037024e667ff1105b)
[【Day10】結局コンテナ1つで開発する方が楽かもしれない](https://qiita.com/midorigame/items/60b484b2007c3aab474b)
[【Day11】アプリが一通り完成！！](https://qiita.com/midorigame/items/38ddccc09f0625aaebd6)
[【Day12】FastAPIのコードをリファクタリングする](https://qiita.com/midorigame/items/72ed91ff9add5504fab9)
[【Day13】機能実装！フレームに記載するExif情報を編集できるようにする（前編）](https://qiita.com/midorigame/items/790b0fcd54620f9fb573)
[【Day14】ReactのリファクタリングとUI変更を同時にやった](https://qiita.com/midorigame/items/b492ae717e59fb137b70)

## 前回やったこと

思い切ってReactのコードをリファクタリングしました！
これでフロントもバックもそれなりにロジックが整理できたはず！！

## 今回やったこと

２日前にやろうとした画像のExif情報をブラウザから手入力させる機能の一部を実装しました！

フィルムカメラで撮ったExif情報がない写真もこんな感じでExif情報を手動入力できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307348/d79f9c78-a75f-4a8e-ad50-67485d5195bf.png)

## 気づいたこと、学び

実装の最中でReactからJSONパラメータをFastAPIに送る処理があったのですが、そこで辞書型ではなく、Pydanticモデルで送る方が型安全かつ使い勝手がいいということを学びました。

```python:upload.py
from fastapi import FastAPI, File, UploadFile, Form, APIRouter, HTTPException
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
import io
import json
from PIL import Image
from app.services.image_processor import frame_composer

class ReqExif(BaseModel):
    Model: str
    LensModel: str
    DateTime: str

router = APIRouter()

@router.post("/upload-image")
async def upload_image(
    file: UploadFile = File(...),
    show_exif: str = Form("true"),
    exif: str = Form(...)
):
    show_exif_flag: bool = show_exif.lower() == "true"

    try:
        exif_dict = json.loads(exif)
        exif_data = ReqExif(**exif_dict)
        print(f"exif_data: {exif_data}")
    except Exception as e:
        raise HTTPException(status_code=400, detail=f"EXIFデータのパースに失敗しました: {str(e)}")

```

`exif_dict = json.loads(exif)`の時点ではExif情報は辞書型になっています。
そのため値を取得するときは`exif_dict["Model"]`を書きます。

ただ、この書き方だと万が一Modelというキーがなかった場合は、KeyErrorの例外になります。

しかし、`exif_data = ReqExif(**exif_dict)`の処理で辞書型を、あらかじめ定義した`ReqExif`というクラスのインスタンスに変換します。

それにより、`exif_data.Model`のように値を取得することができます。
`exif_data`が`ReqExif`クラスになっていることが自明なので、エディタで自動補完もされますし、型定義もしているため、Modelというパラメータがない（中身が空 or nullの可能性はある）という例外は発生しません。

辞書型で持つよりも便利！！

## おわりに

以前実装できなかった機能を実装しました。

実はまだできていないことがちょっとあります。

- 入力値のバリデーション（日本語入力NGなど）
- ほかのExif情報の編集
    - F値、ISO、SSは値の選択肢がほぼ決まっているのでプルダウンで選択させたい

こちらはデプロイするまでには実装したいと思います！