我的副产品编码不明确
My coproduct encoding is ambiguous
这个问题最近出现过几次,所以我在这里问答。假设我有这样的案例 类:
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
,circe-shapes 提供的余积解码器按照元素在余积中出现的顺序尝试元素。
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 模块对没有标签的普通 hlist 和余积进行编码,如上所示:余积作为元素的 JSON 表示,而 hlist 最终只是 JSON数组(与默认元组编码相同):
scala> ("xyz" :: List(1, 2, 3) :: false :: HNil).asJson.noSpaces
res2: String = ["xyz",[1,2,3],false]
在 hlist 的情况下,不存在因名称重叠而导致歧义的危险,但对于联积,则存在歧义。不过,在这两种情况下,您都可以使用 Shapeless 的标签机制向 JSON 表示添加标签,该机制使用类型级符号标记值。
带标签的 hlist 称为 "record",带标签的余积是 "union"。 Shapeless 为这两者提供了特殊的语法和操作,而 circe-shapes 对它们的处理方式与未标记的 hlists 或 coproducts 不同。例如(假设上面的定义和导入):
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 和联积编码包含标签(并且看起来像您从 circe-generic 获得的 case 类 和 sealed trait 层次结构的编码),您可以使用 records 或coproducts 告诉 circe-shapes 以最少的额外语法和运行时开销来执行此操作。
这个问题最近出现过几次,所以我在这里问答。假设我有这样的案例 类:
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
,circe-shapes 提供的余积解码器按照元素在余积中出现的顺序尝试元素。
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 模块对没有标签的普通 hlist 和余积进行编码,如上所示:余积作为元素的 JSON 表示,而 hlist 最终只是 JSON数组(与默认元组编码相同):
scala> ("xyz" :: List(1, 2, 3) :: false :: HNil).asJson.noSpaces
res2: String = ["xyz",[1,2,3],false]
在 hlist 的情况下,不存在因名称重叠而导致歧义的危险,但对于联积,则存在歧义。不过,在这两种情况下,您都可以使用 Shapeless 的标签机制向 JSON 表示添加标签,该机制使用类型级符号标记值。
带标签的 hlist 称为 "record",带标签的余积是 "union"。 Shapeless 为这两者提供了特殊的语法和操作,而 circe-shapes 对它们的处理方式与未标记的 hlists 或 coproducts 不同。例如(假设上面的定义和导入):
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 和联积编码包含标签(并且看起来像您从 circe-generic 获得的 case 类 和 sealed trait 层次结构的编码),您可以使用 records 或coproducts 告诉 circe-shapes 以最少的额外语法和运行时开销来执行此操作。