这个问题最近已经出现过几次了,所以我在这里进行常见问题解答。假设我有一些案例类,例如:

import io.circe._, io.circe.generic.semiauto._

object model {
  case class A(a: String)
  case class B(a: String, i: Int)
  case class C(i: Int, b: Boolean)

  implicit val encodeA: Encoder[A] = deriveEncoder
  implicit val encodeB: Encoder[B] = deriveEncoder
  implicit val encodeC: Encoder[C] = deriveEncoder
  implicit val decodeA: Decoder[A] = deriveDecoder
  implicit val decodeB: Decoder[B] = deriveDecoder
  implicit val decodeC: Decoder[C] = deriveDecoder
}


我想使用circe和Shapeless副产品将一个可以是JSON的值编码。

import io.circe.shapes._, io.circe.syntax._
import shapeless._

import model._

type ABC = A :+: B :+: C :+: CNil

val c: ABC = Coproduct[ABC](C(123, false))


起初看起来不错:

scala> c.asJson
res0: io.circe.Json =
{
  "i" : 123,
  "b" : false
}


但是问题是我永远无法解码包含B元素的副产品,因为任何可以解码为B的有效JSON文档也可以解码为A,并且圆形提供的副产品解码器会尝试元素按在副产品中出现的顺序排列。

scala> val b: ABC = Coproduct[ABC](B("xyz", 123))
b: ABC = Inr(Inl(B(xyz,123)))

scala> val json = b.asJson
json: io.circe.Json =
{
  "a" : "xyz",
  "i" : 123
}

scala> io.circe.jawn.decode[ABC](json.noSpaces)
res1: Either[io.circe.Error,ABC] = Right(Inl(A(xyz)))


如何在编码中消除副产品元素的歧义?

最佳答案

circe-shapes模块对没有标签的普通hlists和coproducts进行编码,如上所示:coproduct作为元素的裸JSON表示,而hlist最终只是JSON数组(与默认的tuple编码相同):

scala> ("xyz" :: List(1, 2, 3) :: false :: HNil).asJson.noSpaces
res2: String = ["xyz",[1,2,3],false]


对于Hlists,由于名称重叠而没有歧义的危险,但对于副产品则没有。但是,在两种情况下,都可以使用Shapeless的标签机制将标签添加到JSON表示中,该机制用类型级别的符号标记值。

具有标签的hlist称为“记录”,具有标签的副产品称为“联合”。 Shapeless为这两种方法提供了特殊的语法和操作,并且圆形与未标记的hlist或副产品区别对待。例如(假设上面的定义和导入):

scala> import shapeless.union._, shapeless.syntax.singleton._
import shapeless.union._
import shapeless.syntax.singleton._

scala> type ABCL = Union.`'A -> A, 'B -> B, 'C -> C`.T
defined type alias ABCL

scala> val bL: ABCL = Coproduct[ABCL]('B ->> B("xyz", 123))
bL: ABCL = Inr(Inl(B(xyz,123)))

scala> val jsonL = bL.asJson
jsonL: io.circe.Json =
{
  "B" : {
    "a" : "xyz",
    "i" : 123
  }
}

scala> io.circe.jawn.decode[ABCL](jsonL.noSpaces)
res3: Either[io.circe.Error,ABCL] = Right(Inr(Inl(B(xyz,123))))


记录的编码类似地包含成员名称:

scala> ('a ->> "xyz" :: 'b ->> List(1) :: 'c ->> false :: HNil).asJson.noSpaces
res4: String = {"c":false,"b":[1],"a":"xyz"}


通常,如果您希望hlist和coproduct编码包含标签(并且看起来像您从circe-generic获得的案例类和密封特征层次结构的编码),则可以使用记录或coproducts告诉circe-shapes来执行此操作最少的语法和运行时开销。

07-26 00:17