Haskellでjsonを楽に扱う方法

Haskellから、twitter APIなどJSONを使いたい場面でどう書くかという話。

よくある書き方

data Tweet=Tweet
    {text :: String
    ,user :: User
    ,id :: Int
    , ...
    }

data User=User
    {screen_name :: String
    ,name :: String
    ,id :: Int
    , ...
    }

jsonToTweet :: JSValue -> Maybe Tweet
jsonToTweet v=do
    arr<-fromJSObject v
    text<-lookup arr "text"
    user<-jsonToUser =<< lookup arr "user"
    -- extract some more entries
    return Tweet{text=text,user=user,...}

jsonToUser :: JSValue -> Maybe User
...

fromJSObject :: JSValue -> Maybe [(String,JSValue)]
...

こういうコードを書くのは面倒で、バグも入り易い。

解決法

実は、jsonライブラリにはText.JSON.Genericがあって、Dataのインスタンスならば自動でencode/decodeできる。

class Typeable a => Data a

なのでTypeableのインスタンスである必要もあるけど、GHC拡張のDeriveDataTypeableを使えばderiving(Typeable,Data)できるようになる。

data Tweet=Tweet{...} deriving(Typeable,Data)
data User=User{...} deriving(Typeable,Data)

-- fromJS_generic :: Data a => JSValue -> Result a を呼ぶだけ!

問題点

レコード名がそのままJSONのキーに対応してしまうので、

  1. レコード名の衝突 ("id"等)
  2. Haskellの識別子でないキー ("user-name"等)

で、前者は型ごとにモジュールを分けることで解決できるけど、後者はちょっと難しくて、

  1. 最初に挙げたようなコードを書く(面倒)
  2. Typeable,Dataのインスタンスを手書き(難しそう)
  3. JSValueの時点で、キーをHaskellの識別子にmapする関数を書く(これが一番楽?)

等の方法が考えられる。もっとも実際にこういう場面に遭遇することはあまりないかもしれないけれど。