taketoncheir.log

Like the Decatoncheir by Poseidon Industrial, This blog is Yet Another Storage for My Long Term Memories.

Data.AesonのJSONパースをデバッグする

Haskell製のWebフレームワーク、ScottyとAngularJSを使ってアプリを作っています。
Magpie

JSONのパース周りで割とはまったので、そのデバッグ過程をメモしておきます。

Model周辺のみを抜き出したテストプロジェクトを用意したので参照してください。

デバッグ環境を整える

cabal環境をいじると依存性地獄に落とされますのでsandbox環境を用意するために、cabal-devを使います。(cabal-devはソースコードからビルドします)

$ cabal-dev install --only-dependencies
$ cabal-dev install

これでData.AesonやDatabase.Persistentなどの必要なライブラリが./dist/binにインストールされます。

以下のコマンドで、ghciでデバッグします。

$ cabal-dev ghci

Userを作成

Model.hsにて以下のデータ型を定義しています。Userと定義した部分にjsonとつけることにより、どのようにJSONとパースするのかの変換式(FromJSON/ToJSON)をTemplate Haskellが自動生成してくれます(参考)。

share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persist|

User json
    name Text
    email Text
    editor Editor
    UniqueUser email
    deriving Show
|]

data Editor
    = VIM | EMACS
    deriving (Show, Read, Eq, Enum)

derivePersistField "Editor"

まずはモジュールを読み込みます。

Prelude Main> :m + Data.Aeson Model

StringをByteStringリテラルとして扱うために、言語拡張をセットします。

Prelude Model Data.Aeson Main> :set -XOverloadedStrings

準備ができました。ユーザーmeを作ります。

Prelude Model Data.Aeson Main> let me = User "taketon_" "take.ton@hoge.com" VIM
Prelude Model Data.Aeson Main> me
User {userName = "taketon_", userEmail = "take.ton@hoge.com", userEditor = VIM}

Userをdecodeしたりencodeしたり

User型のmeをJSONにパースしてみましょう。それにはData.Aesonモジュールのencode関数を使います。

Prelude Model Data.Aeson Main> encode me
Chunk "{\"name\":\"taketon_\",\"editor\":{\"editor\":\"VIM\"},\"email\":\"take.ton@hoge.com\"}" Empty

VIM型が{"editor" : "VIM"}にエンコードされているのは、ToJSONを以下のように定義しているからです。

instance A.ToJSON Editor where
    toJSON VIM = A.object [ "editor" A..= ("VIM" :: String) ]
    toJSON EMACS = A.object [ "editor" A..= ("EMACS" :: String) ]

同様に、JSONをパースしてEditor型に変換する際は以下のFromJSONが使われます。

instance A.FromJSON Editor where
    parseJSON (A.Object v) = read <$> (v A..: "editor")
    parseJSON _            = mzero

JSONをUser型に戻してみましょう。Data.Aesonモジュールのdecode関数を使います。

Prelude Model Data.Aeson Main> decode "{\"name\":\"taketon_\",\"editor\":{\"editor\":\"VIM\"},\"email\":\"take.ton@hoge.com\"}"

<interactive>:6:8:
    No instance for (Data.String.IsString
                       Data.ByteString.Lazy.Internal.ByteString)

エラーです。Data.ByteString.Lazy.Char8を追加してもう一度。

Prelude Model Data.Aeson Main> :m + Data.ByteString.Lazy.Char8
Prelude Model Data.Aeson Data.ByteString.Lazy.Char8 Main> decode "{\"name\":\"taketon_\",\"editor\":{\"editor\":\"VIM\"},\"email\":\"take.ton@hoge.com\"}" :: Maybe User
Just (User {userName = "taketon_", userEmail = "take.ton@hoge.com", userEditor = VIM})

パースした結果がMaybe User型であることを注釈してあげます。
今度はちゃんとUser型が手に入りました。
ここでパースが失敗すると、Maybe User型はNothingになります。その場合は、FromJSON/ToJSONの定義を見直す必要があります。

こうした方法でパースを試すことによって、どのようなJSON型になるのか、復元は可能なのか検証できます。