无形。参考 1 个构造函数参数的所有情况 类

Shapeless. Refer to all case classes of 1 constructor argument

我正在尝试对 1 个参数构造函数的所有情况 类 进行概括,以使用 shapeless 为它们创建 Json 序列化程序。

到目前为止我所做的是:

object ShapelessJsonOFormat extends ProductTypeClassCompanion[OFormat] {
  object typeClass extends ProductTypeClass[OFormat] {
    override def product[H, T <: HList](ch: OFormat[H], ct: OFormat[T]): OFormat[H :: T] = new OFormat[H :: T] {
      override def reads(json: JsValue): JsResult[H :: T] =
        for {
          h <- ch.reads(json)
          t <- ct.reads(json)
        } yield h :: t


      override def writes(o: H :: T): JsObject = ch.writes(o.head) ++ ct.writes(o.tail)
    }

    override def emptyProduct: OFormat[HNil] = new OFormat[HNil] {

      override def reads(json: JsValue): JsResult[HNil] = JsSuccess(HNil)

      override def writes(o: HNil): JsObject = JsObject.empty
    }

    override def project[F, G](
      instance: => OFormat[G],
      to: (F) => G,
      from: (G) => F): OFormat[F] = new OFormat [F] {
      override def writes(o: F): JsObject = instance.writes(to(o))

      override def reads(json: JsValue): JsResult[F] = instance.reads(json).map(from)
    }
  }
}

我没试过,但不是我想要的。问题是扩展 ProductTypeClassCompanion 意味着对所有 HList 进行泛化,以便能够 "add" 两个 json 我被迫在这里玩 Json 对象(参见 override def writes(o: H :: T): JsObject = ch.writes(o.head) ++ ct.writes(o.tail) 中的 ++ ).我真正需要的是泛化所有 T :: HNil 类型的 HList。这可能吗?否则,我将被迫将我的所有案例 类 扩展 Product1[T].

如果您想将其限制为仅适用于单参数大小写-类,则您不能使用ProductTypeClass。它的目的是归纳推广到 all 任意数量的产品。你想坚持 arity 1,所以你有冲突。您只需要自己编写所有内容,而无需为您提供(小)样板 ProductTypeclass 摘要。

object ShapelessJsonOFormat {
  implicit def caseHListArity1[Head](implicit
    headFmt: Lazy[OFormat[Head]] // Use Lazy in some places to get around the (incorrect) implicit divergence check in the compiler
  ): OFormat[Head :: HNil] = new OFormat[Head :: HNil] {
    // ... serialize a Head :: HNil given headFmt.value: OFormat[Head]
  }

  implicit def caseGeneric[I, O](implicit
    gen: Generic.Aux[I, O],
    oFmt: Lazy[OFormat[O]]
  ): OFormat[I] = new OFormat[I] {
    // ... serialize an I given a Generic I => O and oFmt.value: OFormat[O]
  }
}

从表面上看,您的主要抱怨是您需要在元数抽象版本中将多个 JSONObject 组合在一起。这是因为您使用的是 Generic,而不是 LabelledGeneric,因此您无法为产品元素提供字段名称,因此它们在一个级别上混在一起。您通常可以为此使用 LabelledProductTypeClass,但它在这里 相当 不起作用,因此我们只能使用自己的样板。此版本适用于任何元数。

type OFormatObject[A] = OFormat[A] { def write(value: A): JsObject }
object ShapelessJsonOFormat {
  implicit val caseHNil: OFormatObject[HNil] = new OFormat[HNil] {
    override def read(json: JsValue) = HNil
    override def write(value: HNil) = JsObject.empty
  }

  implicit def caseHCons[
    HeadName <: Symbol,
    Head,
    Tail <: HList
  ](implicit
    headFmt: Lazy[OFormat[Head]],
    nameWitness: Witness.Aux[HeadName],
    tailFmt: Lazy[OFormatObject[Tail]]
  ): OFormatObject[FieldType[HeadName, Head] :: Tail]
  = new OFormat[FieldType[HeadName, Head] :: Tail] {
    private val fieldName = nameWitness.value.name // Witness[_ <: Symbol] => Symbol => String
    override def read(json: JsValue): FieldType[HeadName, Head] :: Tail = {
      val headObj = json.asJsObject.get(fieldName)
      val head = headFmt.read(headObj)
      val tail = tailFmt.read(json)
      field[HeadName](head) :: tail
    }
    override def write(value: FieldType[HeadName, Head] :: Tail): JsObject = {
      val tail = tailFmt.write(value.tail)
      val head = headFmt.write(value.head)
      tail + JsObject(fieldName -> head) // or similar
    }
  }

  implicit def caseLabelledGeneric[I, O](implicit
    gen: LabelledGeneric.Aux[I, O],
    oFmt: Lazy[OFormatObject[O]]
  ): OFormatObject[I] = new OFormat[I] {
    override def read(json: JsValue): I = gen.from(oFmt.value.read(json))
    override def write(value: I): JsObject = oFmt.value.write(gen.to(value))
  }
}

这里的思路是用细化OFormatObject讲保证writeJsObjectOFormat。有一个 OFormatObject[HNil] 就是 { read = _ => HNil; write = _ => {} }。如果有一个 Head (OFormat[Head]) 的序列化器,一个 Tail (OFormatObject[Tail]) 的对象序列化器,并且我们有一些表示 [=24] 字段名称的单例类型=](类型参数HeadName <: Symbol,在Witness.Aux[HeadName]中实现),那么caseHCons会产生一个OFormatObject[FieldName[HeadName, Head] :: Tail],看起来像{ read = { headName: head, ..tail } => head :: tail; write = head :: tail => { headName: head, ..tail }。然后我们使用 caseLabelledGenericHLists-with-FieldTypes 的解决方案带入一般情况 类.