circeのEncoder/Decoderを拡張してみる

## 概要

[circe](https://circe.github.io/circe/)のEncoder/Decoderの拡張で試行錯誤した結果をメモしておきます。
内容に間違いや他にもっと良い書き方があるなどありましたら、ご指摘いただけると助かります。

なお、環境は以下で実施しています。

- Scala 2.12.1
- circe 0.6.1


## circeについて

まずはcirceについて。

circeはScalaのJSONライブラリです。
特徴としては以下の通りです。

- [Typelevel.scala](http://typelevel.org/) のプロジェクト
- [Argonaut](http://argonaut.io/) をforkして開発されている（ただし、[Scalaz](https://github.com/scalaz/scalaz) の代わりに [cats](https://github.com/typelevel/cats) に依存している）
- [jawn](https://github.com/non/jawn) による高速なJSONパース
- [shapeless](https://github.com/milessabin/shapeless) によるcase classのジェネリックな自動コーデック
- [Monocle](https://github.com/julien-truffaut/Monocle) のLensによるJSONのトラバース
- Scala.js のサポート

ちなみにcirceの読み方は最初「キルケ」だと思っていたのですが、以下の動画で確認すると「セルスィ」みたいに発音してますね。

- [Generic Derivation: The Hard Parts—Travis Brown](https://www.youtube.com/watch?v=80h3hZidSeE)（動画）

（[公式ガイド](https://circe.github.io/circe/) にも `"pronounced SUR-see"` みたいに書いてあるので、きっとそうなんだろう。）


## 準備

`build.sbt`にcirceの依存を追加します。

```scala
lazy val circeVersion = "0.6.1"
libraryDependencies ++= Seq(
  "io.circe" %% "circe-core" % circeVersion,
  "io.circe" %% "circe-generic" % circeVersion,
  "io.circe" %% "circe-parser" % circeVersion
)
```

## 拡張がいらないケース

circeって普通に使う分には、例えば [この辺り](https://github.com/circe/circe/blob/master/modules/core/shared/src/main/scala/io/circe/Encoder.scala#L119-L261) に定義されているような型だったら特別な定義を書かなくてもEncode/Decodeできるから楽ちんですよね。
他のライブラリみたくimplicitなformat定義を書かなくても良いのでこれはかなり好印象です。


```scala
case class Person(id: Long, name: String, age: Int)

object Example extends App {

  import io.circe._
  import io.circe.generic.auto._
  import io.circe.parser._
  import io.circe.syntax._

  val person = Person(123l, "Taro", 15)

  // Encode (case class -> JSON)
  val encodeData: String = person.asJson.noSpaces
  println(encodeData)
  // {"id":123,"name":"Taro","age":15}

  // Decode (JSON -> case class)
  val decodedData: Either[Error, Person] = decode[Person](encodeData)
  println(decodedData)
  // Right(Person(Id(123),Name(Taro),Age(15)))

}
```

でも、それ以外の型とかデータ構造を扱う場合は一工夫する必要があります。

## 拡張の必要があるケース

拡張の必要があるケースとは、例えば次のようなケースです。

- 値クラスでネストしたくない場合
- 代数的データ型
- Date and Time API (JSR-310)

### 値クラスでネストしたくない場合

最初に値クラスのケースを見てみましょう。

以下は値クラス `Id`、`Name`、`Age` をcase classの `Person` に含めた例です。

```scala
// 値クラス
case class Id(value: Long) extends AnyVal
case class Name(value: String) extends AnyVal
case class Age(value: Int) extends AnyVal

case class Person(id: Id, name: Name, age: Age)

object ValueClassExample extends App {
  import io.circe._
  import io.circe.generic.auto._
  import io.circe.parser._
  import io.circe.syntax._

  val person = Person(
    Id(1234567890l),
    Name("bob"),
    Age(17)
  )

  // Encode (case class -> JSON)
  val encodeData: String = person.asJson.spaces2
  println(encodeData)

  // Decode (JSON -> case class)
  val decodedData: Either[Error, Person] = decode[Person](encodeData)
  println(decodedData)

}
```

これをそのままEncodeすると、以下のようにネストしてしまいます。

```json
{
  "id" : {
    "value" : 1234567890
  },
  "name" : {
    "value" : "bob"
  },
  "age" : {
    "value" : 17
  }
}
```

このネストを回避するには、implicitの探索可能な箇所に以下のようなEncoder/Decoderの定義を追加します。**（以降の例でもそうですが、今回はimplicitの探索が手っ取り早いコンパニオンオブジェクトに定義しました。しかし、実際にはドメインとかのレイヤーの役割を考慮し適切な箇所に定義する必要があります。）**

```scala
case class Id(value: Long) extends AnyVal
object Id {
  import io.circe._
  implicit val encode: Encoder[Id] = Encoder[Long].contramap(_.value)
  implicit val decode: Decoder[Id] = Decoder[Long].map(Id(_))
}

case class Name(value: String) extends AnyVal
object Name {
  import io.circe._
  implicit val encode: Encoder[Name] = Encoder[String].contramap(_.value)
  implicit val decode: Decoder[Name] = Decoder[String].map(Name(_))
}

case class Age(value: Int) extends AnyVal
object Age {
  import io.circe._
  implicit val encode: Encoder[Age] = Encoder[Int].contramap(_.value)
  implicit val decode: Decoder[Age] = Decoder[Int].map(Age(_))
}
```

こうすることで、Encode/Decode時のネストを回避することができます。

```json
{
  "id" : 1234567890,
  "name" : "bob",
  "age" : 17
}
```

なお、代替案として、shapelessを使ってこんな定義を追加すれば、先ほどのEncodeに関しては一括でネストなしに変換してくれるようになります。

```scala
implicit def encodeAnyVal[W <: AnyVal, U](implicit unwrapped: Unwrapped.Aux[W, U], encoderUnwrapped: Encoder[U]): Encoder[W] =
  Encoder.instance[W](v => encoderUnwrapped(unwrapped.unwrap(v)))
```

shapelessは個人的にはまだほとんど触ったことがないので、今後試していきたいと思います。

また、[こういう話](https://github.com/circe/circe/issues/469)も出てるので、今後のバージョンで対応してくれるかもしれませんね。


### 代数的データ型

次は代数的データ型（ADT）です。

以下の`ColorType`は、色を表す代数的データ型です。

```scala
sealed trait ColorType

object ColorType extends (String => ColorType) {

  case object Yellow extends ColorType
  case object Blue extends ColorType
  case object Red extends ColorType

  def apply(color: String): ColorType = color.toLowerCase match {
    case "yellow" => Yellow
    case "blue" => Blue
    case "red" => Red
    case other => throw new IllegalArgumentException(s"invalid color: $other")
  }
}
```

これをcase classの`Colors`のフィールドに定義してEncode/Decodeしてみます。

```scala
case class Colors(id: Long, colorType: ColorType)

object ADTExample extends App {
  import io.circe._
  import io.circe.generic.auto._
  import io.circe.parser._
  import io.circe.syntax._

  val colors = Colors(
    1234567890l,
    ColorType.Yellow
  )

  val encodeData: String = colors.asJson.spaces2
  println(encodeData)

  val decodedData: Either[Error, Colors] = decode[Colors](encodeData)
  println(decodedData)

}
```

すると、Encodeの結果はこうなってしまいます。何か変ですね。

```json
{
  "id" : 1234567890,
  "colorType" : {
    "Yellow" : {
      
    }
  }
}
```

これ、`"colorType" : "Yellow"` という感じでEncodeしたいですよね。
そんな時は、以下の定義を追加します。

```scala
object ColorType extends (String => ColorType) {

  // 省略

  import io.circe._
  implicit val encode: Encoder[ColorType] = Encoder.instance(x => Json.fromString(x.toString))
  implicit val decode: Decoder[ColorType] = Decoder[String].map(ColorType(_))
```

こうすると、期待通りにEncodeしてくれるようになります。

```json
{
  "id" : 1234567890,
  "colorType" : "Yellow"
}
```

ちなみにこのケースの代替案としては、`circe-generic-extras`を使うという手もあります。

`build.sbt`の`libraryDependencies`に以下を追加します。

```scala
"io.circe" %% "circe-generic-extras" % circeVersion
```

そして、Encoder/Decoderはこう書くと先ほどと同じように変換してくれます。

```scala
object ColorType {

  // 省略

  import io.circe._
  import io.circe.generic.extras.semiauto._
  implicit val encode: Encoder[ColorType] = deriveEnumerationEncoder
  implicit val decode: Decoder[ColorType] = deriveEnumerationDecoder
```

なお、`circe-generic-extras`は、それ以外にも次のような機能が提供されているようです。

- JSONのフィールドのキャメルケースとスネークケースの相互変換
- デフォルト値の指定

など


### Date and Time API (JSR-310)

Java8の日付型であるDate and Time APIの場合は、最初自前で色々頑張っていたんですが、別途提供されている`circe-java8`を使えば良いことが分かりました。

`build.sbt`の`libraryDependencies`に以下を追加します。

```scala
"io.circe" %% "circe-java8" % circeVersion
```

使い方はこんな感じです。

```scala
import java.time.{LocalDate, LocalDateTime}

case class DateTimeModel(
  date: LocalDate,
  dateTime: LocalDateTime
)

object DateTimeExample extends App {

  import io.circe._
  import io.circe.generic.auto._
  import io.circe.parser._
  import io.circe.syntax._
  import io.circe.java8.time._

  val original = DateTimeModel(
    LocalDate.now(),
    LocalDateTime.now()
  )

  // Encode
  val encodeData: String = original.asJson.spaces2
  println(encodeData)
  // {
  //   "date" : "2017-01-09",
  //   "dateTime" : "2017-01-09T05:15:37.597"
  // }

  // Decode
  val decodedData: Either[Error, DateTimeModel] = decode[DateTimeModel](encodeData)
  println(decodedData)
  // Right(DateTimeModel(2017-01-09,2017-01-09T05:15:37.597))

}
```

## おわりに

circeは標準で提供されている以外のことをやろうとするとまだまだ情報が少ないのかなと感じました。
それでもやっぱり使っていて楽しいライブラリですね。
あと、個人的にはshapelessを知っていれば他にもっとやりようがあるのかなと感じているので、今年の前半はshapelessを学んでみようと思ってます。