2016-08-23 25 views
1

考虑下面的例子:基于JSON结构解码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] 

我需要编写Id当它具有适当的结构,将解码对象的CodecJson

添加某种歧视字段是一个常见的建议,但我不想更改我已经生产/使用的和json4s的JSON。

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

被提进了一步,让字段顺序不重要,忽略不匹配字段的存在,所以你可能只是这样做:

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部分的代码也很杂乱。除了foldifEmpty一侧的其他类型外,它与DecodeJson.|||中的代码相同。

没有人有写的总和,类型有一个基本的编解码器中淘金是需要鉴别而是可以匹配在结构的JSON的更习惯的方法?

回答

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函数代替类型转换,因此我们可以回到使用|||而不是复制它的实现,这使得代码更具可读性。

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