次の例を考えます。
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
forを記述する必要があります。Id
spray-json
何らかの種類の識別子フィールドを追加することは、これに対する一般的な提案ですが、既に生成/消費している JSON を変更したくありませんjson4s
。
これらのライブラリでは、エンコーダー/デコーダーは基本的に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)
})
)
そのため、いくつかの問題があります。使用する予定のない追加のコーデックを定義する必要があります。EncodeJson
forで case-class エクストラクタを使用する代わりに、CodecJson[Id]
定義した他のエンコーダに委任しています。ただ、フィールドが 2 つまたは 3 つしかないクラスでは、あまり単純でもきれいでもありません。
このセクションのコードDecodeJson
もかなり厄介です。ifEmpty
の側に追加の型キャストがあることを除けばfold
、 のコードと同じですDecodeJson.|||
。
誰かが、ディスクリミネーターを必要とせず、代わりにjsonの構造に一致できるアルゴナウトでSumタイプの基本的なコーデックを書くためのより慣用的な方法を持っていますか?