【问题标题】:Circe instances for encoding/decoding sealed trait instances of arity 0?用于编码/解码 arity 0 的密封特征实例的 Circe 实例?
【发布时间】:2016-08-28 22:57:47
【问题描述】:

我使用密封特征作为枚举来进行详尽的模式匹配。在我有案例对象而不是案例类扩展我的特征的情况下,我想将(通过Circe)编码和解码为纯字符串。

例如:

sealed trait State
case object On extends State
case object Off extends State

val a: State = State.Off
a.asJson.noSpaces // trying for "Off"

decode[State]("On") // should be State.On

我知道这将在 0.5.0 中进行配置,但是任何人都可以帮我写一些东西让我渡过难关,直到它发布?

【问题讨论】:

    标签: scala circe


    【解决方案1】:

    为了突出问题——假设这个 ADT:

    sealed trait State
    case object On extends State
    case object Off extends State
    

    circe 的通用派生将(当前)产生以下编码:

    scala> import io.circe.generic.auto._, io.circe.syntax._
    import io.circe.generic.auto._
    import io.circe.syntax._
    
    scala> On.asJson.noSpaces
    res0: String = {}
    
    scala> (On: State).asJson.noSpaces
    res1: String = {"On":{}}
    

    这是因为通用派生机制是建立在 Shapeless 的 LabelledGeneric 之上的,它将案例对象表示为空的 HLists。这可能始终是默认行为,因为它干净、简单且一致,但并不总是您想要的(正如您注意到即将推出的 configuration options 将支持替代方案)。

    您可以通过为案例对象提供自己的通用实例来覆盖此行为:

    import io.circe.Encoder
    import shapeless.{ Generic, HNil }
    
    implicit def encodeCaseObject[A <: Product](implicit
      gen: Generic.Aux[A, HNil]
    ): Encoder[A] = Encoder[String].contramap[A](_.productPrefix)
    

    这表示,“如果A 的通用表示是空的HList,则将其编码为JSON 字符串的名称”。对于静态类型为自身的案例对象,它的工作方式与我们预期的一样:

    scala> On.asJson.noSpaces
    res2: String = "On"
    

    当值被静态类型化为基类型时,情况就有些不同了:

    scala> (On: State).asJson.noSpaces
    res3: String = {"On":"On"}
    

    我们得到了State 的通用派生实例,它尊重我们为案例对象手动定义的通用实例,但它仍然将它们包装在一个对象中。如果您考虑一下,这是有道理的 - ADT 可以包含案例类,它只能合理地表示为 JSON 对象,因此 object-wrapper-with-constructor-name-key 方法可以说是最合理的做法。

    这不是我们唯一能做的事情,因为我们确实静态地知道 ADT 是包含案例类还是仅包含案例对象。首先,我们需要一个新的类型类来证明 ADT 仅由 case 对象组成(请注意,我在这里假设一个新的开始,但应该可以与泛型派生一起使用):

    import shapeless._
    import shapeless.labelled.{ FieldType, field }
    
    trait IsEnum[C <: Coproduct] {
      def to(c: C): String
      def from(s: String): Option[C]
    }
    
    object IsEnum {
      implicit val cnilIsEnum: IsEnum[CNil] = new IsEnum[CNil] {
        def to(c: CNil): String = sys.error("Impossible")
        def from(s: String): Option[CNil] = None
      }
    
      implicit def cconsIsEnum[K <: Symbol, H <: Product, T <: Coproduct](implicit
        witK: Witness.Aux[K],
        witH: Witness.Aux[H],
        gen: Generic.Aux[H, HNil],
        tie: IsEnum[T]
      ): IsEnum[FieldType[K, H] :+: T] = new IsEnum[FieldType[K, H] :+: T] {
        def to(c: FieldType[K, H] :+: T): String = c match {
          case Inl(h) => witK.value.name
          case Inr(t) => tie.to(t)
        }
        def from(s: String): Option[FieldType[K, H] :+: T] =
          if (s == witK.value.name) Some(Inl(field[K](witH.value)))
            else tie.from(s).map(Inr(_))
      }
    }
    

    然后是我们的通用 Encoder 实例:

    import io.circe.Encoder
    
    implicit def encodeEnum[A, C <: Coproduct](implicit
      gen: LabelledGeneric.Aux[A, C],
      rie: IsEnum[C]
    ): Encoder[A] = Encoder[String].contramap[A](a => rie.to(gen.to(a)))
    

    还不如继续写解码器。

    import cats.data.Xor, io.circe.Decoder
    
    implicit def decodeEnum[A, C <: Coproduct](implicit
      gen: LabelledGeneric.Aux[A, C],
      rie: IsEnum[C]
    ): Decoder[A] = Decoder[String].emap { s =>
      Xor.fromOption(rie.from(s).map(gen.from), "enum")
    }
    

    然后:

    scala> import io.circe.jawn.decode
    import io.circe.jawn.decode
    
    scala> import io.circe.syntax._
    import io.circe.syntax._
    
    scala> (On: State).asJson.noSpaces
    res0: String = "On"
    
    scala> (Off: State).asJson.noSpaces
    res1: String = "Off"
    
    scala> decode[State](""""On"""")
    res2: cats.data.Xor[io.circe.Error,State] = Right(On)
    
    scala> decode[State](""""Off"""")
    res3: cats.data.Xor[io.circe.Error,State] = Right(Off)
    

    这是我们想要的。

    【讨论】:

    • 事实证明,当密封特征包含在对象中时,这种情况就会崩溃。我一直在尝试,但我希望得到一些关于找出解决此问题的方法的指导。
    猜你喜欢
    • 2018-10-31
    • 2021-03-08
    • 2016-08-19
    • 1970-01-01
    • 2018-09-20
    • 1970-01-01
    • 2020-09-10
    • 2017-04-24
    • 2019-03-15
    相关资源
    最近更新 更多