基于JSON结构解码Argonaut中的密封特征?

时间:2016-08-23 19:02:01

标签: scala argonaut

给出以下示例:

sealed trait Id

case class NewId(prefix: String, id: String) extends Id
case class RevisedId(prefix: String, id: String, rev: String) extends Id

case class User(key: Id, name: String)

val json = """
{
  "key": {
    "prefix": "user",
    "id": "Rt01",
    "rev": "0-1"
  },
  "name": "Bob Boberson"
}
"""

implicit val CodecUser: CodecJson[User] = casecodec2(User.apply, User.unapply)("key", "name")

implicit val CodecId: CodecJson[Id] = ???

json.decodeOption[User]

我需要为CodecJson写一个Id,它会在具有适当结构的对象时对其进行解码。

添加某种鉴别器字段是一种常见的建议,但我不想更改我已经使用spray-jsonjson4s生成/使用的JSON。

在这些库中,您的编码器/解码器基本上只是PartialFunction[JValue, A]PartialFunction[A, JValue]。如果您的值未在域中定义,则表示失败。我认为这是一个非常简单,优雅的解决方案。除此之外,您还有JSON类型的Extractors,因此很容易匹配字段/结构上的对象。

Rapture更进了一步,使得字段顺序不重要并忽略了不匹配字段的存在,所以你可以这样做:

case json"""{ "prefix": $prefix, "id": $id, "rev": $rev }""" => 
  RevisedId(prefix, id, rev)

这非常简单/强大。

我无法弄清楚如何使用argonaut做类似的事情。这是迄今为止我提出的最好的:

val CodecNewId = casecodec2(NewId.apply, NewId.unapply)("prefix", "id")
val CodecRevisedId = casecodec3(RevisedId.apply, RevisedId.unapply)("prefix", "id", "rev")

implicit val CodecId: CodecJson[Id] =
  CodecJson.derived[Id](
    EncodeJson {
      case id: NewId => CodecNewId(id)
      case id: IdWithRev => RevisedId(id)
    },
    DecodeJson[Id](c => {
      val q = RevisedId(c).map(a => a: Id)
      q.result.fold(_ => CodecNewId(c).map(a => a: Id), _ => q)
    })
  )

因此存在一些问题。我必须定义我不打算使用的额外编解码器。我没有在EncodeJson CodecJson[Id]中使用案例类提取器,而是委托我定义的其他编码器。只是对于只有2或3个字段的类,感觉不是很直接或干净。

DecodeJson部分的代码也非常混乱。除ifEmpty fold侧的额外拼写版本外,它与DecodeJson.|||中的代码相同。

是否有人有更惯用的方式为argonaut中的Sum-types编写基本编解码器,需要一个鉴别器,而是可以匹配结构 json?

1 个答案:

答案 0 :(得分:1)

这是我能够想到的最好的。部分功能没有那种基本的优雅感,但它比我的第一次尝试更简洁,更容易破译。

val CodecNewId = casecodec2(NewId.apply, NewId.unapply)("prefix", "id")
val CodecRevisedId = casecodec3(RevisedId.apply, RevisedId.unapply)("prefix", "id", "rev")

implicit val CodecId: CodecJson[Id] = CodecJson(
  {
    case id: NewId => CodecNewId(id)
    case id: RevisedId => CodecRevisedId(id)
  },
   (CodecRevisedId ||| CodecNewId.map(a => a: Id))(_))

我们仍在使用"具体"每个子类型的编解码器。但是我们已经离开了CodecJson.derive电话,我们不需要将我们的编码功能包装在EncodeJson中,我们可以map我们的DecodeJson函数而不是类型转换,所以我们可以回到使用|||而不是复制它的实现,这使得代码更具可读性。

这绝对是一个可行的解决方案,如果不是我希望的那样。