I've been working on a project that stores case classes in a database and can take them back out again, storing them works fine but I am having trouble with getting them back out.
For items like Strings, Ints, Floats, etc, are being stored as they are but other types are converted to a JSON string using json4s like so
private def convertToString(obj: AnyRef, objType: Class[_]): String = {
implicit val formats = Serialization.formats(NoTypeHints)
objType match {
case t if t == classOf[String] => obj.asInstanceOf[String]
case t if t == classOf[Int] => obj.toString
case t if t == classOf[Integer] => obj.toString
case t if t == classOf[Boolean] => if (obj.asInstanceOf[Boolean]) "true" else "false"
case t if t == classOf[Short] => obj.toString
case t if t == classOf[Double] => obj.toString
case t if t == classOf[Long] => obj.toString
case t if t == classOf[Float] => obj.toString
case t if t == classOf[Byte] => obj.toString
case _ => write(obj)(formats)
}
}
This is working fine and store items just like I would expect it to, but the problem is converting the items back from JSON.
Lets say I have the case class Test(testInt: Int, testString: String, testMap: Map[String, _]) and I get the data back as 3,'blablabla','{"Test": "Map"}'
I can put all values into a new instance of the class expect for the map, here is the code I am using
private def restoreTypes(objClass: Class[_], argList: Array[Object]): Array[_ <: Object] = {
var correctTypes = Array.empty[Object]
val fields = objClass.getDeclaredFields
for(i <- 0 until fields.length) {
val giveType = argList(i).getClass
val wantedType = fields(i).getType
if(giveType != wantedType && giveType == classOf[String])
read[/*HERE*/](argList(i).asInstanceOf[String])
else
correctTypes = correctTypes :+ argList(i)
}
correctTypes
}
And this method is called list so
objClass.getConstructors()(0).newInstance(restoreTypes(objClass, args): _*)
I am getting stuck on how to pass the wanted type to the read method
Related
I have a couple of Boolean attributes in my API model and would like to accept true/false as well as 1/0 values. My first idea was to implement custom formatter:
object UserJsonProtocol extends DefaultJsonProtocol {
implicit object MyBooleanJsonFormat extends JsonFormat[Boolean] {
def write(value: Boolean): JsString = {
return JsString(value.toString)
}
def read(value: JsValue) = {
value match {
case JsString("1") => true
case JsString("0") => false
case JsString("true") => true
case JsString("false") => false
case _ => throw new DeserializationException("Not a boolean")
}
}
}
implicit val userFormat = jsonFormat15(User.apply)
}
where User is a model with Boolean attributes. Unfortunately above solution has no effect - 1/0 are not accepted as Booleans. Any solution?
After fixing some issues with types and pattern matching it seems to work:
implicit object MyBooleanJsonFormat extends JsonFormat[Boolean] {
def write(value: Boolean): JsBoolean = {
return JsBoolean(value)
}
def read(value: JsValue) = {
value match {
case JsNumber(n) if n == 1 => true
case JsNumber(n) if n == 0 => false
case JsBoolean(true) => true
case JsBoolean(false) => false
case _ => throw new DeserializationException("Not a boolean")
}
}
}
Is there a way to pass parameter into a Writes to I will be able to control they way the JsValue is written?
This is how it looks right now:
implicit val myClassWrites = new Writes[MyClass] {
override def writes(l: MyClass): JsValue = Json.obj("a" -> l.a, "b" -> l.b)
}
But I want to do something like this:
implicit val myClassWrites = new Writes[MyClass] (extended: Option[Boolean]) {
override def writes(l: MyClass): JsValue = {
extended match{
case true => //do something
case false => //do something else
}
}
}
Is there an elegant way to achieve this? or something similar?
I managed do implement this need with Reads like this(dropping the implicit):
def myClassReads(c: String) : Reads[MyClass] = (
Reads.pure(c) and
(JsPath \ "a").read[String] and
(JsPath \ "b").read[String]
) (MyClass.apply _)
And then when I want to use the reads (usually in the controller when I want to validate the body of the request) I do:
request.body.validate[MyClass](MyClass.myClassReads("foo")).fold(
errors => //
myClass=> // do domething
)
So the Writes is still a mystery.
You can do this with implicit parameters. See code below:
case class Extended(b: Boolean)
object MyClass {
implicit def MyClassWrites(implicit extended: Extended): Writes[MyClass] = new Writes[MyClass] {
def writes(l: MyClass) =
if (extended.b) JsString("foo")
else JsString("bar")
}
}
And then use like this
implicit var extd = Extended(true)
println(Json.toJson(myClass)) // foo
extd = Extended(false)
println(Json.toJson(myClass)) // bar
I'm little bit confusing about the expected result of Action.async. Here the use case : from the frontend, I receive a JSON to validate (a Foo), I send this data calling an another web service and I extract and validate the received JSON (Bar case class) which I want to validate too. The problem is when I return a result, I have the following error :
type mismatch;
found : Object
required: scala.concurrent.Future[play.api.mvc.Result]
Here my code :
case class Foo(id : String)
case class Bar(id : String)
def create() = {
Action.async(parse.json) { request =>
val sessionTokenOpt : Option[String] = request.headers.get("sessionToken")
val sessionToken : String = "Bearer " + (sessionTokenOpt match {
case None => throw new NoSessionTokenFound
case Some(session) => session
})
val user = ""
val structureId : Option[String] = request.headers.get("structureId")
if (sessionToken.isEmpty) {
Future.successful(BadRequest("no token"))
} else {
val url = config.getString("createURL").getOrElse("")
request.body.validate[Foo].map {
f =>
Logger.debug("sessionToken = " + sessionToken)
Logger.debug(f.toString)
val data = Json.toJson(f)
val holder = WS.url(url)
val complexHolder =
holder.withHeaders(("Content-type","application/json"),("Authorization",(sessionToken)))
Logger.debug("url = " + url)
Logger.debug(complexHolder.headers.toString)
Logger.debug((Json.prettyPrint(data)))
val futureResponse = complexHolder.put(data)
futureResponse.map { response =>
if(response.status == 200) {
response.json.validate[Bar].map {
b =>
Future.successful(Ok(Json.toJson(b)))
}.recoverTotal { e : JsError =>
Future.successful(BadRequest("The JSON in the body is not valid."))
}
} else {
Logger.debug("status from apex " + response.status)
Future.successful(BadRequest("alo"))
}
}
Await.result(futureResponse,5.seconds)
}.recoverTotal { e : JsError =>
Future.successful(BadRequest("The JSON in the body is not valid."))
}
}
}
}
What is wrong in my function ?
Firstly, this is doing nothing:
futureResponse.map { response =>
if(response.status == 200) {
response.json.validate[Bar].map {
b =>
Future.successful(Ok(Json.toJson(b)))
}.recoverTotal { e : JsError =>
Future.successful(BadRequest("The JSON in the body is not valid."))
}
} else {
Logger.debug("status from apex " + response.status)
Future.successful(BadRequest("alo"))
}
}
Because you're not capturing or assigning the result of it to anything. It's equivalent to doing this:
val foo = "foo"
foo + " bar"
println(foo)
The foo + " bar" statement there is pointless, it achieves nothing.
Now to debug type inference problems, what you need to do is assign results to things, and annotate with the types you're expecting. So, assign the result of the map to something first:
val newFuture = futureResponse.map {
...
}
Now, what is the type of newFuture? The answer is actually Future[Future[Result]], because you're using map, and then returning a future from inside that. If you want to return a future inside your map function, then you have to use flatMap instead, this flattens the Future[Future[Result]] to Future[Result]. But actually in your case, you don't need that you can use map, and just get rid of all those Future.successful calls, because you're not actually doing anything in that map function that needs to return a future.
And then get rid of that await as others have said - using await means blocking, which negates the point of using futures in the first place.
Anyway, this should compile:
def create() = {
Action.async(parse.json) { request =>
val sessionTokenOpt : Option[String] = request.headers.get("sessionToken")
val sessionToken : String = "Bearer " + (sessionTokenOpt match {
case None => throw new NoSessionTokenFound
case Some(session) => session
})
val user = ""
val structureId : Option[String] = request.headers.get("structureId")
if (sessionToken.isEmpty) {
Future.successful(BadRequest("no token"))
} else {
val url = config.getString("createURL").getOrElse("")
request.body.validate[Foo].map {
f =>
Logger.debug("sessionToken = " + sessionToken)
Logger.debug(f.toString)
val data = Json.toJson(f)
val holder = WS.url(url)
val complexHolder =
holder.withHeaders(("Content-type","application/json"),("Authorization",(sessionToken)))
Logger.debug("url = " + url)
Logger.debug(complexHolder.headers.toString)
Logger.debug((Json.prettyPrint(data)))
val futureResponse = complexHolder.put(data)
futureResponse.map { response =>
if(response.status == 200) {
response.json.validate[Bar].map {
b =>
Ok(Json.toJson(b))
}.recoverTotal { e : JsError =>
BadRequest("The JSON in the body is not valid.")
}
} else {
Logger.debug("status from apex " + response.status)
BadRequest("alo")
}
}
}.recoverTotal { e : JsError =>
Future.successful(BadRequest("The JSON in the body is not valid."))
}
}
}
}
Do not Await.result(futureResponse, 5 seconds). Just return the futureResponse as is. The Action.async can deal with it (in fact, it wants to deal with it, it requires you to return a Future).
Note that in your various other codepaths (else, recoverTotal) you are already doing that.
If you use Action.async you don't need to await for result. So try to return future as is, without Await.result
I've just started working with Scala in my new project (Scala 2.10.3, Play2 2.2.1, Reactivemongo 0.10.0), and encountered a pretty standard use case, which is - stream all the users in MongoDB to the external client. After navigating Enumerator, Enumeratee API I have not found a solid solution for that, and so I solved this in following way:
val users = collection.find(Json.obj()).cursor[User].enumerate(Integer.MAX_VALUE, false)
var first:Boolean = true
val indexedUsers = (users.map(u => {
if(first) {
first = false;
Json.stringify(Json.toJson(u))
} else {
"," + Json.stringify(Json.toJson(u))
}
}))
Which, from my point of view, is a little bit tricky - mainly because I needed to add Json Start Array, Json End Array and comma separators in element list, and I was not able to provide it as a pure Json stream, so I converted it to String steam.
What is a standard solution for that, using reactivemongo in play?
I wrote a helper function which does what you want to achieve:
def intersperse[E](e: E, enum: Enumerator[E]): Enumerator[E] = new Enumerator[E] {
val element = Input.El(e)
override def apply[A](i1: Iteratee[E, A]): Future[Iteratee[E, A]] = {
var iter = i1
val loop: Iteratee[E, Unit] = {
lazy val contStep = Cont(step)
def step(in: Input[E]): Iteratee[E, Unit] = in match {
case Input.Empty ⇒ contStep
case Input.EOF ⇒ Done((), Input.Empty)
case e # Input.El(_) ⇒
iter = Iteratee.flatten(iter.feed(element).flatMap(_.feed(e)))
contStep
}
lazy val contFirst = Cont(firstStep)
def firstStep(in: Input[E]): Iteratee[E, Unit] = in match {
case Input.EOF ⇒ Done((), Input.Empty)
case Input.Empty ⇒
iter = Iteratee.flatten(iter.feed(in))
contFirst
case Input.El(x) ⇒
iter = Iteratee.flatten(iter.feed(in))
contStep
}
contFirst
}
enum(loop).map { _ ⇒ iter }
}
}
Usage:
val prefix = Enumerator("[")
val suffix = Enumerator("]")
val asStrings = Enumeratee.map[User] { u => Json.stringify(Json.toJson(u)) }
val result = prefix >>> intersperse(",", users &> asStrings) >>> suffix
Ok.chunked(result)
The spray-json library extends basic Scala types with a toJson method. I'd like to convert an Any into a JsValue if there is such a pimp for the underlying type. My best attempt works, but is verbose:
import cc.spray._
val maybeJson1: PartialFunction[Any, JsValue] = {
case x: BigDecimal => x.toJson
case x: BigInt => x.toJson
case x: Boolean => x.toJson
case x: Byte => x.toJson
case x: Char => x.toJson
case x: Double => x.toJson
case x: Float => x.toJson
case x: Int => x.toJson
case x: Long => x.toJson
case x: Short => x.toJson
case x: String => x.toJson
case x: Symbol => x.toJson
case x: Unit => x.toJson
}
Ideally, I'd prefer something (impossible) like this:
def maybeJson2(any: Any): Option[JsValue] = {
if (pimpExistsFor(any))
Some(any.toJson)
else
None
}
Is there a way to do this without enumerating every type that has been enriched?
There is a way, but it requires a lot of reflection and therefore is quite a headache. The basic idea is as follows. The DefaultJsonProtocol object inherits a bunch of traits that contain implicit objects which contain write methods. Each of those will have an accessor function, but you won't know what it's called. Basically, you'll just take all methods that take no parameters and return one object that has a write method that takes the class of your object and returns a JsValue. If you find exactly one such method that returns one such class, use reflection to call it. Otherwise, bail.
It would look something like this (warning, untested):
def canWriteMe(writer: java.lang.Class[_], me: java.lang.Class[_]):
Option[java.lang.reflect.Method] =
{
writer.getMethods.find(_.getName == "write").filter{ m =>
classOf[JsValue].isAssignableFrom(m.getReturnType) && {
val parm = m.getParameterTypes()
m.length == 1 && parm(0).isAssignableFrom(me)
}
}
}
def maybeJson2(any: Any): Option[JsValue] = {
val couldWork = {
DefaultJsonProtocol.getClass.getMethods.
filter(_.getParameterTypes.length==0).
flatMap(m => canWriteMe(m.getReturnType, any.getClass).map(_ -> m))
}
if (couldWork.length != 1) None else {
couldWork.headOption.map{ case (wrMeth, obMeth) =>
val wrObj = obMeth.invoke(DefaultJsonProtocol)
val answer = wrMeth.invoke(wrObj, any)
}
}
}
Anyway, you're best off pulling the DefaultJsonProtocol class apart in the REPL step by step and finding out how to reliably identify the objects that define the writers, and then get the write methods out of them.
I'm not sure it will fit you needs, but here is an alternative approach wich is really simple and type-safe.
If you kept the type of the argument (instead of using Any) you could rely on implicit parameter resolution to find the correct conversion at compile time:
def toJson[T:JsonFormat]( t: T ): JsValue = implicitly[JsonFormat[T]].write(t)
You won't need an option, because the program will fail at compile time if you try to pass an argument which is not "pimpable".