【问题标题】:Argonaut: decoding a polymorphic arrayArgonaut:解码多态数组
【发布时间】:2016-11-22 20:47:40
【问题描述】:

我尝试为其编写 DecodeJson[T] 的 JSON 对象包含一个不同“类型”的数组(意味着其元素的 JSON 结构是不同的)。唯一的共同特征是type 字段,可用于区分类型。所有其他领域都是不同的。示例:

{
    ...,
    array: [
        { type: "a", a1: ..., a2: ...},
        { type: "b", b1: ...},
        { type: "c", c1: ..., c2: ..., c3: ...},
        { type: "a", a1: ..., a2: ...},
        ...
    ],
    ...
}

使用 argonaut,是否可以将 JSON 数组映射到 Scala Seq[Element],其中 ElementElementAElementB 等类型的合适案例类的超类型?

我对@9​​87654328@ 做了同样的事情,这很容易(基本上是一个Reads[Element] 评估type 字段并相应地转发到更具体的Reads)。但是,我找不到使用 argonaut 的方法。


编辑:示例

Scala 类型(我希望使用):

case class Container(id: Int, events: List[Event])

sealed trait Event
case class Birthday(name: String, age: Int) extends Event
case class Appointment(start: Long, participants: List[String]) extends Event
case class ... extends Event

JSON 实例(不在我的控制之下):

{
   "id":1674,
   "events": {
      "data": [
         {
            "type": "birthday",
            "name": "Jones Clinton",
            "age": 34
         },
         {
            "type": "appointment",
            "start": 1675156665555,
            "participants": [
               "John Doe",
               "Jane Doe",
               "Foo Bar"
            ]
         }
      ]
   }
}

【问题讨论】:

    标签: json scala argonaut


    【解决方案1】:

    您可以创建一个小函数来帮助您构建处理这种格式的解码器。

    请参阅下面的示例。

    import argonaut._, Argonaut._
    
    def decodeByType[T](encoders: (String, DecodeJson[_ <: T])*) = {
      val encMap = encoders.toMap
    
      def decoder(h: CursorHistory, s: String) =
        encMap.get(s).fold(DecodeResult.fail[DecodeJson[_ <: T]](s"Unknown type: $s", h))(d => DecodeResult.ok(d))
    
      DecodeJson[T] { c: HCursor =>
        val tf = c.downField("type")
    
        for {
          tv   <- tf.as[String]
          dec  <- decoder(tf.history, tv)
          data <- dec(c).map[T](identity)
        } yield data
      }
    }
    
    case class Container(id: Int, events: ContainerData)
    case class ContainerData(data: List[Event])
    
    sealed trait Event
    case class Birthday(name: String, age: Int) extends Event
    case class Appointment(start: Long, participants: List[String]) extends Event
    
    implicit val eventDecoder: DecodeJson[Event] = decodeByType[Event](
      "birthday" -> DecodeJson.derive[Birthday],
      "appointment" -> DecodeJson.derive[Appointment]
    )
    
    implicit val containerDataDecoder: DecodeJson[ContainerData] = DecodeJson.derive[ContainerData]
    implicit val containerDecoder: DecodeJson[Container] = DecodeJson.derive[Container]
    
    val goodJsonStr =
      """
        {
           "id":1674,
           "events": {
              "data": [
                 {
                    "type": "birthday",
                    "name": "Jones Clinton",
                    "age": 34
                 },
                 {
                    "type": "appointment",
                    "start": 1675156665555,
                    "participants": [
                       "John Doe",
                       "Jane Doe",
                       "Foo Bar"
                    ]
                 }
              ]
           }
        }
      """
    
    def main(args: Array[String]) = {
      println(goodJsonStr.decode[Container])
    
      // \/-(Container(1674,ContainerData(List(Birthday(Jones Clinton,34), Appointment(1675156665555,List(John Doe, Jane Doe, Foo Bar))))))
    }
    

    【讨论】:

    • 感谢您的回复。问题是没有一个子对象(例如data)——相反,所有的有效负载都在同一个层次上。我在我的问题中编辑了示例。
    • 好的,我明白你在说什么。我用一个新的解码器更新了我原来的帖子,它应该可以处理你需要的东西。
    • 抱歉浪费了您的时间。我的课程看起来有点不同,我无法让它发挥作用。也许我们可以开始最后的尝试——我在上面添加了一个完整的例子。非常感谢。
    • 我做了另一个编辑以反映您的实际格式。希望这次我们成功了!
    猜你喜欢
    • 2016-05-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多