deriveJsonNoPrefixをリリースしました

レコードラベルにprefixを着けざるを得ない人達に送るライブラリーです

Posted by Yuji Yamamoto(@igrep) on July 18, 2018

前回の更新からちょっと時間が空いてしまいました 💦
小ネタです。掲題の通りderiveJsonNoPrefixというパッケージをリリースしました。
地味に有用だと思うので、READMEをやや意訳気味に翻訳して記事にします。
十分に単純なので、仕様が変わることもまさかないでしょうし。

以下、こちらのコミットの時点のREADMEの翻訳です。

Link to
here
deriveJsonNoPrefix

プレフィックスに優しいToJSONFromJSONのインスタンスを定義するTemplate Haskellのマクロを提供します。

Link to
here

こんな感じのJSONを作りたいとしましょう:

{
    "id": "ID STRING",
    "max": 0.789,
    "min": 0.123
}

きっとToJSON(おそらくそれに加えてFromJSONも)のインスタンスを自動的に定義するための、次のようなレコード型を定義したくなるでしょう。

{-# LANGUAGE TemplateHaskell #-}

import Data.Aeson.TH

data SomeRecord = SomeRecord
  { id :: String
  , max :: Double
  , min :: Double
  } deriving (Eq, Show)

$(deriveToJSON defaultOptions ''SomeRecord)

しかし、こんなレコード型は定義すべきではありません。
idmaxminも、Preludeに定義済みなのですから!

この問題を回避するために、レコードラベルに型の名前をプレフィックスとして加える、ということをわれわれはよくやります。

data SomeRecord = SomeRecord
  { someRecordId :: String
  , someRecordMax :: Double
  , someRecordMin :: Double
  } deriving (Eq, Show)

そして、deriveToJSONにデフォルトと異なるオプションを渡して実行します。

deriveToJSON Json.defaultOptions { fieldLabelModifier = firstLower . drop (length "SomeRecord") } ''SomeRecord

firstLower :: String -> String
firstLower (x:xs) = toLower x : xs
firstLower _ = error "firstLower: Assertion failed: empty string"

fieldLabelModifierオプションは文字通り、対象のレコードをJSONに変換するとき、あるいはJSONから対象のレコードの値に変換する時、レコードのラベルを変換する関数を設定するために使います。
👆の場合、プレフィックスであるSomeRecordの文字数分レコードラベルからdropして、先頭の文字someRecordIdで言えばIdIに相当します)を小文字に変換しているのがわかるでしょうか?

そう、これがderiveToJsonNoTypeNamePrefixがやっていることとほぼ同等のことです。
deriveToJsonNoTypeNamePrefixは、実質次のように定義されています。

deriveToJsonNoTypeNamePrefix :: Name -> Q [Dec]
deriveToJsonNoTypeNamePrefix deriver name =
  deriveToJSON Json.defaultOptions { fieldLabelModifier = dropPrefix name } name

dropPrefix :: Name -> String -> String
dropPrefix name = firstLower . drop (length $ nameBase name)

firstLower :: String -> String
firstLower (x:xs) = toLower x : xs
firstLower _ = error "firstLower: Assertion failed: empty string"

結果、これからはfieldLabelModifierをもう自分で定義する必要がありません!🙌

import Data.Aeson.DeriveNoPrefix

$(deriveJsonNoTypeNamePrefix ''SomeRecord)

👆 のderiveJsonNoTypeNamePrefixderiveJSONと同様に、ToJSONFromJSONのインスタンス、両方を生成します。
もちろん、FromJSONのインスタンスを生成するときのオプションとしても、プレフィックスを削除するためのfieldLabelModifierを渡してくれます!

Link to
here
同じ問題を解決するほかのライブラリー

  • extensible.
  • など、ToJSONFromJSONのインスタンスが定義されたextensible recordを提供するライブラリー

なので、そうしたextensible recordを提供するライブラリーが学習コストや依存関係などの事情で「重たい」と感じたときにこのパッケージを使ってください。