Search code examples
scalashapelessplay-json

custom type class with shapless pb with implicit resolution


I am trying to do a play json reads from arbitrary case class using shapeless.

For the moment I'am trying to implement the following steps

From T, I have a FieldType[K1, V1] :: FieldType[K2, V2] :: ... using LabelledGeneric

Then I want to build an HList of type Reads[V1] :: Reads[V2] ...

Here is the code I'am using :

  /*
   * To build the json reads from T 
   */
  trait HReads[PRepr <: HList] {
    type Out
    def reads: Out
  }

  object HReads {
    type Aux[PRepr <: HList, Out1 <: HList] = HReads[PRepr] { type Out = Out1 }

    implicit def readsHNil(): Aux[HNil, HNil] = new HReads[HNil] {
      type Out = HNil
      override def reads: Out = {
        throw new RuntimeException("Oups")
      }
    }

    implicit def readsSingleton[T, K <: Symbol](
       implicit
       kWitness: Witness.Aux[K],
       jsReads: play.api.libs.json.Reads[T]
     ): Aux[FieldType[K, T] :: HNil, Reads[T] :: HNil] = new HReads[FieldType[K, T] :: HNil] {
      type Out = Reads[T] :: HNil
      override def reads: Out = {
        val name: String = kWitness.value.name
        val pathReads: Reads[T] = (__ \ name).read[T](jsReads)
        pathReads :: HNil
      }
    }

    implicit def readsStd[T, K <: Symbol, RestRepr <: HList, Rest <: HList](
     implicit
     kWitness: Witness.Aux[K],
     jsReads: Reads[T],
     hreads: Lazy[HReads.Aux[RestRepr, Rest]]
    ): Aux[FieldType[K, T] :: RestRepr, Reads[T] :: Rest] = new HReads[FieldType[K, T] :: RestRepr] {
      type Out = Reads[T] :: Rest
      override def reads: Out = {
        val name: String = kWitness.value.name
        val pathReads: Reads[T] = (__ \ name).read[T](jsReads)
        val value: Rest = hreads.value.reads
        pathReads :: value
      }
    }

    def jsonReads[P]: JsonReads[P] = new JsonReads[P] {}

    implicit class JsonReadsOps[In](in: JsonReads[In]) {
      def jsonReads[K <: Symbol, T, InRepr <: HList, HR <: HList]()(
          implicit
          gen: LabelledGeneric.Aux[In, FieldType[K, T] :: InRepr],
          hreads: HReads.Aux[FieldType[K, T] :: InRepr, Reads[T] :: HR]
      ): Reads[T] :: HR = {
        hreads.reads
      }
    }
  }

  // And trying to use this like that : 
    import HReads._

  implicit val l = LabelledGeneric[MonPojo]

  private val allReads = jsonReads[MonPojo].jsonReads()
  println(s"All Reads $allReads")
  //[error] validation\validation.scala:428: could not find implicit value for parameter hreads: validation.validations.HReads.Aux[shapeless.labelled.FieldType[K,T] :: InRepr,play.api.libs.json.Reads[T] :: HR]
  //[error]   private val allReads = jsonReads[MonPojo].jsonReads()
  //[error]                                                      ^
  //[error] one error found

Is someone could help me ?

Thanks Alex.


Solution

  • Here is implementation of ReadsWithRules:

      trait ReadsWithRules[T, R <: HList] {
        def withRules(rules: R): Reads[T]
      }
    
      trait ReadsWithRulesLowerPriority {
        implicit def readsNoRule[T](implicit reads: Reads[T]): ReadsWithRules[T, HNil] = new ReadsWithRules[T, HNil] {
          override def withRules(rules: HNil): Reads[T] = reads
        }
    
        implicit def readsGeneric[Repr, A, R <: HList](implicit
                                                       gen: LabelledGeneric.Aux[A, Repr],
                                                       readsRepr: Lazy[ReadsWithRules[Repr, R]]
                                                      ): ReadsWithRules[A, R] =
          new ReadsWithRules[A, R] {
            override def withRules(rules: R): Reads[A] = {
              readsRepr.value.withRules(rules).map(r => gen.from(r))
            }
          }
    
      }
    
      object ReadsWithRules extends ReadsWithRulesLowerPriority {
        implicit def readHNil[R <: HList]: ReadsWithRules[HNil, R] = new ReadsWithRules[HNil, R] {
          override def withRules(rules: R): Reads[HNil] = implicitly[Reads[HNil]]
        }
    
        implicit def readNoRuleForHead[K <: Symbol, H, T <: HList, R <: HList](implicit
                                                                               witness: Witness.Aux[K],
                                                                               noRule: LacksKey[R, K],
                                                                               readsH: Reads[H],
                                                                               readsT: ReadsWithRules[T, R]
                                                                              ): ReadsWithRules[FieldType[K, H] :: T, R] =
          new ReadsWithRules[FieldType[K, H] :: T, R] {
            override def withRules(rules: R): Reads[FieldType[K, H] :: T] = new Reads[FieldType[K, H] :: T] {
              override def reads(json: JsValue): JsResult[FieldType[K, H] :: T] = {
                val name = witness.value
                val rH = (__ \ name).read(readsH)
                (rH and readsT.withRules(rules)) ((a, b) => (name ->> a :: b).asInstanceOf[FieldType[K, H] :: T]).reads(json)
              }
            }
          }
    
        implicit def readRuleForHead[K <: Symbol, H, T <: HList, R <: HList](implicit
                                                                             witness: Witness.Aux[K],
                                                                             at: shapeless.ops.record.Selector.Aux[R, K, Reads[H]],
                                                                             readsH: Reads[H],
                                                                             readsT: ReadsWithRules[T, R]
                                                                            ): ReadsWithRules[FieldType[K, H] :: T, R] =
          new ReadsWithRules[FieldType[K, H] :: T, R] {
            override def withRules(rules: R): Reads[FieldType[K, H] :: T] = new Reads[FieldType[K, H] :: T] {
              override def reads(json: JsValue): JsResult[FieldType[K, H] :: T] = {
                val name = witness.value
                val additionalRule: Reads[H] = at(rules)
                val rH = (__ \ name).read(readsH) andKeep (__ \ name).read(additionalRule)
                (rH and readsT.withRules(rules)) ((a, b) => (name ->> a :: b).asInstanceOf[FieldType[K, H] :: T]).reads(json)
              }
            }
          }
    
      }
    
      def readsWithRules[T, R <: HList](rules: R)(implicit readWithRule: ReadsWithRules[T, R]): Reads[T] =
        readWithRule.withRules(rules)
    
      case class MonPojo(numericField: Int)
    
      val r: Reads[MonPojo] = 
          readsWithRules[MonPojo, FieldType[Symbol with Tagged["numericField"], Reads[Int]] :: HNil](
            ('numericField ->> (min(0) keepAnd max(150))) :: HNil
          )
      println(
        r.reads(Json.obj(
          "stringField" -> "Tata",
          "numericField" -> 42
        ))
      )
    
      //JsSuccess(MonPojo(42),)