写在前面
一款Gson序列化反序列化深度定制工具,具备以下特性:
1.Enum深度定制:反序列化支持code、name、alternative[]、original等复杂枚举值
2.Array/Collection深度定制:解决反序列化时(JSONObject =>Java List)映射问题
3.Map深度定制:反序列化Key问题
4.String深度定制:支持字符串缩略(如日志记录,避免超长字符串,如文件Base64编码)
5.BigDecimal深度定制:反序列化支持更复杂的格式
6.Date、LocalDate、LocalDateTime、LocalTime、Duration深度定制;
一、Gson依赖
XML
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.13.1</version>
</dependency>
二、工具代码
java
import com.google.gson.*;
import com.google.gson.internal.ConstructorConstructor;
import com.gynsh.common.base.gson.*;
import java.lang.reflect.Modifier;
import java.util.*;
/**
* Gson序列化/反序列化定制化工具(基于Gson version:2.13.1)
* <p>
* 用到忽略未知枚举值注解
* <pre><code>
* \@Documented
* \@Retention(RetentionPolicy.RUNTIME)
* \@Target(ElementType.FIELD)
* public @interface IgnoreUnknownEnum {}</code></pre>
* 用的枚举字典基类接口
* <pre><code>
* public interface ApiDict<C, T extends Enum<T>> {
* C getCode();
* String getDesc();
* default String[] alternate() {
* return new String[0];
* }
* }</code></pre>
*
* @param retainLength 缩略字符串后保留的字符数长度,默认-1,即全部输出
*/
public record SuperGsonUtil(int retainLength) {
private static SuperGsonUtil getInstance(int retainLength) {
return new SuperGsonUtil(retainLength);
}
public static Gson gson() {
return gson(-1);
}
public static Gson gson(int retainLength) {
return getInstance(retainLength).builder().create();
}
private GsonBuilder builder() {
GsonBuilder builder = new GsonBuilder()
.setStrictness(Strictness.LENIENT) // 解析Json数据时忽略未知字段
.disableHtmlEscaping()
.disableJdkUnsafe()
.serializeNulls()
.excludeFieldsWithModifiers(Modifier.TRANSIENT, Modifier.STATIC)
.enableComplexMapKeySerialization();
ConstructorConstructor constructor = new ConstructorConstructor(Collections.emptyMap(), false, Collections.emptyList());
// 注册枚举字典类型适配器
builder.registerTypeAdapterFactory(new ApiDictTypeAdapterFactory());
// 注册String类型适配器工厂
builder.registerTypeAdapterFactory(new StringTypeAdapterFactory(this.retainLength));
// 注册BIgDecimal类型适配器工厂
builder.registerTypeAdapterFactory(new BigDecimalTypeAdapterFactory());
// 注册Date相关适配器工厂
builder.registerTypeAdapterFactory(new DateTypeAdapterFactory());
// 注册集合适配器工厂
builder.registerTypeAdapterFactory(new CollectionTypeAdapterFactory(constructor));
// 注册自定义Array适配器
builder.registerTypeAdapterFactory(new ArrayTypeAdapterFactory());
// 注册Map适配器
builder.registerTypeAdapterFactory(new MapTypeAdapterFactory(constructor, false));
return builder;
}
}
java
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.util.Arrays;
import java.util.Optional;
import java.util.stream.Stream;
/**
* ApiDict Gson适配器
*/
public record ApiDictTypeAdapterFactory() implements TypeAdapterFactory {
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
Class<?> rawType = type.getRawType();
if (ApiDict.class.isAssignableFrom(rawType) && rawType.isEnum()) {
@SuppressWarnings("unchecked")
TypeAdapter<T> adapter = (TypeAdapter<T>) new Adapter<>((Class<T>) rawType);
return adapter;
}
return null;
}
static class Adapter<T> extends TypeAdapter<ApiDict<?, ?>> {
final Class<ApiDict<?, ?>> enumType;
@SuppressWarnings("unchecked")
Adapter(Class<T> enumType) {
this.enumType = (Class<ApiDict<?, ?>>) enumType;
}
@Override
public void write(JsonWriter out, ApiDict value) throws IOException {
if (value == null) {
out.nullValue();
return;
}
switch (value.getCode()) {
case String v -> out.value(v);
case Boolean b -> out.value(b);
case Number v -> out.value(v);
case null -> out.nullValue();
default -> out.value(String.valueOf(value.getCode()));
}
}
@Override
public ApiDict<?, ?> read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
return convert(enumType, in.nextString().trim(), enumType.isAnnotationPresent(IgnoreUnknownEnum.class));
}
ApiDict<?, ?> convert(Class<ApiDict<?, ?>> enumCls, Object code, boolean ignoreUnknown) {
Optional.ofNullable(enumCls).orElseThrow(() -> new JsonSyntaxException("未指定枚举类型"));
final String value = String.valueOf(code);
if (value == null || value.trim().isEmpty()) {
return null;
}
ApiDict<?, ?>[] enums = enumCls.getEnumConstants();
// 根据code查找
Optional<ApiDict<?, ?>> optional = Stream.of(enums)
.filter(item -> value.equals(String.valueOf(item.getCode())))
.findFirst();
if (optional.isPresent()) {
return optional.get();
}
// 根据可替代值查找
optional = Stream.of(enums).filter(item -> item.alternate() != null)
.filter(item -> Arrays.asList(item.alternate()).contains(value))
.findFirst();
if (optional.isPresent()) {
return optional.get();
}
// 根据Enum.name查找
optional = Stream.of(enums).filter(item -> ((Enum<?>) item).name().equals(value))
.findFirst();
if (optional.isPresent()) {
return optional.get();
}
// 根据Enum.ordinal查找
try {
return enums[Integer.parseInt(value)];
} catch (Exception ex) {
if (ignoreUnknown) {
return null;
}
throw new JsonSyntaxException(String.format("枚举字典[%s]中未定义枚举值[%s]", enumCls.getSimpleName(), code));
}
}
}
}
java
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.GsonTypes;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Type;
import java.util.ArrayList;
/**
* Array Gson适配器
*/
public record ArrayTypeAdapterFactory() implements TypeAdapterFactory {
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Type type = typeToken.getType();
if (!(type instanceof GenericArrayType
|| (type instanceof Class && ((Class<?>) type).isArray()))) {
return null;
}
Type componentType = GsonTypes.getArrayComponentType(type);
TypeAdapter<?> componentTypeAdapter = gson.getAdapter(TypeToken.get(componentType));
@SuppressWarnings({"unchecked", "rawtypes"})
TypeAdapter<T> arrayAdapter = new Adapter(gson, componentTypeAdapter, GsonTypes.getRawType(componentType));
return arrayAdapter;
}
private static final class Adapter<T> extends TypeAdapter<Object> {
private final Class<T> componentType;
private final TypeAdapter<T> componentTypeAdapter;
public Adapter(
Gson context, TypeAdapter<T> componentTypeAdapter, Class<T> componentType) {
this.componentTypeAdapter =
new TypeAdapterRuntimeTypeWrapper<>(context, componentTypeAdapter, componentType);
this.componentType = componentType;
}
@Override
public Object read(JsonReader in) throws IOException {
JsonToken token = in.peek();
if (token == JsonToken.NULL) {
in.nextNull();
return null;
} else {
ArrayList<T> list = new ArrayList<>();
if (token == JsonToken.BEGIN_ARRAY) {
in.beginArray();
while (in.hasNext()) {
T instance = componentTypeAdapter.read(in);
list.add(instance);
}
in.endArray();
} else {
T instance = componentTypeAdapter.read(in);
list.add(instance);
}
int size = list.size();
// Have to copy primitives one by one to primitive array
if (componentType.isPrimitive()) {
Object array = Array.newInstance(componentType, size);
for (int i = 0; i < size; i++) {
Array.set(array, i, list.get(i));
}
return array;
}
// But for Object[] can use ArrayList.toArray
else {
@SuppressWarnings("unchecked")
T[] array = (T[]) Array.newInstance(componentType, size);
return list.toArray(array);
}
}
}
@Override
public void write(JsonWriter out, Object array) throws IOException {
if (array == null) {
out.nullValue();
return;
}
out.beginArray();
for (int i = 0, length = Array.getLength(array); i < length; i++) {
@SuppressWarnings("unchecked")
T value = (T) Array.get(array, i);
componentTypeAdapter.write(out, value);
}
out.endArray();
}
}
}
java
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.stream.Stream;
/**
* BigDecimal Gson适配器
*/
public record BigDecimalTypeAdapterFactory() implements TypeAdapterFactory {
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
if (BigDecimal.class.isAssignableFrom(typeToken.getRawType())) {
@SuppressWarnings("unchecked")
TypeAdapter<T> adapter = (TypeAdapter<T>) new Adapter();
return adapter;
}
return null;
}
static class Adapter extends TypeAdapter<BigDecimal> {
@Override
public BigDecimal read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
String s = in.nextString();
// 去除空格及逗号分隔符
String num = s.trim().replaceAll("[\\s,,]", "");
// 处理.xx的情况为0.xx
num = num.startsWith(".") ? "0" + num : num;
char last = num.charAt(num.length() - 1);
if (Character.isDigit(last)) {
return new BigDecimal(num);
}
// 处理百分号、千分号、万分号
BigDecimal percentDecimal = Stream.of(PercentDecimal.values())
.filter(item -> item.symbol == last)
.findFirst()
.orElseThrow(() -> new JsonSyntaxException("Unexpected symbol [" + last + "] in token: " + s))
.decimal;
return new BigDecimal(num.substring(0, num.length() - 1)).multiply(percentDecimal);
}
@Override
public void write(JsonWriter out, BigDecimal value) throws IOException {
if (value == null) {
out.nullValue();
return;
}
out.value(value.toPlainString());
}
}
enum PercentDecimal {
ONE_PERCENT('%', new BigDecimal("0.01")),
ONE_PERCENT_CN('%', new BigDecimal("0.01")),
ONE_THOUSANDTH('‰', new BigDecimal("0.001")),
ONE_TEN_THOUSANDTH('‱', new BigDecimal("0.0001")),
;
private final char symbol;
private final BigDecimal decimal;
PercentDecimal(char symbol, BigDecimal decimal) {
this.symbol = symbol;
this.decimal = decimal;
}
}
}
java
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.ConstructorConstructor;
import com.google.gson.internal.GsonTypes;
import com.google.gson.internal.ObjectConstructor;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Collection;
/**
* Collection Gson适配器
*/
public record CollectionTypeAdapterFactory(
ConstructorConstructor constructorConstructor) implements TypeAdapterFactory {
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Type type = typeToken.getType();
Class<? super T> rawType = typeToken.getRawType();
if (!Collection.class.isAssignableFrom(rawType)) {
return null;
}
Type elementType = GsonTypes.getCollectionElementType(type, rawType);
TypeAdapter<?> elementTypeAdapter = gson.getAdapter(TypeToken.get(elementType));
TypeAdapter<?> wrappedTypeAdapter =
new TypeAdapterRuntimeTypeWrapper<>(gson, elementTypeAdapter, elementType);
// Don't allow Unsafe usage to create instance; instances might be in broken state and calling
// Collection methods could lead to confusing exceptions
boolean allowUnsafe = false;
ObjectConstructor<T> constructor = constructorConstructor.get(typeToken, allowUnsafe);
@SuppressWarnings({"unchecked", "rawtypes"}) // create() doesn't define a type parameter
TypeAdapter<T> result = new Adapter(wrappedTypeAdapter, constructor);
return result;
}
private static final class Adapter<E> extends TypeAdapter<Collection<E>> {
private final TypeAdapter<E> elementTypeAdapter;
private final ObjectConstructor<? extends Collection<E>> constructor;
public Adapter(
TypeAdapter<E> elementTypeAdapter, ObjectConstructor<? extends Collection<E>> constructor) {
this.elementTypeAdapter = elementTypeAdapter;
this.constructor = constructor;
}
@Override
public Collection<E> read(JsonReader in) throws IOException {
JsonToken token = in.peek();
if (token == JsonToken.NULL) {
in.nextNull();
return null;
}
Collection<E> collection = constructor.construct();
if (token == JsonToken.BEGIN_ARRAY) {
in.beginArray();
while (in.hasNext()) {
E instance = elementTypeAdapter.read(in);
collection.add(instance);
}
in.endArray();
} else {
E instance = elementTypeAdapter.read(in);
collection.add(instance);
}
return collection;
}
@Override
public void write(JsonWriter out, Collection<E> collection) throws IOException {
if (collection == null) {
out.nullValue();
return;
}
out.beginArray();
for (E element : collection) {
elementTypeAdapter.write(out, element);
}
out.endArray();
}
}
}
java
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Date/LocalDate/LocalDateTime/Duration Gson适配器
*/
public record DateTypeAdapterFactory() implements TypeAdapterFactory {
@SuppressWarnings("unchecked")
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
Class<? super T> rawType = type.getRawType();
if (Date.class.isAssignableFrom(rawType)) {
return (TypeAdapter<T>) new DateAdapter();
} else if (LocalDate.class.isAssignableFrom(rawType)) {
return (TypeAdapter<T>) new LocalDateAdapter();
} else if (LocalDateTime.class.isAssignableFrom(rawType)) {
return (TypeAdapter<T>) new LocalDateTimeAdapter();
} else if (LocalTime.class.isAssignableFrom(rawType)) {
return (TypeAdapter<T>) new LocalTimeAdapter();
} else if (Duration.class.isAssignableFrom(rawType)) {
return (TypeAdapter<T>) new DurationAdapter();
}
// 其他类型返回null,使用Gson默认适配器
return null;
}
static class LocalTimeAdapter extends TypeAdapter<LocalTime> {
final DateTimeFormatter format = DateTimeFormatter.ofPattern("HH:mm:ss");
@Override
public void write(JsonWriter out, LocalTime value) throws IOException {
if (value == null) {
out.nullValue();
} else {
out.value(format.format(value));
}
}
@Override
public LocalTime read(JsonReader in) throws IOException {
if (JsonToken.STRING == in.peek()) {
String val = in.nextString().trim();
LocalTime time = TimePattern.TIME.parse(val);
if (time != null) {
return time;
}
throw new JsonSyntaxException("Unexpected time token: " + val);
}
return null;
}
}
static class LocalDateTimeAdapter extends TypeAdapter<LocalDateTime> {
final DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override
public void write(JsonWriter out, LocalDateTime value) throws IOException {
if (value == null) {
out.nullValue();
} else {
out.value(format.format(value));
}
}
@Override
public LocalDateTime read(JsonReader in) throws IOException {
if (JsonToken.STRING == in.peek()) {
String val = in.nextString();
for (DatetimePattern p : DatetimePattern.values()) {
LocalDateTime date = p.parse(val.trim());
if (date != null) {
return date;
}
}
throw new JsonSyntaxException("Unexpected datetime token: " + val);
}
return null;
}
}
static class LocalDateAdapter extends TypeAdapter<LocalDate> {
final DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd");
@Override
public void write(JsonWriter out, LocalDate value) throws IOException {
if (value == null) {
out.nullValue();
} else {
out.value(format.format(value));
}
}
@Override
public LocalDate read(JsonReader in) throws IOException {
if (JsonToken.STRING == in.peek()) {
String val = in.nextString();
for (DatePattern p : DatePattern.values()) {
LocalDate date = p.parse(val.trim());
if (date != null) {
return date;
}
}
throw new JsonSyntaxException("Unexpected date token: " + val);
}
return null;
}
}
static class DurationAdapter extends TypeAdapter<Duration> {
@Override
public void write(JsonWriter out, Duration value) throws IOException {
if (value == null) {
out.nullValue();
} else {
out.value(value.getSeconds());
}
}
@Override
public Duration read(JsonReader in) throws IOException {
if (JsonToken.NUMBER == in.peek()) {
return Duration.ofSeconds(in.nextLong());
}
return null;
}
}
static class DateAdapter extends TypeAdapter<Date> {
final Pattern defaultPattern = Pattern.compile(".*?(?i)(AM|PM)$");
final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
final ZoneId defaultZoneId = ZoneId.systemDefault();
@Override
public void write(JsonWriter out, Date value) throws IOException {
if (value == null) {
out.nullValue();
} else {
out.value(format.format(value));
}
}
@Override
public Date read(JsonReader in) throws IOException {
if (JsonToken.STRING == in.peek()) {
String val = in.nextString().trim();
LocalDateTime date = null;
for (DatetimePattern p : DatetimePattern.values()) {
date = p.parse(val);
if (date != null) {
break;
}
}
if (date != null) {
return Date.from(date.atZone(defaultZoneId).toInstant());
}
if (defaultPattern.matcher(val).matches()) {
try {
return DateFormat.getDateInstance().parse(val);
} catch (ParseException e) {
throw new JsonSyntaxException("Unexpected datetime token: " + val);
}
}
}
return null;
}
}
enum DatePattern {
DATE_PATTERN("^(\\d{2}|\\d{4})([-/.])(1[012]|0?[1-9])\\2(3[01]|[12]\\d|0?[1-9])$",
1, 3, 4),
DATE_CN_ZH("^(\\d{2}|\\d{4})年(1[012]|0?[1-9])月(3[01]|[12]\\d|0?[1-9])[日号]$",
1, 2, 3),
DATE_6("^\\d{6}$"),
DATE_8("^\\d{8}$");
private final Pattern p;
private final int[] groups;
DatePattern(String reg, int... groups) {
this.p = Pattern.compile(reg);
this.groups = groups;
}
LocalDate parse(String dateStr) {
Matcher m = p.matcher(dateStr);
if (m.find()) {
return switch (this) {
case DATE_PATTERN, DATE_CN_ZH -> LocalDate.of(
Integer.parseInt(m.group(groups[0])),
Integer.parseInt(m.group(groups[1])),
Integer.parseInt(m.group(groups[2]))
);
case DATE_6 -> LocalDate.parse(dateStr, DateTimeFormatter.ofPattern("yyMMdd"));
case DATE_8 -> LocalDate.parse(dateStr, DateTimeFormatter.ofPattern("yyyyMMdd"));
};
}
return null;
}
}
enum TimePattern {
TIME("^(0?\\d|1\\d|2[0123])[时点:](0?[1-9]|[1-5]\\d)[分:](0?[1-9]|[1-5]\\d)秒?$");
private final Pattern p;
TimePattern(String reg) {
this.p = Pattern.compile(reg);
}
LocalTime parse(String timeStr) {
Matcher m = p.matcher(timeStr);
if (m.find()) {
return LocalTime.of(
Integer.parseInt(m.group(1)),
Integer.parseInt(m.group(2)),
Integer.parseInt(m.group(3))
);
}
return null;
}
}
enum DatetimePattern {
DATE_TIME("^(\\d{2}|\\d{4})([-/.])(1[012]|0?[1-9])\\2(3[01]|[12]\\d|0?[1-9])[\\sTt](0?\\d|1\\d|2[0123])(:)(0?[1-9]|[1-5]\\d)\\6(0?[1-9]|[1-5]\\d)$",
1, 3, 4, 5, 7, 8),
DATE_TIME_CN("^(\\d{2}|\\d{4})\\年(1[012]|0?[1-9])月(3[01]|[12]\\d|0?[1-9])[日号][\\sTt]?(0?\\d|1\\d|2[0123])[时点:](0?[1-9]|[1-5]\\d)[分:](0?[1-9]|[1-5]\\d)秒?$",
1, 2, 3, 4, 5, 6),
DATE_TIME_6("^(\\d{6})[\\sTt](0?\\d|1\\d|2[0123])(:)(0?[1-9]|[1-5]\\d)\\3(0?[1-9]|[1-5]\\d)$",
1, 2, 4, 5),
DATE_TIME_8("^(\\d{8})[\\sTt](0?\\d|1\\d|2[0123])(:)(0?[1-9]|[1-5]\\d)\\3(0?[1-9]|[1-5]\\d)$",
1, 2, 4, 5),
// yyyyMMddHHmmss 到 yyyyMMddHHmmssSSSSSSSSS
DATE_TIME_14_TO_23("^\\d{14,23}$"),
// yyyyMMddHHmmss.S 到 yyyyMMddHHmmss.SSSSSSSSS
DATE_TIME_16_TO_23_WITH_DOT("^\\d{14}[.]\\d{1,9}$"),
;
private final Pattern p;
private final int[] groupIndex;
DatetimePattern(String reg, int... groupIndex) {
this.p = Pattern.compile(reg);
this.groupIndex = groupIndex;
}
LocalDateTime parse(String dateStr) {
Matcher m = p.matcher(dateStr);
if (m.find()) {
return switch (this) {
case DATE_TIME, DATE_TIME_CN -> LocalDateTime.of(
Integer.parseInt(m.group(groupIndex[0])),
Integer.parseInt(m.group(groupIndex[1])),
Integer.parseInt(m.group(groupIndex[2])),
Integer.parseInt(m.group(groupIndex[3])),
Integer.parseInt(m.group(groupIndex[4])),
Integer.parseInt(m.group(groupIndex[5]))
);
case DATE_TIME_6 -> LocalDateTime.of(
LocalDate.parse(m.group(groupIndex[0]), DateTimeFormatter.ofPattern("yyMMdd")),
LocalTime.of(Integer.parseInt(m.group(groupIndex[1])),
Integer.parseInt(m.group(groupIndex[2])),
Integer.parseInt(m.group(groupIndex[3])))
);
case DATE_TIME_8 -> LocalDateTime.of(
LocalDate.parse(m.group(groupIndex[0]), DateTimeFormatter.ofPattern("yyyyMMdd")),
LocalTime.of(Integer.parseInt(m.group(groupIndex[1])),
Integer.parseInt(m.group(groupIndex[2])),
Integer.parseInt(m.group(groupIndex[3])))
);
case DATE_TIME_14_TO_23 -> {
int len = dateStr.length();
StringBuilder sb = new StringBuilder(len);
sb.append("yyyyMMddHHmmss");
for (int i = 0, s = len - 14; i < s; i++) {
sb.append("S");
}
yield LocalDateTime.parse(dateStr, DateTimeFormatter.ofPattern(sb.toString()));
}
case DATE_TIME_16_TO_23_WITH_DOT -> {
int len = dateStr.length();
StringBuilder sb = new StringBuilder(len);
sb.append("yyyyMMddHHmmss.S");
for (int i = 0, s = len - 16; i < s; i++) {
sb.append("S");
}
yield LocalDateTime.parse(dateStr, DateTimeFormatter.ofPattern(sb.toString()));
}
};
}
return null;
}
}
}
java
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.ConstructorConstructor;
import com.google.gson.internal.GsonTypes;
import com.google.gson.internal.JsonReaderInternalAccess;
import com.google.gson.internal.ObjectConstructor;
import com.google.gson.internal.Streams;
import com.google.gson.internal.bind.TypeAdapters;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Map Gson适配器
*/
public record MapTypeAdapterFactory(ConstructorConstructor constructorConstructor,
boolean complexMapKeySerialization) implements TypeAdapterFactory {
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Type type = typeToken.getType();
Class<? super T> rawType = typeToken.getRawType();
if (!Map.class.isAssignableFrom(rawType)) {
return null;
}
Type[] keyAndValueTypes = GsonTypes.getMapKeyAndValueTypes(type, rawType);
Type keyType = keyAndValueTypes[0];
Type valueType = keyAndValueTypes[1];
TypeAdapter<?> keyAdapter = getKeyAdapter(gson, keyType);
TypeAdapter<?> wrappedKeyAdapter =
new TypeAdapterRuntimeTypeWrapper<>(gson, keyAdapter, keyType);
TypeAdapter<?> valueAdapter = gson.getAdapter(TypeToken.get(valueType));
TypeAdapter<?> wrappedValueAdapter =
new TypeAdapterRuntimeTypeWrapper<>(gson, valueAdapter, valueType);
// Don't allow Unsafe usage to create instance; instances might be in broken state and calling
// Map methods could lead to confusing exceptions
boolean allowUnsafe = false;
ObjectConstructor<T> constructor = constructorConstructor.get(typeToken, allowUnsafe);
@SuppressWarnings({"unchecked", "rawtypes"})
// we don't define a type parameter for the key or value types
TypeAdapter<T> result = new Adapter(wrappedKeyAdapter, wrappedValueAdapter, constructor, complexMapKeySerialization);
return result;
}
/**
* Returns a type adapter that writes the value as a string.
*/
private TypeAdapter<?> getKeyAdapter(Gson context, Type keyType) {
return (keyType == boolean.class || keyType == Boolean.class)
? TypeAdapters.BOOLEAN_AS_STRING
: context.getAdapter(TypeToken.get(keyType));
}
private static final class Adapter<K, V> extends TypeAdapter<Map<K, V>> {
private final TypeAdapter<K> keyTypeAdapter;
private final TypeAdapter<V> valueTypeAdapter;
private final ObjectConstructor<? extends Map<K, V>> constructor;
private final boolean complexMapKeySerialization;
public Adapter(
TypeAdapter<K> keyTypeAdapter,
TypeAdapter<V> valueTypeAdapter,
ObjectConstructor<? extends Map<K, V>> constructor, boolean complexMapKeySerialization) {
this.keyTypeAdapter = keyTypeAdapter;
this.valueTypeAdapter = valueTypeAdapter;
this.constructor = constructor;
this.complexMapKeySerialization = complexMapKeySerialization;
}
@Override
public Map<K, V> read(JsonReader in) throws IOException {
JsonToken peek = in.peek();
if (peek == JsonToken.NULL) {
in.nextNull();
return null;
}
Map<K, V> map = constructor.construct();
if (peek == JsonToken.BEGIN_ARRAY) {
in.beginArray();
while (in.hasNext()) {
in.beginArray(); // entry array
handleMapKeyAndValue(in, (Map<K, V>) map);
in.endArray();
}
in.endArray();
} else {
in.beginObject();
while (in.hasNext()) {
JsonReaderInternalAccess.INSTANCE.promoteNameToValue(in);
handleMapKeyAndValue(in, (Map<K, V>) map);
}
in.endObject();
}
return map;
}
@SuppressWarnings("unchecked")
private void handleMapKeyAndValue(JsonReader in, Map<K, V> map) throws IOException {
K key;
if (in.peek() == JsonToken.STRING) {
key = (K) in.nextString();
} else {
key = keyTypeAdapter.read(in);
}
V value = valueTypeAdapter.read(in);
V replaced = map.put(key, value);
if (replaced != null) {
throw new JsonSyntaxException("duplicate key: " + key);
}
}
@Override
public void write(JsonWriter out, Map<K, V> map) throws IOException {
if (map == null) {
out.nullValue();
return;
}
if (!complexMapKeySerialization) {
out.beginObject();
for (Map.Entry<K, V> entry : map.entrySet()) {
out.name(String.valueOf(entry.getKey()));
valueTypeAdapter.write(out, entry.getValue());
}
out.endObject();
return;
}
boolean hasComplexKeys = false;
List<JsonElement> keys = new ArrayList<>(map.size());
List<V> values = new ArrayList<>(map.size());
for (Map.Entry<K, V> entry : map.entrySet()) {
JsonElement keyElement = keyTypeAdapter.toJsonTree(entry.getKey());
keys.add(keyElement);
values.add(entry.getValue());
hasComplexKeys |= keyElement.isJsonArray() || keyElement.isJsonObject();
}
if (hasComplexKeys) {
out.beginArray();
for (int i = 0, size = keys.size(); i < size; i++) {
out.beginArray(); // entry array
Streams.write(keys.get(i), out);
valueTypeAdapter.write(out, values.get(i));
out.endArray();
}
out.endArray();
} else {
out.beginObject();
for (int i = 0, size = keys.size(); i < size; i++) {
JsonElement keyElement = keys.get(i);
out.name(keyToString(keyElement));
valueTypeAdapter.write(out, values.get(i));
}
out.endObject();
}
}
private String keyToString(JsonElement keyElement) {
if (keyElement.isJsonPrimitive()) {
JsonPrimitive primitive = keyElement.getAsJsonPrimitive();
if (primitive.isNumber()) {
return String.valueOf(primitive.getAsNumber());
} else if (primitive.isBoolean()) {
return Boolean.toString(primitive.getAsBoolean());
} else if (primitive.isString()) {
return primitive.getAsString();
} else {
throw new AssertionError();
}
} else if (keyElement.isJsonNull()) {
return "null";
} else {
throw new AssertionError();
}
}
}
}
java
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
/**
* String Gson适配器
*/
public record StringTypeAdapterFactory(int retainLength) implements TypeAdapterFactory {
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
if (String.class.isAssignableFrom(typeToken.getRawType())) {
@SuppressWarnings("unchecked")
TypeAdapter<T> adapter = (TypeAdapter<T>) new Adapter(retainLength);
return adapter;
}
return null;
}
private static class Adapter extends TypeAdapter<String> {
final int retainLength;
Adapter(int retainLength) {
this.retainLength = retainLength;
}
@Override
public String read(JsonReader in) throws IOException {
JsonToken peek = in.peek();
if (peek == JsonToken.NULL) {
in.nextNull();
return null;
}
/* coerce booleans to strings for backwards compatibility */
if (peek == JsonToken.BOOLEAN) {
return Boolean.toString(in.nextBoolean());
}
String str = in.nextString();
return handleValue(retainLength, str);
}
@Override
public void write(JsonWriter out, String value) throws IOException {
out.value(handleValue(retainLength, value));
}
private String handleValue(int retainLength, String value) {
if (value == null) {
return null;
}
if (retainLength > 0 && value.length() > retainLength) {
return value.substring(0, retainLength) + "...";
}
return value;
}
}
}
java
package com.gynsh.common.base.gson;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory;
import com.google.gson.internal.bind.SerializationDelegatingTypeAdapter;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
final class TypeAdapterRuntimeTypeWrapper<T> extends TypeAdapter<T> {
private final Gson context;
private final TypeAdapter<T> delegate;
private final Type type;
TypeAdapterRuntimeTypeWrapper(Gson context, TypeAdapter<T> delegate, Type type) {
this.context = context;
this.delegate = delegate;
this.type = type;
}
@Override
public T read(JsonReader in) throws IOException {
return delegate.read(in);
}
@Override
public void write(JsonWriter out, T value) throws IOException {
// Order of preference for choosing type adapters
// First preference: a type adapter registered for the runtime type
// Second preference: a type adapter registered for the declared type
// Third preference: reflective type adapter for the runtime type
// (if it is a subclass of the declared type)
// Fourth preference: reflective type adapter for the declared type
TypeAdapter<T> chosen = delegate;
Type runtimeType = getRuntimeTypeIfMoreSpecific(type, value);
if (runtimeType != type) {
@SuppressWarnings("unchecked")
TypeAdapter<T> runtimeTypeAdapter =
(TypeAdapter<T>) context.getAdapter(TypeToken.get(runtimeType));
// For backward compatibility only check ReflectiveTypeAdapterFactory.Adapter here but not any
// other wrapping adapters, see
// https://github.com/google/gson/pull/1787#issuecomment-1222175189
if (!(runtimeTypeAdapter instanceof ReflectiveTypeAdapterFactory.Adapter)) {
// The user registered a type adapter for the runtime type, so we will use that
chosen = runtimeTypeAdapter;
} else if (!isReflective(delegate)) {
// The user registered a type adapter for Base class, so we prefer it over the
// reflective type adapter for the runtime type
chosen = delegate;
} else {
// Use the type adapter for runtime type
chosen = runtimeTypeAdapter;
}
}
chosen.write(out, value);
}
/**
* Returns whether the type adapter uses reflection.
*
* @param typeAdapter the type adapter to check.
*/
private static boolean isReflective(TypeAdapter<?> typeAdapter) {
// Run this in loop in case multiple delegating adapters are nested
while (typeAdapter instanceof SerializationDelegatingTypeAdapter) {
TypeAdapter<?> delegate =
((SerializationDelegatingTypeAdapter<?>) typeAdapter).getSerializationDelegate();
// Break if adapter does not delegate serialization
if (delegate == typeAdapter) {
break;
}
typeAdapter = delegate;
}
return typeAdapter instanceof ReflectiveTypeAdapterFactory.Adapter;
}
/** Finds a compatible runtime type if it is more specific */
private static Type getRuntimeTypeIfMoreSpecific(Type type, Object value) {
if (value != null && (type instanceof Class<?> || type instanceof TypeVariable<?>)) {
type = value.getClass();
}
return type;
}
}