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
)
}
Related
override def accessToken(): ServiceCall[RequestTokenLogIn, Done] = {
request=>
val a=request.oauth_token.get
val b=request.oauth_verifier.get
val url=s"https://api.twitter.com/oauth/access_token?oauth_token=$a&oauth_verifier=$b"
ws.url(url).withMethod("POST").get().map{
res=>
println(res.body)
}
The output which I am getting on terminal is
oauth_token=xxxxxxxxx&oauth_token_secret=xxxxxxx&user_id=xxxxxxxxx&screen_name=xxxxx
I want to convert this response in json format.like
{
oauth_token:"",
token_secret:"",
}
When Calling res.json.toString its not converting into jsValue.
Is there any other way or am I missing something?
According to the documentation twitter publishes, it seems that the response is not a valid json. Therefore you cannot convert it automagically.
As I see it you have 2 options, which you are not going to like. In both options you have to do string manipulations.
The first option, which I like less, is actually building the json:
print(s"""{ \n\t"${res.body.replace("=", "\": \"").replace("&", "\"\n\t\"")}" \n}""")
The second option, is to extract the variables into a case class, and let play-json build the json string for you:
case class TwitterAuthToken(oauth_token: String, oauth_token_secret: String, user_id: Long, screen_name: String)
object TwitterAuthToken {
implicit val format: OFormat[TwitterAuthToken] = Json.format[TwitterAuthToken]
}
val splitResponse = res.body.split('&').map(_.split('=')).map(pair => (pair(0), pair(1))).toMap
val twitterAuthToken = TwitterAuthToken(
oauth_token = splitResponse("oauth_token"),
oauth_token_secret = splitResponse("oauth_token_secret"),
user_id = splitResponse("user_id").toLong,
screen_name = splitResponse("screen_name")
)
print(Json.toJsObject(twitterAuthToken))
I'll note that Json.toJsObject(twitterAuthToken) returns JsObject, which you can serialize, and deserialize.
I am not familiar with any option to modify the delimiters of the json being parsed by play-json. Given an existing json you can manipulate the paths from the json into the case class. But that is not what you are seeking for.
I am not sure if it is requires, but in the second option you can define user_id as long, which is harder in the first option.
Hi everyone recently I faced an issue in converting json into my own data model.
I have a json format message which may contain an empty string:
{
"name" : "John Doe",
"hobbies": ""
}
or a list of hobby types:
{
"name" : "John Doe",
"hobbies": [{"name":"basketball"}]
}
And the following is my case class data model in scala play framework:
case class Person(name: String, hobbies: List[Hobby])
case class Hobby(name: String)
Right now I'm using the default json formatter but of course it's not working well when we have empty string as value.
implicit val HobbyJson= Json.format[Hobby]
implicit val PersonJson = Json.format[Person]
it will throw exception if the hobbies has a empty string. I want to convert it into an empty list when it's the empty string. I search the document Play provides but couldn't find infomation. Can anyone give some suggestions?
Thanks in advance.
As you mentioned, the default Format macros won't work for you here because of the inconsistent treatment of hobbies. So you need to implement your own Reads[Person] - here's how I'd do it:
object PersonJson {
implicit val hobbyConverter = Json.format[Hobby]
val personReads = new Reads[Person] {
override def reads(json: JsValue): JsResult[Person] = {
for {
personName <- (json \ "name").validate[String]
hobbies <- (json \ "hobbies").validate[JsValue]
} yield {
val maybeHobbyList = hobbies.validate[List[Hobby]].asOpt
Person(personName, maybeHobbyList.getOrElse(Nil))
}
}
}
implicit val personConverter = Format(personReads, Json.writes[Person])
}
The key thing to note here is surrounding the whole thing in a JsResult courtesy of the for-comprehension and the yield. This gives us all the necessary checking (like the name field being there and being a String, and the hobbies field being there).
The code within the yield block only runs if we've got something that looks pretty close to a Person. Then we can safely try validating the hobbies as a List[Hobby], and convert the result to an Option[List[Hobby]]. It'll be a None if it didn't work (thus it must have been a string) and so we default it to the empty list as required.
Thanks #millhouse answer, it definitely works. Like he said we need a custom Reads[Person] to properly convert it.
I also post my code as reference.
implicit val personJsonReads: Reads[Person] = (
(__ \ "name").read[String] and
(__ \ "hobbies").read[List[Hobby]].orElse(Reads.pure(List()))
) (Person.apply _)
read[List[Hobby]].orElse(Reads.pure(List())) will generate the empty list when the value cannot convert to List[Hobby].
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.
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.
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.