Error in json serialization in Scala with Play2 - json

i have following three case classes
case class Delete(var deleteStatus : DeleteStatus , var deleteReason : DeleteReason) // DeleteStatus and DeleteReason are enums
case class Message(val uuid: Int ,val subject : String, val body : String, var awt : Int,val dateTime : LocalDateTime = LocalDateTime.now(), delete : Delete)
case class Inbox( val uuid : Int,var messageList : ListBuffer[Message] )
i want to serialize them to Json and but i am not sure how should i do this
i have tried it like this
def writedelete(delete: Delete) = Json.obj(
"deleteStatus" -> delete.getDeleteStatusInt.toString,
"deleteReason" -> delete.getDeleteReasonInt.toString
)
def writeMessage(mgs : Message)= Json.obj(
"uuid" -> mgs.getUuid ,
"subject" -> mgs.getSubject,
"body" -> mgs.getBody,
"awt" -> mgs.getAwt,
"datetime" -> mgs.getdateTime.toString,
"delete" -> mgs.delete
)
def writeInbox(inbox : Inbox)= Json.obj(
"uuid" -> inbox.getUuid,
"mgslist" -> Seq(inbox.getMessageList)
)
but it gives following error on mgs.delete in writeMessage and mgslist in writeInbox
type mismatch; found : models.UserNotifications.MailMessages.Delete
required: play.api.libs.json.Json.JsValueWrapper
type mismatch; found :
Seq[scala.collection.mutable.ListBuffer[models.UserNotifications.MailMessages.Message]]
required: play.api.libs.json.Json.JsValueWrapper
please guide me how can i get get rid off it
and also is there any better way of doing this?

When you use Json.obj(...) to construct a JSON object various implicit conversions are available to convert common types (e.g. String, Int) to their JSON wrapper types (JsString, JsNumber). The problem with your code is there are no implicit conversions available to convert your Delete and Message types to JSON. One option would be to use your explicit conversion functions directly, e.g:
"delete" -> writeDelete(mgs.delete)
and (using Json.arr(...) to construct a JSON array):
"msglist" -> Json.arr(inbox.getMessageList.toSeq.map(writeMessage): _*)
However, the more idiomatic way to do this would be to use the JSON Inception macros to automatically generate (implicit) serializers for your types.
Simplifying slightly, this would look something like this:
case class Delete(deleteStatus: DeleteStatus, deleteReason: DeleteReason)
object Delete {
implicit val _format = Json.format[Delete]
}
case class Message(uuid: Int, subject: String, body: String, awt: Int, dateTime: LocalDateTime, delete: Delete)
object Message {
implicit val _format = Json.format[Message]
}
case class Inbox(uuid: Int, messageList: ListBuffer[Message])
object Inbox {
implicit val _format = Json.format[Message]
}
You should now be able to automatically serialize (and de-serialize) your Delete, Message, and Inbox objects using Json.toJson(thing) because it will find an implicit Format object (a combined Reads and Writes) on the companion object of each custom type.
One complication here is that your case classes contain enums; if they're Scala enumerations see this answer for how to convert them. I leave that as an exercise for the reader.

Related

Format an ISODate form field in ReactiveMongo Play JSON

Using MongoDB with Play Framework and the ReactiveMongo Play JSON library; I am trying to correctly configure a form. I have some dates stored in MongoDB which are in an ISODate wrapper/monad and handled as a JSObject:
Json.obj("dateOfBirth" -> Json.obj("$date" -> dateTimeLong))
An example of my case class using automated mapping in Play is:
case class FormData(_id: Option[BSONObjectID], name: String, dateOfBirth: Option[JSObject])
This is the form:
object MyForm {
val form = Form(
mapping(
"_id" -> ignored(Option.empty[BSONObjectID]),
"name" -> nonEmptyText,
"dateofBirth" -> ? // not `date` or `optional(date)`
)
)
}
I think that I need to custom bind the dateOfBirth field in the form but this is where I am having difficulty. I couldn't find where to include an implicit object to format a new type as shown here and this also didn't help me with this (and looks like it might be outdated by now).
I think that the bind method might look something like this:
def bind(date: Date, dateFieldName: String): JsObject = {
val longDate = new DateTime(date.getTime())
.withZoneRetainFields(DateTimeZone.UTC)
.withZone(DateTimeZone.getDefault())
.getMillis
Json.obj(s"$$$dateFieldName" -> JsNumber(longDate))
}
But I could be wrong and it would be nice to try however as I say I can't seem to find where the correct place to insert a custom formatter - I thought this should be in the companion object of FormData (where formats would be for nested fields). This wasn't the case so I am approaching the community once more. Thanks for any suggestions - particularly if there is a better way to handle this.
I knew that the answer was out there but it definitely shouldn't have been so well hidden! This one is for anyone that is feeling my pain. So I changed the following:
case class FormData(_id: Option[BSONObjectID], name: String, dateOfBirth: Option[Date])
And the form..
object MyForm {
val form = Form(
mapping(
"_id" -> ignored(Option.empty[BSONObjectID]),
"name" -> nonEmptyText,
"dateOfBirth" -> optional(date)
)(FormData.apply)(FormData.unapply)
)
}
And now the critical part. Within the companion object of the case class (mine is FormData) is where to put the following (this was altered from Stephane Godbillon's answer here):
implicit val dateRead: Reads[Date] = (__ \ "$date").read[Long].map { date =>
new Date(date)
}
implicit val dateWrite: Writes[Date] = new Writes[Date] {
def writes(date: Date): JsValue = Json.obj(
"$date" -> date.getTime
)
}

Extracting certain fields from scala object <-> Json

I'm trying to extract certain fields from Scala object before converting to Json. Is there an easy way to do this.
It would also work if i could make a new Json with certain fields from a Json.
You can simply extract out the value of a Json and scala gives you the corresponding map. Example:
var myJson = Json.obj(
"customerId" -> "xyz",
"addressId" -> "xyz",
"firstName" -> "xyz",
"lastName" -> "xyz",
"address" -> "xyz"
)
Suppose you have the Json of above type. To convert it into map simply do:
var mapFromJson = myJson.value
This gives you a map of type : scala.collection.immutable.HashMap$HashTrieMap
Hard to say without more details. Suppose that you have the following Scala case class...
case class SomeObject(customerId: Long, addressId: Long, firstName: String, lastName: String, address: String)
...and that you wanted to extract the 'firstName', 'lastName', and address fields and then convert the object to Json. Using play-json you could define an implicit conversion on the companion object for the SomeObject class...
object SomeObject {
implicit val someObjectWrites = new Writes[SomeObject] {
def writes(object: SomeObject) = Json.obj(
"firstName" -> object.firstName,
"lastName" -> object.lastName,
"address" -> object.address
)
}
}
Then you could just use the code as follows:
val obj = SomeObject(12345, 678910, "John", "Doe", "My Address")
val json = Json.toJson(obj)
Note that there are probably other JSON libraries, besides play-json, that support similar functionality.

json4s extract error in scala with map values

I use json4s native,with a json string like this
val myjson = """
{
"normative":"C",
"prefixType":{
"cod":["smallint", "int", "varchar(5)"],
"des":["varchar", "string"],
"fec":["timestamp"],
"hms":["timestamp"],
"tim":["timestamp"],
"imp":["decimal","Float", "Double"]
},
"fixcolname":{
"aud_usuario":"varchar(8)",
"aud_fec":"timestamp",
"aud_tim":"timestamp"
},
"symSep":"_",
"maxLength":26
}"""
And a case class
case class colVerify(prefixType: Map[String, Array[String]], fixcolname: Map[String, String], symSep: String, maxLength: Int)
and I want to extract it from the json String
val t = parse(myjson)
implicit val formats = DefaultFormats
val myvfy = t.extract[colVerify]
then got an error like this
Exception in thread "main" org.json4s.package$MappingException: Parsed JSON values do not match with class constructor
args=Map(des -> [Ljava.lang.String;#d7b1517, fec -> [Ljava.lang.String;#16c0663d, tim -> [Ljava.lang.String;#23223dd8, hms -> [Ljava.lang.String;#4ec6a292, imp -> [Ljava.lang.String;#1b40d5f0, cod -> [Ljava.lang.String;#ea4a92b),Map(aud_usuario -> varchar(8), aud_fec -> timestamp, aud_tim -> timestamp),_,26
arg types=scala.collection.immutable.HashMap$HashTrieMap,scala.collection.immutable.Map$Map3,java.lang.String,java.lang.Integer
constructor=public colVerify(scala.collection.mutable.Map,scala.collection.mutable.Map,java.lang.String,int)
Seems like it has problem with the type of Map, but how can I convert it implicitly?
The problem is that the maps in your case class are mutable maps, is this intentional or did you accidently import collection.mutable.Map?
If you really want the mutable maps, you could implement a custom Serializer as described here: https://github.com/json4s/json4s#serializing-non-supported-types
My first idea to add another constructor with immutable maps in case class doesn't seem to work reliably.

How do you create Json object with values of different types?

How do you create Json object with values of different types ?
I'm using spray-json
Here is the code
val images : List[JsObject] = fetchImageUrls(url).map((url: String) => {
JsObject(List(
"link_path" -> JsString(url),
"display_name" -> JsString("image"),
"size" -> JsString(""),
"modified" -> JsString(""),
"thumbnail" -> JsString(url),
"filename" -> JsString("image"),
"is_dir" -> JsBoolean(x = false),
"thumb_exists" -> JsBoolean(x = true)) )
})
val jsonAst: JsObject = JsObject(List(
"client" -> JsString("urlimages"),
"view" -> JsString("thumbnails"),
"contents" -> JsArray(images)
))
It works but looks really heavy. Is there a way to define json with code like this ?
val images : List[List[(String, Any)]] = fetchImageUrls(url).map((url: String) => {
List(
"link_path" -> url,
"display_name" -> "image",
"size" -> "",
"modified" -> "",
"thumbnail" -> url,
"filename" -> "image",
"is_dir" -> false,
"thumb_exists" -> true)
})
val jsonAst = List(
"client" -> "urlimages",
"view" -> "thumbnails",
"contents" -> images
).toJson
It doesn't work saying that
Cannot find JsonWriter or JsonFormat type class for List[(String, Object)]
).toJson
^
Which I get, type of each field is not defined at compile time. But why wouldn't it work if serializer does pattern matching anyway ?
Thanks!
I agree with #alex23 that a case class based approach will be better. Using spray-json, you would first define your case class structure as well as an extension of DefaultJsonProtocol to describe the case classes you want to be able to serialize. That would look like this:
case class Image(link_path:String, display_name:String, size:Option[String],
modified:Option[String], thumbnail:String, filename:String, is_dir:Boolean, thumb_exists:Boolean)
object Image
case class UrlImages(client:String, view:String, contents:List[Image])
object UrlImages
object MyJsonProtocol extends DefaultJsonProtocol {
implicit val imageFormat = jsonFormat8(Image.apply)
implicit val urlImagesFormat = jsonFormat3(UrlImages.apply)
}
Then, a modified version of your example would look like this:
import MyJsonProtocol._
val images : List[Image] = fetchImageUrls(url).map((url: String) => {
Image(url, "image", None, None, url, "image", false, true)
})
val jsonAst = UrlImages("urlimages", "thumbnails", images).toJson
The reason why you were seeing that error is that spray-json does not know how to serialize the Lists of tuples you are creating. If you really want to use that structure and not go the case class route, then you could look into providing a custom serializer for List[(String,Any)]. Check out the section in the spray-json docs titled "Providing JsonFormats for other Types". If you want to go this route and need more help, let me know.
You are going for the wrong approach here. For consistency purposes I would strongly encourage you to use a case class.
Say you have this
case class Image(
url: String,
size: Double,
properties: Map[String][String]
optionalProperty: Option[String]
// etc.
);
And then you use parse and decompose to deal with this.
val image = parse(jsonString).extract[Image]; // extracts an Image from JSON.
val jsonForImage: JValue = decompose(image); // serializes an Image to JSON.
And if you want to serialize a List[Image] to JSON:
def serialize(images: List[Image]) : JValue = {
for (image <- images)
yield decompose(image);
};
To parse a list of images from JSON:
val images: List[Image] = parse(jsonString).extract[List[Image]];
Using Option[SomeType] in the Image case class will deal with missing/optional parameters automatically.

Deserializing Json array into Scala object

I have been having major problems trying to deserialize a JSON array to a Scala object
[{"name":"Cool","city":"College Park","address":"806","id":1},{"name":"Mars ","city":"Durham","address":"12","id":2},{"name":"Something","city":"Raleigh
","address":"","id":3},{"name":"test","city":"","address":"","id":5}]
I have tried gson, jerkson(jackson scala wrapper), sjson, flexjson. None of them have worked. What I have here is a List of Customers. List[Customer].
This is the closest I've got:
val array = new JsonParser().parse( customers ).getAsJsonArray()
This gave me an 4 arrays. It obviously didn't give me a customer object though. I tried Jerkson.
val array = parse[List[Customer]](customers)
But I get this.
GenericSignatureFormatError occured : null
I'm just trying to find a simple way like I would in Java.
Here is my Scala class.
case class Customer(
id : Pk[ Int ],
name : String,
address : Option[ String ],
city : Option[ String ],
state : Option[ String ],
user_id : Int )
object Customer extends Magic[ Customer ]( Option( "Customer" ) ) {
def apply( name : String, address : String, city : String, state : String, user_id : Int ) = {
new Customer( NotAssigned, name, Some( address ), Some( city ), Some( state ), user_id )
}
def delete( id : Int ) = {
SQL( "DELETE from Customer where id = {id}" ).onParams( id ).executeUpdate()
}
}
Thanks for any help.
With gson, you could write your own json reader:
case class Customer(id: Int, name: String, address: Option[String],
city: Option[String], state: Option[String], user_id: Int)
object CustomerJsonReader {
def read(in: Reader) = readCustomers(new JsonReader(in))
def readCustomers(reader: JsonReader) = {
val customers = new ListBuffer[Customer]
reader.beginArray()
while (reader.hasNext()) {
customers += readCustomer(reader)
}
reader.endArray()
customers toList
}
def readCustomer(reader: JsonReader): Customer = {
var id = 0
var customerName = ""
var address: Option[String] = None
var city: Option[String] = None
var state: Option[String] = None
var userId = 0
reader.beginObject()
while (reader.hasNext()) {
val name = reader.nextName()
name match {
case "id" => id = reader.nextInt()
case "address" => address = Some(reader.nextString())
case "state" => state = Some(reader.nextString())
case "user_id" => userId = reader.nextInt()
case "name" => customerName = reader.nextString()
case "city" => city = Some(reader.nextString())
case _ => reader.skipValue()
}
}
reader.endObject()
Customer(id, customerName, address, city, state, userId)
}
}
val json =
"""
[{"name":"Cool","city":"College Park","address":"806","id":1},
{"name":"Mars ","city":"Durham","address":"12","id":2},
{"name":"Something","city":"Raleigh ","address":"","id":3},
{"name":"test","city":"","address":"","id":5}]
"""
val customers: List[Customer] =
CustomerJsonReader.read(new StringReader(json))
I know that with gson, you would need Array instead of a scala.List. I would suggest giving that a shot. You should use that with gson.fromJson, I think.
You can also try Jerkson = Jackson + Scala
Quite easy to use even if I had problems with special JSON fields containing "-"
A small tuto I saw on twitter recently: http://logician.free.fr/index.php/2011/09/16/play-scala-and-json/
I've been driven insane by this now and went through trying GSON, Lift-Json, Sjson and finally Jerkson, and found peace with that one.
Here's how I use it in combination with Play:
http://logician.eu/index.php/2011/09/16/play-scala-and-json/
http://logician.eu/index.php/2011/11/01/writing-custom-deserializers-for-jerkson/
I use Lift's json library for this purpose, it easily lets you parse JSON and extract values into case classes. It's packaged as a separate jar so you don't need the whole lift framework to use it.
import net.liftweb.json._
import net.liftweb.json.JsonDSL._
implicit val formats = DefaultFormats
val json: String = "[{...."
val parsed: JValue = parse(json)
val customers: List[Customer] = parsed.extract[List[Customer]]
Just make sure any optional fields are defined in the case class using Option. I noticed in your code the objects are missing the user_id field, which would cause a parse error if the user_id field is declared as Int instead of Option[Int].
Aside from trying to make Jerkson (which is a great library to use from what I have heard), you could also try Jackson's Scala module -- modules are the official way Jackson is extended to deal with 3rd party datatypes as well as native datatypes and constructs of other JVM languages.
(this is not to say this is more official than Jerkson, just that there are many useful Jackson extension modules that many developers are not familiar with)
Issues with Scala module are discussed on main Jackson mailing lists (user#jackson.codehaus.org); you may have found an edge case that could be fixed.
I have written a parser/validator dsl, which allows you to explicitly resolve any type erasure issue. Out of the box it handles case classes, tuples, Option, Either, List, Map, joda DatetTime, piping to functions, multiple key mapping and key name remapping.
It uses the Jackson parser
https://github.com/HigherState/jameson
Its pretty simple to with scala and with play libraries and the code
import play.api.libs.json.{Format, Json}
case class Customer(name:String, city:String, address:String, id:Int)
object Customer {
implicit val jsonFormat: Format[Customer] = Json.format(Customer)
}
val jsonDef = Json.parse(<json-string>)
val customers = jsonDef.as[List[Customer]]
customers is list of Customer objects.