Request body to case class conversion in play & scala - json

I've a case class similar to this found here :
case class WebCategory(topGroupName: String,
topGroupID: String,
webCategoryName : String,
webCategoryID : String,
subWebCats:Seq[SubWebCat])
case class SubWebCat(name:String, id:String)
And my request body json is having the exact same key names as of case class. For ex:
{
"webCategoryID" : "blah",
"webCategoryName" : "abcabc",
"topGroupID" : "blah",
"topGroupName" : "namehere",
"subWebCats" : [
{
"name" : "blah",
"id" : "idblah"
},
{
"name" : "another blah",
"id" : "another idblah"
}
]
}
The case class & req body keys are same then is it possible to directly build the case class object from request json? If it is possible then how can I do this? Any references would help. If it is not possible then this means I've to define my custom implicit converter explained in the answer in which I don't have any problem implementing it.
Note: I'm using Play 2.3 & Scala 11 for my development

You can use Play's built-in JSON validation for this quite easily. You don't need to add any third-party dependencies for this.
case class WebCategory(topGroupName: String,
topGroupID: String,
webCategoryName : String,
webCategoryID : String,
subWebCats:Seq[SubWebCat])
object WebCategory {
implicit val fmt = Json.format[WebCategory]
}
case class SubWebCat(name:String, id:String)
object SubWebCat {
implicit val fmt = Json.format[SubWebCat]
}
Then, in your controller action:
def save: Action(parse.json) { implicit request =>
request.body.validate[WebCategory].fold(
errors => BadRequest(errors.mkString),
category => Ok("saved category")
)
}

We use FasterXml for serialization and deserialization as follows.
include this dependency in your build.sbt
"com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.4.0-rc2"
Create a two helper functions toJson and fromJson to serialize and deserialize
object JsonProvider {
//create mapper and register scala module
private val mapper = new ObjectMapper
mapper.registerModule(DefaultScalaModule)
def toJson(obj: Object): String = {
val writer = new StringWriter
mapper.writeValue(writer, obj)
writer.toString
}
def fromJson[T: scala.reflect.Manifest](json: String): T = {
mapper.readValue(json, scala.reflect.classTag[T].runtimeClass).asInstanceOf[T]
}
}
Use it as follows to cast your request body into case class.
JsonProvider.fromJson[WebCategory](request.body.toString())
to convert caseclass to json use it like this.
JsonProvider.toJson(obj);
where "obj" is the object of case class.
Problem with Play-Json default.
Suppose you have case class A with three parameters as follows
case class A(id:String,name:String,roll:Int)
and if your Json you want to parse is as follows
{
name:"XYZ",
roll:22
}
you can't parse this Json with play-json because of the missing field or one way is to define your Read and write functions which is very cumbersome.
but with fasterXML you can easily parse this Json like :
val a = JsonProvider.fromJsonA
and you can assign Id to case class A later like val a1 = a.copy(id="xyz")
i had this problem then i switched from play-json to fasterXML the problem was
How to send Json from client with missing fields for its corresponding Case Class after using Json.format function

Related

Circe : Serialize case class body fields to JSON

I have a case class with some val (which is not a constructor param). How can I get those fields also in the generated json ?
I was using Json4s before, and used FieldSerializer which did this trick. But unable to get this with Circe.
What I want is to define all the required fields in a trait, sometimes, the field may be a part of the case class. But there are cases, where it doesn't make sense to keep them as part of case class, but still needed in the json.
Please note the difference between EntityWithBodyParams and AnotherEntity below.
Here is my sample case class.
trait NamedEntity {
def name:String
}
case class EntityWithBodyParams(id:Long) extends NamedEntity {
override val name:String = "Name"
}
case class AnotherEntity(id:Long, name:String) extends NamedEntity
Response after asJson
{
"id" : 100
}
But my expectation is :
{
"id" : 100,
"name":"Name"
}
You can create your own Encoder.
import io.circe.{Encoder, Json}
case class EntityWithBodyParams(id: Long) {
val name: String = "Name"
}
implicit val encoder: Encoder[EntityWithBodyParams] = new
Encoder[EntityWithBodyParams] {
override def apply(entity: EntityWithBodyParams): Json = Json.obj(
"id" -> Json.fromLong(entity.id),
"name" -> Json.fromString(entity.name)
)
}
Reason for this behavior is fact that circe auto encoder uses only product fields of case class. More info you can find here https://github.com/milessabin/shapeless
Try writing your case class like this instead.
case class EntityWithBodyParams(id:Long, val name:String = "Name")

How can I set a field to be kept as plain while Finatra is parsing a request into a case class?

I have a controller handling a route like 'POST /doit', the json body is automatically parsed into a case class using Finatra built in tools (Jackson, etc), something like this:
class MyController extends Controller {
post("/doit") { request: MyRequest =>
// something
}
}
case class MyRequest(
id: String,
custom: String
)
Here are some valid requests:
{ "id": "my id", "custom": "my custom" }
{ "id": "my id", "custom": "{'x': 'y'}" }
As you can see, 'custom' field can be a JSON which can't be deserialized because Jackson expect it to be a POJO instead of a String, I tried wrapping this JSON with quotes but they are ignored and the field is handled as JSON.
How can I let Jackson library to know that this field should be kept plain?
I had read and the best solution I came up with is to write a custom deserializer, in this case, I have not idea how to integrate with Finatra.
As "Ryan O'Neill" pointed out in Finatra Google Group, there are examples for writing a custom deserializer in ExampleCaseClasses.scala.
I'm copying the following code from previous scala source:
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
case class CaseClassWithCustomDecimalFormat(
#JsonDeserialize(using = classOf[MyBigDecimalDeserializer])
myBigDecimal: BigDecimal,
#JsonDeserialize(using = classOf[MyBigDecimalDeserializer])
optMyBigDecimal: Option[BigDecimal])
class MyBigDecimalDeserializer extends JsonDeserializer[BigDecimal] {
override def deserialize(jp: JsonParser, ctxt: DeserializationContext): BigDecimal = {
val jsonNode: ValueNode = jp.getCodec.readTree(jp)
BigDecimal(jsonNode.asText).setScale(2, RoundingMode.HALF_UP)
}
override def getEmptyValue: BigDecimal = BigDecimal(0)
}
Thanks Ryan.

In play framework 2.5.3, parse a JsValue to a java object

my question is very simple, it's that, how can I convert a json to a third-part java class which has no method of apply and unapply but not scala case class. The detailed code is like this following
def login = Action { implicit request =>
val jsonBody = request.body.asJson // the jsonBody is value of Option[JsValue]
val myClass = //some method can be provided to convert the jsonBody to myClass(a third-part java class)
}
Thanks in advance
You can define a case class like MyCaseClass(a: String, b: Int), and validate the json you get like this:
request.body.asJson.validate[MyCaseClass] match {
case myCaseClass: JsSuccess[MyCaseClass] => new JavaClass(a, b)
case error: JsError => //handle the error
}
Note that you can use Action(parse.json) { implicit request => in order to validate that you have a valid Json object.

How do I deserialize JSON into a List<SomeType> with Kotlin + Jackson [duplicate]

This question already has answers here:
How to use jackson to deserialize to Kotlin collections
(3 answers)
Closed 7 years ago.
What is the correct syntax to deserialize the following JSON:
[ {
"id" : "1",
"name" : "Blues"
}, {
"id" : "0",
"name" : "Rock"
} ]
I tried:
//Works OK
val dtos = mapper.readValue(json, List::class.java)
However I want:
val dtos : List<GenreDTO> = mapper.readValue(json,
List<GenreDTO>::class.java)
The above syntax is not correct and gives: only classes are allowed on the left hand side of a class literal
NOTE: The answer from #IRus is also correct, it was being modified at the same time I wrote this to fill in more details.
You should use the Jackson + Kotlin module or you will have other problems deserializing into Kotlin objects when you do no have a default constructor.
Your first sample of the code:
val dtos = mapper.readValue(json, List::class.java)
Is returning an inferred type of List<*> since you did not specify more type information, and it is actually a List<Map<String,Any>> which is not really "working OK" but is not producing any errors. It is unsafe, not typed.
The second code should be:
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
val mapper = jacksonObjectMapper()
// ...
val genres: List<GenreDTO> = mapper.readValue(json)
You do not need anything else on the right side of the assignment, the Kotlin module for Jackson will reify the generics and create the TypeReference for Jackson internally. Notice the readValue import, you need that or .* for the com.fasterxml.jackson.module.kotlin package to have the extension functions that do all of the magic.
A slightly different alternative that also works:
val genres = mapper.readValue<List<GenreDTO>>(json)
There is no reason to NOT use the extension functions and the add-on module for Jackson. It is small and solves other issues that would require you to jump through hoops to make a default constructor, or use a bunch of annotations. With the module, your class can be normal Kotlin (optional to be data class):
class GenreDTO(val id: Int, val name: String)
The error you're getting is about following expression:
List<GenreDTO>::class.java
Because of how jvm treats generics there's no separate class for List<GenreDTO> thus compiler complains. Similarly in Java the following will not compile:
List<GenreDTO>.getClass()
Here's a sample that will deserialize the list properly:
val value:List<GenreDTO> = mapper.readValue(json, object : TypeReference<List<GenreDTO>>() {})
As #JaysonMinard has pointed out you can use jackson-module-kotlin to simplify the invocation to:
val genres: List<GenreDTO> = mapper.readValue(json)
// or
val genres = mapper.readValue<List<GenreDTO>>(json)
This is possible because of reified type parameters. Consider looking at Extensions to find out details.
Following code works well for me:
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
val json = """[ {
"id" : "1",
"name" : "Blues"
}, {
"id" : "0",
"name" : "Rock"
} ]"""
data class GenreDTO(val id: Int, val name: String)
val mapper = ObjectMapper().registerKotlinModule()
fun main(args: Array<String>) {
val obj: List<GenreDTO> = mapper.readValue(json)
obj.forEach {
println(it)
}
}
This work because of extension function defined inside jackson-kotlin-module (that used reified generics):
public inline fun <reified T: Any> ObjectMapper.readValue(content: String): T = readValue(content, object: TypeReference<T>() {})
Thanks #JaysonMinard for notify me about it.
Output:
GenreDTO(id=1, name=Blues)
GenreDTO(id=0, name=Rock)

Scala - Instantiate classes dynamically from a String

I am trying to create a dynamic parser which allows me to parse json content into different classes depending on a class name.
I will get the json and the class name (as String) and I would like to do something like this:
val theCaseClassName = "com.ardlema.JDBCDataProviderProperties"
val myCaseClass = Class.forName(theCaseClassName)
val jsonJdbcProperties = """{"url":"myUrl","userName":"theUser","password":"thePassword"}"""
val json = Json.parse(jsonJdbcProperties)
val value = Try(json.as[myClass])
The above code obviously does not compile because the json.as[] method tries to convert the node into a "T" (I have an implicit Reads[T] defined for my case class)
What would be the best way to get a proper "T" to pass in to the json.as[] method from the original String?
A great solution that might work would be to do polymorphic deserialization. This allows you to add a field (like "type") to your json and allow Jackson (assuming you're using an awesome json parser like Jackson) to figure out the proper type on your behalf. It looks like you might not be using Jackson; I promise it's worth using.
This post gives a great introduction to polymorphic types. It covers many useful cases including the case where you can't modify 3rd party code (here you add a Mixin to annotate the type hierarchy).
The simplest case ends up looking like this (and all of this works great with Scala objects too -- jackson even has a great scala module):
object Test {
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type"
)
#JsonSubTypes(Array(
new Type(value = classOf[Cat], name = "cat"),
new Type(value = classOf[Dog], name = "dog")
))
trait Animal
case class Dog(name: String, breed: String, leash_color: String) extends Animal
case class Cat(name: String, favorite_toy: String) extends Animal
def main(args: Array[String]): Unit = {
val objectMapper = new ObjectMapper with ScalaObjectMapper
objectMapper.registerModule(DefaultScalaModule)
val dogStr = """{"type": "dog", "name": "Spike", "breed": "mutt", "leash_color": "red"}"""
val catStr = """{"type": "cat", "name": "Fluffy", "favorite_toy": "spider ring"}"""
val animal1 = objectMapper.readValue[Animal](dogStr)
val animal2 = objectMapper.readValue[Animal](catStr)
println(animal1)
println(animal2)
}
}
This generates this output:
// Dog(Spike,mutt,red)
// Cat(Fluffy,spider ring)
You can also avoid listing the subtype mapping, but it requires that the json "type" field is a bit more complex. Experiment with it; you might like it. Define Animal like this:
#JsonTypeInfo(
use = JsonTypeInfo.Id.CLASS,
include = JsonTypeInfo.As.PROPERTY,
property = "type"
)
trait Animal
And it produces (and consumes) json like this:
/*
{
"breed": "mutt",
"leash_color": "red",
"name": "Spike",
"type": "classpath.to.Test$Dog"
}
{
"favorite_toy": "spider ring",
"name": "Fluffy",
"type": "classpath.to.Test$Cat"
}
*/
You should select your Reads[T] based on the class name. Unfortunately this will probably have to be a manual pattern match:
val r: Reads[_] = theCaseClassName match {
case "com.ardlema.JDBCDataProviderProperties" => JDBCReads
case ... => ...
}
val value = json.as(r).asInstanceOf[...]
Alternately, look at the implementation of json.as; at some point it's probably requiring a classTag and then calling .runtimeClass on it. Assuming that's so, you can just do whatever it is and pass your own myCaseClass there.