LoginSignup
3
1

More than 3 years have passed since last update.

circe trait継承クラスのEncoder/Decoder

Last updated at Posted at 2020-02-21

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))
}
3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1