Home > Blockchain >  How to parse json to case class with map by jsonter, plokhotnyuk
How to parse json to case class with map by jsonter, plokhotnyuk

Time:11-08

I want to read json messages from Kafka and put them into another structure of SpecificRecordBase class (avro). The part of the json has dynamic structure for example

{"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"}}

I want to use a flexible table and put it in Map[String, String] where every key = attribute name and value = attribute value in string and where there is no validation. The example of target classes instances:

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"))

I wrote the code with a circle but want to get the same with plokhotnyuk/jsoniter-scala, could you help me please find the way.

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)

CodePudding user response:

One of possible solutions for the proposed data structure that passes decoding tests from the question:

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
  • Related