使用 circe 在 Scala 中 JSON 将嵌套字段解码为 Map[String, String]

Posted

技术标签:

【中文标题】使用 circe 在 Scala 中 JSON 将嵌套字段解码为 Map[String, String]【英文标题】:JSON decode nested field as Map[String, String] in Scala using circe 【发布时间】:2020-10-28 00:23:06 【问题描述】:

这里是个新手。我正在尝试使用 circe 将 JSON 字符串解码为 Scala 中的案例类。我希望将输入 JSON 中的嵌套字段之一解码为 Map[String, String] 而不是为其创建单独的案例类。

示例代码:

import io.circe.parser
import io.circe.generic.semiauto.deriveDecoder

case class Event(
  action: String,
  key: String,
  attributes: Map[String, String],
  session: String,
  ts: Long
)
case class Parsed(
  events: Seq[Event]
)

Decoder[Map[String, String]]

val jsonStr = """
  "events": [
      "ts": 1593474773,
      "key": "abc",
      "action": "hello",
      "session": "def",
      "attributes": 
          "north_lat": -32.34375,
          "south_lat": -33.75,
          "west_long": -73.125,
          "east_long": -70.3125
      
  ]
""".stripMargin

implicit val eventDecoder = deriveDecoder[Event]
implicit val payloadDecoder = deriveDecoder[Parsed]
val decodeResult = parser.decode[Parsed](jsonStr)
val res = decodeResult match 
  case Right(staff) => staff
  case Left(error) => error

我在属性字段上出现解码错误,如下所示:

DecodingFailure(String, List(DownField(north_lat), DownField(attributes), DownArray, DownField(events)))

我在这里找到了一个关于如何将 JSON 字符串解码为地图的有趣链接:Convert Json to a Map[String, String]

但我不知道该怎么做。

如果有人能指出我正确的方向或帮助我解决这个问题,那就太棒了。

【问题讨论】:

虽然您当然可以,但您更喜欢使用 Map[String, String] 而不是适当的案例类?还是属性是动态的? 我正在尝试将此案例类转换为另一个具有键和值字段的案例类的数组。我之前尝试过将属性的案例类转换为 Scala 中的映射,但结果很丑陋。所以我一直在寻找一种方法,可以将这个对象作为 Map[String,String] 从 JSON 本身读取。 我不明白你的意思:“我正在尝试将此案例类转换为另一个具有键和值字段的案例类的数组” - 为什么你想把一个案例类变成一个Map?我最初的问题仍然是,Map 为您提供了案例类没有的什么? 这是我需要做的事情 Luis。对我来说,将 Map[String, String] tp 转换为 Attribute(key: String, value: String) 类型的案例类列表比将一个案例类转换为其他案例类的列表更容易一些字段可能出现在此 JSON 中的属性中,也可能不出现。要回答您的问题,是的,这些字段是动态的,很遗憾,我目前无法要求我们的合作伙伴更改流量模式。 好的,在这种情况下,您需要做什么就很清楚了。我会这样做,首先在您的案例类上使用 List[Attribute] 而不是 Map[String, String] 。为 List[Attribute] 定义您自己的显式解码器,然后使用它自动派生 Event 的解码器。我现在在手机上,所以我无法提供代码,但是如果当我可以访问计算机时你仍然没有收到答案,我会试一试。 【参考方案1】:

让我们解析错误:

DecodingFailure(String, List(DownField(geotile_north_lat), DownField(attributes), DownArray, DownField(events)))

这意味着我们应该在“事件”中查找名为“attributes”的数组,并在该数组中查找名为“geotile_north_lat”的字段。最后一个错误是该字段无法作为字符串读取。实际上,在您提供的有效负载中,该字段不是字符串,而是双精度。

所以你的问题与地图解码无关。只需使用 Map[String, Double] 就可以了。

【讨论】:

太棒了!谢谢你的快速反应。这对我有用。为愚蠢的错误道歉。 没关系 :-) Circe 错误已经习惯了,但它们实际上是相当强大的工具,即使它们看起来有点难看! @ArunShyam 所以你说属性是动态的,但你确定它们总是数字吗?我认为 Map[String, String] 的想法是因为这些值也可以是您稍后需要根据它们的名称再次解析的任何内容。 是的,路易斯他们是动态的,但目前他们都以双重身份出现。你是对的,未来可能会或可能不会出现这种情况,这肯定会导致问题。【参考方案2】:

所以你可以这样做:

final case class Attribute(
    key: String,
    value: String
)

object Attribute 
  implicit val attributesDecoder: Decoder[List[Attribute]] =
    Decoder.instance  cursor =>
      cursor
        .value
        .asObject
        .toRight(
          left = DecodingFailure(
            message = "The attributes field was not an object",
            ops = cursor.history
          )
        ).map  obj =>
          obj.toList.map 
            case (key, value) =>
              Attribute(key, value.toString)
          
        
    


final case class Event(
    action: String,
    key: String,
    attributes: List[Attribute],
    session: String,
    ts: Long
)

object Event 
  implicit val eventDecoder: Decoder[Event] = deriveDecoder

你可以这样使用:

val result = for 
  json <- parser.parse(jsonStr).left.map(_.toString)
  obj <- json.asObject.toRight(left = "The input json was not an object")
  eventsRaw <- obj("events").toRight(left = "The input json did not have the events field")
  events <- eventsRaw.as[List[Event]].left.map(_.toString)
 yield events

// result: Either[String, List[Event]] = Right(
//   List(Event("hello", "abc", List(Attribute("north_lat", "-32.34375"), Attribute("south_lat", "-33.75"), Attribute("west_long", "-73.125"), Attribute("east_long", "-70.3125")), "def", 1593474773L))
// )

您可以自定义 Attribute 类及其 Decoder,因此它们的值是 DoubleJsons

【讨论】:

这是超级路易斯!当然是通用和健壮的。我更新了我的代码以处理这些行。

以上是关于使用 circe 在 Scala 中 JSON 将嵌套字段解码为 Map[String, String]的主要内容,如果未能解决你的问题,请参考以下文章

使用 circe 将 Scala None 编码为 JSON 值

使用 circe 时如何在 Scala 中表示动态 JSON 键

使用 circe 解码 JSON 对象时捕获未使用的字段

从 Scala Circe 中的 Map 列表创建一个 json

使用 Circe 将包含 HList 的案例类解析为 JSON 字符串

如何在circe中编码/解码json的时间戳?