给定以下示例:
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]
我需要为Id
编写一个CodecJson
,当它具有适当的结构时,它将解码对象。
添加某种类型的鉴别符字段是对此的常见建议,但我不想改变我已经使用spray-json
和json4s
生产/消费的JSON。
在这些库中,编码器/解码器基本上只是PartialFunction[JValue, A]
和PartialFunction[A, JValue]
。如果您的值没有在域中定义,那么它就是失败的。我认为这是一个非常简单,优雅的解决方案。除此之外,您还获得了JSON类型的提取器,因此很容易在字段/结构上匹配对象。
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)
})
)
所以这有一些问题。我必须定义我不打算使用的额外编解码器。我没有为CodecJson[Id]
使用EncodeJson
中的大小写类提取器,而是将其委托给我定义的其他编码器。对于只有2到3个字段的类来说,只是感觉不是很直接或干净。
DecodeJson
部分的代码也相当混乱。除了fold
的ifEmpty
部分有一个额外的类型强制转换外,它与DecodeJson.|||
中的代码相同。
有没有人有一个更习惯的方法来为argonaut中的Sum-types编写一个基本的编解码器,使不需要标识符,而是可以匹配json的结构 ?
这是我能想到的最好的。它没有部分函数所具有的那种基本的优雅感,但它比我的第一次尝试更简洁,更容易理解。
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
函数而不是类型转换,所以我们可以回到使用|||
而不是复制它的实现,这使得代码更具可读性。
这绝对是一个可用的解决方案,如果不完全是我所希望的。