Aeson を使って Haskell で JSON


Aeson を調べてみました。

Aeson はいろんな使い方があるけど、`Data.Aeson.TH` で提供される `deriveJSON` を使うとはかどると思います。

```AesonTest.hs
-- Aeson は ByteString から生成するのでこのプラグマを使用する
{-# LANGUAGE OverloadedStrings #-}
-- Data.Aeson.TH を利用するとはかどるのでこのプラグマを使用する
{-# LANGUAGE TemplateHaskell #-}

import Data.Aeson (decode, encode)
import Data.Aeson.TH (deriveJSON, defaultOptions, Options(..))
import Data.Char (toLower)

data Foo a = Foo { fooId :: a } deriving (Show, Read, Eq)
-- ここ重要！
-- TH を使っているので初見では読めないが、こんな書き方なんだ〜、ぐらいで OK。
-- ポイントは defaultOptions を元に fieldLabelModifier を
-- 先頭 3 文字を削除して小文字に置き換えるようにしていること。
-- こうすると {"fooId":1} という JSON が {"id":1} という JSON になる。
deriveJSON defaultOptions { fieldLabelModifier = map toLower . drop 3 } ''Foo

data Bar = Bar {} deriving (Show, Read, Eq)
deriveJSON defaultOptions ''Bar

data Baz = Baz | Qux  deriving (Show, Read, Eq)
deriveJSON defaultOptions ''Baz

main :: IO ()
main = do
  let json1 = "{\"id\": 1}"
      json2 = "{\"id\": \"2\"}"
      decoded1  = decode json1 :: Maybe (Foo Int) -- 型指定が必要
      decoded1' = decode json1 :: Maybe (Foo String) -- 型指定を誤ると Noghing になる
      decoded2  = decode json2 :: Maybe (Foo Int)
      decoded2' = decode json2 :: Maybe (Foo String)
      encoded1  = encode decoded1
      encoded1' = encode decoded1'
      encoded2  = encode decoded2
      encoded2' = encode decoded2'
      foo3 = Foo 3 :: Foo Int
      foo4 = Foo (Just "4") :: Foo (Maybe String)
      foo5 = Foo (Nothing) :: Foo (Maybe Int)
      encoded3 = encode foo3
      encoded4 = encode foo4
      encoded5 = encode foo5
      json6 = "{\"id\":[]}"
      decoded6   = decode json6 :: Maybe (Foo [Int]) -- 空配列は型指定次第で何にでもなれる
      decoded6'  = decode json6 :: Maybe (Foo String) -- String は配列とはみなされないようだ
      decoded6'' = decode json6 :: Maybe (Foo [String])
      bar7 = Bar
      baz8 = Baz
      qux9 = Qux
      encoded7 = encode bar7 -- "{}" を期待するも "[]"
      encoded8 = encode baz8
      encoded9 = encode qux9
      decoded8  = decode encoded8 :: Maybe Baz -- 何故か Nothing になる
      decoded8' = decode "\"Baz\"" :: Maybe Baz -- こちらでも同様に Nothing になる
      json10 = "{\"id\":{})" -- これを decode する方法がわからない
      decoded10   = decode json10 :: Maybe (Foo ())
      decoded10'  = decode json10 :: Maybe (Foo Bar)
      json11 = "{\"id\":[1,true,\"3\"]}" -- これを decode する方法がわからない
  print decoded1   -- Just (Foo {fooId = 1})
  print decoded1'  -- Nothing
  print decoded2   -- Nothing
  print decoded2'  -- Just (Foo {fooId = "2"})
  print encoded1   -- "{\"id\":1}"
  print encoded1'  -- "null"
  print encoded2   -- "null"
  print encoded2'  -- "{\"id\":\"2\"}"
  print foo3       -- Foo {fooId = 3}
  print foo4       -- Foo {fooId = Just "4"}
  print foo5       -- Foo {fooId = Nothing}
  print encoded3   -- "{\"id\":3}"
  print encoded4   -- "{\"id\":\"4\"}"
  print encoded5   -- "{\"id\":null}"
  print decoded6   -- Just (Foo {fooId = []})
  print decoded6'  -- Nothing
  print decoded6'' -- Just (Foo {fooId = []})
  print bar7       -- Bar
  print encoded7   -- "[]"
  print baz8       -- Baz
  print encoded8   -- "\"Baz\""
  print decoded8   -- Nothing
  print decoded8'  -- Nothing
  print qux9       -- Qux
  print encoded9   -- \"Qux\""
  print decoded10  -- Noghing
  print decoded10' -- Nothing
```

JSON は Haskell よりも型がゆるいので、すべての JSON が Haskell で扱えるわけではないと思います。特に `[1, true, "3"]` のような異なる型を持つ配列や、`{}` のような空のオブジェクトです。

また、JSON の `null` は Haskell の `Nothing` にマッピングされるので、`null` を取りうる値は `Maybe` 型にしておかなければならず、コードが冗長になってしまいます。

とは言え、どれも設計次第で解決可能なので、Aeson は便利なライブラリです。

## 追試

型コンストラクタをひとつだけ持つデータと、ふたつ以上持つデータを比較してみました。

結果としては、型コンストラクタをひとつだけ持つデータは `[]` という JSON にエンコードされ、この JSON をデコードすると、指定した Haskell の型が得られました。

型コンストラクタがふたつ以上あるデータは `"Bar"` などのコンストラクタの文字列表記の JSON にエンコードされるのですが、この文字列の JSON はデコードすることができず `Nothing` になります。

どちらの動作も実用的ではないので、不具合ではないかという気がしています。ちなみに利用した Aeson のバージョンは 0.8.0.2 です。

```ConstructorDecoder.hs 
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}

import Data.Aeson (decode, encode)
import Data.Aeson.TH (deriveJSON, defaultOptions, Options(..))

data Foo = Foo deriving (Show, Read, Eq)
deriveJSON defaultOptions ''Foo

data Bar = Bar | Baz deriving (Show, Read, Eq)
deriveJSON defaultOptions ''Bar

main :: IO ()
main = do
  let encFoo   = encode Foo -- 何故か "[]"
      decFoo   = decode encFoo :: Maybe Foo -- Just Foo
      decFoo'  = decode "[]" :: Maybe Foo -- Just Foo
      decFoo'' = decode "\"Foo\"" :: Maybe Foo -- Nothing
  print encFoo
  print decFoo
  print decFoo'
  print decFoo''
  let encBar   = encode Bar -- "\"Bar\""
      decBar   = decode encBar :: Maybe Bar -- Nothing
      decBar'  = decode "\"Bar\"" :: Maybe Bar -- Nothing
      decBar'' = decode "[]" :: Maybe Bar -- Nothing
  print encBar
  print decBar
  print decBar'
  print decBar''
```