Gson 增加额外校验功能

栏目: 服务器 · 发布时间: 5年前

内容简介:在数据Model实现类中过滤掉null值。例如设计一个List,只有非null值才能被添加进去,取出Item时就不需要判空了。可以封装一个ItemNonNullList,在ArrayList外面包一层,并特殊处理add/addAll相关方法,保证为null的Item不会被添加进去。不需要自定义TypeAdapter修改Gson解析过程。对于特定类型,全局替换Gson解析过程。例如String类型null解析为空串"";数组、List解析为空数组、空List而不是null等。
  1. 解析完数据,使用时经常需要对数据判空,以免后台返回数据格式有误导致客户端Crash,非常繁琐,且容易遗漏。
  2. 解析完数据除了判空,有时还要对一些字段有效性做判断(例如id有效)。能否在解析过程中直接过滤掉无效异常数据,用的时候不需要再判断呢?
  3. 接口返回的数据自动解析后,经常和最终需要的数据格式不完全一致,需要进行预处理。例如User的名称返回空串,则需要在客户端显示成“匿名”;再例如接口返回二维数组,而UI组件需要的是带有Type信息的一维数组来实现分组列表。

解析的数据免判空

修改数据实现类

在数据Model实现类中过滤掉null值。例如设计一个List,只有非null值才能被添加进去,取出Item时就不需要判空了。

可以封装一个ItemNonNullList,在ArrayList外面包一层,并特殊处理add/addAll相关方法,保证为null的Item不会被添加进去。不需要自定义TypeAdapter修改Gson解析过程。

public class ItemNonNullList<E> implements List<E>, RandomAccess, Cloneable, Serializable {

    @NotNull
    private final ArrayList<E> mList;

    public ItemNonNullList() {
        mList = new ArrayList<>();
    }

    @SuppressWarnings("unchecked")
    private ItemNonNullList(@NotNull ItemNonNullList<E> other) {
        mList = (ArrayList<E>) other.mList.clone();
    }

    @Override
    public int size() {
        return mList.size();
    }

    @Override
    public boolean isEmpty() {
        return mList.isEmpty();
    }

    @Override
    public boolean contains(Object o) {
        return mList.contains(o);
    }

    @NotNull
    @Override
    public Iterator<E> iterator() {
        return mList.iterator();
    }

    @NotNull
    @Override
    public Object[] toArray() {
        return mList.toArray();
    }

    @NotNull
    @Override
    public <T> T[] toArray(@NotNull T[] a) {
        return mList.toArray(a);
    }

    @Override
    public boolean add(E e) {
        return e != null && mList.add(e);
    }

    @Override
    public boolean remove(Object o) {
        return mList.remove(o);
    }

    @Override
    public boolean containsAll(@NotNull Collection<?> c) {
        return mList.containsAll(c);
    }

    @Override
    public boolean addAll(@NotNull Collection<? extends E> c) {
        mList.ensureCapacity(mList.size() + c.size());
        boolean result = false;
        for (E e : c) {
            result |= e != null && mList.add(e);
        }
        return result;
    }

    @SuppressWarnings("unchecked")
    @Override
    public boolean addAll(int index, @NotNull Collection<? extends E> c) {
        ArrayList<E> list = null;
        for (Object o : c) {
            if (o != null) {
                if (list == null) {
                    list = new ArrayList<>(c.size());
                }
                list.add((E) o);
            }
        }
        return list != null && mList.addAll(index, list);
    }

    @Override
    public boolean removeAll(@NotNull Collection<?> c) {
        return mList.removeAll(c);
    }

    @Override
    public boolean retainAll(@NotNull Collection<?> c) {
        return mList.retainAll(c);
    }

    @Override
    public void clear() {
        mList.clear();
    }

    @NotNull
    @Override
    public E get(int index) {
        return mList.get(index);
    }

    @Nullable
    @Override
    public E set(int index, E element) {
        return element == null ? null : mList.set(index, element);
    }

    @Override
    public void add(int index, E element) {
        if (element != null) {
            mList.add(index, element);
        }
    }

    @NotNull
    @Override
    public E remove(int index) {
        return mList.remove(index);
    }

    @Override
    public int indexOf(Object o) {
        return mList.indexOf(o);
    }

    @Override
    public int lastIndexOf(Object o) {
        return mList.lastIndexOf(o);
    }

    @NotNull
    @Override
    public ListIterator<E> listIterator() {
        return mList.listIterator();
    }

    @NotNull
    @Override
    public ListIterator<E> listIterator(int index) {
        return mList.listIterator(index);
    }

    @NotNull
    @Override
    public List<E> subList(int fromIndex, int toIndex) {
        return mList.subList(fromIndex, toIndex);
    }

    @SuppressWarnings("MethodDoesntCallSuperMethod")
    @Override
    public ItemNonNullList<E> clone() {
        return new ItemNonNullList<>(this);
    }
}
复制代码

全局替换解析过程

对于特定类型,全局替换Gson解析过程。例如String类型null解析为空串"";数组、List解析为空数组、空List而不是null等。

以String为例,可以把TypeAdapters.STRING复制出来,并修改其中代码如下,将null解析为空字符串,然后注册到Gson中,覆盖String默认的TypeAdapter。

修改前:

public static final TypeAdapter<String> STRING = new TypeAdapter<String>() {
    @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());
        }
        return in.nextString();
    }
    
    @Override
    public void write(JsonWriter out, String value) throws IOException {
        out.value(value);
    }
};
复制代码

修改后:

public static final TypeAdapter<String> STRING = new TypeAdapter<String>() {
    public String read(JsonReader reader) {
        try {
            if (reader.peek() == JsonToken.NULL) {
                reader.nextNull();
                return ""; // 原先是返回null,这里改为返回空字符串
            }
            return reader.nextString();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }
    public void write(JsonWriter writer, String value) {
        try {
            if (value == null) {
                writer.nullValue();
                return;
            }
            writer.value(value);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
};
复制代码

类似的,还可以覆盖Gson内置的TypeAdapters.INTEGER、CollectionTypeAdapterFactory、ArrayTypeAdapter等,实现Integer、Collection、数组等类型的免判空。

public static final TypeAdapter<Number> INTEGER = new TypeAdapter<Number>() {
    @Override
    public Number read(JsonReader in) throws IOException {
      if (in.peek() == JsonToken.NULL) {
        in.nextNull();
        return null;
      }
      try {
        return in.nextInt();
      } catch (NumberFormatException e) {
        throw new JsonSyntaxException(e);
      }
    }
    @Override
    public void write(JsonWriter out, Number value) throws IOException {
      out.value(value);
    }
};

/**
 * 自定义adapter,解决由于数据类型为Int,实际传过来的值为Float,导致解析出错的问题
 * 目前的解决方案为将所有Int类型当成Double解析,再强制转换为Int
 */
public static final TypeAdapter<Number> INTEGER = new TypeAdapter<Number>() {
    @Override
    public Number read(JsonReader in) throws IOException {
        if (in.peek() == JsonToken.NULL) {
            in.nextNull();
            return 0;
        }
        try {
            double i = in.nextDouble();//当成double来读取
            return (int) i;//强制转为int
        } catch (NumberFormatException e) {
            throw new JsonSyntaxException(e);
        }
    }

    @Override
    public void write(JsonWriter out, Number value) throws IOException {
        out.value(value);
    }
};
复制代码

数组部分略麻烦,由于gson用以数组解析的Adapter是不可重写的,只好拷贝出来,重新写了个类。注意上面的TypeAdapterRuntimeTypeWrapper类不是public的,所以也得拷贝出来写一个到本地。

/**
 * 自定义CollectionTypeAdapterFactory,使json内的数组为null时,返回空数组而不是null对象
 */
public final class CollectionTypeAdapterFactory implements TypeAdapterFactory {
    private final ConstructorConstructor constructorConstructor;

    public CollectionTypeAdapterFactory(ConstructorConstructor constructorConstructor) {
        this.constructorConstructor = constructorConstructor;
    }

    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 = $Gson$Types.getCollectionElementType(type, rawType);
        TypeAdapter<?> elementTypeAdapter = gson.getAdapter(TypeToken.get(elementType));
        ObjectConstructor<T> constructor = constructorConstructor.get(typeToken);

        @SuppressWarnings({"unchecked", "rawtypes"}) // create() doesn't define a type parameter
                TypeAdapter<T> result = new Adapter(gson, elementType, elementTypeAdapter, 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(Gson context, Type elementType,
                       TypeAdapter<E> elementTypeAdapter,
                       ObjectConstructor<? extends Collection<E>> constructor) {
            this.elementTypeAdapter =
                    new TypeAdapterRuntimeTypeWrapper<E>(context, elementTypeAdapter, elementType);
            this.constructor = constructor;
        }

        public Collection<E> read(JsonReader in) throws IOException {
            if (in.peek() == JsonToken.NULL) {
                in.nextNull();
                //这里做了修改,原先是返回null,改为返回空数组
                return constructor.construct();
            }

            Collection<E> collection = constructor.construct();
            in.beginArray();
            while (in.hasNext()) {
                E instance = elementTypeAdapter.read(in);
                collection.add(instance);
            }
            in.endArray();
            return collection;
        }

        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();
        }
    }
}
复制代码

进行注册:

static {
        GsonBuilder gsonBulder = new GsonBuilder();
        gsonBulder.registerTypeAdapter(String.class, STRING);   //所有String类型null替换为字符串“”
        gsonBulder.registerTypeAdapter(int.class, INTEGER); //int类型对float做兼容

        //通过反射获取instanceCreators属性
        try {
            Class builder = (Class) gsonBulder.getClass();
            Field f = builder.getDeclaredField("instanceCreators");
            f.setAccessible(true);
            Map<Type, InstanceCreator<?>> val = (Map<Type, InstanceCreator<?>>) f.get(gsonBulder);//得到此属性的值
            //注册数组的处理器
            gsonBulder.registerTypeAdapterFactory(new CollectionTypeAdapterFactory(new ConstructorConstructor(val)));
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        gson = gsonBulder.create();
    }
复制代码

定制化解析

全局替换的方式比较粗暴,对于复杂工程可能会引起预料不到的问题,可以结合注解等方式,对指定的元素进行特殊处理。

设计一个NonNullField字段,注解到自定义类的成员变量上,可以确保解析时该字段不会为null,会使用默认的实例来代替。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface NonNullField {
    Class<? extends InstanceCreator> value() default NonNullFieldConstructor.class;
}
复制代码

NonNullField注解通过NonNullFieldFactory实现如下。此Factory在创建TypeAdapter时,先搜索Class和父类中包含NonNullField注解的成员变量:

  1. 如果没找到,则返回null,由其他Factory创建TypeAdapter;
  2. 如果找到了,则在DelegateAdapter外包裹一层,DelegateAdapter解析完成后调用replaceNonNullFields方法,将NonNullField的null值替换为默认实例。
public class NonNullFieldFactory implements TypeAdapterFactory {

    private static final String ANNOTATION_NAME = NonNullField.class.getSimpleName();
    /**
     * 保存Type及其对应的NonNullField
     */
    private static final Map<Type, List<Field>> fieldMap = new ConcurrentHashMap<>();
    /**
     * InstanceCreator缓存
     */
    private static final Map<Class<? extends InstanceCreator>, InstanceCreator> creatorCache = new ConcurrentHashMap<>();

    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {

        List<Field> fields = findMatchedFields(typeToken);
        final Type type = typeToken.getType();

        // 如果找到了,则包裹一层Adapter
        if (fields != null && !fields.isEmpty()) {
            fieldMap.put(type, fields);

            final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, typeToken);
            log("create wrapper adapter, type = %s, find %d fields, delegate = %s", typeToken, fields.size(), delegate);
            return new TypeAdapter<T>() {
                @Override
                public void write(JsonWriter out, T value) throws IOException {
                    delegate.write(out, value);
                }

                @Override
                public T read(JsonReader in) throws IOException {
                    T t = delegate.read(in);
                    log("  finish read, data = %s, type = %s, delegate = %s", t, type, delegate);
                    replaceNonNullFields(t, typeToken);
                    return t;
                }
            };
        }
        return null;
    }

    private static void log(String msg, Object... args) {
        L.d(GsonUtils.TAG, "[NonNullFieldFactory]  " + msg, args);
    }

    /**
     * 是否需要搜索Type中的Field
     */
    @SuppressWarnings("RedundantIfStatement")
    private static boolean shouldSearch(Class clazz) {
        // 跳过不需要搜索的类
        if (clazz == null || clazz == Object.class || clazz.isPrimitive() || clazz.isEnum() || clazz.isArray()) {
            log("skip search class %s", clazz);
            return false;
        }
        // 跳过 Java 和Android系统中的类
        String packageName = clazz.getPackage().getName();
        if (packageName.startsWith("java") || packageName.startsWith("android")) {
            log("skip search class %s by package", clazz);
            return false;
        }
        // 只匹配特定的类、跳过其他第三方库的类……
        return true;
    }

    /**
     * 找到某个Type中的NonNullField,包括继承的
     */
    private static List<Field> findMatchedFields(TypeToken typeToken) {
        List<Field> list = null;
        Class raw = typeToken.getRawType();
        while (shouldSearch(raw)) {
            Field[] fields = raw.getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                if (field.getAnnotation(NonNullField.class) != null) {
                    if (list == null) {
                        list = new ArrayList<>();
                    }
                    list.add(field);
                }
            }
            // 解析父类
            typeToken = TypeToken.get($Gson$Types.resolve(typeToken.getType(), typeToken.getRawType(), raw.getGenericSuperclass()));
            raw = typeToken.getRawType();
        }
        return list == null ? Collections.EMPTY_LIST : list;
    }

    /**
     * 解析Field的Type,处理泛型参数
     *
     * @param typeToken Field所在类的Type
     * @param field     要解析的Field
     */
    private static Type resolveFieldType(TypeToken typeToken, Field field) {
        return $Gson$Types.resolve(typeToken.getType(), typeToken.getRawType(), field.getGenericType());
    }

    /**
     * 填充对象中的NonNullField
     */
    private static void replaceNonNullFields(Object o, TypeToken typeToken) {
        if (o == null) {
            return;
        }
        // 对于嵌套注解的情况(NonNullField对应类型中又有NonNullField),
        // 由于Gson会先解析内部数据,其TypeAdapter已经创建,此处map可以取到值
        List<Field> fields = fieldMap.get(typeToken.getType());
        if (fields == null || fields.isEmpty()) {
            return;
        }
        for (Field field : fields) {
            try {
                Object fieldValue = field.get(o);
                if (fieldValue == null) {
                    Object value = constructField(field, resolveFieldType(typeToken, field));
                    if (value == null) {
                        throw new RuntimeException(String.format("Create field %s for type %s failure",
                                field.getName(), typeToken.getType()));
                    }
                    field.set(o, value);
                    log("    --> set field '%s.%s' to '%s'", typeToken.getType().getTypeName(), field.getName(), value);
                }
            } catch (IllegalArgumentException | IllegalAccessException e) {
                L.e(e);
            }
        }
    }

    private static Object constructField(Field field, Type type) {
        NonNullField annotation = field.getAnnotation(NonNullField.class);
        Class<? extends InstanceCreator> creatorClass = annotation.value();
        InstanceCreator creator = getCreator(creatorClass);
        Object instance = creator.createInstance(type);
        replaceNonNullFields(instance, TypeToken.get(type));
        return instance;
    }

    private static synchronized InstanceCreator getCreator(Class<? extends InstanceCreator> creatorClass) {
        InstanceCreator creator = creatorCache.get(creatorClass);
        if (creator == null) {
            try {
                creator = creatorClass.newInstance();
                creatorCache.put(creatorClass, creator);
            } catch (InstantiationException | IllegalAccessException e) {
                throw new RuntimeException("InstanceCreator " + creatorClass + " create failure", e);
            }
        }
        return creator;
    }
}
复制代码

在replaceNonNullFields方法中,调用InstanceCreator创建实例,然后通过反射设置给Java对象。创建默认实例时,还会递归替换默认实例中嵌套的NonNullField,从而支持嵌套class。

InstanceCreator可由NonNullField注解指定,默认值为NonNullFieldConstructor。NonNullFieldConstructor中先判断如果是基本类型或数组,则直接创建,否则调用Gson内部的ConstructorConstructor工具类创建实例。

public class NonNullFieldConstructor implements InstanceCreator<Object> {
    /**
     * 保存基本类型及其默认值。基本类型默认值的内容不能被修改,因此可以重复利用,赋值给多个Field。
     */
    private static final Map<Class, Object> basicMap = new HashMap<>();
    /**
     * Gson的Constructor
     */
    private static final ConstructorConstructor constructor = new ConstructorConstructor(new HashMap<>());

    static {
        basicMap.put(Boolean.class, false);
        basicMap.put(Byte.class, (byte) 0);
        basicMap.put(Character.class, (char) 0);
        basicMap.put(Short.class, (short) 0);
        basicMap.put(Integer.class, 0);
        basicMap.put(Long.class, 0L);
        basicMap.put(Float.class, 0F);
        basicMap.put(Double.class, (double) 0);
        basicMap.put(String.class, "");
    }

    @Override
    public Object createInstance(Type type) {
        if (type instanceof Class) {
            Object o = basicMap.get(type);
            if (o != null) { // Integer.class
                return o;
            } else if (((Class) type).isArray()) { // String[].class
                return Array.newInstance($Gson$Types.getRawType(((Class) type).getComponentType()), 0);
            }
        } else if (type instanceof GenericArrayType) { // String[]
            return Array.newInstance($Gson$Types.getRawType(((GenericArrayType) type).getGenericComponentType()), 0);
        }
        // 其他类型使用constructor创建
        TypeToken<?> typeToken = TypeToken.get(type);
        return constructor.get(typeToken).construct();
    }
}
复制代码

注册:

Gson gson = new GsonBuilder().registerTypeAdapterFactory(new NonNullFieldFactory()).create();
复制代码

解析后校验数据

可以设计一个IDataValidateAction接口定义如下。自动解析完成后,如果对象实现了这个接口,Gson就会调用isDataValid校验数据,如果数据无效,则直接过滤掉这个对象,返回null。

public interface IDataValidateAction {
    boolean isDataValid();
}
复制代码
public static class User implements IAfterDeserializeAction {

    private long id;
    private String name;

    public long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    @Override
    public void doAfterDeserialize() {
        if (name == null || name.length() == 0) {
            name = "匿名";
        }
    }
}

public static class ValidUser extends User implements IDataValidateAction {

    @Override
    public boolean isDataValid() {
        // 如果id为0或负值,说明接口异常,视为无效数据,丢弃不用
        return getId() > 0;
    }
}
复制代码

只需要给Gson注册一个DeserializeActionAdapterFactory即可。这个Factory会判断如果Type实现了DeserializeAction相关接口,则在DelegateAdapter外包裹一层进行相应的处理;否则直接返回DelegateAdapter。

public class DeserializeActionFactory implements TypeAdapterFactory {

    public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {

        // 获取其他低优先级Factory创建的DelegateAdapter
        final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);

        // 如果type实现了DeserializeAction,则返回包裹后的TypeAdapter
        if (shouldWrap(type.getRawType())) {

            L.d(GsonUtils.TAG, "[DeserializeAction] create return new adapter, type = %s, delegate = %s", type, delegate);
            return new TypeAdapter<T>() {

                public void write(JsonWriter out, T value) throws IOException {
                    delegate.write(out, value);
                }

                public T read(JsonReader in) throws IOException {
                    T t = delegate.read(in);
                    L.d(GsonUtils.TAG, "[DeserializeAction] finish read, data = %s, type = %s, delegate = %s", t, type, delegate);
                    if (isInvalidData(t)) {
                        return null;
                    }
                    doAfterDeserialize(t);
                    return t;
                }
            };

        } else {
            L.d(GsonUtils.TAG, "[DeserializeAction] create return delegate, type = %s, delegate = %s", type, delegate);
            return delegate;
        }
    }

    public static boolean isInvalidData(Object t) {
        if (t instanceof IDataValidateAction) {
            if (!((IDataValidateAction) t).isDataValid()) {
                L.d(GsonUtils.TAG, "[DeserializeAction]     --> data is invalid");
                return true;
            }
        }
        return false;
    }

    public static <T> void doAfterDeserialize(Object t) {
        if (t instanceof IAfterDeserializeAction) {
            ((IAfterDeserializeAction) t).doAfterDeserialize();
            L.d(GsonUtils.TAG, "[DeserializeAction]     --> processed data = %s", t);
        }
    }

    private boolean shouldWrap(Class clazz) {
        return IAfterDeserializeAction.class.isAssignableFrom(clazz) ||
                IDataValidateAction.class.isAssignableFrom(clazz);
    }
}
复制代码

注册:

Gson gson = new GsonBuilder().registerTypeAdapterFactory(new DeserializeActionFactory()).create();
复制代码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

We Are the Nerds

We Are the Nerds

Christine Lagorio-Chafkin / Hachette Books / 2018-10-2 / USD 18.30

Reddit hails itself as "the front page of the Internet." It's the third most-visited website in the United States--and yet, millions of Americans have no idea what it is. We Are the Nerds is an eng......一起来看看 《We Are the Nerds》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

html转js在线工具
html转js在线工具

html转js在线工具