deserializing json4s with generic type - json

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.

Related

Scala: Map part of Json to Object with playframework

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.

How to convert arry:string to specific model in Scala

I want to convert array: String to Seq[Message]...
Case class:
case class Message(name: String, sex: String)
Source:
[
{ "name": "Bean",
"sex": "F"
},
{
"name": "John",
"sex": "M"
}
]
Destination
Seq[Person]
How to convert?? code...
You need to use some sort of decoders/deserialisers to decode string into case class. There are tons of decoders in scala. One of my favourite is circe as it is functional and also works pretty well with scalajs.
import io.circe._, io.circe.generic.auto._, io.circe.parser._, io.circe.syntax._
case class Message(name: String, sex: String)
val encoded =
"""
|[
| { "name": "Bean",
| "sex": "F"
| },
| {
| "name": "John",
| "sex": "M"
| }
|]
""".stripMargin
val decoded: Either[Error, List[Message]] = decode[List[Message]](encoded)
decoded match {
case Right(e) => println("success: " + e)
case Left(l) => println("failure: "+ l)
}
output:
success: List(Message(Bean,F), Message(John,M))
If you looking for plain simple compatible with java, take a look at https://github.com/FasterXML/jackson-module-scala
Also see: Scala deserialize JSON to Collection

Deserialize JSON based on Field in Payload in Scala

I have a similar question with Deserialize json based on fields in .Net (C#), but do it in Scala.
I have an app which streams in 2 types of json objects (Account and User).
Account:
{
"data_type": "account",
"id": 1,
"type": "Trial",
"created_at": 1523982003,
}
User:
{
"data_type": "user",
"id": 1,
"account_id": 1,
"department": "Finance"
"created_at": 1523982122
}
I need to deserialize the two above json objects based on the field data_type in Scala with help of Circe library.
How can I do this?
This snippet works for me in Ammonite:
import $ivy.`io.circe:circe-core_2.12:0.9.3`, io.circe._
import $ivy.`io.circe:circe-generic_2.12:0.9.3`, io.circe.generic._
import $ivy.`io.circe:circe-generic-extras_2.12:0.9.3`, io.circe.generic.extras._
interp.load.plugin.ivy("org.scalamacros" % "paradise_2.12.4" % "2.1.1")
implicit val config: Configuration = Configuration.
default.
withSnakeCaseMemberNames.
withDiscriminator("data_type").
copy(transformConstructorNames = _.toLowerCase)
{
#ConfiguredJsonCodec
sealed trait InputEntity
object InputEntity {
#ConfiguredJsonCodec case class Account(id: Long, `type`: String, createdAt: Long) extends InputEntity
#ConfiguredJsonCodec case class User(id: Long, accountId: Long, department: String, createdAt: Long) extends InputEntity
}
}
import $ivy.`io.circe:circe-parser_2.12:0.9.3`, io.circe.parser._
val accountJson = """
{
"data_type": "account",
"id": 1,
"type": "Trial",
"created_at": 1523982003
}
"""
val account = decode[InputEntity](accountJson)
// account: Either[Error, InputEntity] = Right(Account(1L, "Trial", 1523982003L)
val userJson = """
{
"data_type": "user",
"id": 1,
"account_id": 1,
"department": "Finance",
"created_at": 1523982122
}
"""
val user = decode[InputEntity](userJson)
// user: Either[Error, InputEntity] = Right(User(1L, 1L, "Finance", 1523982122L))
(BTW: you had syntax errors in your JSON examples that would make the parser fail, so I fixed them in code above).
The most important here is
Configuration from io.circe.generic.extras._ that defines discriminator field,
keeping classes as sum type,
if you use annotations for generating codecs replace #JsonCodec with #ConfiguredJsonCodec.
Actually, you could also replace these Strings with enums, and read createdAt as LocalDateTime or similar, but that would be out of scope of this question.

How to deserialize a JSON object of type and value to map in Scala?

I need to deserialize some JSON that looks like the following:
{ "states":
{ "Position" : { "x": 1, "y": 2, "z": 3 },
"Timestamp" : { "value" : 123 } }
}
The fields named Position and Timestamp are the name of the class to be serialized.
The only way I have been able to deserialize this at the moment is to transform this JSON this into a format that lift web JSON understands. For example:
{ "states": [
{ "jsonClass": "Position", "x": 1, "y": 2, "z": 3 },
{ "jsonClass": "Timestamp", "value" : 123 }
]}
With formats as follows
implicit val formats = new DefaultFormats {
override val typeHintFieldName = "type"
override val typeHints = ShortTypeHints(List(classOf[Position], classOf[Timestamp]))
}
Is it possible to deserialize the top form?
Using Jackson, then:
case class Position(x: Int, y: Int, z: Int)
case class Timestamp(value: Int)
case class State(position: Position, timestamp: Timestamp)
case class States(states: Seq[State])
object Test extends App {
val mapper = new ObjectMapper with ScalaObjectMapper
mapper.registerModule(DefaultScalaModule)
mapper.configure(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED, true)
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true)
val states = mapper.readValue[Seq[States]]( """{
| "states": {
| "position" : { "x": 1, "y": 2, "z": 3 },
| "timestamp" : { "value" : 123 }
| }
|}""".stripMargin)
println(states)
}
Gives
List(States(List(State(Position(1,2,3),Timestamp(123)))))
Note1: The configure lines allow Jackson to treat an {} as a single element array, in cases where the Json uses badgerfish notation
Note2: If you have upper case field names, rename the field names in the case class to match, eg case class State(Position: Position, Timestamp: Timestamp)

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
}
}
}