使用Play框架的JSON库自定义JodaTime序列化器?

18
如何在JSON中实现自定义的JodaTime的DateTime序列化/反序列化?我倾向于使用Play框架的JSON库(2.1.1版本)。默认的DateTime序列化器使用dt.getMillis而不是.toString,后者会返回一个符合ISO标准的字符串。
为case类编写Reads[T]Writes[T]似乎相当简单,但我无法弄清楚如何对DateTime进行同样的操作。
3个回答

25

有一个默认的DateTime序列化器,但它使用dt.getMillis而不是.toString,后者将返回符合ISO标准的字符串。

如果你查看源代码, Reads.jodaDateReads已经使用DateTimeFormatter.forPattern处理了数字和字符串。如果你想处理ISO8601字符串,只需将其替换为ISODateTimeFormat:

  implicit val jodaISODateReads: Reads[org.joda.time.DateTime] = new Reads[org.joda.time.DateTime] {
    import org.joda.time.DateTime

    val df = org.joda.time.format.ISODateTimeFormat.dateTime()

    def reads(json: JsValue): JsResult[DateTime] = json match {
      case JsNumber(d) => JsSuccess(new DateTime(d.toLong))
      case JsString(s) => parseDate(s) match {
        case Some(d) => JsSuccess(d)
        case None => JsError(Seq(JsPath() -> Seq(ValidationError("validate.error.expected.date.isoformat", "ISO8601"))))
      }
      case _ => JsError(Seq(JsPath() -> Seq(ValidationError("validate.error.expected.date"))))
    }

    private def parseDate(input: String): Option[DateTime] =
      scala.util.control.Exception.allCatch[DateTime] opt (DateTime.parse(input, df))

  }

请按需简化,例如删除数字处理

  implicit val jodaDateWrites: Writes[org.joda.time.DateTime] = new Writes[org.joda.time.DateTime] {
    def writes(d: org.joda.time.DateTime): JsValue = JsString(d.toString())
  }

很棒的答案。不过我做了一些修改才让它正常工作。虽然提到了“pattern”,但它并不在范围内。我用“df”替换了它。这是你的意图吗? - justinhj
@justinhj 谢谢!df在那里没有意义,我已经修复了。 - Alexey Romanov

19

我使用Play 2.3.7,并在伴随对象中定义了带有字符串模式的隐式读取/写入:

case class User(username:String, birthday:org.joda.time.DateTime)

object User {
  implicit val yourJodaDateReads = Reads.jodaDateReads("yyyy-MM-dd'T'HH:mm:ss'Z'")
  implicit val yourJodaDateWrites = Writes.jodaDateWrites("yyyy-MM-dd'T'HH:mm:ss'Z'")
  implicit val userFormat = Json.format[User]
}

嗨,我尝试了你的代码,但它对我不起作用,我已经创建了一个关于这个问题的新帖子 - Guillaume
在你的代码中使用Json.fromJson [User](value)而不是Json.fromJson(value)。更多细节请参见您发布的答案 - Evghenii Todorov
1
你是不是想用 "yyyy-MM-dd'T'HH:mm:ssZ" 而不是 "yyyy-MM-dd'T'HH:mm:ss'Z'" - Jake Greene
@JakeGreene:这取决于您项目中定义的时间字符串格式。在我的应用程序中,我使用了“yyyy-MM-dd'T'HH:mm:ss'Z'”。 - Evghenii Todorov
是的,这对我也没用,我不得不使用@Guillaume帖子中的代码。 - jakehschwartz

9
另一种也许更简单的解决方法是进行映射,例如:

case class GoogleDoc(id: String, etag: String, created: LocalDateTime)

object GoogleDoc {
  import org.joda.time.LocalDateTime
  import org.joda.time.format.ISODateTimeFormat

  implicit val googleDocReads: Reads[GoogleDoc] = (
      (__ \ "id").read[String] ~
      (__ \ "etag").read[String] ~
      (__ \ "createdDate").read[String].map[LocalDateTime](x => LocalDateTime.parse(x, ISODateTimeFormat.basicdDateTime()))
  )(GoogleDoc)
}

更新

如果您需要经常进行此转换,则可以创建自己的隐式转换,这只需要几行代码:

import org.joda.time.LocalDateTime
import org.joda.time.format.ISODateTimeFormat

implicit val readsJodaLocalDateTime = Reads[LocalDateTime](js =>
  js.validate[String].map[LocalDateTime](dtString =>
    LocalDateTime.parse(dtString, ISODateTimeFormat.basicDateTime())
  )
)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接