How to use default values with Spray Json? - json

I'm using Spray Json and want to use the default values defined in case classes, if a value is missing in the Json that populates the objects.
Example
Let's say I want to create an object from the case class Person but using a json document without age to do so:
case class Person(name: String, city: String, age: Int = -1)
{"name": "john", "city": "Somecity"}
How can I use the default value with Spray Json?

The only way I know how to do this is to use Option[T] as field type. And, if the field is optional, this is the semantically right way to do it:
case class Person(name: String, city: String, age: Option[Int])
When age is not present, age will be None. Since from your example, you use a absurd value (-1) as a marker that age is absent, using an Option will help you much more.
But, if you really need to have a default value, you can either have another case class that is filled from the one you've got from the JSON, using getOrElse, or use getOrElse(defaultValue) in your code when you need it.

Related

How to properly use JSON.parse in kotlinjs with enums?

During my fresh adventures with kotlin-react I hit a hard stop when trying to parse some data from my backend which contains enum values.
Spring-Boot sends the object in JSON form like this:
{
"id": 1,
"username": "Johnny",
"role": "CLIENT"
}
role in this case is the enum value and can have the two values CLIENT and LECTURER. If I were to parse this with a java library or let this be handled by Spring-Boot, role would be parsed to the corresponding enum value.
With kotlin-js' JSON.parse, that wouldn't work and I would have a simple string value in there.
After some testing, I came up with this snippet
val json = """{
"id": 1,
"username": "Johnny",
"role": "CLIENT",
}"""
val member: Member = JSON.parse(json) { key: String, value: Any? ->
if (key == "role") Member.Role.valueOf(value.toString())
else value
}
in which I manually have to define the conversion from the string value to the enum.
Is there something I am missing that would simplify this behaviour?
(I am not referring to using ids for the JSON and the looking those up, etc. I am curious about some method in Kotlin-JS)
I have the assumption there is not because the "original" JSON.parse in JS doesn't do this and Kotlin does not add any additional stuff in there but I still have hope!
As far as I know, no.
The problem
Kotlin.JS produces an incredibly weird type situation when deserializing using the embedded JSON class, which actually is a mirror for JavaScript's JSON class. While I haven't done much JavaScript, its type handling is near non-existent. Only manual throws can enforce it, so JSON.parse doesn't care if it returns a SomeCustomObject or a newly created object with the exact same fields.
As an example of that, if you have two different classes with the same field names (no inheritance), and have a function that accepts a variable, it doesn't care which of those (or a third for that matter) it receives as long as the variables it tries accessing on the class exists.
The type issues manifest themselves into Kotlin. Now wrapping it back to Kotlin, consider this code:
val json = """{
"x": 1, "y": "yes", "z": {
"x": 42, "y": 314159, "z": 444
}
}""".trimIndent()
data class SomeClass(val x: Int, val y: String, val z: Struct)
data class Struct(val x: Int, val y: Int, val z: Int)
fun main(args: Array<String>) {
val someInstance = JSON.parse<SomeClass>(json)
if(someInstance.z::class != Struct::class) {
println("Incompatible types: Required ${Struct::class}, found ${someInstance.z::class}");
}
}
What would you expect this to print? The natural would be to expect a Struct. The type is also explicitly declared
Unfortunately, that is not the case. Instead, it prints:
Incompatible types: Required class Struct, found class Any
The point
The embedded JSON de/serializer isn't good with types. You might be able to fix this by using a different serializing library, but I'll avoid turning this into a "use [this] library".
Essentially, JSON.parse fails to parse objects as expected. If you entirely remove the arguments and try a raw JSON.parse(json); on the JSON in your question, you'll get a role that is a String and not a Role, which you might expect. And with JSON.parse doing no type conversion what so ever, that means you have two options: using a library, or using your approach.
Your approach will unfortunately get complicated if you have nested objects, but with the types being changed, the only option you appear to have left is explicitly parsing the objects manually.
TL;DR: your approach is fine.

Play Json Parser: How to ignore field during json parser

I am using Scala with play JSON library for parsing JSON. We are the facing the problem using JSON parsing is, we have same JSON structure, but some JSON files contain different with values structure with the same key name. Let's take an example:
json-1
{
"id": "123456",
"name": "james",
"company": {
"name": "knoldus"
}
}
json-2
{
"id": "123456",
"name": "james",
"company": [
"knoldus"
]
}
my case classes
case class Company(name: String)
object Company{
implicit val _ = Json.format[Company]
}
case class User(id: String, name: String, company: Company)
object User{
implicit val _ = Json.format[Company]
}
while JSON contains company with JSON document, we are getting successfully parsing, but if company contains an array, we are getting parsing exception. Our requirements, are is there anyway, we can use play JSON library and ignore the fields if getting parsing error rather that, ignore whole JSON file. If I am getting, company array values, ignore company field and parse rest of them and map corresponding case class.
I would do a pre-parse function that will rename the 'bad' company.
See the tutorial for inspiration: Traversing-a-JsValue-structure
So your parsing will work, with this little change:
case class User(id: String, name: String, company: Option[Company])
The company needs to be an Option.
Final we found the answer to resolving this issue, as we know, we have different company structure within JSON, so what we need to do, we need to declare company as a JsValue because in any case, whatever the company structure is, it is easily assigned to JsValue type. After that, our requirements are, we need to use object structure, and if JSON contains array structure, ignore it. After that, we used pattern matching with our company JsValue type and one basis of success and failure, we parse or JSON. The solution with code is given below:
case class Company(name: String)
object Company{
implicit val _ = Json.format[Company]
}
case class User(id: String, name: String, company: JsValue)
object User{
implicit val _ = Json.format[Company]
}
Json.parse("{ --- whatevery json--string --- }").validate[User].asOpt match {
case Some(obj: JsObject) => obj.as[Company]
case _ => Company("no-name")
}

HowTo enforce Play framework 2.4.x to serialize field with empty list

I'm using scala play! 2.4.x and trying to searialize case class:
case class MyEvent(
id: String,
parentId: Option[ParentRef] = None,
stepStatus: String = "undefined",
artifacts:Seq[String] = Seq.empty,
events:Seq[String] = Seq.empty
)
The problem is that serialized json doesn't contain fields artifacts and events since their default values are empty sequences. Receiver expects to get field names even if they are empty.
I have to force json serializer to add "artifacts": [], "events":[]
What is the right way to do it without writing whole formatter manually? I have dozens fields.
So, for 2.4.x it works this way:
case class MyEvent(
id: String,
parentId: Option[ParentRef] = None,
stepStatus: String = "undefined",
artifacts:Seq[String] = Seq(),
events:Seq[String] = Seq()
)
Seq() forces play json to generate "events": [] which is fine for me. I find play-json stuff a bit over engineered. :( I really miss google Gson from my Java past :) Dead simple and just works.

how to format scala's output from JSON to text file format

I am using Scala with Spark with below version.
Scala - 2.10.4
Spark - 1.2.0
I am mentioning below my situation.
I have a RDD(Say - JoinOp) with nested tuples(having case classes), for example -
(123,(null,employeeDetails(Smith,NY,DW)))
(456,(null,employeeDetails(John,IN,CS)))
This RDD is being created from a Join with two files.
Now, my requirement is to convert this JSON format to text file format without any "Null" and any case class name(here 'employeeDetails').
My desired output is =
123,Smith,NY,DW
456,John,IN,CS
I have tried with String Interpolation for the same but with partial success.
val textOp = JoinOp.map{jm => s"${jm._1},${jm._2._2}"}
if I print textOp then it will give me below output.
123,employeeDetails(Smith,NY,DW)
456,employeeDetails(John,IN,CS)
Now if I try to access nested elements in "employeeDetails" case class with String interpolation, it will throwing error like below.
JoinOp.map{jm => s"${jm._1},${jm._2._2._1}"}.foreach(println)
<console> :23: Error : value _1 is not member of jm
Here I can understand that, with the above syntax, it's unable to access nested element for "employeeDetails" case class.
What might be the solution for this issue. Any help or point forward would be of much help.
Many Thanks,
Pralay
Case classes have field names. So, instead of ._1 you need to use the field name for that position. Assuming the following definition:
case class EmployeeDetails(name: String, state: String)
you would access it
JoinOp.map{jm => s"${jm._1},${jm._2._2.name}"}.foreach(println)
If you just need to print all fields of case class, you may use productIterator to traverse field list.
val textOp = JoinOp.map { jm =>
s"""${jm._1},${jm._2._2.productIterator.mkString(",")}"""
}
You can do it like this:
case class EmployeeDetails(var0: String, var1: String, var2: String)
val data = List((123,(null, EmployeeDetails("Smith", "NY", "DW"))))
data.map {case (num, (sth, EmployeeDetails(var0, var1, var2))) =>
s"$num,$var0,$var1,$var2"}

Ignoring fields when deserialising with lift-json in Scala

How does I deserialise data like this into a case class like this:
case class SoundCloudUser (
id: Int,
permalink: String,
username: String,
country: String,
full_name: String,
city: String,
description: String)
(that is, where the case class has less constructor arguments than the JSON has values)
I tried creating a FieldSerializer to do this, but I could only work out how to ignore fields when serialising, not deserialising.
As long as the fields in the JSON data are a superset of the fields in your case class, you don't need to do anything special to ignore the fields in the JSON data that aren't in your case class. It should "just work". Are you getting any errors?