circeのdecodeでデフォルト値を設定する
Scala の JSONライブラリ circe で、JSON を decode する際にフィールドが欠落していたらデフォルト値に置き換える方法について書きます。結構前からある機能なのでご存知の方も多いかもしれません。
つまり、こういうケースですね。
import io.circe.generic.auto._ import io.circe.parser._ val json1 = """ |{ | "name": "Taro", | "body": "よろしくお願いします" |} """.stripMargin val json2 = """ |{ | "name": "Taro" |} """.stripMargin final case class Message( name: String, body: String ) // これはうまくいくけど println(decode[Message](json1)) // => Right(Message(Taro,よろしくお願いします)) // フィールドが欠落しているので decode に失敗する。デフォルト値に置き換えたい。 println(decode[Message](json2)) // => Left(DecodingFailure(Attempt to decode value on failed cursor, List(DownField(body))))
実行環境
今回実行した環境は以下になります。
- Scala 2.12.6
- circe 0.9.3
実現方法
方法1
最初は、case class
のそれぞれのフィールドにデフォルト値を設定しておき、io.circe.generic.extras.Configuration
の指定で欠落するフィールドをデフォルト値へ置き換える方法です。
build.sbt
は以下のように、基本セットの他に circe-generic-extras
を依存に追加します。
libraryDependencies ++= Seq( "io.circe" %% "circe-core", "io.circe" %% "circe-generic", "io.circe" %% "circe-parser", "io.circe" %% "circe-generic-extras" ).map(_ % circeVersion) val circeVersion = "0.9.3"
そして、ポイントとなるのは、
io.circe.generic.auto._
ではなく、io.circe.generic.extras.auto._
を import するimplicit val customConfig: Configuration = Configuration.default.withDefaults
を定義する
コードを見てみましょう。
import io.circe.generic.extras.auto._ import io.circe.generic.extras.Configuration import io.circe.parser._ val json1 = """ |{ | "name": "Taro" |} """.stripMargin val json2 = """ |{ | "body": "よろしくお願いします" |} """.stripMargin val json3 = "{}" // case class のフィールドにはデフォルト値が設定されている final case class Message( name: String = "匿名希望", body: String = "ノーコメントです" ) implicit val customConfig: Configuration = Configuration.default.withDefaults println(decode[Message](json1)) // => Right(Message(Taro,ノーコメントです)) println(decode[Message](json2)) // => Right(Message(匿名希望,よろしくお願いします)) println(decode[Message](json3)) // => Right(Message(匿名希望,ノーコメントです))
このように欠落するフィールドに対してデフォルト値が置き換わりました。
方法2
もう一つの方法は、デフォルト値を設定しておいた case class
を別途用意しておき、それで置き換えるやり方です。
build.sbt
は 方法1 とは異なり、circe-generic-extras
を依存に追加する必要はありません。
libraryDependencies ++= Seq( "io.circe" %% "circe-core", "io.circe" %% "circe-generic", "io.circe" %% "circe-parser" ).map(_ % circeVersion) val circeVersion = "0.9.3"
そして、こうに書きます。
import io.circe.generic.auto._ import io.circe.parser._ val json1 = """ |{ | "name": "Taro" |} """.stripMargin val json2 = """ |{ | "body": "よろしくお願いします" |} """.stripMargin val json3 = "{}" // 方法1と違ってここではデフォルト値は設定していない final case class Message( name: String, body: String ) // 適用させるデフォルト値 lazy val defaultValue = Message("匿名希望", "ノーコメントです") println(decode[Message => Message](json1).map(_(defaultValue))) // => Right(Message(Taro,ノーコメントです)) println(decode[Message => Message](json2).map(_(defaultValue))) // => Right(Message(匿名希望,よろしくお願いします)) println(decode[Message => Message](json3).map(_(defaultValue))) // => Right(Message(匿名希望,ノーコメントです))
この方法でも欠落するフィールドに対してデフォルト値を置き換えることができました。ちなみに以下の issue にもあるように、方法1 の方が公式のやり方っぽいですね。