Skip to content

Instantly share code, notes, and snippets.

@takezoe
Last active November 13, 2019 14:41
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save takezoe/8a10eec791ebdaf88b8b8fa09147a083 to your computer and use it in GitHub Desktop.
Save takezoe/8a10eec791ebdaf88b8b8fa09147a083 to your computer and use it in GitHub Desktop.
Java8 Date & Time API support for json4s
package org.scalatra.json
import java.time._
import java.util.{ Date, TimeZone }
import org.json4s._
import org.json4s.ext.DateParser
object JavaDateTimeSerializers {
def all = List(LocalDateTimeSerializer, ZonedDateTimeSerializer, OffsetDateTimeSerializer, LocalDateSerializer())
private[json] def getZoneOffset(timezone: TimeZone): ZoneOffset = {
OffsetDateTime.now(timezone.toZoneId).getOffset
}
}
case object LocalDateTimeSerializer extends CustomSerializer[LocalDateTime](format => (
{
case JString(s) =>
val zonedInstant = DateParser.parse(s, format)
LocalDateTime.ofInstant(Instant.ofEpochMilli(zonedInstant.instant), zonedInstant.timezone.toZoneId)
case JNull => null
},
{
case d: LocalDateTime => JString(format.dateFormat.format(
Date.from(d.toInstant(JavaDateTimeSerializers.getZoneOffset(format.dateFormat.timezone)))
))
}
))
case object ZonedDateTimeSerializer extends CustomSerializer[ZonedDateTime](format => (
{
case JString(s) =>
val zonedInstant = DateParser.parse(s, format)
ZonedDateTime.ofInstant(Instant.ofEpochMilli(zonedInstant.instant), zonedInstant.timezone.toZoneId)
case JNull => null
},
{
case d: ZonedDateTime => JString(format.dateFormat.format(Date.from(d.toInstant())))
}
))
case object OffsetDateTimeSerializer extends CustomSerializer[OffsetDateTime](format => (
{
case JString(s) =>
val zonedInstant = DateParser.parse(s, format)
OffsetDateTime.ofInstant(Instant.ofEpochMilli(zonedInstant.instant), zonedInstant.timezone.toZoneId)
case JNull => null
},
{
case d: OffsetDateTime => JString(format.dateFormat.format(Date.from(d.toInstant())))
}
))
private[json] case class _LocalDate(year: Int, month: Int, day: Int)
object LocalDateSerializer {
def apply() = new ClassSerializer(new ClassType[LocalDate, _LocalDate]() {
def unwrap(d: _LocalDate)(implicit format: Formats) = LocalDate.of(d.year, d.month, d.day)
def wrap(d: LocalDate)(implicit format: Formats) =
_LocalDate(d.getYear(), d.getMonthValue, d.getDayOfMonth)
})
}
private[json] trait ClassType[A, B] {
def unwrap(b: B)(implicit format: Formats): A
def wrap(a: A)(implicit format: Formats): B
}
private[json] case class ClassSerializer[A: Manifest, B: Manifest](t: ClassType[A, B]) extends Serializer[A] {
private val Class = implicitly[Manifest[A]].runtimeClass
def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), A] = {
case (TypeInfo(Class, _), json) => json match {
case JNull => null.asInstanceOf[A]
case xs: JObject if (xs.extractOpt[B].isDefined) => t.unwrap(xs.extract[B])
case value => throw new MappingException(s"Can't convert $value to $Class")
}
}
def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
case a: A if a.asInstanceOf[AnyRef].getClass == Class => Extraction.decompose(t.wrap(a))
}
}
package org.scalatra.json
import java.time._
import org.json4s.DefaultFormats
import org.scalatest.FunSuite
import org.json4s.jackson.Serialization.{ read, write }
class JavaDateTimeSerializersSpec extends FunSuite {
case class LocalDateTest(date: LocalDate)
case class LocalDateTimeTest(date: LocalDateTime)
case class ZonedDateTimeTest(date: ZonedDateTime)
case class OffsetDateTimeTest(date: OffsetDateTime)
test("LocalDate") {
implicit val formats = DefaultFormats + LocalDateSerializer()
val json = write(LocalDateTest(LocalDate.of(2017, 10, 4))).toString
assert(json == """{"date":{"year":2017,"month":10,"day":4}}""")
val obj = read[LocalDateTest](json)
assert(obj.date.getYear == 2017)
assert(obj.date.getMonthValue == 10)
assert(obj.date.getDayOfMonth == 4)
}
test("LocalDateTime serialization") {
implicit val formats = DefaultFormats + LocalDateTimeSerializer
val date = LocalDateTime.of(2014, 10, 4, 12, 34, 56)
val json = write(LocalDateTimeTest(date)).toString
assert(json == """{"date":"2014-10-04T12:34:56Z"}""")
val obj = read[LocalDateTimeTest](json)
assert(obj.date == date)
}
test("ZonedDateTime serialization") {
implicit val formats = DefaultFormats + ZonedDateTimeSerializer
val date = ZonedDateTime.of(2014, 10, 4, 12, 34, 56, 0, ZoneId.of("Asia/Tokyo"))
val json = write(ZonedDateTimeTest(date)).toString
assert(json == """{"date":"2014-10-04T03:34:56Z"}""")
val obj = read[ZonedDateTimeTest](json)
assert(obj.date.withZoneSameInstant(ZoneId.of("Asia/Tokyo")) == date)
}
test("OffsetDateTime serialization") {
implicit val formats = DefaultFormats + OffsetDateTimeSerializer
val date = OffsetDateTime.of(2014, 10, 4, 12, 34, 56, 0, ZoneOffset.of("+09:00"))
val json = write(OffsetDateTimeTest(date)).toString
assert(json == """{"date":"2014-10-04T03:34:56Z"}""")
val obj = read[OffsetDateTimeTest](json)
assert(obj.date.withOffsetSameInstant(ZoneOffset.of("+09:00")) == date)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment