Accessing inner JSON field in Scala - json

I have JSON string as
{
"whatsNew" : {
"oldNotificationClass" : "WhatsNewNotification",
"notificationEvent" : { ..... },
"result" : {
"notificationCount" : 10
.....
}
},
......
"someEmpty": { },
......
}
I am trying to get notificationCount field using json4s in Scala as follow but notificationCount is coming as empty for all. Any help?
UPDATE
Also if some pair is empty how can I handle empty condition and continue to loop?
Function returning JSON string from file
def getData(): Map[String, AnyRef] = {
val jsonString = scala.io.Source.fromInputStream(this.getClass.getResourceAsStream("/sample.json")).getLines.mkString
val jsonObject = parse( s""" $jsonString """)
jsonObject.values.asInstanceOf[Map[String, AnyRef]]
}
Code to get fields
val myMap: Map[String, AnyRef] = MyDataLoader.getData
for((key, value) <- myMap) {
val id = key
val eventJsonStr: String = write(value.asInstanceOf[Map[String, String]] get "notificationEvent")
val resultJsonStr: String = write(value.asInstanceOf[Map[String, String]] get "result")
//val notificationCount: String = write(value.asInstanceOf[Map[String, Map[String, String]]] get "notificationCount")
}

You can use path and extract like so:
val count: Int = (parse(jsonString) \ "whatsNew" \ "result" \ "notificationCount").extract[Int]
you will need this import for the .extract[Int] to work:
implicit val formats = DefaultFormats
To do it in a loop:
parse(jsonString).children.map { child =>
val count: Int = (child \ "result" \ "notificationCount").extract[Int]
...
}

Related

HowTo skip deserialization for a field in json4s

Here is my json:
{
"stringField" : "whatever",
"nestedObject": { "someProperty": "someValue"}
}
I want to map it to
case class MyClass(stringField: String, nestedObject:String)
nestedObject should not be deserialized, I want json4s to leave it as string.
resulting instance shouldBe:
val instance = MyClass(stringField="whatever", nestedObject= """ { "someProperty": "someValue"} """)
Don't understand how to do it in json4s.
You can define a custom serializer:
case object MyClassSerializer extends CustomSerializer[MyClass](f => ( {
case jsonObj =>
implicit val format = org.json4s.DefaultFormats
val stringField = (jsonObj \ "stringField").extract[String]
val nestedObject = compact(render(jsonObj \ "nestedObject"))
MyClass(stringField, nestedObject)
}, {
case myClass: MyClass =>
("stringField" -> myClass.stringField) ~
("nestedObject" -> myClass.nestedObject)
}
))
Then add it to the default formatter:
implicit val format = org.json4s.DefaultFormats + MyClassSerializer
println(parse(jsonString).extract[MyClass])
will output:
MyClass(whatever,{"someProperty":"someValue"})
Code run at Scastie

Dynamic type casting for JsValue field in Scala Play

Since I'm writing a function to request data from another API in my Scala code, the response Json has the format like this:
"data": {
"attributeName": "some String",
"attributeValue": false,
"attributeSource": "Manual",
"attributeValueLabel": null
},
"data": {
"attributeName": "some String",
"attributeValue": "daily",
"attributeSource": "Manual",
"attributeValueLabel": "Almost Daily"
}
Note that sometimes the type of attributeValue is String value, some other time it's a Boolean value.
So I'm trying to write my own Reads and Writes to read the type dynamically.
case class Data(attributeName: Option[String], attributeValue: Option[String], attributeSource: Option[String], attributeValueLabel: Option[String])
object Data{
implicit val readsData: Reads[Data] = {
new Reads[Data] {
def reads(json: JsValue) = {
val attrValue = (json \ "attributeValue").as[] // How to cast to Boolean some time, but some other time is a String here
......
}
}
}
So as you can see in my comment, I'm stuck at the part to cast the (json \ "attributeValue") to String/Boolean, base on the return type of the API. How can I do this?
You can try to parse it as String first and then as Boolean:
val strO = (json \ "attributeValue").asOpt[String]
val value: Option[String] = strO match {
case str#Some(_) => str
case None => (json \ "attributeValue").asOpt[Boolean].map(_.toString)
}
You can use the .orElse function when you are trying to read an attribute in different ways:
import play.api.libs.json.{JsPath, Json, Reads}
import play.api.libs.functional.syntax._
val json1 =
"""
|{
| "attributeName": "some String",
| "attributeValue": false
|}
""".stripMargin
val json2 =
"""
|{
| "attributeName": "some String",
| "attributeValue": "daily"
|}
""".stripMargin
// I modified you case class to make the example short
case class Data(attributeName: String, attributeValue: String)
object Data {
// No need to define a reads function, just assign the value
implicit val readsData: Reads[Data] = (
(JsPath \ "attributeName").read[String] and
// Try to read String, then fallback to Boolean (which maps into String)
(JsPath \ "attributeValue").read[String].orElse((JsPath \ "attributeValue").read[Boolean].map(_.toString))
)(Data.apply _)
}
println(Json.parse(json1).as[Data])
println(Json.parse(json2).as[Data])
Output:
Data(some String,false)
Data(some String,daily)

JSONPath and json4s

I need to extract a field value from the message:
{
"data": {
"code": "404",
...
}
}
JSONPath expression is stored in a variable:
val path = "/data/code"
I'm using json4s for manipulating JSON.
From the doc, one can achieve this using DSL:
val json = parse(message)
val code = json \ "data" \ "code"
It works, but obviously the JSONPath expression should be hardcoded.
Is there any way to evaluate expression stored as string?
Something like:
val code = json.evaluateJSONPath(path)
assuming the json path is provided in a string format delimited by comma the below would work.
import org.json4s._
import org.json4s.native.JsonMethods._
val json = parse(""" { "data": { "code": "404", "foo": "bar" } } """)
val path = "data,code" // json path in string
path.split(',').foldLeft(json)({ case (acc, node) => acc \ node })
Edit:
Adding a implicit class to simplify the access
implicit class JsonHelper(json: JValue) {
def customExtract[T](path: String)(implicit formats: Formats, mf: Manifest[T]) = {
path.split(',').foldLeft(json)({ case (acc: JValue, node: String) => acc \ node }).extract[T]
}
}
json.customExtract[String]("data,code")
res23: String = "404"
How about use the high order function to map the extract command, like:
val extractCodeFunc = (j: JValue) => j \ "data" \ "code"
and extract json:
val res = extractCodeFunc.apply(json)

Try to update\fetch Postgres json column into JsValue using anorm [duplicate]

I am using anorm to query and save elements into my postgres database.
I have a json column which I want to read as class of my own.
So for example if I have the following class
case class Table(id: Long, name:String, myJsonColumn:Option[MyClass])
case class MyClass(site: Option[String], user:Option[String])
I am trying to write the following update:
DB.withConnection { implicit conn =>
val updated = SQL(
"""UPDATE employee
|SET name = {name}, my_json_column = {myClass}
|WHERE id = {id}
""".stripMargin)
.on(
'name -> name,
'myClass -> myClass,
'custom -> id
).executeUpdate()
}
}
I also defined a implicit convertor from json to my object
implicit def columnToSocialData: Column[MyClass] = anorm.Column.nonNull[MyClass] { (value, meta) =>
val MetaDataItem(qualified, nullable, clazz) = meta
value match {
case json: org.postgresql.util.PGobject => {
val result = Json.fromJson[MyClass](Json.parse(json.getValue))
result.fold(
errors => Left(TypeDoesNotMatch(s"Cannot convert $value: ${value.asInstanceOf[AnyRef].getClass} to Json for column $qualified")),
valid => Right(valid)
)
}
case _ => Left(TypeDoesNotMatch(s"Cannot convert $value: ${value.asInstanceOf[AnyRef].getClass} to Json for column $qualified"))
}
And the error I get is:
type mismatch;
found : (Symbol, Option[com.MyClass])
required: anorm.NamedParameter
'myClass -> myClass,
^
The solution is just to add the following:
implicit val socialDataToStatement = new ToStatement[MyClass] {
def set(s: PreparedStatement, i: Int, myClass: MyClass): Unit = {
val jsonObject = new org.postgresql.util.PGobject()
jsonObject.setType("json")
jsonObject.setValue(Json.stringify(Json.toJson(myClass)))
s.setObject(i, jsonObject)
}
}
and:
implicit object MyClassMetaData extends ParameterMetaData[MyClass] {
val sqlType = "OTHER"
val jdbcType = Types.OTHER
}

Convert Json to scala object

I have the following Json string:
{
"references": {
"configuratorId": "conf id",
"seekId": "seekid",
"hsId": "hsid",
"fpId": "fpid"
}
}
And i want to create an object from it in my Rest API Controller.
The code:
case class References(configuratorId: Option[String], seekId: Option[String], hsId: Option[String], fpId: Option[String]) {}
The Formatter:
trait ProductFormats extends ErrorFormats {
implicit val referencesFormat = Json.format[References]
implicit val referenceFormat = new Format[References]{
def writes(item: References):JsValue = {
Json.obj(
"configuratorId" -> item.configuratorId,
"seekId" -> item.seekId,
"hsId" -> item.hsId,
"fpId" -> item.fpId
)
}
def reads(json: JsValue): JsResult[References] =
JsSuccess(new References(
(json \ "configuratorId").as[Option[String]],
(json \ "seekId").as[Option[String]],
(json \ "hsId").as[Option[String]],
(json \ "fpId").as[Option[String]]
))
}
Code in my controller:
def addProducts(lang: String, t: String) = Action {
request =>
request.body.asJson.map {
json =>
val req = json.as[References]
println(req.configuratorId.getOrElse("it was empty !!"))
Ok(Json.toJson((req)))
}.getOrElse {
println("Bad json:" + request.body.asText.getOrElse("No data in body"))
BadRequest("Incorrect json data")
}
}
The object is full with null values.. I guess my reads is wrong - but i cant figure out why.
Thanks!!
You should change the code in your controller to:
val req = (json \ "references").as[References]