首页 > 解决方案 > 从 JSON 字符串创建 ListMap[String,Int] 以在案例类中使用

问题描述

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

{"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) 做同样的事情,它工作正常,所以我可以使用这个,但我很好奇我是否遗漏了 spray-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}}

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

    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]] ) }

这工作顺利但是因为我使用“项目”属性的地图,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]] ) }

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

编辑 - 用 circe-json 解决

在对 play-json 进行了一些测试之后,将 ListMap 用于 Reads 宏有点棘手,所以我阅读并尝试了有关 circe-json 的内容,最后这对两者都很简单,解析中的顺序和转换为 ListMap。只需一些导入和一行代码就足以完成解析并转换为案例类:

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] 以仍然在生成的地图中保持顺序。从 JSON 到 ListMap 的映射可以通过支持 ListMap 并透明地处理这种转换的“io.circe.generic.auto._”来实现。

标签: jsonscalaapache-kafkaapache-flinkplay-json

解决方案


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

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


推荐阅读