feat(gson): 添加 Gson 适配器以支持 Java 8 日期时间 API
- 新增 `LocalDateTimeTypeAdapter`、`LocalDateTypeAdapter` 和 `ZonedDateTimeTypeAdapter` - 将 Gson 作为可选依赖
This commit is contained in:
parent
1239a11cd7
commit
2e73ca5f6d
@ -55,6 +55,13 @@
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Gson -->
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- ========== Test Dependencies ========== -->
|
||||
|
||||
<dependency>
|
||||
@ -111,12 +118,6 @@
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- Gson -->
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
import xyz.zhouxy.plusone.commons.util.AssertTools;
|
||||
|
||||
/**
|
||||
* {@code LocalDateTime} 的 {@code TypeAdapter},
|
||||
* 用于 Gson 对 {@code LocalDateTime} 进行相互转换。
|
||||
*
|
||||
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
|
||||
* @since 1.1.0
|
||||
* @see TypeAdapter
|
||||
* @see com.google.gson.GsonBuilder
|
||||
*/
|
||||
public class LocalDateTimeTypeAdapter extends TypeAdapter<LocalDateTime> {
|
||||
|
||||
private final DateTimeFormatter dateTimeFormatter;
|
||||
|
||||
/**
|
||||
* 默认构造函数,
|
||||
* 使用 {@link DateTimeFormatter#ISO_LOCAL_DATE_TIME} 进行 {@link LocalDateTime} 的序列化与反序列化。
|
||||
*/
|
||||
public LocalDateTimeTypeAdapter() {
|
||||
this.dateTimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造函数,
|
||||
* 使用传入的 {@link DateTimeFormatter} 进行 {@link LocalDateTime} 的序列化与反序列化。
|
||||
*
|
||||
* @param formatter 用于序列化 {@link LocalDateTime} 的格式化器,不可为 {@code null}。
|
||||
*/
|
||||
public LocalDateTimeTypeAdapter(DateTimeFormatter formatter) {
|
||||
AssertTools.checkArgumentNotNull(formatter, "formatter can not be null.");
|
||||
this.dateTimeFormatter = formatter;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void write(JsonWriter out, LocalDateTime value) throws IOException {
|
||||
out.value(dateTimeFormatter.format(value));
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public LocalDateTime read(JsonReader in) throws IOException {
|
||||
return LocalDateTime.parse(in.nextString(), dateTimeFormatter);
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
import xyz.zhouxy.plusone.commons.util.AssertTools;
|
||||
|
||||
/**
|
||||
* {@code LocalDate} 的 {@code TypeAdapter},
|
||||
* 用于 Gson 对 {@code LocalDate} 进行相互转换。
|
||||
*
|
||||
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
|
||||
* @since 1.1.0
|
||||
* @see TypeAdapter
|
||||
* @see com.google.gson.GsonBuilder
|
||||
*/
|
||||
public class LocalDateTypeAdapter extends TypeAdapter<LocalDate> {
|
||||
|
||||
private final DateTimeFormatter dateTimeFormatter;
|
||||
|
||||
/**
|
||||
* 默认构造函数,
|
||||
* 使用 {@link DateTimeFormatter#ISO_LOCAL_DATE} 进行 {@link LocalDate} 的序列化与反序列化。
|
||||
*/
|
||||
public LocalDateTypeAdapter() {
|
||||
this.dateTimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造函数,
|
||||
* 使用传入的 {@link DateTimeFormatter} 进行 {@link LocalDate} 的序列化与反序列化。
|
||||
*
|
||||
* @param formatter 用于序列化 {@link LocalDate} 的格式化器,不可为 {@code null}。
|
||||
*/
|
||||
public LocalDateTypeAdapter(DateTimeFormatter formatter) {
|
||||
AssertTools.checkArgumentNotNull(formatter, "formatter can not be null.");
|
||||
this.dateTimeFormatter = formatter;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void write(JsonWriter out, LocalDate value) throws IOException {
|
||||
out.value(dateTimeFormatter.format(value));
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public LocalDate read(JsonReader in) throws IOException {
|
||||
return LocalDate.parse(in.nextString(), dateTimeFormatter);
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
import xyz.zhouxy.plusone.commons.util.AssertTools;
|
||||
|
||||
/**
|
||||
* {@code ZonedDateTime} 的 {@code TypeAdapter},
|
||||
* 用于 Gson 对 {@code ZonedDateTime} 进行相互转换。
|
||||
*
|
||||
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
|
||||
* @since 1.1.0
|
||||
* @see TypeAdapter
|
||||
* @see com.google.gson.GsonBuilder
|
||||
*/
|
||||
public class ZonedDateTimeTypeAdapter extends TypeAdapter<ZonedDateTime> {
|
||||
|
||||
private final DateTimeFormatter dateTimeFormatter;
|
||||
|
||||
/**
|
||||
* 默认构造函数,
|
||||
* 使用 {@link DateTimeFormatter#ISO_ZONED_DATE_TIME} 进行 {@link ZonedDateTime} 的序列化与反序列化。
|
||||
*/
|
||||
public ZonedDateTimeTypeAdapter() {
|
||||
this.dateTimeFormatter = DateTimeFormatter.ISO_ZONED_DATE_TIME;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造函数,
|
||||
* 使用传入的 {@link DateTimeFormatter} 进行 {@link ZonedDateTime} 的序列化与反序列化。
|
||||
*
|
||||
* @param formatter 用于序列化 {@link ZonedDateTime} 的格式化器,不可为 {@code null}。
|
||||
*/
|
||||
public ZonedDateTimeTypeAdapter(DateTimeFormatter formatter) {
|
||||
AssertTools.checkArgumentNotNull(formatter, "formatter can not be null.");
|
||||
this.dateTimeFormatter = formatter;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void write(JsonWriter out, ZonedDateTime value) throws IOException {
|
||||
out.value(dateTimeFormatter.format(value));
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public ZonedDateTime read(JsonReader in) throws IOException {
|
||||
return ZonedDateTime.parse(in.nextString(), dateTimeFormatter);
|
||||
}
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
/*
|
||||
* 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 static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeFormatterBuilder;
|
||||
import java.time.temporal.ChronoField;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class GsonTypeAdapterTests {
|
||||
|
||||
final Gson gsonWithDefaultFormatter = new GsonBuilder()
|
||||
.registerTypeAdapter(LocalDate.class, new LocalDateTypeAdapter().nullSafe())
|
||||
.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeTypeAdapter().nullSafe())
|
||||
.registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeTypeAdapter().nullSafe())
|
||||
.create();
|
||||
|
||||
final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
|
||||
final DateTimeFormatter localDateTimeFormatter = new DateTimeFormatterBuilder()
|
||||
.appendPattern("yyyy/MM/dd HH:mm:ss")
|
||||
.appendValue(ChronoField.MILLI_OF_SECOND, 3)
|
||||
.toFormatter();
|
||||
final DateTimeFormatter zonedDateTimeFormatter = new DateTimeFormatterBuilder()
|
||||
.appendPattern("yyyy/MM/dd HH:mm:ss")
|
||||
.appendValue(ChronoField.MILLI_OF_SECOND, 3)
|
||||
.appendZoneId()
|
||||
.toFormatter();
|
||||
final Gson gsonWithSpecifiedFormatter = new GsonBuilder()
|
||||
.registerTypeAdapter(LocalDate.class, new LocalDateTypeAdapter(dateFormatter).nullSafe())
|
||||
.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeTypeAdapter(localDateTimeFormatter).nullSafe())
|
||||
.registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeTypeAdapter(zonedDateTimeFormatter).nullSafe())
|
||||
.create();
|
||||
|
||||
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"));
|
||||
|
||||
@Test
|
||||
void test_serialize_defaultFormatter() {
|
||||
Foo foo = new Foo();
|
||||
foo.localDate = date;
|
||||
foo.localDateTime = localDateTime;
|
||||
foo.zonedDateTime = zonedDateTime;
|
||||
|
||||
String json = String.format(
|
||||
"{\"localDate\":\"%s\",\"localDateTime\":\"%s\",\"zonedDateTime\":\"%s\"}",
|
||||
DateTimeFormatter.ISO_LOCAL_DATE.format(date),
|
||||
DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(localDateTime),
|
||||
DateTimeFormatter.ISO_ZONED_DATE_TIME.format(zonedDateTime)
|
||||
);
|
||||
|
||||
assertEquals(json, gsonWithDefaultFormatter.toJson(foo));
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_serialize_specifiedFormatter() {
|
||||
Foo foo = new Foo();
|
||||
foo.localDate = date;
|
||||
foo.localDateTime = localDateTime;
|
||||
foo.zonedDateTime = zonedDateTime;
|
||||
|
||||
String json = String.format(
|
||||
"{\"localDate\":\"%s\",\"localDateTime\":\"%s\",\"zonedDateTime\":\"%s\"}",
|
||||
dateFormatter.format(date),
|
||||
localDateTimeFormatter.format(localDateTime),
|
||||
zonedDateTimeFormatter.format(zonedDateTime)
|
||||
);
|
||||
|
||||
assertEquals(json, gsonWithSpecifiedFormatter.toJson(foo));
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_deserialize_defaultFormatter() {
|
||||
String json = String.format(
|
||||
"{\"localDate\":\"%s\",\"localDateTime\":\"%s\",\"zonedDateTime\":\"%s\"}",
|
||||
DateTimeFormatter.ISO_LOCAL_DATE.format(date),
|
||||
DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(localDateTime),
|
||||
DateTimeFormatter.ISO_ZONED_DATE_TIME.format(zonedDateTime)
|
||||
);
|
||||
Foo foo = gsonWithDefaultFormatter.fromJson(json, Foo.class);
|
||||
assertEquals(date, foo.localDate);
|
||||
assertEquals(localDateTime, foo.localDateTime);
|
||||
assertEquals(zonedDateTime, foo.zonedDateTime);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_deserialize_specifiedFormatter() {
|
||||
String json = String.format(
|
||||
"{\"localDate\":\"%s\",\"localDateTime\":\"%s\",\"zonedDateTime\":\"%s\"}",
|
||||
dateFormatter.format(date),
|
||||
localDateTimeFormatter.format(localDateTime),
|
||||
zonedDateTimeFormatter.format(zonedDateTime)
|
||||
);
|
||||
Foo foo = gsonWithSpecifiedFormatter.fromJson(json, Foo.class);
|
||||
assertEquals(date, foo.localDate);
|
||||
assertEquals(localDateTime, foo.localDateTime);
|
||||
assertEquals(zonedDateTime, foo.zonedDateTime);
|
||||
}
|
||||
|
||||
static class Foo {
|
||||
LocalDate localDate;
|
||||
LocalDateTime localDateTime;
|
||||
ZonedDateTime zonedDateTime;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user