2

I have implemented a class following Scala documentation

case class Creature(
  name: String, 
  isDead: Boolean, 
  weight: Float,
  dob: java.sql.Date
)

import play.api.libs.json._
import play.api.libs.functional.syntax._

implicit val creatureFormat = (
  (__ \ "name").format[String] and
  (__ \ "isDead").format[Boolean] and
  (__ \ "weight").format[Float] and
  (__ \ "dob").format[java.sql.Date]
)(Creature.apply, unlift(Creature.unapply))

Then I call the json wrapper like this Json.toJson(Creature("John Doe", false, 100.0, new java.sql.Date(1363456800000))) and expect to see an output like {"name": "John Doe", "isDead": false, "weight": 100.0, "dob": "2013-03-17"}. Instead, I am getting an output like {"name": "John Doe", "isDead": false, "weight": 100.0, "dob": 1363456800000}.

Please note that, in the database, I can see the dob as 2013-03-17.

3 Answers 3

1

By default the java.util.Date Json serializer produces a number containing the date timestamp.

Alternatively, you can use a date serializer that produces a String containing a representation of the date. However, because there is no standard representation of dates in JSON, you have to explicitly supply the pattern to use to produce the text representation:

implicit val creatureFormat = (
  (__ \ "name").format[String] and
  (__ \ "isDead").format[Boolean] and
  (__ \ "weight").format[Float] and
  (__ \ "dob").format(sqlDateWrites("YYYY-MM-DD"))(sqlDateReads("YYYY-MM-DD"))
)(Creature.apply, unlift(Creature.unapply))
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks a lot for the insight. Problem with this approach is that, if I have several SQL Date fields in my model/class, I will need to implement the formatter for all the Date fields. The problem furthers for the fact, in most real project, there will be many models/class with many date fields. I am not a self-righteous person, but I still think that I need to define the SQL date formatter once so that any model can use that serializer.
You can define it once: val sqlDateFormat = Format(sqlDateReads("YYYY-MM-DD"), sqlDateWrites("YYYY-MM-DD")). And then (__ \ "dob").format(sqlDateFormat). You can even declare sqlDateFormat as an implicit value and import it in your other format definitions, so it will be automatically used if you write ( (__ \ "dob").format[java.sql.Date]
1

Here's how I resolved it (I explicitly defined apply and unapply methods)

val sdf = new java.text.SimpleDateFormat("yyyy-MM-dd")
implicit val creatureFormat = (
  (__ \ "name").format[String] and
  (__ \ "isDead").format[Boolean] and
  (__ \ "weight").format[Float] and
  (__ \ "dob").format[String])
    (((name, isDead, weight, dob) => Creature(name, isDead, weight, new java.sql.Date(sdf.parse(dob).getTime()))),
    unlift((cr: Creature) => Some(cr.name, cr.isDead, cr.weight, sdf.format(cr.dob))))

I do not know whether there is any better solutions.

Update

Finally, I implemented a formatter for java.sql.Date

import play.api.libs.json._
import play.api.libs.functional.syntax._
import play.api.data.validation.ValidationError
import play.api.libs.json.{ Json => PlayJson, _ }

case class Creature(
  name: String, 
  isDead: Boolean, 
  weight: Float,
  dob: java.sql.Date
)

implicit val sqlDateWrite = new Format[SqlDate] {
  def reads(json: JsValue) = json match {
    case JsString(d) => {
      val theDate = new SqlDate(sdf.parse(d).getTime)
      if (d.matches(sdfPattern) && theDate.compareTo(new Date(0)) > 0) JsSuccess(new SqlDate(sdf.parse(d).getTime))
      else JsError(Seq(JsPath() -> Seq(ValidationError("validate.error.expected.date.in.format(dd-MM-yyyy)"))))
    }
    case _ => JsError(Seq(JsPath() -> Seq(ValidationError("validate.error.expected.date.in.String"))))
  }

  def writes(sd: SqlDate): JsValue = JsString(sdf.format(sd))
}

implicit val creatureFormat = PlayJson.format[Creature]

Now, both these lines works

val mcJson = PlayJson.toJson(Creature("John Doe", false, 100, new SqlDate(1368430000000L)))
val mcObj = PlayJson.fromJson[Creature](PlayJson.obj("name"-> "Abul Khan", "isDead"-> true, "weight"-> 115, "dob"-> "17-05-2011")).getOrElse(null)

3 Comments

There is a better solution :) I'll post it asap. In resume, define your own java.sql.Date writer
@JulienLafont Did you get time to prepare the better solution? Thanks in anticipation.
I forgot the date patterns val sdfPattern = "\\b(0[1-9]|[1-3][0-9])-(0[1-9]|1[0-2])-[1-9][0-9]{3}\\b" val dtFmt = new java.text.SimpleDateFormat("dd-MM-yyyy", java.util.Locale.ENGLISH)
0

Since you expect strings you'd have to convert everything to string and lose typing.

Json.toJson(
  Creature(
    "John Doe", "false", "100.0",(new java.sql.Date(1363456800000)).toString
  )
)

2 Comments

Did you really try with your own code? Please notice my Creature class and the data types of its attribs. I cannot convert every params to String, that way I will lose my original object. Thanks anyway for your thoughts.
Apologies. You can use unapply to do this. Since the data types have toString methods defined. You can implement unapply to convert every field to string while unboxing.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.