From 27eb6971a5f58ea7c768393c25b41e21f5d6f24d Mon Sep 17 00:00:00 2001 From: "p.vezarko" Date: Wed, 8 May 2024 12:55:05 +0300 Subject: [PATCH] JsonWriter and JsonReader for Instant, LocalDate, LocalDateTime, OffsetDateTime, ZonedDateTime --- .../main/scala/tethys/readers/KeyReader.scala | 24 +++++++++++++++ .../readers/instances/AllJsonReaders.scala | 10 +++++++ .../main/scala/tethys/writers/KeyWriter.scala | 15 ++++++++++ .../writers/instances/AllJsonWriters.scala | 30 +++++++++++++++++++ .../tethys/readers/DefaultReadersTest.scala | 21 ++++++++++++- .../tethys/writers/DefaultWritersTest.scala | 21 ++++++++++++- 6 files changed, 119 insertions(+), 2 deletions(-) diff --git a/modules/core/src/main/scala/tethys/readers/KeyReader.scala b/modules/core/src/main/scala/tethys/readers/KeyReader.scala index c4814ff8..49aed76f 100644 --- a/modules/core/src/main/scala/tethys/readers/KeyReader.scala +++ b/modules/core/src/main/scala/tethys/readers/KeyReader.scala @@ -20,4 +20,28 @@ object KeyReader { implicit lazy val longKeyReader: KeyReader[Long] = new KeyReader[Long] { override def read(s: String)(implicit fieldName: FieldName): Long = s.toLong } + + implicit lazy val instantKeyReader: KeyReader[java.time.Instant] = new KeyReader[java.time.Instant] { + override def read(s: String)(implicit fieldName: FieldName): java.time.Instant = java.time.Instant.parse(s) + } + + implicit lazy val localDateKeyReader: KeyReader[java.time.LocalDate] = new KeyReader[java.time.LocalDate] { + override def read(s: String)(implicit fieldName: FieldName): java.time.LocalDate = + java.time.LocalDate.parse(s, java.time.format.DateTimeFormatter.ISO_LOCAL_DATE) + } + + implicit lazy val localDateTimeKeyReader: KeyReader[java.time.LocalDateTime] = new KeyReader[java.time.LocalDateTime] { + override def read(s: String)(implicit fieldName: FieldName): java.time.LocalDateTime = + java.time.LocalDateTime.parse(s, java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME) + } + + implicit lazy val offsetDateTimeKeyReader: KeyReader[java.time.OffsetDateTime] = new KeyReader[java.time.OffsetDateTime] { + override def read(s: String)(implicit fieldName: FieldName): java.time.OffsetDateTime = + java.time.OffsetDateTime.parse(s, java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME) + } + + implicit lazy val zonedDateTimeKeyReader: KeyReader[java.time.ZonedDateTime] = new KeyReader[java.time.ZonedDateTime] { + override def read(s: String)(implicit fieldName: FieldName): java.time.ZonedDateTime = + java.time.ZonedDateTime.parse(s, java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME) + } } diff --git a/modules/core/src/main/scala/tethys/readers/instances/AllJsonReaders.scala b/modules/core/src/main/scala/tethys/readers/instances/AllJsonReaders.scala index 19591382..c5e6bc89 100644 --- a/modules/core/src/main/scala/tethys/readers/instances/AllJsonReaders.scala +++ b/modules/core/src/main/scala/tethys/readers/instances/AllJsonReaders.scala @@ -136,4 +136,14 @@ trait AllJsonReaders extends OptionReaders { implicit lazy val javaBigIntegerReader: JsonReader[java.math.BigInteger] = bigIntReader.map(_.bigInteger) implicit lazy val javaUUIDReader: JsonReader[java.util.UUID] = stringReader.map(java.util.UUID.fromString(_)) + implicit lazy val javaInstantReader: JsonReader[java.time.Instant] = stringReader.map(java.time.Instant.parse) + implicit lazy val javaLocalDateReader: JsonReader[java.time.LocalDate] = + stringReader.map(java.time.LocalDate.parse(_, java.time.format.DateTimeFormatter.ISO_LOCAL_DATE)) + implicit lazy val javaLocalDateTimeReader: JsonReader[java.time.LocalDateTime] = + stringReader.map(java.time.LocalDateTime.parse(_, java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME)) + implicit lazy val javaOffsetDateTimeReader: JsonReader[java.time.OffsetDateTime] = + stringReader.map(java.time.OffsetDateTime.parse(_, java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME)) + implicit lazy val javaZonedDateTimeReader: JsonReader[java.time.ZonedDateTime] = + stringReader.map(java.time.ZonedDateTime.parse(_, java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME)) + } diff --git a/modules/core/src/main/scala/tethys/writers/KeyWriter.scala b/modules/core/src/main/scala/tethys/writers/KeyWriter.scala index f22acac1..c624e110 100644 --- a/modules/core/src/main/scala/tethys/writers/KeyWriter.scala +++ b/modules/core/src/main/scala/tethys/writers/KeyWriter.scala @@ -13,4 +13,19 @@ object KeyWriter { implicit lazy val intKeyWriter: KeyWriter[Int] = _.toString implicit lazy val longKeyWriter: KeyWriter[Long] = _.toString + + implicit lazy val instantKeyWriter: KeyWriter[java.time.Instant] = _.toString + + implicit lazy val localDateKeyWriter: KeyWriter[java.time.LocalDate] = + _.format(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE) + + implicit lazy val localDateTimeKeyWriter: KeyWriter[java.time.LocalDateTime] = + _.format(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME) + + implicit lazy val offsetDateTimeKeyWriter + : KeyWriter[java.time.OffsetDateTime] = + _.format(java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME) + + implicit lazy val zonedDateTimeKeyWriter: KeyWriter[java.time.ZonedDateTime] = + _.format(java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME) } diff --git a/modules/core/src/main/scala/tethys/writers/instances/AllJsonWriters.scala b/modules/core/src/main/scala/tethys/writers/instances/AllJsonWriters.scala index d7683dd4..3407149b 100644 --- a/modules/core/src/main/scala/tethys/writers/instances/AllJsonWriters.scala +++ b/modules/core/src/main/scala/tethys/writers/instances/AllJsonWriters.scala @@ -79,4 +79,34 @@ trait AllJsonWriters extends OptionWriters with EitherWriters { implicit lazy val nullWriter: JsonWriter[Null] = new JsonWriter[Null] { override def write(value: Null, tokenWriter: TokenWriter): Unit = tokenWriter.writeNull() } + + implicit lazy val instantWriter: JsonWriter[java.time.Instant] = + new JsonWriter[java.time.Instant] { + override def write(value: java.time.Instant, tokenWriter: TokenWriter): Unit = + tokenWriter.writeString(value.toString) + } + + implicit lazy val localDateWriter: JsonWriter[java.time.LocalDate] = + new JsonWriter[java.time.LocalDate] { + override def write(value: java.time.LocalDate, tokenWriter: TokenWriter): Unit = + tokenWriter.writeString(value.format(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE)) + } + + implicit lazy val localDateTimeWriter: JsonWriter[java.time.LocalDateTime] = + new JsonWriter[java.time.LocalDateTime] { + override def write(value: java.time.LocalDateTime, tokenWriter: TokenWriter): Unit = + tokenWriter.writeString(value.format(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME)) + } + + implicit lazy val offsetDateTimeWriter: JsonWriter[java.time.OffsetDateTime] = + new JsonWriter[java.time.OffsetDateTime] { + override def write(value: java.time.OffsetDateTime, tokenWriter: TokenWriter): Unit = + tokenWriter.writeString(value.format(java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME)) + } + + implicit lazy val zonedDateTimeWriter: JsonWriter[java.time.ZonedDateTime] = + new JsonWriter[java.time.ZonedDateTime] { + override def write(value: java.time.ZonedDateTime, tokenWriter: TokenWriter): Unit = + tokenWriter.writeString(value.format(java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME)) + } } diff --git a/modules/core/src/test/scala/tethys/readers/DefaultReadersTest.scala b/modules/core/src/test/scala/tethys/readers/DefaultReadersTest.scala index fe6f66f5..58225a75 100644 --- a/modules/core/src/test/scala/tethys/readers/DefaultReadersTest.scala +++ b/modules/core/src/test/scala/tethys/readers/DefaultReadersTest.scala @@ -13,6 +13,11 @@ import scala.reflect.ClassTag class DefaultReadersTest extends AnyFlatSpec { private val randomUUID = java.util.UUID.randomUUID() + private val instantNow = java.time.Instant.now() + private val localDateNow = java.time.LocalDate.now() + private val localDateTimeNow = java.time.LocalDateTime.now() + private val offsetDateTimeNow = java.time.OffsetDateTime.now() + private val zonedDateTimeNow = java.time.ZonedDateTime.now() private def test[A](result: A)(implicit jsonReader: JsonReader[A], ct: ClassTag[A]): TestDefinition[A] = { TestDefinition(result, jsonReader, ct.toString()) @@ -36,6 +41,15 @@ class DefaultReadersTest extends AnyFlatSpec { test(List[Int](), "Seq.empty") -> arr(), test(Map("a" -> 1, "b" -> 2)) -> obj("a" -> 1, "b" -> 2), test(Map(randomUUID -> 1),"Map with UUID keys") -> obj(randomUUID.toString -> 1), + test(Map(instantNow -> 1), "Map with Instant keys") -> obj(instantNow.toString -> 1), + test(Map(localDateNow -> 1), "Map with LocalDate keys") -> + obj(localDateNow.format(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE) -> 1), + test(Map(localDateTimeNow -> 1), "Map with LocalDateTime keys") -> + obj(localDateTimeNow.format(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME) -> 1), + test(Map(offsetDateTimeNow -> 1), "Map with OffsetDateTime keys") -> + obj(offsetDateTimeNow.format(java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME) -> 1), + test(Map(zonedDateTimeNow -> 1), "Map with ZonedDateTime keys") -> + obj(zonedDateTimeNow.format(java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME) -> 1), test(Map(1L -> 1), "Map with Long keys") -> obj("1" -> 1), test(Map(1 -> 1), "Map with Int keys") -> obj("1" -> 1), test(Option(1), "Option.nonEmpty") -> value(1), @@ -47,7 +61,12 @@ class DefaultReadersTest extends AnyFlatSpec { test(1d: java.lang.Double) -> value(1d), test(java.math.BigDecimal.valueOf(1)) -> value(1: BigDecimal), test(java.math.BigInteger.valueOf(1)) -> value(1: BigInt), - test(randomUUID) -> value(randomUUID.toString) + test(randomUUID) -> value(randomUUID.toString), + test(instantNow) -> value(instantNow.toString), + test(localDateNow) -> value(localDateNow.format(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE)), + test(localDateTimeNow) -> value(localDateTimeNow.format(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME)), + test(offsetDateTimeNow) -> value(offsetDateTimeNow.format(java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME)), + test(zonedDateTimeNow) -> value(zonedDateTimeNow.format(java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME)) ) behavior of "Default readers" diff --git a/modules/core/src/test/scala/tethys/writers/DefaultWritersTest.scala b/modules/core/src/test/scala/tethys/writers/DefaultWritersTest.scala index ffc26fb0..d674c299 100644 --- a/modules/core/src/test/scala/tethys/writers/DefaultWritersTest.scala +++ b/modules/core/src/test/scala/tethys/writers/DefaultWritersTest.scala @@ -12,6 +12,11 @@ import scala.reflect.ClassTag class DefaultWritersTest extends AnyFlatSpec { private val randomUUID = java.util.UUID.randomUUID() + private val instantNow = java.time.Instant.now() + private val localDateNow = java.time.LocalDate.now() + private val localDateTimeNow = java.time.LocalDateTime.now() + private val offsetDateTimeNow = java.time.OffsetDateTime.now() + private val zonedDateTimeNow = java.time.ZonedDateTime.now() private def test[A](value: A)(implicit jsonWriter: JsonWriter[A], ct: ClassTag[A]): TestDefinition[A] = { TestDefinition(value, jsonWriter, ct.toString()) @@ -38,6 +43,15 @@ class DefaultWritersTest extends AnyFlatSpec { test(Map(randomUUID -> 1),"Map with UUID keys") -> obj(randomUUID.toString -> 1), test(Map(1L -> 1), "Map with Long keys") -> obj("1" -> 1), test(Map(1 -> 1), "Map with Int keys") -> obj("1" -> 1), + test(Map(instantNow -> 1), "Map with Instant keys") -> obj(instantNow.toString -> 1), + test(Map(localDateNow -> 1), "Map with LocalDate keys") -> + obj(localDateNow.format(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE) -> 1), + test(Map(localDateTimeNow -> 1), "Map with LocalDateTime keys") -> + obj(localDateTimeNow.format(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME) -> 1), + test(Map(offsetDateTimeNow -> 1), "Map with OffsetDateTime keys") -> + obj(offsetDateTimeNow.format(java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME) -> 1), + test(Map(zonedDateTimeNow -> 1), "Map with ZonedDateTime keys") -> + obj(zonedDateTimeNow.format(java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME) -> 1), test(Option(1), "Option.nonEmpty") -> value(1), test(Option.empty[Int], "Option.empty") -> List(NullValueNode), test(Right(1): Either[String, Int], "Either.right") -> value(1), @@ -49,7 +63,12 @@ class DefaultWritersTest extends AnyFlatSpec { test(1d: java.lang.Double) -> value(1d), test(java.math.BigDecimal.valueOf(1)) -> value(1: BigDecimal), test(java.math.BigInteger.valueOf(1)) -> value(1: BigInt), - test(randomUUID) -> value(randomUUID.toString) + test(randomUUID) -> value(randomUUID.toString), + test(instantNow) -> value(instantNow.toString), + test(localDateNow) -> value(localDateNow.format(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE)), + test(localDateTimeNow) -> value(localDateTimeNow.format(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME)), + test(offsetDateTimeNow) -> value(offsetDateTimeNow.format(java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME)), + test(zonedDateTimeNow) -> value(zonedDateTimeNow.format(java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME)) ) behavior of "Default writers"