如何在 Scala Play 框架 2 中将 JSON 字段转换为 Seq?

How do I transform a JSON fields into a Seq in Scala Play framework 2?

我有一些 JSON 来自我无法控制的外部 API。 JSON 的一部分格式如下:

{
  "room_0": {
    "area_sq_ft": 151.2
  },
  "room_1": {
    "area_sq_ft": 200.0
  }
}

他们没有像他们应该使用的那样使用数组,而是使用 room_n 作为 n 个元素的键。我不想用 room_0、room_1、room_2 等创建案例 class,而是想将其转换为 Seq[Room],这是我的 Room 案例 class:

case class Room(area: Double)

我正在使用来自 play.api.libs.json 的 Reads 将 JSON 的其他部分转换为大小写 classes,并且更愿意使用 Reads 进行此转换。我怎样才能做到这一点?

这是我试过的方法。

val sqFtReads = (__ \ "size_sq_ft").read[Double]
val roomReads = (__ \ "size_sq_ft").read[Seq[Room]](sqFtReads).map(Room) 
cmd19.sc:1: overloaded method value read with alternatives:
  (t: Seq[$sess.cmd17.Room])play.api.libs.json.Reads[Seq[$sess.cmd17.Room]] <and>
  (implicit r: play.api.libs.json.Reads[Seq[$sess.cmd17.Room]])play.api.libs.json.Reads[Seq[$sess.cmd17.Room]]
 cannot be applied to (play.api.libs.json.Reads[Double])
val roomReads = (__ \ "size_sq_ft").read[Seq[Room]](sqFtReads).map(Room)

一个棘手的小挑战,但 Reads 完全可以实现。

首先,Reads[Room] - 即单个 Room 实例的转换器:

val roomReads = new Reads[Room] {
  override def reads(json: JsValue): JsResult[Room] = {
    (json \ "area_sq_ft").validate[Double].map(Room(_))
  }
}

非常简单;我们查看 JSON 并尝试找到一个名为 area_sq_ft 的 top-level 字段,它验证为 Double。如果一切顺利,我们将根据需要 return 填充 Room 实例。

接下来,上游对象的转换器,以良好的方式 Postel's Law,您正在为自己的消费者进行清理。

val strangeObjectReads = new Reads[Seq[Room]] {
  override def reads(json: JsValue): JsResult[Seq[Room]] = {

    json.validate[JsObject].map { jso =>

      val roomsSortedNumerically = jso.fields.sortBy { case (name, contents) =>
        val numericPartOfRoomName = name.dropWhile(!_.isDigit)
        numericPartOfRoomName.toInt
      }

      roomsSortedNumerically.map { case (name, contents) =>
        contents.as[Room](roomReads)
      }

    }
  }
}

这里的关键是整个地段的 json.validate[JsObject]。通过 mapping 这个我们得到了我们需要包装整个东西的 JsResult,另外,我们可以访问 JSON 对象中的 fields,这是定义为 Seq[(String, JsValue)].

为了确保我们在输出序列中以正确的顺序放置字段,我们进行了一些字符串操作,获取 room_1 字符串的数字部分,并将其用作 sortBy 标准。我在这里有点天真并假设您的上游服务器不会做任何像跳过房间号这样令人讨厌的事情!

一旦您按照数字对房间进行排序,我们就可以 map 覆盖它们,用我们的 roomReads 转换器转换每个房间。

您可能已经注意到我的自定义 Reads 实现绝对是 而不是 one-liners。这来自于处理古怪的上游 JSON 格式的痛苦经历。有点冗长,当上游服务器突然改变其 JSON 格式时,使用更多的变量并稍微分解一下会得到很大的回报!