Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Circe generic codec derivation for records with object keys

I'm trying to implement something like

object Claims {
  import shapeless._
  import shapeless.labelled.FieldType
  import io.circe._
  import io.circe.generic.semiauto._
  import java.util.UUID

  type ClaimOf[V] = FieldOf[V]
  object iss extends ClaimOf[String]
  object subj extends ClaimOf[String]
  object aud extends ClaimOf[Set[String]]
  object client_id extends ClaimOf[UUID]

  implicit val encoder =
    deriveEncoder[FieldType[iss.type, String] :: FieldType[subj.type, String] :: HNil]
}

It does not compiles with an error error: could not find implicit value for parameter encode: shapeless.Lazy[io.circe.generic.encoding.DerivedObjectEncoder[shapeless.::[shapeless.labelled.FieldType[Claims.iss.type,String],shapeless.::[shapeless.labelled.FieldType[Claims.subj.type,String],shapeless.HNil]]]] deriveEncoder[FieldType[iss.type, String] :: FieldType[subj.type, String] :: HNil]

like image 329
Vladimir Koshelev Avatar asked Oct 18 '22 13:10

Vladimir Koshelev


1 Answers

According to Travis Brown the problem is "The generic derivation is designed to work with Shapeless's generic instances and expects the FieldType key to be a symbol." See discussion on Gitter.

My solution:

object Claims {
  import shapeless._
  import shapeless.labelled.FieldType
  import io.circe._
  import io.circe.syntax._
  import io.circe.generic.semiauto._
  import io.circe.generic.encoding.DerivedObjectEncoder
  import java.util.UUID

  abstract case class ClaimOf[V](name: String) extends FieldOf[V]

  object iss extends ClaimOf[String]("iss")
  object subj extends ClaimOf[String]("subj")
  object aud extends ClaimOf[Set[String]]("aud")
  object client_id extends ClaimOf[UUID]("client_id")

  implicit final def encodeClaims[K, H, T <: HList](implicit key: Witness.Aux[K],
                                                       claim: K <:< ClaimOf[H],
                                                       encodeHead: Lazy[Encoder[H]],
                                                       encodeTail: Lazy[DerivedObjectEncoder[T]]
                                                      ): DerivedObjectEncoder[FieldType[K, H] :: T] =
  new DerivedObjectEncoder[FieldType[K, H] :: T] {
    final def encodeObject(a: FieldType[K, H] :: T): JsonObject = a match {
      case h :: t =>
        (key.value.name -> encodeHead.value(h)) +: encodeTail.value.encodeObject(t)
    }
  }

  val encoder = deriveEncoder[FieldType[iss.type, String] :: FieldType[subj.type, String] :: HNil]

  val rec = (iss ->> "issuer") :: (subj ->> "subject") :: HNil

  val json = rec.asJson.spaces2
}

The encodeClaims function is basically a copy of the original function from circe, replacing constraint on the keys.

This answer can be used to implement another solution.

like image 183
Vladimir Koshelev Avatar answered Nov 15 '22 07:11

Vladimir Koshelev