bizzabo / play-json-extensions

+22 field case class formatter and more for play-json
http://cvogt.org/play-json-extensions/api/
Other
196 stars 44 forks source link

Jsonx formatCaseClass for joda time DateTime field #59

Open lucky0604 opened 5 years ago

lucky0604 commented 5 years ago

When I use jsonx and joda time in a case class.

import ai.x.play.json.Jsonx
import org.joda.time.DateTime
// case class
case class A (var a: Option[DateTime] = None)
// json object
object A { implicit val a = Jsonx.formatCaseClass[A]}

I also use slickless and shapeless to resolve the 22 params. It throws the error:

could not find implicit value for parameter helper: play.api.libs.json.Reads[Option[org.joda.time.DateTime]]
TRIGGERED BY: could not find implicit value for parameter helper: ai.x.play.json.OptionValidationDispatcher[Option[org.joda.time.DateTime]]
TO SOLVE THIS
1. Make sure there is a Reads[Option[org.joda.time.DateTime]] or Format[Option[org.joda.time.DateTime]] in the implicit scope
2. In case of Reads[Option[...]] you need to either
   import ai.x.play.json.implicits.optionWithNull // suggested
   or
   import ai.x.play.json.implicits.optionNoError // buggy play-json 2.3 behavior
3. In case of Reads[... .type]
   import ai.x.play.json.SingletonEncoder.simpleName
   import ai.x.play.json.implicits.formatSingleton
franklinchou commented 5 years ago

I'm not sure if there should be forward looking support for joda time, since Java 8's java.time library is widely considered to be superior and that any new applications developed on Java should be using java.time. See here.

ThatGuyMichael commented 2 years ago

To get joda time to work with Jsonx, you'll need to include an implicit joda format. Below is a full implementation for both Format[DateTime] and Format[Option[DateTime]]:

def jsonWrites(format: String): Writes[DateTime] = { dt =>
    JsString(dt.toString(format))
}

def jsonOptionalWrites(format: String): Writes[Option[DateTime]] = { dtOpt =>
    dtOpt.map(dt => JsString(dt.toString(format))).getOrElse(JsNull)
}

val dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"

def jsonReads(): Reads[DateTime] = {
    case JsNumber(d) => JsSuccess(new DateTime(d.toLong))
    case JsString(s) =>
        scala.util.control.Exception.nonFatalCatch[DateTime].opt(DateTime.parse(s, DateTimeFormat.forPattern(dateFormat))) match {
            case Some(d) => JsSuccess(d)
            case _ => JsError(Seq(JsPath() -> Seq(
                JsonValidationError("error.expected.jodadate.format")
            )))
        }
    case _ => JsError(Seq(JsPath() -> Seq(JsonValidationError("error.expected.date"))))
}

def jsonOptionalReads(): Reads[Option[DateTime]] = {
    case JsNumber(d) => JsSuccess(Some(new DateTime(d.toLong)))
    case JsString(s) =>
        scala.util.control.Exception.nonFatalCatch[DateTime].opt(DateTime.parse(s, DateTimeFormat.forPattern(dateFormat))) match {
            case Some(d) => JsSuccess(Some(d))
            case _ => JsError(Seq(JsPath() -> Seq(
                JsonValidationError("error.expected.jodadate.format")
            )))
        }
    case _ => JsError(Seq(JsPath() -> Seq(JsonValidationError("error.expected.date"))))
}

implicit val dateTimeFormat: Format[DateTime] = new Format[DateTime] {
    override def writes(o: DateTime): JsValue = jsonWrites("yyyy-MM-dd HH-mm-ss").writes(o)
    override def reads(json: JsValue): JsResult[DateTime] = jsonReads().reads(json)
}

implicit val dateTimeFormatOption: Format[Option[DateTime]] = new Format[Option[DateTime]] {
    override def writes(o: Option[DateTime]): JsValue = jsonOptionalWrites("yyyy-MM-dd HH-mm-ss").writes(o)
    override def reads(json: JsValue): JsResult[Option[DateTime]] = jsonOptionalReads().reads(json)
}