【问题标题】:How to parse json to case class with map by jsonter, plokhotnyuk如何使用 jsonter、plokhotnyuk 的地图将 json 解析为案例类
【发布时间】:2021-12-14 07:32:17
【问题描述】:

我想从 Kafka 读取 json 消息并将它们放入 SpecificRecordBase 类 (avro) 的另一个结构中。 json的部分例如具有动态结构

{"known_type": "test", 
 "unknown_type": {"attr1": true, "attr2": "value2", "attr3": 777}}

{"known_type": "test", 
 "unknown_type": {"field1":[1,3,4,7], "field2": {"sub_field": "test"}}}

{"known_type": "test", 
"unknown_type": {"param": "some_value"}}

我想使用一个灵活的表并将其放在 Map[String, String] 中,其中每个键 = 属性名称,值 = 字符串中的属性值,并且没有验证。目标类实例示例:

Example(test,Map(attr1 -> true, attr2 -> "value2", attr3 -> 777))

Example(test,Map(field1 -> [1,3,4,7], field2 -> {"sub_field" : "sub_value"}))

Example(test,Map(param -> "some_value"))

我用一个圆圈写了代码,但想用 plokhotnyuk/jsoniter-scala 得到同样的结果,你能帮我找到方法吗。

case class Example(known_type: String = "", unknown_type: Map[String, String])

val result: Option[Example] = for {
    json       <- parse(rawJson2).toOption
    t          <- json.hcursor.downField("known_type").as[String].toOption
    map        <- json.hcursor.downField("unknown_type").as[JsonObject].toOption
    m = map.toMap.map { case (name, value) => name -> value.toString() }
} yield Example(t, m)

【问题讨论】:

  • 可以使用自定义编解码器来完成。请详细描述您的用例以避免the XY problem。我的意思是为什么您决定将Map[String, String] 用于unknown_type 以及解析后将如何使用它?例如,序列化Exampleunknown_type 字段值未触及use the raw value codec for that
  • String 使用起来太贵了。对于压缩的 32 位引用,空字符串值在 Java 堆中占用 40 字节。此外,Map 的默认实现非常低效,为vulnerable under DoS attacks。如果您已经开始使用 jsoniter-scala 来提高效率,请创建基准和 run them with async-profiler 以了解幕后发生的情况。
  • @Andriy Plokhotnyuk 感谢您的回复。我更新了问题。

标签: json scala parsing circe


【解决方案1】:

通过问题的解码测试的建议数据结构的一种可能解决方案:

case class Example(known_type: String = "", unknown_type: Map[String, String])
      
implicit val mapCodec: JsonValueCodec[Map[String, String]] = new JsonValueCodec[Map[String, String]] {
  override def decodeValue(in: JsonReader, default: Map[String, String]): Map[String, String] = {
    val b = in.nextToken()
    if (b == '{') {
      if (in.isNextToken('}')) nullValue
      else {
        in.rollbackToken()
        var i = 0
        val x = Map.newBuilder[String, String]
        while ({
          i += 1
          if (i > 1000) in.decodeError("too many keys") // To save from DoS attacks that exploit Scala's map vulnerability: https://github.com/scala/bug/issues/11203
          x += ((in.readKeyAsString(), new String({
            in.nextToken()
            in.rollbackToken()
            in.readRawValAsBytes()
          }, StandardCharsets.UTF_8)))
          in.isNextToken(',')
        }) ()
        if (in.isCurrentToken('}')) x.result()
        else in.objectEndOrCommaError()
      }
    } else in.readNullOrTokenError(default, '{')
  }

  override def encodeValue(x: Map[String, String], out: JsonWriter): Unit = ???

  override val nullValue: Map[String, String] = Map.empty
}

implicit val exampleCodec: JsonValueCodec[Example] = make

【讨论】:

  • 非常感谢。代码有效且工作正常。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-06-25
  • 2019-01-18
  • 2020-02-10
  • 2021-09-10
  • 2017-04-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多