diff --git a/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/gson/InstantTypeAdapter.java b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/gson/InstantTypeAdapter.java new file mode 100644 index 0000000..24e6fb3 --- /dev/null +++ b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/gson/InstantTypeAdapter.java @@ -0,0 +1,52 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package xyz.zhouxy.plusone.commons.gson; + +import java.io.IOException; +import java.time.Instant; +import java.time.format.DateTimeFormatter; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +/** + * {@code Instant} 的 {@code TypeAdapter}, + * 用于 Gson 对 {@code Instant} 进行相互转换。 + * + *

+ * 使用 {@link DateTimeFormatter#ISO_INSTANT} 进行 {@link Instant} 的序列化与反序列化。 + *

+ * + * @author ZhouXY + * @since 1.1.0 + * @see TypeAdapter + * @see com.google.gson.GsonBuilder + */ +public final class InstantTypeAdapter extends TypeAdapter { + + /** {@inheritDoc} */ + @Override + public void write(JsonWriter out, Instant value) throws IOException { + out.value(DateTimeFormatter.ISO_INSTANT.format(value)); + } + + /** {@inheritDoc} */ + @Override + public Instant read(JsonReader in) throws IOException { + return Instant.parse(in.nextString()); + } +} diff --git a/plusone-commons/src/test/java/xyz/zhouxy/plusone/commons/gson/GsonTypeAdapterTests.java b/plusone-commons/src/test/java/xyz/zhouxy/plusone/commons/gson/GsonTypeAdapterTests.java index b1d2022..b00f5da 100644 --- a/plusone-commons/src/test/java/xyz/zhouxy/plusone/commons/gson/GsonTypeAdapterTests.java +++ b/plusone-commons/src/test/java/xyz/zhouxy/plusone/commons/gson/GsonTypeAdapterTests.java @@ -17,6 +17,7 @@ package xyz.zhouxy.plusone.commons.gson; import static org.junit.jupiter.api.Assertions.assertEquals; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; @@ -25,6 +26,7 @@ import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.time.temporal.ChronoField; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import com.google.gson.Gson; @@ -39,6 +41,7 @@ public class GsonTypeAdapterTests { .registerTypeAdapter(LocalDate.class, new LocalDateTypeAdapter().nullSafe()) .registerTypeAdapter(LocalDateTime.class, new LocalDateTimeTypeAdapter().nullSafe()) .registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeTypeAdapter().nullSafe()) + .registerTypeAdapter(Instant.class, new InstantTypeAdapter().nullSafe()) .create(); final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd"); @@ -60,24 +63,29 @@ public class GsonTypeAdapterTests { final LocalDate date = LocalDate.of(2025, 6, 6); final LocalDateTime localDateTime = date.atTime(6, 6, 6, 666000000); final ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.of("+08:00")); + final Instant instant = zonedDateTime.toInstant(); + @DisplayName("测试使用 TypeAdapter 中默认的 formatter 进行序列化") @Test void test_serialize_defaultFormatter() { Foo foo = new Foo(); foo.localDate = date; foo.localDateTime = localDateTime; foo.zonedDateTime = zonedDateTime; + foo.instant = instant; String json = String.format( - "{\"localDate\":\"%s\",\"localDateTime\":\"%s\",\"zonedDateTime\":\"%s\"}", + "{\"localDate\":\"%s\",\"localDateTime\":\"%s\",\"zonedDateTime\":\"%s\",\"instant\":\"%s\"}", DateTimeFormatter.ISO_LOCAL_DATE.format(date), DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(localDateTime), - DateTimeFormatter.ISO_ZONED_DATE_TIME.format(zonedDateTime) + DateTimeFormatter.ISO_ZONED_DATE_TIME.format(zonedDateTime), + DateTimeFormatter.ISO_INSTANT.format(instant) ); assertEquals(json, gsonWithDefaultFormatter.toJson(foo)); } + @DisplayName("测试指定 formatter 进行序列化") @Test void test_serialize_specifiedFormatter() { Foo foo = new Foo(); @@ -95,20 +103,24 @@ public class GsonTypeAdapterTests { assertEquals(json, gsonWithSpecifiedFormatter.toJson(foo)); } + @DisplayName("测试使用 TypeAdapter 中默认的 formatter 进行反序列化") @Test void test_deserialize_defaultFormatter() { String json = String.format( - "{\"localDate\":\"%s\",\"localDateTime\":\"%s\",\"zonedDateTime\":\"%s\"}", + "{\"localDate\":\"%s\",\"localDateTime\":\"%s\",\"zonedDateTime\":\"%s\",\"instant\":\"%s\"}", DateTimeFormatter.ISO_LOCAL_DATE.format(date), DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(localDateTime), - DateTimeFormatter.ISO_ZONED_DATE_TIME.format(zonedDateTime) + DateTimeFormatter.ISO_ZONED_DATE_TIME.format(zonedDateTime), + DateTimeFormatter.ISO_INSTANT.format(instant) ); Foo foo = gsonWithDefaultFormatter.fromJson(json, Foo.class); assertEquals(date, foo.localDate); assertEquals(localDateTime, foo.localDateTime); assertEquals(zonedDateTime, foo.zonedDateTime); + assertEquals(instant, foo.instant); } + @DisplayName("测试指定 formatter 进行反序列化") @Test void test_deserialize_specifiedFormatter() { String json = String.format( @@ -127,5 +139,6 @@ public class GsonTypeAdapterTests { LocalDate localDate; LocalDateTime localDateTime; ZonedDateTime zonedDateTime; + Instant instant; } }