scala json parse and get a nested key and value - scala

I have a json like below,
I have json like below I need to extract the value from them
{
"filed1": "value1",
"message": {
"payload": [{
"type": ["Extra","ID"],
info": {
"value": 8
}
}, {
"type": ["Free"],
info": {
"value": 99
}
}, {
"type": ["Actual"],
info": {
"value": 100
}
}]
},
"code": "0000"
}
{
"filed1": "value1",
"message": {
"payload": [{
"type": ["Extra", "new"],
"value": 9
}]
},
"code": "0001"
}
from the above two types of json .
If the input json has list of type keys then look for type field which has element Extra and get the value inside info
If the input json has one type key then check type field , if it has element Extra and get the direct va;ue
I am trying like below for type but it fails for list of types json, i.e first json input
import org.json4s._
import org.json4s.jackson.JsonMethods._
import org.json4s.JsonDSL._
val json = parse(myjsonString, true)
val field = compact(render(json \\ "type"))
val ok = field.contains("[\"Extra\"")
if(ok == true){
println("value " + compact(render(json \\ "value")))
}

You need to use json4s to do the work for you. Specifically, you need to use the extract method on the json to convert it into your particular data structure. Once you have done that you can process it as Scala types.
This is my attempt at matching the structure of your JSON with Scala case classes:
case class PayloadInfo(value: Int)
case class Payload(`type`: List[String], info: PayloadInfo)
case class Message(payload: List[Payload])
case class Full(filed1: String, message: Message, code: String)
implicit val formats = DefaultFormats
val json = parse(myjsonString, true)
val full = json.extract[Full]
val res = full.message.payload.filter(_.`type`.contains("Extra"))
[ The backticks around type are required because it is a reserved word ]
I may have got the types a bit wrong but this should give you the idea of how to go about it.
You can also process the JValues directly but that is going to be much messier.

Related

Decode List[String] to List[JSONObject(key,value)] in circe scala

Given incoming json like below, how can i decode it the given case class based on condition.
Incoming JSON
{
"config": {
"files": ["welcome"],
"channel": "media"
}
}
Case Classes
case class File(`type`: String, value: String)
case class Config(files: List[File],
channel: String = "BC")
object Config{
implicit val FileDecoder: Decoder[File] = deriveDecoder[File]
implicit val ConfigDecoder: Decoder[Config] = deriveDecoder[Config]
}
case class Inventory(config: Config)
object Inventory {
implicit val InventoryDecoder: Decoder[Inventory] = deriveDecoder[Inventory]
}
I do not have control on incoming files values in json it can be List[String] or List[File] so i need to handle both cases in my decoding logic.
So as we can see above my aim is to check if the incomes files values is List[String] then transform that values to below where type is hardcoded to "audio"
"files": [{
"type": "audio",
"value": "welcome.mp3"
}],
The overall json should look like below before it mapped into case classes for auto decoding.
{
"config": {
"files": [{
"type": "audio",
"value": "welcome.mp3"
}],
"channel": "media"
}
}
what i understood that this can be achieved either by transforming the json before decoding or can also be achieved during the decoding of files.
I tried writing the decoding logic at File level but i am not able to succeed. I am not getting the crux of how to do this.
Tried code
implicit val FileDecoder: Decoder[File] = deriveDecoder[File].prepare { (aCursor: ACursor) =>
{
if(!aCursor.values.contains("type")){
aCursor.values.map( v =>
Json.arr(
Json.fromFields(
Seq(
("type", Json.fromString("audio")),
("value", v.head)
)
)
)
)
}
}
}
We can use a custom Decoder for File to provide a default value to type
final case class File(`type`: String, value: String)
object File {
implicit final val FileDecoder: Decoder[File] =
Decoder.instance { cursor =>
(
cursor.getOrElse[String](k = "type")(fallback = "audio"),
cursor.get[String](k = "value")
).mapN(File.apply)
}.or(
Decoder[String].map(value => File(`type` = "audio", value))
)
}
Which can be used like this:
val data =
"""[
{
"type": "audio",
"value": "welcome.mp3"
},
{
"value": "foo.mp3"
},
"bar.mp3"
]"""
parser.decode[List[File]](data)
// res: Either[io.circe.Error, List[File]] =
// Right(List(
// File("audio", "welcome.mp3"),
// File("audio", "foo.mp3"),
// File("audio", "bar.mp3")
// ))
You can see the code running here.

issue with circe serialization from json response in akka http

serialization issue in a field that can be of type int or of type List[String], it is a field that in each element is a list that always has 2 elements in the list the first element the value field is always a list [ String] and in the second element it is always of type int; the issue is in the value field and it fails to infer it because of this behavior.
body:
json response:
{
"custom_fields": [
{
"id": "d0c016df-e09a-492e-a7a2-cc92e1993627",
"name": "Sprint",
"type": "labels",
"date_created": "1586927852599",
"hide_from_guests": false,
"value": [
"64e19188-8440-4b35-8459-d49348c92e55"
],
"required": false
},
{
"id": "4513e77f-9866-4139-bcc5-458945db0deb",
"name": "Story Points",
"type": "drop_down",
"date_created": "1579556449403",
"hide_from_guests": false,
"value": 1,
"required": false
}
]
}
issue on field:
value
scala cire code serializer:
implicit val decodeCustomFields: Decoder[CustomFieldsItem] = new Decoder[CustomFieldsItem] {
final def apply(c: HCursor): Decoder.Result[CustomFieldsItem] =
for {
id <- c.downField("id").as[Option[String]]
name <- c.downField("name").as[Option[String]]
typeF <- c.downField("type").as[Option[String]]
date_created <- c.downField("date_created").as[Option[String]]
hide_from_guests <- c.downField("hide_from_guests").as[Option[Boolean]]
value <- c.downField("value").as[Option[List[String]]] // here it fails !!
required <- c.downField("required").as[Option[Boolean]]
} yield {
CustomFieldsItem( id, name, typeF, date_created, hide_from_guests, value , required )
}
}
scala unmarshal code for http response, it takes the circe serializer :
private def runRequest(req: HttpRequest): Future[CustomFieldsItem] =
Http()
.singleRequest(req)
.flatMap {
res =>
val ser = Unmarshal(res).to[CustomFieldsItem]
ser
}
message error on unmarshal:
An error has occurred: C[A]: DownField(value),MoveRight,DownArray,DownField(custom_fields),DownArray,DownField(tasks)
How could I improve or fix this serialization?
Make CustomFields be of type (CustomField[List[String]], CustomField[Int]) with a generic typed CustomField case class ?
The issue is that your modelisation doesn't reflect the reality of what you're receiving, and you're trying to fix that afterwards. If it always has 2 elements, it's not a list, it's a Tuple.

Scala Read Json as Map[String,T]

I am trying to read below json structure using scala code but i am not able to read it as case class object.
"a": {
"source": [
{
"name": "test",
"basePath": "/Users/bounce/anuj/data/test",
"granularity": "None"
},
{
"name": "test1",
"basePath": "/Users/bounce/anuj/data/test1",
"granularity": "None"
}
]
}
}
Below is the code that i am using :
val mapper = new ObjectMapper().registerModule(DefaultScalaModule)
val mapData = mapper.readValue(source, classOf[Map[String,FactConfig]])
#JsonInclude(Include.NON_EMPTY)
case class Source(name: String,
basePath: String,
granularity: String)
case class FactConfig(
source : List[Source])
val d = mapData.get("a").asInstanceOf[FactConfig]
When I m trying to convert it to FactConfig object I am getting below exception
Exception in thread "main" java.lang.ClassCastException: scala.collection.immutable.Map$Map1 cannot be cast to com.bounce.dp.etl.config.FactConfig
Please suggest to me what I am doing wrong.
In both Java and Scala world we normally lose generic type information during compilation. By using classOf operator jackson is not aware what should it do with map keys and values. The workaround is to use com.fasterxml.jackson.core.type.TypeReference, namely
val mapData = mapper.readValue(source, new TypeReference[Map[String, FactConfig]] {})

Parsing two kinds of json with one case class?

I have a case class in scala that needs to parse a json object. However the json object can look two different ways. Like this:
"hint": {
"structure": [
"HIDE"
]
}
Or like this:
"hint": {
"type": "1",
"template": "A"
}
I want to parse them both into the same case class in Scala using circe. I've trie doing something like this:
case class Hint(`type`:Option[String] = None,template:Option[String], structure: Option[List[String]])
object Hint {
implicit val hintJsonDecoder: Decoder[Hint] = deriveDecoder[Hint]
implicit val hintJsonEncoder: ObjectEncoder[Hint] = deriveEncoder[rHint]
}
But it seems like there should be a neater way of doing this so that I can just return say a list of strings in the case of the first instance, and just the type and template in the second instance. But I can't figure out how to do this using Circe.
Grateful for your help!
Maybe I'm not understand your question, but the use of Option gives you what you wan't. If you wanted a less verbose solution, you can use auto-derivation like as follows:
import io.circe.generic.auto._
import io.circe.parser.decode
case class HintContainer(hint: Hint)
case class Hint(`type`: Option[String], template: Option[String], structure: Option[List[String]])
object Sample extends App {
val testData1 =
"""
|{
| "hint": {
| "structure": [
| "HIDE"
| ]
| }
|}
|""".stripMargin
val testData2 =
"""
|{
| "hint": {
| "type": "1",
| "template": "A"
| }
|}
|""".stripMargin
println(decode[HintContainer](testData1))
println(decode[HintContainer](testData2))
}
Which gives:
Right(HintContainer(Hint(None,None,Some(List(HIDE)))))
Right(HintContainer(Hint(Some(1),Some(A),None)))

Flatten extranous fields when extracting using LiftJson or Json4s

I want to extract using LiftJson or Json4s the following Json (not quite but something similar) to the following case classes.
{
"data": [
{
"id": "1234",
"message": "Test",
"comments": {
"data": [
{
"id": "4321",
"content": "Test2",
}
]
}
}
The case classes:
case class A(id: String, message: string, comments: List[B])
case class B(id: String, content: String)
For the top level I can do: (val \ "data").extract[List[A]] to flatten the extra data field. But for the second level one, I don't see I way to use extract DIRECTLY.
Could I use a custom serializer (exemple here) or any of the following function (json4s) to remove the extraneous "data" field? Or any idea to make it simple?
def mapField(f: JField => JField): JValue
def transformField(f: PartialFunction[JField, JField]): JValue
Want I want to avoid is creating others intermidiate case class to extract the data, and then create the shown case class with it.
I found the solution a while ago, but haven't had time to reply. I was thinking backward, this is easy:
def transformListData(src: JValue, field: String): JValue = {
src.transformField {
case JField(x, v) if x == field => JField(field, v \ "data")
}
}
transformListData(json, "comments")
The following would remove the extra data and would flatten the list.