json - 我的副产品编码不明确

标签 json scala union circe coproduct

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

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 将可能是其中任何一个的值编码为 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 和 coproducts 进行编码,如上所示:coproduct 作为元素的裸 JSON 表示,hlist 最终只是一个 JSON 数组(与默认的元组编码相同):

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

在 hlist 的情况下,不存在由于名称重叠而产生歧义的危险,但对于副产品则存在。不过,在这两种情况下,您都可以使用 Shapeless 的标签机制向 JSON 表示添加标签,该机制使用类型级符号标记值。

带有标签的 hlist 称为“记录”,带有标签的副产品称为“联合”。 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 和 coproduct 编码包含标签(并且看起来像您从 circe-generic 获得的案例类和密封特征层次结构的编码),您可以使用记录或 coproducts 来告诉 circe-shapes 执行此操作以最少的额外语法和运行时开销。

关于json - 我的副产品编码不明确,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44309520/

相关文章:

scala - 制作经过 sanitizer 的字符串类型时的对称相等(使用 scala.Proxy)

mysql - 在 SQL Union 语法中组合数据

ios - 发布 JSON 时出现 JSON 错误

python - Flask:将 Python dict 转换为客户端 api 的 json 对象

ios - 统计某个对象在 JSON 查询中出现的次数

scala - 如何使用 Play Framework (scala)在HTML中打印@符号

php - 通过AJAX、PHP和MySQL系统地更新页面

scala - Spark 按列值分区数据集

sql - 在 UNION 中排序

mysql - 联合两个 SQL 查询