Convert Json to scala object - json

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]

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

validate json with type value

I wand to validate json that has a type value
I have a case class SearchRequestMessage with a companion object with json writer
Value T can be Long or UUID
case class SearchRequestMessage[T](jobId: UUID, offerId: UUID, search: T, searchByType: SearchByType)
object SearchRequestMessage{
implicit def searchResultsWrites[T: Writes]: Writes[SearchRequestMessage[T]] = new Writes[SearchRequestMessage[T]] {
def writes(searchSesult: SearchRequestMessage[T]) =
JsObject(
Seq(
"jobId" -> JsString(searchSesult.jobId.toString),
"offerId" -> JsString(searchSesult.offerId.toString),
"search" -> JsString(searchSesult.search.toString),
"searchByType" -> JsString(searchSesult.searchByType.toString)
))
}
}
Also I have SearchByTypes enum
object SearchByTypes extends Enumeration {
type SearchByType = Value
val Emails: SearchByType = Value(0, "EMAILS")
val Offer: SearchByType = Value(1, "OFFER")
implicit val SearchByTypeFormat: Format[SearchByTypes.Value] = JsonFormatter.enumFormat(SearchByTypes)
}
enum formatter
import play.api.libs.json._
object JsonFormatter {
def enumFormat[T <: Enumeration](enum: T): Format[T#Value] = new EnumFormatter[T](enum)
def enumWithIdsFormat[T <: Enumeration](enum: T): Format[T#Value] = new EnumWithIdsFormatter[T](enum)
class EnumFormatter[T <: Enumeration](enum: T) extends Format[T#Value] {
override def writes(o: T#Value): JsValue = o match {
case null => JsNull
case _ => JsString(o.toString)
}
override def reads(json: JsValue): JsResult[T#Value] = json match {
case JsString(value) => {
try {
JsSuccess(enum.withName(value))
} catch {
case _: NoSuchElementException =>
JsError(
s"Enumeration expected of type: '${enum.getClass}', but it does not appear to contain the value: '$value'")
}
}
case _ => JsError(s"Invalid JSON: $json. Error in '${enum.getClass}' field. Possible values: ${enum.values}")
}
}
class EnumWithIdsFormatter[T <: Enumeration](enum: T) extends Format[T#Value] {
private val nameField = "name"
private val idField = "id"
override def writes(o: T#Value): JsValue = JsObject(Map(idField -> JsNumber(o.id), nameField -> JsString(o.toString)))
override def reads(json: JsValue): JsResult[T#Value] = json match {
case JsObject(values) =>
values.get(idField) match {
case Some(JsNumber(value)) if value <= enum.maxId && value >= 0 ⇒
try {
JsSuccess(enum(value.toInt))
} catch {
case _: NoSuchElementException =>
JsError(
s"Enumeration expected of type: '${enum.getClass}', but it does not appear to contain the value: '$value'")
}
case Some(JsNumber(x)) ⇒
JsError(s"Invalid JSON:$json. Field '$idField': '$x' is out of range. Possible values:${enum.values.map(_.id)}")
case Some(_) ⇒
JsError(s"Invalid JSON:$json. Field '$idField' isn't number. Possible values:${enum.values.map(_.id)}")
case None ⇒
JsError(s"Invalid JSON:$json. Missing field '$idField'")
}
case _ =>
JsError(s"Invalid JSON: $json")
}
}
}
Now I want to validate json
import play.api.libs.json.Json
import play.api.libs.functional.syntax._
val obj = SearchRequestMessage(UUID.randomUUID(), UUID.randomUUID(), "email", SearchByTypes.Emails)
val json = Json.toJson(obj)
val result = ((json \ "jobId").validate[UUID] and
(json \ "offerId").validate[UUID] and
(json \ "search").validate[String] and
(json \ "searchByType").validate[SearchByType])(SearchRequestMessage[String].apply(_,_,_,_))
and an error appear:
Error:(41, 75) missing argument list for method apply in object SearchRequestMessage
Unapplied methods are only converted to functions when a function type is expected.
You can make this conversion explicit by writing `apply _` or `apply(_,_,_,_)` instead of `apply`.
(json \ "searchByType").validate[SearchByType])(SearchRequestMessage[String].apply(_,_,_,_))
How I can fix this error?
try
(json \ "searchByType").validate[SearchByType])(SearchRequestMessage[String].apply)
or (which is a bit verbose)
(json \ "searchByType").validate[SearchByType])((a,b,c,d) => SearchRequestMessage[String].apply(a,b,c,d))
This works
((json \ "jobId").validate[UUID] and
(json \ "offerId").validate[UUID] and
(json \ "search").validate[String] and
(json \ "searchByType").validate[SearchByType])((a: UUID, b: UUID, c: String, d: SearchByType) => SearchRequestMessage.apply[String](a,b,c,d))

Scala Graph JSON with Play Framework

I am trying to pass in a POST request to a REST API developped with Play! (2.5) an object that I would like to use a Scala Graph (from the graph-core dependency).
It looks like the graph already has JSON serialization/deserialization methods based on lift-json, but I am not sure how to "plug" that into Play Json library. Until now I was using implicit converters (with Reads/Writes methods) but I would like to avoid having to write my own methods for the graph part since it is already part of the library itself.
For instance, let's say I have this code:
import java.util.UUID
import scalax.collection.Graph
case class Task(
id: UUID,
status: String)
case class Stuff(
id: UUID = UUID.randomUUID(),
name: String,
tasks: Option[Graph[Task, DiEdge]])
implicit val stuffWrites: Writes[Stuff] = (
(JsPath \ "id").write[UUID] and
(JsPath \ "name").write[String]
)(unlift(Stuff.unapply))
implicit val stuffReads: Reads[Stuff] = (
(JsPath \ "id").read[UUID] and
(JsPath \ "name").read[String]
)(Stuff.apply _)
implicit val taskWrite: Writes[Task] = (
(JsPath \ "id").write[UUID] and
(JsPath \ "status").write[String]
)(unlift(Task.unapply))
implicit val taskReads: Reads[Task] = (
(JsPath \ "id").read[UUID] and
(JsPath \ "status").read[String]
)(Task.apply _)
I miss the part to serialize the graph and the parenting. Should I rewrite everything from scratch, or can I rely on methods toJson/fromJson from scalax.collection.io.json ?
Since I struggled a bit to get this working, I thought I would share the code:
class UUIDSerializer extends Serializer[UUID] {
private val UUIDClass = classOf[UUID]
def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), UUID] = {
case (TypeInfo(UUIDClass, _), json) => json match {
case JString(id) => UUID.fromString(id)
case x => throw new MappingException("Can't convert " + x + " to UUID")
}
}
def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
case x: UUID => JString(x.toString)
}
}
val extraSerializers = new UUIDSerializer :: Nil
implicit val formats = Serialization.formats(NoTypeHints) ++ extraSerializers
val taskDescriptor = new NodeDescriptor[Task](typeId = "Tasks", customSerializers=extraSerializers) {
def id(node: Any) = node match {
case Task(id, _) => id.toString
}
}
val quickJson = new Descriptor[Task](
defaultNodeDescriptor = taskDescriptor,
defaultEdgeDescriptor = Di.descriptor[Task]()
)
implicit val tasksWrites = new Writes[Graph[Task, DiEdge]] {
def writes(graph: Graph[Task, DiEdge]): JsValue = {
val json = graph.toJson(quickJson)
Json.parse(json.toString)
}
}
implicit val tasksReads = new Reads[Graph[Task, DiEdge]] {
def reads(json: JsValue): JsResult[Graph[Task, DiEdge]] = {
try {
val graph = Graph.fromJson[Task, DiEdge](json.toString, quickJson)
JsSuccess(graph)
}
catch {
case e: Exception =>
JsError(e.toString)
}
}
}
implicit def stuffModelFormat = Jsonx.formatCaseClass[Stuff]
You can try writing companion objects for yours case classes where you specify the formatting.
Example:
object Task {
implicit val taskModelFormat = Json.format[Task]
}
object Stuff {
implicit val staffModelFormat = Json.format[Stuff]
}
instead of the above implicits. With this solution compiler will resolve the known formatters for you and you could be only required to specify the missing/unknown types instead of the whole structure.

Accessing inner JSON field in Scala

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

play anorm, create a model from json without passing anorm PK value in the json

object Users {
implicit object UserReads extends Reads[User] {
def reads(json: JsValue) = JsSuccess(User(
Id((json \ "id").as[String].toLong),
(json \ "name").as[String],
(json \ "email").as[String]
}
implicit object UserWrites extends Writes[User] {
def writes(user: User) = JsObject(Seq(
"id" -> JsNumber(user.id.get),
"name" -> JsString(user.name),
"email" -> Json.toJson(user.email)
}
def view(id: Long) = Action { implicit request =>
Ok(Json.toJson(User.find(id)))
}
def all() = Action { implicit request =>
Ok(Json.toJson(User.findAll()))
}
def save = Action(parse.json) { request =>
val userJson = request.body
val user = userJson.as[User]
try {
User.create(user)
Ok("Saved")
} catch {
case e: IllegalArgumentException => BadRequest("Error")
}
}
}
case class User(
id: Pk[Long] = NotAssigned,
name: String = "",
email: String = "")
The above two are my controller & model, when I send the User data [id, name, email] as json using angular JS, it creates the User object in the database. But it should be able to create when I input only [name, email] or just [name] as email could be null. If I'm not wrong I should adjust these in the reads and writes method of User, is it?.
Also, can we have two reads/writes for different pursposes, if so how can that be achieved - throw some light. Thanks.
One Issue fixed with the following:
case class User(
id: Pk[Long] = NotAssigned,
name: String = "",
email: Option[String])
implicit object UserReads extends Reads[User] {
def reads(json: JsValue) = JsSuccess(User(
Id((json \ "id").as[String].toLong),
(json \ "name").as[String],
(json \ "email").asOpt[String])
}
implicit object UserWrites extends Writes[User] {
def writes(user: User) = JsObject(Seq(
"id" -> JsNumber(user.id.get),
"name" -> JsString(user.name),
"email" -> Json.toJson(user.email))
}
Now email can be null, but what about id - PK[Long]
I did not compile this, but it should work:
case class User(
id: Pk[Long] = NotAssigned,
name: String = "",
email: Option[String])
implicit object UserReads extends Reads[User] {
def reads(json: JsValue) = JsSuccess(User(
(json \ "id").asOpt[Long].map(id => Id[Long](id)).getOrElse(NotAssigned)
(json \ "name").as[String],
(json \ "email").asOpt[String])
}
implicit object UserWrites extends Writes[User] {
def writes(user: User) = JsObject(Seq(
"id" -> JsNumber(user.id.get),
"name" -> JsString(user.name),
"email" -> Json.toJson(user.email))
}
Basically you take the id as Option[Long] like you did with the email. Then you check if it is set and if yes you create the Pk instance with Id[Long](id) and if not you provide the NotAssigned Pk singleton instance.
Additional Tip
Alternatively you can try to use
implicit val jsonFormatter = Json.format[User]
It will create the Reads and the Writes for you directly at compile time.
There are two problem with this if you are using an anorm object directly:
First you need a Format for the Pk[Long]
implicit object PkLongFormat extends Format[Pk[Long]] {
def reads(json: JsValue): JsResult[Pk[Long]] = {
json.asOpt[Long].map {
id => JsSuccess(Id[Long](id))
}.getOrElse(JsSuccess(NotAssigned))
}
def writes(id: Pk[Long]): JsNumber = JsNumber(id.get)
}
Second it does not work, if you do not send an id at all, not even with value null, so your client needs to send {"id": null, "name": "Tim"} because it does not even try to call the PkFormatter which could handle the JsUndefined, but simply gives you an "validate.error.missing-path" error
If you don't want to send null ids you cannot use the macro Json.format[User]
Type anorm.Pk is almost exactly like scala.Option in form and function. Avoid writing concrete Reads and Writes for all types it might possibly contain. An example that follows the OptionReads and OptionWrites implementations is as follows:
implicit def PkWrites[T](implicit w: Writes[T]): Writes[Pk[T]] = new Writes[Pk[T]] {
def writes(o: Pk[T]) = o match {
case Id(value) => w.writes(value)
case NotAssigned => JsNull
}
}
implicit def PkReads[T](implicit r: Reads[T]): Reads[Pk[T]] = new Reads[Pk[T]] {
def reads(js: JsValue): JsResult[Pk[T]] =
r.reads(js).fold(e => JsSuccess(NotAssigned), v => JsSuccess(Id(v)))
}
This way, you support every T for which there's a respective Reads[T] or Writes[T], and it's more reliable in handling Id and NotAssigned.