Convert Json to Joda DateTime in Play! scala 2.5 - json

I have read this question (and the other ones on SO), but I still don't manage to convert a JsValue to a case class with a Joda DateTime.
Here is the JSON I have:
val json = Json.parse(
"""
{
"message": "Bla bla bla",
"updated_time": "2016-09-17T12:48:12+0000"
}
"""
)
And the corresponding case class is:
import org.joda.time.DateTime
final case class FacebookPost(message: String, updated_time: DateTime)
I also added this implicit DateTime reader (I tried some other as well):
implicit val readsJodaLocalDateTime = Reads[DateTime](js =>
js.validate[String].map[DateTime](dtString => new DateTime(dtString)))
But when I try to convert my json to the corresponding case class (json.as[FacebookPost]), I get this error:
play.api.libs.json.JsResultException: `JsResultException(errors:List((/updated_time,List(ValidationError(List(error.expected.jodadate.format),WrappedArray(yyyy-MM-dd))))))`
What am I doing wrong?

Your problem is the format of your datetime: 2016-09-17T12:48:12+0000.
By default, Joda's DateTime class can't automatically figure out the format you're using, so you have to provide a hint using DateTimeFormat.
Example for our custom Reads[DateTime]:
import play.api.libs.json._
import play.api.libs.json.Reads._
import play.api.libs.functional.syntax._
val dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
val customReads = Reads[DateTime](js =>
js.validate[String].map[DateTime](dtString =>
DateTime.parse(dtString, DateTimeFormat.forPattern(dateFormat))
)
)
implicit val reads: Reads[FacebookPost] = (
(__ \ "message").read[String] and
(__ \ "updated_time").read[DateTime](customReads)
)(FacebookPost.apply _)
Then, it's possible to do the following:
val json = Json.parse(
"""
{
"message": "Bla bla bla",
"updated_time": "2016-09-17T12:48:12+0000"
}
""")
val post = json.validate[FacebookPost]
// => JsSuccess(FacebookPost(Bla bla bla,2016-09-17T14:48:12.000+02:00))
edit:
You don't need to create the Read[DateTime] from scratch, there is a predefined method in Play that helps you with that:
object CustomReads /* or wherever you want to define it */ {
val dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
implicit val jodaDateReads = play.api.libs.json.Reads.jodaDateReads(dateFormat)
}
Done. Just import whenever you need it, e.g. in the companion object of your model classes:
object FacebookPost {
import utils.CustomReads._
implicit val reads: Reads[FacebookPost] = Json.reads[FacebookPost]
}
You can "override" the implicit Read[DateTime] provided by Play by exluding it from your imports:
import play.api.libs.json.Reads.{DefaultJodaDateReads => _, _}

Related

Error message: Try to implement an implicit Writes or Format for this type

I have the following json string. In scala I want to extract some fields and save them in a new json.
{"query":"doi:10.1186/s13612-016-0045-3","result":[{"total":"1","start":"1","pageLength":"10"}],"records":[{"identifier":"doi:10.1186/s13612-016-0045-3","url":[{"format":"","platform":"","value":"http://dx.doi.org/10.1186/s13612-016-0045-3"}],"title":"Technology
and Reflection: Mood and Memory Mechanisms for
Well-Being","creators":[{"creator":"Konrad,
Artie"},{"creator":"Tucker, Simon"},{"creator":"Crane,
John"},{"creator":"Whittaker, Steve"}],"publicationName":"Psychology
of
Well-Being","issn":"2211-1522","openaccess":"true","journalid":"13612","doi":"10.1186/s13612-016-0045-3","publisher":"Springer","publicationDate":"2016-06-15","volume":"6","number":"1","issuetype":"","topicalCollection":"","startingPage":"1","copyright":"©2016
The
Author(s)","genre":"OriginalPaper","abstract":"AbstractBackgroundWe
report a
..."}],"facets":[{"name":"subject","values":[{"value":"Biological
Psychology","count":"1"},{"value":"Health
Psychology","count":"1"},{"value":"Neuropsychology","count":"1"},{"value":"Positive
Psychology","count":"1"},{"value":"Psychology","count":"1"},{"value":"Quality
of Life
Research","count":"1"}]},{"name":"pub","values":[{"value":"Psychology
of
Well-Being","count":"1"}]},{"name":"year","values":[{"value":"2016","count":"1"}]},{"name":"country","values":[{"value":"United
States","count":"1"},{"value":"USA","count":"1"}]},{"name":"type","values":[{"value":"Journal","count":"1"}]},{"name":"keyword","values":[{"value":"Emotion","count":"1"},{"value":"Memory","count":"1"},{"value":"Mood","count":"1"},{"value":"Reflection","count":"1"},{"value":"Reminiscence","count":"1"},{"value":"Technology
mediated
reflection","count":"1"},{"value":"Well-being","count":"1"}]}]}
In particular I want to get the following new json object (i.e. extract fields "creators","publicationName" and "abstract", and rename them accordingly):
{"creators":[{"creator":"Konrad, Artie"},{"creator":"Tucker,
Simon"},{"creator":"Crane, John"},{"creator":"Whittaker,
Steve"}],"pubTitle":"Psychology of
Well-Being","pubAbstract":"AbstractBackgroundWe report ..."}
This is my current code, but I the error message says Error:(137, 27) No Json serializer found for type play.api.libs.json.JsResult[org.test.Publication]. Try to implement an implicit Writes or Format for this type. val json = Json.toJson(processedPubl).
import play.api.libs.json._
import play.api.libs.functional.syntax._
import play.api.data.validation.ValidationError
import play.api.libs.json.Reads._
case class Creator(creator: String)
case class Publication(pubTitle: String, creators: Seq[Creator], pubAbstract: String)
val jsonstring = ... // this is the raw string provided
// at the beginning of this post
implicit val publReads: Reads[Publication] = (
(JsPath \ "publicationName").read[String] and
(JsPath \ "creators").read[Seq[Creator]] and
(JsPath \ "abstract").read[String]
) (Publication.apply _)
val processedPubl = Json.parse(jsonstring).validate[Publication](publReads)
val json = Json.toJson(processedPubl)
You should try this:
import play.api.libs.functional.syntax._
import play.api.libs.json._
case class Creator(creator: String)
case class Publication(pubTitle: String, creators: Seq[Creator], pubAbstract: String)
val jsonString = ...
implicit val creatorFormat = Json.format[Creator]
implicit val publWrites = Json.writes[Publication]
implicit val publReads: Reads[Publication] = (
(JsPath \ "publicationName").read[String] and
(JsPath \ "creators").read[Seq[Creator]] and
(JsPath \ "abstract").read[String]
)(Publication.apply _)
val input = Json.parse(jsonString).as[Publication]
val output = Json.toJson(input)
Reads is something that is used during reading from JSON (input uses this) and Writes is something that is used during writing to JSON (output uses it). Together they form something called Format. Since you're using case classes in your code, you can autogenerate those Reads/Writes/Formats using play-json methods.

No Json serializer as JsObject found for type play.api.libs.json.JsObject

I have the following code that works in a console app when referencing "org.reactivemongo" %% "play2-reactivemongo" % "0.10.5.0.akka23"
when I update the reference to "org.reactivemongo" % "play2-reactivemongo_2.11" % "0.11.0.play23-M3" I get:
No Json serializer as JsObject found for type play.api.libs.json.JsObject. Try to implement an implicit OWrites or OFormat for this type.
import org.joda.time.DateTime
import reactivemongo.bson.BSONObjectID
import play.modules.reactivemongo.json.BSONFormats._
case class GoogleToken
(
id: Option[BSONObjectID],
name: String,
emailAddress: String,
refreshToken: String,
expires: DateTime
)
object GoogleToken {
import play.api.libs.json.Json
// Generates Writes and Reads
implicit val googleTokenFormat = Json.format[GoogleToken]
}
and then
val collection = db.collectionJSONCollection
val query = Json.obj()
val cursor = collection.find(query).
cursor[GoogleToken](ReadPreference.nearest).
collect[List]()
What am I doing wrong?
The final release of ReactiveMongo 0.11 has been published ("org.reactivemongo" %% "play2-reactivemongo" % "0.11.0.play23").
As indicated on the updated documentation, for the default BSON/JSON conversions, it's recommended to have: import play.modules.reactivemongo.json._, ImplicitBSONHandlers._.
In my case, I was feeding ReactiveMongo (insert) with a JsValue instead than a JsObject. In order to fix it, behind adding import play.modules.reactivemongo.json._, I also had to change my implicit Writes in OWrites:
from
implicit val myWrites: Writes[A] = new Writes[A] {
def writes(a: A) = Json.obj(...)
to
implicit val myWrites: OWrites[A] = new OWrites[A] { <-- NOTE THE 'O' before 'Writes'
def writes(a: A) = Json.obj(...)
Mine worked out after adding:
import play.modules.reactivemongo.json._
import play.modules.reactivemongo.json.collection._
try to add
import reactivemongo.play.json._
For me adding this import worked.
import play.modules.reactivemongo.json._

Play Scala Json Writer for Seq of Tuple

I'm trying to find a way to use the built in Macro Json Writer in order to serialize Seq[(String,Customer)]
I managed to do this for Seq[Customer] but when adding the touple, the compiler starts screaming at me.
This code works:
package models.health
import play.api.libs.json._
case class Customer(name: String, age: Int)
//we use the dummy var as a workaround to the json writer limitations (cannot handle single argument case class)
case class Demo(customers: Seq[Customer], dummy: Option[String] = None)
object Demo {
import play.api.libs.functional.syntax._
implicit val customer_writer = Json.writes[Customer]
implicit val writes: Writes[Demo] = (
(__ \ "customers").write[Seq[Customer]] and
(__ \ "dummy").writeNullable[String]) {
(d: Demo) => (d.customers,d.dummy)
}
}
BUT the below code (simply change from Seq[Customer] to Seq[(String,Customer)] Doesn't Copmile... Any help is really appreciated:
package models.health
import play.api.libs.json._
case class Customer(name: String, age: Int)
//we use the dummy var as a workaround to the json writer limitations (cannot handle single argument case class)
case class Demo(customers: Seq[(String,Customer], dummy: Option[String] = None)
object Demo {
import play.api.libs.functional.syntax._
implicit val customer_writer = Json.writes[Customer]
implicit val writes: Writes[Demo] = (
(__ \ "customers").write[Seq[(String,Customer)]] and
(__ \ "dummy").writeNullable[String]) {
(d: Demo) => (d.customers,d.dummy)
}
}
this is the compiler error I got:
No Json serializer found for type Seq[(String,models.health.Customer)]
The library makes no assumption as to how you want your tuple to serialize. You could use an array, an object, etc.
By adding this implicit Writes function, your serializer will write it out as an array.
implicit def tuple2Writes[A, B](implicit a: Writes[A], b: Writes[B]): Writes[Tuple2[A, B]] = new Writes[Tuple2[A, B]] {
def writes(tuple: Tuple2[A, B]) = JsArray(Seq(a.writes(tuple._1), b.writes(tuple._2)))
}

Parsing JSON Date Time in Scala/Play

I have the following Read defined:
import org.joda.time.DateTime;
implicit val userInfoRead: Reads[UserInfo] = (
(JsPath \ "userName").readNullable[String] and
] (JsPath \ "startDate").readNullable[DateTime]
(UserInfo.apply _)
With the following JSON object being passed in:
"userInfo" : {
"userName": "joeuser",
"startDate": "2006-02-28"
}
When I validate this data I get the following error:
(/startDate,List(ValidationError(validate.error.expected.jodadate.format,WrappedArray(yyyy-MM-dd))))))
Any suggestions on what I'm missing in the formatting?
As far as I can see, the issue is probably just the format not matching what Joda is expecting. I simplified a bit, and this worked for me:
scala> import org.joda.time.DateTime
import org.joda.time.DateTime
scala> case class UserInfo(userName: String, startDate: DateTime)
defined class UserInfo
scala> implicit val dateReads = Reads.jodaDateReads("yyyy-MM-dd")
dateReads: play.api.libs.json.Reads[org.joda.time.DateTime] = play.api.libs.json.DefaultReads$$anon$10#22db02cb
scala> implicit val userInfoReads = Json.reads[UserInfo]
userInfoReads: play.api.libs.json.Reads[UserInfo] = play.api.libs.json.Reads$$anon$8#52bcbd5d
scala> val json = Json.parse("""{
| "userName": "joeuser",
| "startDate": "2006-02-28"
| }""")
json: play.api.libs.json.JsValue = {"userName":"joeuser","startDate":"2006-02-28"}
scala> json.validate[UserInfo]
res12: play.api.libs.json.JsResult[UserInfo] = JsSuccess(UserInfo(joeuser,2006-02-28T00:00:00.000-05:00),)

Play 2.1-RC2: Read keys from JSON

I try to migrate my web application from Play 2.0.4 to Play 2.1-RC2.
I have JSON data with a list of unknown keys (key1, key2) like this:
{description: "Blah",
tags: [
key1: ["value1", "value2"],
key2: ["value3"]
]
}
I want to store the data from the JSON in a List of Metatags. In Play 2.0.4 I have used something like this to read the tags-list:
def readMetatags(meta: JsObject): List[Metatag] =
meta.keys.toList.map(x => Metatag(x, (meta \ x).as[List[String]])
Now I want to use the new Play 2.1-JSON-API (Prototype):
import play.api.libs.json._
import play.api.libs.functional.syntax._
object Metatags {
implicit val metatagsRead: Read[Metatags] = (
(JsPath \ "description").read[String] and
(JsPath \ "tags").read[List[Metatag]]
)(Metatags.apply _, unlift(Metatags.unapply _))
implicit val metatagRead: Read[Metatag] = (
JsPath().key. ?? read[String] and // ?!? read key
JsPath().values. ?? read[List[String]] // ?!? read value list
)(Metatag.apply _, unlift(Metatag.unapply _))
}
case class Metatags(description: String, tags: List[Metatag])
case class Metatag(key: String, data: List[String])
How can I read the keys from the JSON?
This is a solution with a custom reader for the MetaTag class. The read just convert the JsValue to a JsObject, which have the useful fieldSet method.
For MetaTags, the macro inception works perfectly
object Application extends Controller {
case class MetaTags(description: String, tags: List[MetaTag])
case class MetaTag(key: String, data: List[String])
implicit val readMetaTag = Reads(js =>
JsSuccess(js.as[JsObject].fieldSet.map(tag =>
MetaTag(tag._1, tag._2.as[List[String]])).toList))
implicit val readMetaTags = Json.reads[MetaTags]
def index = Action {
val json = Json.obj(
"description" -> "Hello world",
"tags" -> Map(
"key1" -> Seq("key1a", "key1b"),
"key2" -> Seq("key2a"),
"key3" -> Seq("Key3a", "key3b", "key3c")))
val meta = json.as[MetaTags]
Ok(meta.tags.map(_.data.mkString(",")).mkString("/"))
// key1a,key1b/key2a/Key3a,key3b,key3c
}
}