portから受け取るJSONをデコードしてみようの巻

nekoroll.hatenablog.com
の続き

完成品はコチラ
ellie-app.com

guide.elm-lang.jp
🐊「受け取る値を厳密にするためにJSON.Decode.Value使えって書いてるな…」
🐊「今回やりたいのは受け取るだけだしMaybeだけやっちゃおw」
🐊「なんかここだけ厳密じゃないどまあ大丈夫っしょw」

そして時は流れ…

🐊「あっさり出来た!ツイートして終わりっ!」
🐊「ポチ!w」


AB先生に全部見抜かれてンだわ

ということでJSON Decoderが気になったので触ります

JSON Decoderとは

guide.elm-lang.jp

JavaScriptの世界で使われる手法は、JSONを単にJavaScriptオブジェクトに変換し、何も間違いが起こらないよう祈るだけです。
しかし、もし何らかのタイプミスや想定外のデータに遭遇すると、あなたのコードのどこかで実行時例外が発生してしまいます。
コードの間違いなのか?それともデータの間違いなのか?その原因を探る作業の始まりです!

確かにサーバから来るJSONは不確定で大変なイメージある

Elmの世界では、我々のプログラムにデータを取り込むより前にJSONを検証します。
もしデータに想定外の形式が含まれていれば、すぐにその点について気づくはずです。
おかしなデータが忍びこんで、忍び込んだところからちょっと離れた別の場所で実行時例外を発するような余地はありません。
これはJSONデコーダーによって達成されるのです。

あったら良いなが全部書いてあった。Elm考えた人天才かな?
こんな事書かれちゃ試すしか無い!

JSON Decoderを実装していく

今回来るJSON

key自体がない時

null

nullである

keyはあるけどデータが空の時

[]

空の配列である

データがある時

[
  {
    "id": 0,
    "todo": "todo1",
    "editing": false
  },
  {
    "id": 1,
    "todo": "todo2",
    "editing": false
  },
]

配列の中にobjectが入ったデータが来る

各TODOのDecoderを定義する

id

idDecoder : Decoder Int
idDecoder =
    D.field "id" D.int

idフィールドはIntであるべき事を定義

todo

todoFieldDecoder : Decoder String
todoFieldDecoder =
    D.field "todo" D.string

todoフィールドはStringであるべき事を定義

ここでTodo型とtodoの名前を被らせたことを後悔
ここだけFieldと関数名に入っているのは後悔の証である

editting

edittingDecoder : Decoder Bool
edittingDecoder =
    D.field "editting" D.bool

edittingBoolであるべき事を定義

TODO自体のDecoderを定義する

Todo

todoDecoder : Decoder Todo
todoDecoder =
    D.map3 Todo idDecoder todoFieldDecoder edittingDecoder
type alias Todo =
    { id : Int
    , todo : String
    , editting : Bool
    }

この形になることを期待しTodo型であることを定義
JSON.Decode.mapNは要素ごとに増えていくらしい。わかりやすいネ

TODOリストのDecoderを定義する

List Todo

todoListDecoder : Decoder (List Todo)
todoListDecoder =
    D.list todoDecoder

List Todoの形であるべき事を定義
Json.Decode.listに作ったTodoであることをチェックするtodoDecoderを渡すだけ

実際にデコードする

main

main : Program D.Value Model Msg
main =
    Browser.element
        { init = init
        , view = view
        , update = persistenceEvenryUpdate
        , subscriptions = \_ -> Sub.none
        }

(Maybe (List Todo))で受けていた部分をJSON.Decode.Valueに変更

init

init : D.Value -> ( Model, Cmd Msg )
init todoList =
    ( { todoList =
            let
                result =
                    D.decodeValue todoListDecoder todoList
            in
            case result of
                Ok decodedTodoList ->
                    decodedTodoList

                Err _ ->
                    []
      , inputText = ""
      , errorMessage = NO_ERROR
      }
    , Cmd.none
    )

引数をJSON.Decode.Valueに変更
JustorNothingでパターンマッチしていた部分を
todoListDecoderに変更しOkorErrorでパターンマッチに変更

これで出来上がりである
試しにlocalStorage.todoListjsonを壊してみると[]で初期化された
エラーハンドリングもしたいけどそれは次回のお楽しみ

まとめ

  • AB先生の言う通りめっちゃ面白かった
    これは完全にレゴブロック
    JSONの期待する形を考えて、それにそって組み合わせていくだけだった
    f:id:nekorollykk:20200903230821p:plain
  • エラーハンドリングもやりたい
    Errの時にとりあえず画面に出力するようにしたい