从 JSON 字符串创建一个 ListMap[String,Int] 以在 case class 中使用

Create a ListMap[String,Int] from a JSON string to use within a case class

我有这个 JSON 来自 Kafka 主题的字符串

{"id": 12345, "items": {"unit17": 0, "unit74": 0, "unit42": 0, "unit96": 0, "unit13": 0, "unit16": 0, "unit11": 0, "z10": 0, "z0": 1}}

通过使用 spray-json(版本 1.3.5),我想解析它,所以我这样做:

    val parsedStream = stream.map( event => event.parseJson )

这很好用,但是当使用“parseJson”时,项目的嵌套 JSON 按字母顺序排列并在列表中:

    parsedStream.print()
--> List(12345,{"unit11": 0, "unit13": 0, "unit16": 0, "unit17":0, "unit42": 0, "unit74": 0, "unit96": 0, "z0": 1, "z10": 0}}

知道为什么 spray-json 会这样并自动排序吗?是否有任何设置可以避免这种情况或应用选项?

我尝试对另一个库 play-json (play.api.libs.json.Json) 做同样的事情,它工作正常,所以我可以使用这个,但我很好奇我是否遗漏了什么带喷雾-json:

    val parsedStream = stream.map( event => Json.parse(event))
    parsedStream.print()

    --> {"id":12345,"items":{"unit17":0,"unit74":0,"unit42":0,"unit96":0,"unit13":0,"unit16":0,"unit11":0,"z10":0,"z0":1}}

最后,我想用该流的值提供一个案例 class;为此,我实现了下一个元素:

    case class MyCaseClass(id:Int,items:Map[String,Int])
    val parsedStream = stream.map{ value => MyCaseClass( (value \ "id").as[Int], 
                                                         (value \ "items").as[Map[String,Int]] ) }

这很顺利,但是因为我为“items”属性使用了一个 Map,JSON 中的顺序没有被强制保留,我需要它,因为这将被发送到模型来预测一些东西,并且模型训练是使用与 Kafka 主题生成的相同项目顺序完成的。我知道 scala 提供了一个 ListMap ,它是一种带有 List 接口的“有序”映射,但是当像这样使用它时:

    case class MyCaseClass(id:Int,items:ListMap[String,Int])
    val parsedStream = stream.map{ value => MyCaseClass( (value \ "id").as[Int], 
                                                         (value \ "items").as[ListMap[String,Int]] ) }

编译器声明“未找到类型 scala.collection.immutable.ListMap[String,Int] 的 Json 反序列化器。尝试为此类型实现隐式读取或格式。”,我猜是因为没有方便的 Play 自动 JSON 这种类型的宏,它应该被实现。您是否有关于如何为 ListMap 执行此操作的任何提示或我可以实现的任何其他方式让我的案例 class 中的项目与它们在 JSON 中的顺序相同?也许将“项目”JSON 字符串更改为 JSON 数组可以解决问题,关于我如何实现它的任何提示以及它是否是一个好主意?

编辑 - 用 circe-json

解决

在使用 play-json 进行了一些测试之后,将 ListMap 用于 Reads 宏有点棘手,所以我阅读并尝试了 circe-json 和最后一个它对于解析中的顺序和到 ListMap 的转换,这两者都非常简单。只需一些导入和一行代码就可以完成解析并转换为大小写 class:

import io.circe._
import io.circe.parser._
import io.circe.generic.auto._

[...]

case class MyCaseClass(id:Int,items:ListMap[String,Int])

[...]

val streamParsed = stream.map{ event => parser.decode[MyCaseClass](event) match {
   case Left(failure) => [...]
   case Right(myCaseClassInstance) => { myCaseClassInstance }
}}

[...]

这将自动解析保持顺序并使用 ListMap 生成 DataStream[MyCaseClass] 以仍然保持生成的地图中的顺序。使用支持 ListMap 并透明地处理此转换的“io.circe.generic.auto._”可以实现从 JSON 到 ListMap 的映射。

这似乎是 1.3.0 中引入的 github 上的未决问题。您可以尝试降级到 1.2.6 或使用另一个 json 库,例如 circe

https://github.com/spray/spray-json/issues/119

JSON 格式不保证键的顺序,Spray 遵守规范。

就像 bellam 所说的那样,您可能想要使用一个比规范做得更多的库,并保持您的密钥有序,like circe