HudsonHornet
HudsonHornet

Reputation: 89

how to write circe decoder/encoder for abstract class

I need to write decoder/encoder for akka.http.scaladsl.model.HttpHeader

abstract class HttpHeader extends jm.HttpHeader with ToStringRenderable {
  def name: String
  def value: String
  def lowercaseName: String
  def is(nameInLowerCase: String): Boolean = lowercaseName == nameInLowerCase
  def isNot(nameInLowerCase: String): Boolean = lowercaseName != nameInLowerCase
}

i've tried to use derive encode and decode, but it didn't work

implicit val jsonDecoder: Decoder[HttpHeader] = deriveDecoder[HttpHeader]
implicit val jsonEncoder: Encoder[HttpHeader] = deriveEncoder[HttpHeader]

just getting an error could not find Lazy implicit value of type io.circe.generic.decoding.DerivedDecoder[akka.http.scaladsl.model.HttpHeader]

Upvotes: 0

Views: 1580

Answers (1)

Travis Brown
Travis Brown

Reputation: 139058

Circe's generic derivation (deriveDecoder, etc.) only works for types that it recognizes as "algebraic data types" (which in Scala means case classes and sealed hierarchies of case classes).

You can definitely use Circe with types like your HttpHeader, but you'll have to be a little more explicit about how you want them encoded. Part of the reason for this is that there are many possible ways you could encode values of this type—e.g. do you want derived properties like the lowercaseName value included in the serialisation?

Here's one possible way to write instances for this type:

import io.circe.{Decoder, Encoder}

abstract class HttpHeader {
  def name: String
  def value: String
  def lowercaseName: String
  def is(nameInLowerCase: String): Boolean = lowercaseName == nameInLowerCase
  def isNot(nameInLowerCase: String): Boolean = lowercaseName != nameInLowerCase
}

implicit val decodeHttpHeader: Decoder[HttpHeader] =
  Decoder.forProduct2("name", "value") {
    (n: String, v: String) => new HttpHeader {
      val name = n
      val value = v
      def lowercaseName = v.toLowerCase
    }
  }

implicit val encodeHttpHeader: Encoder[HttpHeader] =
  Encoder.forProduct2("name", "value") { header =>
    (header.name, header.value)
  }

Which works like this:

scala> import io.circe.syntax._, io.circe.jawn.decode
import io.circe.syntax._
import io.circe.jawn.decode

scala> decode[HttpHeader]("""{ "name": "ABC", "value": "XYZ" }""")
res0: Either[io.circe.Error,HttpHeader] = Right($anon$1@dcd9c89b)

scala> res0.map(_.asJson)
res1: scala.util.Either[io.circe.Error,io.circe.Json] =
Right({
  "name" : "ABC",
  "value" : "XYZ"
})

You could also get lower-level, building up the instances using flatMap or other combinators directly, but most of the time in situations like this forProductN is what you want.

Upvotes: 8

Related Questions