Last active
November 13, 2019 14:41
-
-
Save takezoe/8a10eec791ebdaf88b8b8fa09147a083 to your computer and use it in GitHub Desktop.
Java8 Date & Time API support for json4s
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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