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