从 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
JSON 格式不保证键的顺序,Spray 遵守规范。
就像 bellam 所说的那样,您可能想要使用一个比规范做得更多的库,并保持您的密钥有序,like circe。
我有这个 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
JSON 格式不保证键的顺序,Spray 遵守规范。
就像 bellam 所说的那样,您可能想要使用一个比规范做得更多的库,并保持您的密钥有序,like circe。