circe概要
- Scalaの純粋関数型Jsonライブラリ
encoder decoder定義するケース
- generic.autoでは対応できない場合
- 下記
ContentList
要素value
をjsonの階層に含めたくない場合 - 出力項目をカスタマイズ
- trait継承クラスの識別条件カスタマイズ
For large or deeply-nested case classes and sealed trait hierarchies, the generic derivation provided by the generic subproject may stack overflow during compilation, which will result in the derived encoders or decoders simply not being found. Increasing the stack size available to the compiler (e.g. with sbt -J-Xss64m if you’re using SBT) will help in many cases, but we have at least one report of a case where it doesn’t. It might be simpler and safer to add .sbtopts file with SBT parameters (-J-Xss64m) in root of project.
circe versoin : 0.10.0
object main extends App {
import io.circe.{Encoder, Decoder, HCursor, Json}
import io.circe.parser._
import io.circe.generic.auto._
case class ContentList(value:Seq[BaseTrait])
object ContentList {
implicit val encoder: Encoder[ContentList] = Encoder[Seq[BaseTrait]].contramap(_.value)
implicit val decoder: Decoder[ContentList] = Decoder[Seq[BaseTrait]].map(ContentList(_))
}
sealed trait BaseTrait {
val classType:String
}
object BaseTrait {
implicit val encoder: Encoder[BaseTrait] = new Encoder[BaseTrait] {
// don't do r.asJson for avoiding from possibility of stack overflow
override final def apply(r: BaseTrait): Json = {
r match {
case a:A => a.asJson
case b:B => b.asJson
case c:C => c.asJson
}
}
}
implicit val decoder: Decoder[BaseTrait] = for {
classType <- Decoder[String].prepare(_.downField("classType"))
value <- classType match {
case "A" => Decoder[A]
case "B" => Decoder[B]
case "C" => Decoder[C]
case _ => Decoder.failedWithMessage("invalid classType")
}
} yield value
case class A (valueStr:String) extends BaseTrait {
override val classType: String = "A"
}
object A {
// Note this encoder for encode classType of trait
implicit val encoder: Encoder[A] = new Encoder[A] {
override final def apply(r: A): Json = Json.obj(
"classType" -> Json.fromString(r.classType),
"valueStr" -> Json.fromString(r.valueStr)
)
}
}
case class B (valueStr:String) extends BaseTrait {
override val classType: String = "B"
}
object B {
implicit val encoder: Encoder[B] = new Encoder[B] {
override final def apply(r: B): Json = Json.obj(
"classType" -> Json.fromString(r.classType),
"valueStr" -> Json.fromString(r.valueStr)
)
}
}
case class C (valueInt:Int) extends BaseTrait {
override val classType: String = "C"
}
object C {
implicit val encoder: Encoder[C] = new Encoder[C] {
override final def apply(r: C): Json = Json.obj(
"classType" -> Json.fromString(r.classType),
"valueInt" -> Json.fromInt(r.valueInt)
)
}
}
}
val jsonString = """[{"classType" : "A", "valueStr" : "A value"}, {"classType" : "B", "valueStr" : "B value"}, {"classType" : "C", "valueInt" : 1}]"""
val decodedClass = decode[ContentList](jsonString).right.get
val encodedJson = decodedClass.asJson
decodedClass
備考
BaseTrait継承クラスがAとCだけの場合、class内容が異なる為、下記定義が可能
implicit val decode: Decoder[BaseTrait] = {
Decoder[A].map[BaseTrait](identity)
.or(Decoder[C].map[BaseTrait](identity))
}