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''