JSON序列化/反序列化工具改进版

写在前面

一款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;
  }
}
相关推荐
小北方城市网2 小时前
Redis 分布式锁高可用实现:从原理到生产级落地
java·前端·javascript·spring boot·redis·分布式·wpf
六义义3 小时前
java基础十二
java·数据结构·算法
毕设源码-钟学长3 小时前
【开题答辩全过程】以 基于SpringBoot的智能书城推荐系统的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
笨手笨脚の4 小时前
深入理解 Java 虚拟机-03 垃圾收集
java·jvm·垃圾回收·标记清除·标记复制·标记整理
莫问前路漫漫4 小时前
WinMerge v2.16.41 中文绿色版深度解析:文件对比与合并的全能工具
java·开发语言·python·jdk·ai编程
九皇叔叔4 小时前
【03】SpringBoot3 MybatisPlus BaseMapper 源码分析
java·开发语言·mybatis·mybatis plus
挖矿大亨4 小时前
c++中的函数模版
java·c++·算法
a程序小傲5 小时前
得物Java面试被问:RocketMQ的消息轨迹追踪实现
java·linux·spring·面试·职场和发展·rocketmq·java-rocketmq
青春男大5 小时前
Redis和RedisTemplate快速上手
java·数据库·redis·后端·spring·缓存
Ghost Face...5 小时前
i386 CPU页式存储管理深度解析
java·linux·服务器