JSON序列化/反序列化工具(基于Gson深度定制)

写在前面

迫于生计,一直忙于路上,没时间更新。

正好本次有用上用到Gson序列化反序列化工具,且极大程度不满足应用场景,特地对Gson花了两天做了深度定制。记录上,也为了正好需要的朋友提供一些助力,开箱即用!


本次遇到不满足场景及定制化大致如下:

1.对于BIgDecimal的反序列化时,数字字符串是千分位分隔符、包含%、‰、‱,原生Gson无法满足;

2.对于自定义枚举值的序列化、反序列化更是复杂化;

3.对与接口JavaBean的双方约定的List类型的属性,数据交换时,如果该数组仅有一个对象时,对方很有可能给你得是一个JSON Object而非JSON Array(反正我是遇上了)。这种场景只能定制化反序列化对List的特殊处理;

4.对于String字符串的序列化/反序列化,对于接口日志记录,如果某个文件转为Base64或Hex传输,但此时记录日志,如果不省略,得不偿失,所以也需要定制化;

5.除此之外,对Map、Date、LocalDate、LocalDateTime、LocalTime、Duration等也做了定制化。

一、所需依赖
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.13.1</version>
</dependency>
二、Gson定制化工具
import com.google.gson.*;
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 com.kosmian.annotation.IgnoreUnknownEnum;
import com.kosmian.dict.ApiDict;

import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

/**
 * 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>
 *
 * @author Ian
 */
public final class SuperGsonUtil {

    private static class Holder {
        private static final SuperGsonUtil INSTANCE = new SuperGsonUtil();
    }

    private static SuperGsonUtil getInstance() {
        return Holder.INSTANCE;
    }

    public static Gson gson() {
        return gson(0);
    }

    public static Gson gson(int retainLength) {
        return getInstance().builder(retainLength).create();
    }

    private GsonBuilder builder(int retainLength) {
        GsonBuilder builder = new GsonBuilder()
                .setStrictness(Strictness.LENIENT) // 解析Json数据时忽略未知字段
                .disableHtmlEscaping()
                .disableJdkUnsafe()
                .serializeNulls()
                .excludeFieldsWithModifiers(Modifier.TRANSIENT, Modifier.STATIC)
                .enableComplexMapKeySerialization();
        // 1.注册枚举字典类型适配器
        builder.registerTypeAdapterFactory(new ApiDictTypeAdapterFactory());
        // 2.注册String类型适配器工厂
        builder.registerTypeAdapterFactory(new StringTypeAdapterFactory(retainLength));
        // 3.注册BIgDecimal类型适配器工厂
        builder.registerTypeAdapterFactory(new BigDecimalTypeAdapterFactory());
        // 4.注册Date相关适配器工厂
        builder.registerTypeAdapterFactory(new DateTypeAdapterFactory());
        // 5.注册集合适配器工厂
        builder.registerTypeAdapterFactory(new CollectionTypeAdapterFactory(retainLength));
        // 6.注册自定义Array适配器
        builder.registerTypeAdapterFactory(new ArrayTypeAdapterFactory(retainLength));
        // 7.注册Map适配器
        builder.registerTypeAdapterFactory(new MapTypeAdapterFactory(retainLength));
        // 8.注册自定义Object适配器(核心:拦截所有Object类型中的字符串)
        builder.registerTypeAdapterFactory(new ObjectTypeAdapterFactory(retainLength));
        return builder;
    }

    private 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());
            }
        }
    }

    private 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:m: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;
            }
        }
    }

    private 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));
                }
            }
        }
    }

    private record MapTypeAdapterFactory(int retainLength) implements TypeAdapterFactory {
        @Override
        public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
            if (!Map.class.isAssignableFrom(typeToken.getRawType())) {
                return null;
            }
            TypeAdapter<?> valueAdapter = new ObjectTypeAdapterFactory.Adapter(gson, retainLength);
            @SuppressWarnings({"unchecked", "rawtypes"})
            TypeAdapter<T> adapter = new Adapter(valueAdapter, newConstructor().get(typeToken));
            return adapter;
        }

        static class Adapter<T> extends TypeAdapter<Map<String, T>> {
            final TypeAdapter<T> valueAdapter;
            final ObjectConstructor<? extends Map<String, T>> constructor;

            public Adapter(TypeAdapter<T> valueAdapter, ObjectConstructor<? extends Map<String, T>> constructor) {
                this.valueAdapter = valueAdapter;
                this.constructor = constructor;
            }

            @Override
            public Map<String, T> read(JsonReader in) throws IOException {
                JsonToken token = in.peek();
                if (token == JsonToken.NULL) {
                    in.nextNull();
                    return null;
                }
                Map<String, T> map = constructor.construct();
                in.beginObject();
                while (in.hasNext()) {
                    String key = in.nextName();
                    T value = valueAdapter.read(in);
                    map.put(key, value);
                }
                in.endObject();
                return map;
            }

            @Override
            public void write(JsonWriter out, Map<String, T> map) throws IOException {
                if (map == null) {
                    out.nullValue();
                    return;
                }
                out.beginObject();
                for (Map.Entry<String, T> entry : map.entrySet()) {
                    out.name(entry.getKey());
                    T value = entry.getValue();
                    valueAdapter.write(out, value);
                }
                out.endObject();
            }
        }
    }

    private 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;
        }

        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;
                }
                if (peek == JsonToken.BOOLEAN) {
                    return Boolean.toString(in.nextBoolean());
                }
                // 处理数字类型(可选,根据需求)
                if (peek == JsonToken.NUMBER) {
                    return in.nextString();
                }
                String str = in.nextString();
                return handleValue(retainLength, str);
            }

            @Override
            public void write(JsonWriter out, String value) throws IOException {
                out.value(handleValue(retainLength, value));
            }
        }
    }

    private record ObjectTypeAdapterFactory(int retainLength) implements TypeAdapterFactory {
        @Override
        public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
            // 匹配所有Object类型(包括泛型中的Object)
            if (typeToken.getRawType() == Object.class) {
                @SuppressWarnings("unchecked")
                TypeAdapter<T> adapter = (TypeAdapter<T>) new Adapter(gson, retainLength);
                return adapter;
            }
            return null;
        }

        static class Adapter extends TypeAdapter<Object> {
            final Gson gson;
            final int retainLength;

            Adapter(Gson gson, int retainLength) {
                this.gson = gson;
                this.retainLength = retainLength;
            }

            @Override
            public Object read(JsonReader in) throws IOException {
                JsonToken token = in.peek();
                return switch (token) {
                    case NULL -> {
                        in.nextNull();
                        yield null;
                    }
                    case BOOLEAN -> in.nextBoolean();
                    case NUMBER -> {
                        // 委托给Number适配器
                        TypeToken<?> typeToken = TypeToken.getParameterized(Number.class);
                        TypeAdapter<?> typeAdapter = gson.getAdapter(typeToken);
                        yield typeAdapter.read(in);
                    }
                    case STRING -> {
                        // 委托给String适配器
                        TypeToken<?> typeToken = TypeToken.getParameterized(String.class);
                        TypeAdapter<?> typeAdapter = gson.getAdapter(typeToken);
                        yield typeAdapter.read(in);
                    }
                    case BEGIN_ARRAY -> {
                        // 委托给Collection适配器
                        TypeToken<?> typeTokenColl = TypeToken.getParameterized(Collection.class, Object.class);
                        TypeAdapter<?> adapter = gson.getAdapter(typeTokenColl);
                        yield adapter.read(in);
                    }
                    case BEGIN_OBJECT -> {
                        // 解析对象为Map适配器
                        TypeToken<?> mapTypeToken = TypeToken.getParameterized(Map.class, String.class, Object.class);
                        TypeAdapter<?> mapAdapter = gson.getAdapter(mapTypeToken);
                        yield mapAdapter.read(in);
                    }
                    default -> throw new JsonSyntaxException("Unexpected token: " + token);
                };
            }

            @Override
            public void write(JsonWriter out, Object value) throws IOException {
                switch (value) {
                    case null -> out.nullValue();
                    case String str -> {
                        TypeToken<?> typeToken = TypeToken.getParameterized(String.class);
                        @SuppressWarnings("unchecked")
                        TypeAdapter<String> adapter = (TypeAdapter<String>) gson.getAdapter(typeToken);
                        adapter.write(out, str);
                    }
                    case Number num -> {
                        TypeToken<?> typeToken = TypeToken.getParameterized(Number.class);
                        @SuppressWarnings("unchecked")
                        TypeAdapter<Number> adapter = (TypeAdapter<Number>) gson.getAdapter(typeToken);
                        adapter.write(out, num);
                    }
                    case Boolean bool -> out.value(bool);
                    case ApiDict<?, ?> dict -> out.value(String.valueOf(dict.getCode()));
                    case Collection<?> coll -> {
                        TypeToken<?> typeToken = TypeToken.getParameterized(Collection.class, Object.class);
                        @SuppressWarnings("unchecked")
                        TypeAdapter<? super Collection<?>> adapter = (TypeAdapter<? super Collection<?>>) gson.getAdapter(typeToken);
                        adapter.write(out, coll);
                    }
                    case Map<?, ?> map -> {
                        TypeToken<?> typeToken = TypeToken.getParameterized(Map.class, String.class, Object.class);
                        @SuppressWarnings("unchecked")
                        TypeAdapter<? super Map<?, ?>> adapter = (TypeAdapter<? super Map<?, ?>>) gson.getAdapter(typeToken);
                        adapter.write(out, map);
                    }
                    default -> gson.toJson(value, value.getClass(), out);
                }
            }
        }
    }

    private record ArrayTypeAdapterFactory(int retainLength) 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<?> elementTypeAdapter = new ObjectTypeAdapterFactory.Adapter(gson, retainLength);
            @SuppressWarnings({"unchecked", "rawtypes"})
            TypeAdapter<T> adapter = new Adapter(elementTypeAdapter, newConstructor().get(typeToken),
                    GsonTypes.getRawType(componentType));
            return adapter;
        }

        static class Adapter<E> extends TypeAdapter<Object> {
            final TypeAdapter<E> elementTypeAdapter;
            final ObjectConstructor<? extends E[]> constructor;
            final Class<E> componentType;

            Adapter(TypeAdapter<E> elementTypeAdapter, ObjectConstructor<? extends E[]> constructor, Class<E> componentType) {
                this.elementTypeAdapter = elementTypeAdapter;
                this.constructor = constructor;
                this.componentType = componentType;
            }

            @Override
            public Object read(JsonReader in) throws IOException {
                JsonToken token = in.peek();
                return switch (token) {
                    case NULL -> {
                        in.nextNull();
                        yield null;
                    }
                    case BEGIN_ARRAY -> {
                        ArrayList<E> list = new ArrayList<>();
                        in.beginArray();
                        while (in.hasNext()) {
                            E instance = elementTypeAdapter.read(in);
                            list.add(instance);
                        }
                        in.endArray();
                        int size = list.size();
                        if (componentType.isPrimitive()) {
                            Object array = Array.newInstance(componentType, size);
                            for (int i = 0; i < size; i++) {
                                Array.set(array, i, list.get(i));
                            }
                            yield array;
                        } else {
                            @SuppressWarnings("unchecked")
                            E[] array = (E[]) Array.newInstance(componentType, size);
                            yield list.toArray(array);
                        }
                    }
                    case BEGIN_OBJECT, NUMBER, STRING, BOOLEAN -> {
                        E instance = elementTypeAdapter.read(in);
                        Object array = Array.newInstance(componentType, 1);
                        Array.set(array, 0, instance);
                        yield array;
                    }
                    default -> throw new JsonSyntaxException("Expected array or single value but was " + token);
                };
            }

            @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")
                    E value = (E) Array.get(array, i);
                    elementTypeAdapter.write(out, value);
                }
                out.endArray();
            }
        }
    }

    private record CollectionTypeAdapterFactory(int retainLength) implements TypeAdapterFactory {
        @Override
        public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
            if (!Collection.class.isAssignableFrom(typeToken.getRawType())) {
                return null;
            }
            TypeAdapter<?> elementTypeAdapter = new ObjectTypeAdapterFactory.Adapter(gson, retainLength);
            @SuppressWarnings({"unchecked", "rawtypes"})
            TypeAdapter<T> adapter = new Adapter(elementTypeAdapter, newConstructor().get(typeToken));
            return adapter;
        }

        static class Adapter<E> extends TypeAdapter<Collection<E>> {
            final TypeAdapter<E> elementTypeAdapter;
            final ObjectConstructor<? extends Collection<E>> constructor;

            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();
                return switch (token) {
                    case NULL -> {
                        in.nextNull();
                        yield null;
                    }
                    case BEGIN_ARRAY -> {
                        Collection<E> collect = constructor.construct();
                        in.beginArray();
                        while (in.hasNext()) {
                            E instance = elementTypeAdapter.read(in);
                            collect.add(instance);
                        }
                        in.endArray();
                        yield collect;
                    }
                    case BEGIN_OBJECT, NUMBER, STRING, BOOLEAN -> {
                        Collection<E> collect = constructor.construct();
                        E instance = elementTypeAdapter.read(in);
                        collect.add(instance);
                        yield collect;
                    }
                    default -> throw new JsonSyntaxException("Expected array or single value but was " + token);
                };
            }

            @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();
            }
        }
    }

    private static String handleValue(int retainLength, String value) {
        if (value == null) {
            return null;
        }
        if (retainLength > 0 && value.length() > retainLength) {
            return value.substring(0, retainLength) + "…";
        }
        return value;
    }

    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;
        }
    }

    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[] groups;

        DatetimePattern(String reg, int... groups) {
            this.p = Pattern.compile(reg);
            this.groups = groups;
        }

        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(groups[0])),
                            Integer.parseInt(m.group(groups[1])),
                            Integer.parseInt(m.group(groups[2])),
                            Integer.parseInt(m.group(groups[3])),
                            Integer.parseInt(m.group(groups[4])),
                            Integer.parseInt(m.group(groups[5]))
                    );
                    case DATE_TIME_6 -> LocalDateTime.of(
                            LocalDate.parse(m.group(groups[0]), DateTimeFormatter.ofPattern("yyMMdd")),
                            LocalTime.of(Integer.parseInt(m.group(groups[1])),
                                    Integer.parseInt(m.group(groups[2])),
                                    Integer.parseInt(m.group(groups[3])))
                    );
                    case DATE_TIME_8 -> LocalDateTime.of(
                            LocalDate.parse(m.group(groups[0]), DateTimeFormatter.ofPattern("yyyyMMdd")),
                            LocalTime.of(Integer.parseInt(m.group(groups[1])),
                                    Integer.parseInt(m.group(groups[2])),
                                    Integer.parseInt(m.group(groups[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;
        }
    }

    private static ConstructorConstructor newConstructor() {
        return new ConstructorConstructor(
                Collections.emptyMap(), // 空的自定义实例创建器
                false, // 关闭 JDK Unsafe
                Collections.emptyList() // 无反射过滤
        );
    }

}
三、两个辅助代码
public interface ApiDict<C, T extends Enum<T>> {
    C getCode();

    String getDesc();

    default String[] alternate() {
        return null;
    }
}
import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface IgnoreUnknownEnum {
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流沙QS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值