3

Trying to migrate my code to play 2.6 Everything is fine except the Format for DateTime type.

As part of the migration I did add the play-json-joda as dependency.

However, something like this:

case class GeoArea(id: Option[Int] = None,
                   continentId: Option[Int] = None,
                   countryId: Option[Int] = None,
                   code: String,
                   name: String,
                   discr: Discriminator.Value,
                   createdAt: DateTime = DateTime.now,
                   updatedAt: DateTime = DateTime.now,
                   deletedAt: Option[DateTime] = None,
                   createdBy: Option[String] = None,
                   updatedBy: Option[String] = None)

With format object defined as:

implicit lazy val geoAreaFormat: Format[GeoArea] = Json.format[GeoArea]

I am getting an error:

No instance of play.api.libs.json.Format is available for org.joda.time.DateTime, org.joda.time.DateTime, scala.Option[org.joda.time.DateTime] in the implicit scope (Hint: if declared in the same file, make sure it's declared before) [error]
implicit lazy val geoAreaFormat: Format[GeoArea] = Json.format[GeoArea]

What am I missing? What else do I need to have in scope to resolve that?

My imports look like this:

import driver.PGDriver.api._
import org.joda.time.DateTime
import play.api.libs.json._
import slick.lifted.Tag
import model.GeoAreas.Discriminator
import converters.{JsonEnumeration, SlickEnumeration}

And they didn't change as during the migration, but these ones were enough for everything to work.

Shurik Agulyansky
  • 2,607
  • 2
  • 34
  • 76
  • 1
    https://stackoverflow.com/questions/31462673/how-to-use-joda-datetime-with-play-json (also https://stackoverflow.com/questions/45509268/no-instance-of-play-api-libs-json-format-is-available-for-scala-iterablejava-la and https://stackoverflow.com/questions/44849424/no-instance-of-play-api-libs-json-format-is-available-for-models-accountstatus-i ) – Dmytro Mitin Oct 16 '17 at 07:17
  • First one talks about play 2.3 which is a different story than 2.6. The other two completely unrelated. There is a standard Format for Joda.DateTime. I probably don't know how to use it. – Shurik Agulyansky Oct 16 '17 at 07:21
  • 1
    No, for example this answer says about 2.6 (it's from first of my links): https://stackoverflow.com/a/45440349/5249621 – Dmytro Mitin Oct 16 '17 at 07:54
  • So, does it mean that in 2.6 I must define my own? There is no default implementation ? – Shurik Agulyansky Oct 16 '17 at 08:06
  • FYI, the [Joda-Time](http://www.joda.org/joda-time/) project is now in [maintenance mode](https://en.wikipedia.org/wiki/Maintenance_mode), with the team advising migration to the [java.time](http://docs.oracle.com/javase/9/docs/api/java/time/package-summary.html) classes. See [Tutorial by Oracle](https://docs.oracle.com/javase/tutorial/datetime/TOC.html). – Basil Bourque Feb 15 '18 at 07:49

3 Answers3

9

In your build.sbt add this:

libraryDependencies += "com.typesafe.play" % "play-json-joda_2.12" % "2.6.0"

then in file with your model import this:

import play.api.libs.json.JodaWrites._
import play.api.libs.json.JodaReads._
cutoffurmind
  • 497
  • 8
  • 19
2

I've run into this inconvenience as well moving to the new library. You can work around it by creating an implicit format based on their new defaults.

implicit val dateFormat = new OFormat[DateTime] {
    override def reads(json: JsValue): JsResult[DateTime] = JodaReads.DefaultJodaDateTimeReads.reads(json)
    override def writes(o: DateTime): JsValue = JodaWrites.JodaDateTimeWrites.writes(o)
}

Do note however that the above doesn't include the old default writes which used epoch millis. To ensure your migration doesn't break existing functionality, you probably want to hold the old defaults:

implicit val dateFormatDefault = new Format[DateTime] {
    override def reads(json: JsValue): JsResult[DateTime] = JodaReads.DefaultJodaDateTimeReads.reads(json)
    override def writes(o: DateTime): JsValue = JodaDateTimeNumberWrites.writes(o)
}

This is because the old default in play-json used the millis (https://github.com/playframework/play-json/blob/master/play-json/jvm/src/main/scala/play/api/libs/json/EnvWrites.scala#L326-L328):

@deprecated("Include play-json-joda as a dependency and use JodaWrites.JodaDateNumberWrites", "2.6.0")
object DefaultJodaDateWrites extends Writes[DateTime] {
    def writes(d: DateTime): JsValue = JsNumber(d.getMillis)
}

And the the new default uses ISO8601 (https://github.com/playframework/play-json/blob/b4f812df628787e2d83131ceafbecb0d6d769704/play-json-joda/src/main/scala/play/api/libs/json/JodaWrites.scala#L33-L35):

/**
 * Default Serializer LocalDate -> JsString(ISO8601 format (yyyy-MM-dd))
 */
implicit object JodaDateTimeWrites extends Writes[DateTime] {
   def writes(d: DateTime): JsValue = JsString(d.toString)
}
trapd0or
  • 21
  • 4
0

You should use nscala-time time which is a wrapper of Joda

Then for a Json formatter you create this object

package formatters

import org.joda.time.{DateTime, DateTimeZone}
import org.joda.time.format.ISODateTimeFormat
import play.api.libs.json.{JsString, JsSuccess, JsValue, Format}

object DateTimeFormatter {
  private lazy val ISODateTimeFormatter = ISODateTimeFormat.dateTime.withZone(DateTimeZone.UTC)
  private lazy val ISODateTimeParser = ISODateTimeFormat.dateTimeParser

  implicit val dateTimeFormatter = new Format[DateTime] {
    def reads(j: JsValue) = JsSuccess(ISODateTimeParser.parseDateTime(j.as[String]))
    def writes(o: DateTime): JsValue = JsString(ISODateTimeFormatter.print(o))
  }
}

And in your companion object

object GeoArea {
  import formatters.DateTimeFormatter._
  implicit val geoAreaFormat = Json.format[GeoArea]
}
agusgambina
  • 6,229
  • 14
  • 54
  • 94