Scala Read Json as Map[String,T] - scala

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]] {})

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.

scala json parse and get a nested key and value

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.

Building Reads converter and case class at runtime in Play Framework

I have a file that contains the following array of JSON objects:
[
{
"type": "home",
"number": 1111
},
{
"type": "office",
"number": 2222
},
{
"type": "mobile",
"number": 3333
}
]
In Play Framework 2.x I would define an implicit Reads converter to read the file and convert it to a Scala structure:
implicit val implicitRead : Reads[MyClass] = (
(JsPath \ "type").read[String] and
(JsPath \ "number").read[Int]
) (MyClass.apply _)
the Scala case class defined as:
case class MyClass (myType: String, myNumber: Int)
and parsing the JSON with:
val json = // file record content
json.validate[MyClass] match {
case s: JsSuccess[MyClass] => {
val myObject: MyClass = s.get
// do something with myObject
}
case e: JsError => {
// error handling flow
}
Now, my problem is that I know the structure of the JSON file only at runtime, not at compilation time. Is it possible to build both the implicit Reads converter and the case class at runtime?
Use case classes directly with play-json:
Change the case class to:
case class MyClass (`type`: String, number: Int)
Add the json-formatter to the companion object:
object MyClass {
implicit val format = Json.format[MyClass]
}
The validate function looks now:
val myClass = // file record content
json.validate[Seq[MyClass]] match {
case JsSuccess(myClasses, _) => myClasses
case e: JsError => // handle error case
}
That's all you need. If you are not happy with the parameter names, you can use a Wrapper case class.

Parsing JSON in Scala using ScalaObjectMapper

I am trying to parse json using case class but running into an issue.
I have the following json
{
"general": {
"table": "123",
},
"employee" : {
"table": "employee_data"
},
"fulltime" : {
"table": "fulltime_employee_data"
},
"consultant" : {
"table": "consultant_employee_data"
}
}
Here's my case class:
case class EmployeeInfo(employees: List[Map[String, String]])
I'm trying to parse the above json using the case class using the following code. It returns the object as null.
val mapper = new ObjectMapper() with ScalaObjectMapper
mapper.registerModule(new JavaTimeModule())
mapper.registerModule(DefaultScalaModule)
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
val str = Source.fromFile("employeeInfo.json").mkString
val temp = mapper.readValue[EmployeeInfo](str)
temp here is being returned as null. My json seems to be list of maps which is what I provided in my case class. Any thoughts on what I'm missing?
I figured it out. My case class needed the variable name as defined in the json.
case class EmployeeInfo(general: TableDetails, employee: TableDetails, fulltime: TableDetails, consultant: TableDetails)
class TableDetails {
val table: String = ""
//getter and setter for table field
}

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.