【问题标题】:Decode incomplete ADT with Circe使用 Circe 解码不完整的 ADT
【发布时间】:2019-03-06 23:02:26
【问题描述】:

对于case class Apple(color:String, sweetness:Double),我可以通过 generic.(semi)auto 或 generic.extras.(semi)auto 定义一个Decoder[String => Apple]

但是对于密封的特征层次结构(ADT)我不能:

sealed trait Fruit {
 def color:String
}

case class Apple(color:String, sweetness:Double) extends Fruit

sealed trait SpecialFruit extends Fruit

case class Camachile(color:String, burstyness:Double) extends SpecialFruit

case class Langsat(color:String, transparency:Double) extends SpecialFruit

Decoder[String => Fruit] // <--- wont compile

如何创建这样的解码器?


更新 我需要这样一个解码器的原因是因为 - 我正在解析的 json 不包含所有字段。 - 获取缺失字段的解码器并非易事。

最后一点使得通过解码器[水果]不可行

【问题讨论】:

  • 那么为什么不将可能缺少的字段设置为Option 呢?
  • 即使我这样做了,circes internals 仍然需要一个 Decoder 才能为缺失的字段派生。在我的情况下它是不可推导的。提供更多上下文:缺失字段的类型是 Ordering。

标签: json scala abstract-data-type circe


【解决方案1】:

decode[Fruit](jsonString) 的例子如下:

https://scalafiddle.io/sf/jvySm0B/0

circe的主页上有一个类似的例子:https://circe.github.io/circe/

【讨论】:

  • 就我而言,恐怕我无法通过 Decoder[Fruit]。我已更新问题以反映这一点。
  • 在这种情况下,您应该将可选字段放在Option 中,例如:case class Apple(color:Option[String], sweetness:Option[Double]) extends Fruit
【解决方案2】:

这是我的实现尝试。警告:它可能有点偏离 Circe 的编码标准,但对于我的目的来说它似乎可以正常工作。还支持嵌套的密封特征。

package no.kodeworks.kvarg.json

import io.circe.generic.extras.Configuration
import io.circe.{Decoder, HCursor}
import no.kodeworks.kvarg.util._
import shapeless.ops.function.FnFromProduct
import shapeless.ops.union.UnzipFields
import shapeless.{Coproduct, HList, LabelledGeneric, _}

trait AdtConfiguredIncompleteDecoders {
  implicit def decodeIncompleteAdt[
  Missing <: HList
  , Adt
  , Func
  , Subtypes <: Coproduct
  , SubtypeKeys <: HList
  , SubtypeValues <: Coproduct
  , ParamsSubtypes <: HList
  , SubtypeFuncs <: HList
  ]
  (implicit
   func: FnFromProduct.Aux[Missing => Adt, Func],
   sub: LabelledGeneric.Aux[Adt, Subtypes],
   uz: UnzipFields.Aux[Subtypes, SubtypeKeys, SubtypeValues],
   subtypeFuncs: SubtypeFunc.Aux[Missing, SubtypeValues, SubtypeFuncs],
   configuration: Configuration = null
  ): Decoder[Func] = {
    val conf = Option(configuration).getOrElse(Configuration.default)
    val disc = conf.discriminator.getOrElse("type")
    val keys = hlistToList[Symbol](uz.keys()).map(_.name)
      .map(conf.transformConstructorNames)
    val subtypeFuncs0 = hlistToList[Decoder[Func]](subtypeFuncs())
    Decoder.withReattempt {
      case h: HCursor =>
        h.downField(disc).as[String] match {
          case Right(decodedType) =>
            val subtypeFuncDecoder = subtypeFuncs0(keys.indexOf(decodedType))
            subtypeFuncDecoder(h)
        }
    }
  }

  trait SubtypeFunc[P <: HList, A <: Coproduct] extends DepFn0 with Serializable {
    type Out <: HList
  }

  object SubtypeFunc {
    type Aux[P <: HList, A <: Coproduct, Out0 <: HList] = SubtypeFunc[P, A] {
      type Out = Out0}

    implicit def cnilSubtypeFunc[P <: HList]: Aux[P, CNil, HNil] = new SubtypeFunc[P, CNil] {
      type Out = HNil

      override def apply(): HNil = HNil
    }

    implicit def cconsSubtypeFunc[
    Missing <: HList
    , CaseClass
    , Func
    , Rest <: Coproduct]
    (implicit
     func: FnFromProduct.Aux[Missing => CaseClass, Func],
     subtypefuncHead: Decoder[Func],
     subtypeFuncRest: SubtypeFunc[Missing, Rest],
    ): SubtypeFunc.Aux[Missing, CaseClass :+: Rest, Decoder[Func] :: subtypeFuncRest.Out] = {
      new SubtypeFunc[Missing, CaseClass :+: Rest] {
        type Out = Decoder[Func] :: subtypeFuncRest.Out

        override def apply() =
          subtypefuncHead :: subtypeFuncRest()
      }
    }
  }

}

从 no.kodeworks.kvarg.util 包对象中截取:

  def hlistToList[T](hlist: shapeless.HList): List[T] = {
    import shapeless._
    hlist match {
      case HNil => Nil
      case head :: tail =>
        collection.immutable.::(head.asInstanceOf[T], hlistToList[T](tail))
    }
  }

【讨论】:

    猜你喜欢
    • 2018-03-10
    • 2017-07-07
    • 2017-06-29
    • 1970-01-01
    • 2020-06-02
    • 1970-01-01
    • 2017-01-11
    • 2017-06-12
    • 1970-01-01
    相关资源
    最近更新 更多