Scala: Map part of Json to Object with playframework - json

I'm relatively new to Scala. I would like to map part of my Json to my Object. Code looks like this:
def seasons = (json \\ "season")
case class:
case class Season(startDate: LocalDate, endDate: LocalDate)
json-structure:
[
{
"id": "",
"name": "",
"season": {
"start": "0",
"end": "0"
}
}
]
I would somehow like to end up with a List[Season], so I can loop through it.
Question #2
json-structure:
[
{
"id": "",
"name": "",
"season": {
"start": "0",
"end": "0"
}
},
{
"id": "",
"name": "",
"season": {
"start": "0",
"end": "0"
}
}...
]
Json (which is a JsValue btw) brings multiple regions as can be seen above. Case classed are provided (Region holds a Season), naming is the same as in json.
Formats look like this:
implicit val seasonFormat: Format[Season] = Json.format[Season]
implicit val regionFormat: Format[Region] = Json.format[Region]
So what would I need to call in order to get a List[Region]? I thought of something like regionsJson.as[List[Region]] as I defined the Format, which provides me the Read/Write possibilities. But unfortunately, it's not working.
What is the best way doing this? I've tried it with an JsArray, but I have difficulties with mapping it...
Any input would be much appreciated!

I've added some changes to your original case class and renamed its fields to match json fields.
The following code does parsing of the json into Seq[Session]
import java.time.LocalDate
import play.api.libs.json._
case class Season(start: LocalDate, end: LocalDate)
implicit val sessionFormat: Format[Season] = Json.format[Season]
val json =
"""
|[
| {
| "id": "",
| "name": "",
| "season": {
| "start": "2020-10-20",
| "end": "2020-10-22"
| }
| }
|]
|""".stripMargin
val seasonsJson: collection.Seq[JsValue] = Json.parse(json) \\ "season"
val seasons: collection.Seq[Season] = seasonsJson.map(_.as[Season])
seasons.foreach(println)
Please note that I changed the data of your json and instead of 0, which is not a valid date, I provided dates in iso format yyyy-mm-dd.
The above code works with play-json version 2.9.0.
---UPDATE---
Following up comment by #cchantep.
Method as will produce an exception if json cannot be mapped in case class, a non-exception option is to use asOpt that does not throw an exception but returns a None if mapping is not possible.

Related

deserializing json4s with generic type

Update: Looked closer into the rest of my code and I had an issue elsewhere which is why it was not working. Thanks
I wanted to know if one can use json4 serializer to deserialize and object that uses generic.
My json data has similar traits with different information for one part
For example, I have Superhero and who has skills different
*Json Data
{
"type": "Blue",
"name": "Aquaman",
"age": "4",
"skills": {
"Cooking": 9,
"Swimming": 4
}
}
{
"type": "Red",
"name": "Flash",
"age": "8",
"skills": {
"Speed": 9,
"Punctual": 10
}
}
So what I wanted to do was
case class Superhero[T](
`type`: String,
name: String,
age: Int,
skills: T
)
and the respective skill case class
case class BlueSkill(
Cooking: Int,
Swimming: Int
)
case class RedSkill(
Speed: Int,
Punctual: Int
)
but when I read and try to map it to another object I get null in my dataframe.
val bluePerson = read[Superhero[BlueSkill]](jsonBody)
So wanted to know if reading generic object is possible with json4.
Sure it can be done, why would it work any differently from non-generic types?
import org.json4s.native.{Serialization => S}
import org.json4s.DefaultFormats
implicit val fmts = DefaultFormats
S.read[Superhero[RedSkill]]("""|{
| "type": "Red",
| "name": "Flash",
| "age": 8,
| "skills": {
| "Speed": 9,
| "Punctual": 10
| }
|}""".stripMargin)
But frankly, I'd stay away from json4s or any other introspection nonsense and use a typeclass-based library such as circe instead.

How to insert an empty object into JSON using Circe?

I'm getting a JSON object over the network, as a String. I'm then using Circe to parse it. I want to add a handful of fields to it, and then pass it on downstream.
Almost all of that works.
The problem is that my "adding" is really "overwriting". That's actually ok, as long as I add an empty object first. How can I add such an empty object?
So looking at the code below, I am overwriting "sometimes_empty:{}" and it works. But because sometimes_empty is not always empty, it results in some data loss. I'd like to add a field like: "custom:{}" and then ovewrite the value of custom with my existing code.
Two StackOverflow posts were helpful. One worked, but wasn't quite what I was looking for. The other I couldn't get to work.
1: Modifying a JSON array in Scala with circe
2: Adding field to a json using Circe
val js: String = """
{
"id": "19",
"type": "Party",
"field": {
"id": 1482,
"name": "Anne Party",
"url": "https"
},
"sometimes_empty": {
},
"bool": true,
"timestamp": "2018-12-18T11:39:18Z"
}
"""
val newJson = parse(js).toOption
.flatMap { doc =>
doc.hcursor
.downField("sometimes_empty")
.withFocus(_ =>
Json.fromFields(
Seq(
("myUrl", Json.fromString(myUrl)),
("valueZ", Json.fromString(valueZ)),
("valueQ", Json.fromString(valueQ)),
("balloons", Json.fromString(balloons))
)
)
)
.top
}
newJson match {
case Some(v) => return v.toString
case None => println("Failure!")
}
We need to do a couple of things. First, we need to zoom in on the specific property we want to update, if it doesn't exist, we'll create a new empty one. Then, we turn the zoomed in property in the form of a Json into JsonObject in order to be able to modify it using the +: method. Once we've done that, we need to take the updated property and re-introduce it in the original parsed JSON to get the complete result:
import io.circe.{Json, JsonObject, parser}
import io.circe.syntax._
object JsonTest {
def main(args: Array[String]): Unit = {
val js: String =
"""
|{
| "id": "19",
| "type": "Party",
| "field": {
| "id": 1482,
| "name": "Anne Party",
| "url": "https"
| },
| "bool": true,
| "timestamp": "2018-12-18T11:39:18Z"
|}
""".stripMargin
val maybeAppendedJson =
for {
json <- parser.parse(js).toOption
sometimesEmpty <- json.hcursor
.downField("sometimes_empty")
.focus
.orElse(Option(Json.fromJsonObject(JsonObject.empty)))
jsonObject <- json.asObject
emptyFieldJson <- sometimesEmpty.asObject
appendedField = emptyFieldJson.+:("added", Json.fromBoolean(true))
res = jsonObject.+:("sometimes_empty", appendedField.asJson)
} yield res
maybeAppendedJson.foreach(obj => println(obj.asJson.spaces2))
}
}
Yields:
{
"id" : "19",
"type" : "Party",
"field" : {
"id" : 1482,
"name" : "Anne Party",
"url" : "https"
},
"sometimes_empty" : {
"added" : true,
"someProperty" : true
},
"bool" : true,
"timestamp" : "2018-12-18T11:39:18Z"
}

value keys is not a member of play.api.libs.json.JsValue

I'm try to get the head from the keys of JsValue type in Scala. I googled a lot to know how to get the head key from JsValue type.
Finally, I found that result.keys.head is the way to get the head key, but it throws error value keys is not a member of play.api.libs.json.JsValue.
And my result variable has the below form of data:
{
"intents": [{
"intent": "feeling",
"confidence": 0.1018563217175903
}],
"entities": [],
"input": {
"text": "{reset-encounter}"
},
"output": "Good"
}
Code:
import play.api.libs.json._
val jsonStr = """
{
"intents": [{
"intent": "feeling",
"confidence": 0.1018563217175903
}],
"entities": [],
"input": {
"text": "{reset-encounter}"
},
"output": "Good"
}
"""
val result = Json.parse(jsonStr)
println("key: ", result.keys.head)
At result.keys.head line, throws error.
I'm not sure but I think, may be I'm doing something wrong here.
Json.parse produces a JsValue, which could represent any type of json object (boolean, number, array, etc). If you know you're working with an object, you can use .as[JsObject]:
import play.api.libs.json._
val result = Json.parse(jsonStr).as[JsObject]
println("key: " + result.keys.head)
What are you trying to get? That's not the way to deal with play.api.Json objects.
.keys would result in a Map, not in a JsValue.
Check the documentation: https://www.playframework.com/documentation/2.5.x/ScalaJson
If you want to access a specific key (https://www.playframework.com/documentation/2.5.x/ScalaJson#Traversing-a-JsValue-structure) you should try:
result \ "keyName"
or for a recursive search:
result \\ "keyName"

extract case classes from json file scala play

im trying to extract my data from json into a case class without success.
the Json file:
[
{
"name": "bb",
"loc": "sss",
"elements": [
{
"name": "name1",
"loc": "firstHere",
"elements": []
}
]
},
{
"name": "ca",
"loc": "sss",
"elements": []
}
]
my code :
case class ElementContainer(name : String, location : String,elements : Seq[ElementContainer])
object elementsFormatter {
implicit val elementFormatter = Json.format[ElementContainer]
}
object Applicationss extends App {
val el = new ElementContainer("name1", "firstHere", Seq.empty)
val el1Cont = new ElementContainer("bb","sss", Seq(el))
val source:String=Source.fromFile("src/bin/elementsTree.json").getLines.mkString
val jsonFormat = Json.parse(source)
val r1= Json.fromJson[ElementContainer](jsonFormat)
}
after running this im getting inside r1:
JsError(List((/elements,List(ValidationError(List(error.path.missing),WrappedArray()))), (/name,List(ValidationError(List(error.path.missing),WrappedArray()))), (/location,List(ValidationError(List(error.path.missing),WrappedArray())))))
been trying to extract this data forever, please advise
You have location instead loc and, you'll need to parse file into a Seq[ElementContainer], since it's an array, not a single ElementContainer:
Json.fromJson[Seq[ElementContainer]](jsonFormat)
Also, you have the validate method that will return you either errors or parsed json object..

How do I parse a deeply nested JSON document that may have some missing or extra fields using Scala?

I have read other Scala JSON parsing questions but they all seem to assume a very basic document that is not deeply nested or of mixed types. Or they assume you know all of the members of the document or that some will never be missing.
I am currently using Jackson's Streaming API but the code required is difficult to understand and maintain. Instead, I'd like to use Jerkson to return an object representing the parsed JSON if possible.
Basically: I'd like to replicate the JSON parsing functionality that's so familiar to me in dynamic languages. I realize that's probably very wrong, and so I'm here to be educated.
Say we have a Tweet:
{
"id": 100,
"text": "Hello, world."
"user": {
"name": "Brett",
"id": 200
},
"geo": {
"lat": 10.5,
"lng": 20.7
}
}
Now, the Jerkson Case Class examples make a lot of sense when you only want to parse out, say, the ID:
val tweet = """{...}"""
case class Tweet(id: Long)
val parsed = parse[Tweet](tweet)
But how do I deal with something like the Tweet above?
Some gotchas:
Some fields can be null or missing, for example "geo" above may be null, or one day they may drop a field and I don't want my parsing code to fail.
Some fields will be extra, or fields will be added and I want to be able to ignore them.
Lift json-scalaz is the best way to read and write JSON that I have come across. Docs explain it's usage pretty well here:
https://github.com/lift/framework/tree/master/core/json-scalaz
Of course right after I post this I find some help elsewhere. :)
I believe the "richer JSON example" towards the bottom of this post is a step in the right direction: http://bcomposes.wordpress.com/2012/05/12/processing-json-in-scala-with-jerkson/
Another alternatie using Lift-json could be:
package code.json
import org.specs2.mutable.Specification
import net.liftweb.json._
class JsonSpecs extends Specification {
implicit val format = DefaultFormats
val a = parse("""{
| "id": 100,
| "text": "Hello, world."
| "user": {
| "name": "Brett",
| "id": 200
| },
| "geo": {
| "lat": 10.5,
| "lng": 20.7
| }
|}""".stripMargin)
val b = parse("""{
| "id": 100,
| "text": "Hello, world."
| "user": {
| "name": "Brett",
| "id": 200
| }
|}""".stripMargin)
"Lift Json" should{
"find the id" in {
val res= (a \ "id").extract[String]
res must_== "100"
}
"find the name" in{
val res= (a \ "user" \ "name").extract[String]
res must_== "Brett"
}
"find an optional geo data" in {
val res= (a \ "geo" \ "lat").extract[Option[Double]]
res must_== Some(10.5)
}
"ignore missing geo data" in {
val res= (b \ "geo" \ "lat").extract[Option[Double]]
res must_== None
}
}
}
Note how when the geo data is missing on the val b, the parsing works just fine, expecting a None.
Or do you want to get case classes as the result?
For a case class example, see:
package code.json
import org.specs2.mutable.Specification
import net.liftweb.json._
class JsonSpecs extends Specification {
implicit val format = DefaultFormats
case class Root(id: Int, text: Option[String], user: Option[User], geo: Option[Geo])
case class User(name: String, id: Int)
case class Geo(lat: Double, lng: Double)
val c = parse("""{
| "id": 100
| "user": {
| "name": "Brett",
| "id": 200
| },
| "geo": {
| "lng": 20.7
| }
|}""".stripMargin)
"Lift Json" should{
"return none for geo lat data" in {
val res= c.extract[Root].geo.map(_.lat)
res must_== None
}
}
}