• Gson:自定义TypeAdapter


    当前项目解析json用的工具是google的gson,原因嘛,因为有GsonFormat插件,可以直接把服务端传回的json字符串转成Bean对象。不过在实际使用中出现了以下两个问题:

    1. 传回的字符串或者数组为null,使用时若不加空指针判断,容易出现空指针异常。
    2. 测试用的数值为0,结果用GsonFomat生成的对象默认为int类型,但可能该字段的真实类型为float,所以之后收到类型为float的数据时,就可能导致解析出错。

    针对上述问题,都可以通过自定义TypeAdapter解决。

    阅读过Gson的源码后发现,Gson的数据解析都是委托到各个TypeAdapter内进行处理的。在Gson的构造函数内会预先加载一部分TypeAdapter,包含String、int、long、double等类型,都存放在factories中,如下:

    Gson(final Excluder excluder, final FieldNamingStrategy fieldNamingPolicy,
          final Map<Type, InstanceCreator<?>> instanceCreators, boolean serializeNulls,
          boolean complexMapKeySerialization, boolean generateNonExecutableGson, boolean htmlSafe,
          boolean prettyPrinting, boolean serializeSpecialFloatingPointValues,
          LongSerializationPolicy longSerializationPolicy,
          List<TypeAdapterFactory> typeAdapterFactories) {
        this.constructorConstructor = new ConstructorConstructor(instanceCreators);
        this.serializeNulls = serializeNulls;
        this.generateNonExecutableJson = generateNonExecutableGson;
        this.htmlSafe = htmlSafe;
        this.prettyPrinting = prettyPrinting;
    
        List<TypeAdapterFactory> factories = new ArrayList<TypeAdapterFactory>();
    
        // built-in type adapters that cannot be overridden
        factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
        factories.add(ObjectTypeAdapter.FACTORY);
    
        // the excluder must precede all adapters that handle user-defined types
        factories.add(excluder);
    
        // user's type adapters
        factories.addAll(typeAdapterFactories);
    
        // type adapters for basic platform types
        factories.add(TypeAdapters.STRING_FACTORY);
        factories.add(TypeAdapters.INTEGER_FACTORY);
        factories.add(TypeAdapters.BOOLEAN_FACTORY);
        factories.add(TypeAdapters.BYTE_FACTORY);
        factories.add(TypeAdapters.SHORT_FACTORY);
        factories.add(TypeAdapters.newFactory(long.class, Long.class,
                longAdapter(longSerializationPolicy)));
        factories.add(TypeAdapters.newFactory(double.class, Double.class,
                doubleAdapter(serializeSpecialFloatingPointValues)));
        factories.add(TypeAdapters.newFactory(float.class, Float.class,
                floatAdapter(serializeSpecialFloatingPointValues)));
        factories.add(TypeAdapters.NUMBER_FACTORY);
        factories.add(TypeAdapters.CHARACTER_FACTORY);
        factories.add(TypeAdapters.STRING_BUILDER_FACTORY);
        factories.add(TypeAdapters.STRING_BUFFER_FACTORY);
        factories.add(TypeAdapters.newFactory(BigDecimal.class, TypeAdapters.BIG_DECIMAL));
        factories.add(TypeAdapters.newFactory(BigInteger.class, TypeAdapters.BIG_INTEGER));
        factories.add(TypeAdapters.URL_FACTORY);
        factories.add(TypeAdapters.URI_FACTORY);
        factories.add(TypeAdapters.UUID_FACTORY);
        factories.add(TypeAdapters.LOCALE_FACTORY);
        factories.add(TypeAdapters.INET_ADDRESS_FACTORY);
        factories.add(TypeAdapters.BIT_SET_FACTORY);
        factories.add(DateTypeAdapter.FACTORY);
        factories.add(TypeAdapters.CALENDAR_FACTORY);
        factories.add(TimeTypeAdapter.FACTORY);
        factories.add(SqlDateTypeAdapter.FACTORY);
        factories.add(TypeAdapters.TIMESTAMP_FACTORY);
        factories.add(ArrayTypeAdapter.FACTORY);
        factories.add(TypeAdapters.ENUM_FACTORY);
        factories.add(TypeAdapters.CLASS_FACTORY);
    
        // type adapters for composite and user-defined types
        factories.add(new CollectionTypeAdapterFactory(constructorConstructor));
        factories.add(new MapTypeAdapterFactory(constructorConstructor, complexMapKeySerialization));
        factories.add(new ReflectiveTypeAdapterFactory(
            constructorConstructor, fieldNamingPolicy, excluder));
    
        this.factories = Collections.unmodifiableList(factories);
      }

    我们可以自定义TypeAdapter,将其放入facotries中,并且gson在解析json时使用对应的TypeAdapter来的,而我们手动添加的TypeAdapter会优先于预设的TypeAdapter被使用。有兴趣的可以看看源码,还是比较简单的。

    为了解决问题1,我们先定义一个StringAdapter,代码如下:

     /**
         * 自定义TypeAdapter ,null对象将被解析成空字符串
         */
        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();
                }
            }
        };

    这样在读取到null节点时,就自动变成返回空字符串了。

    接下去定义一个Int类型的TypeAdapter:

    /**
         * 自定义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是不可重写的,只好拷贝出来,重新写了个类,如下

    import com.google.gson.Gson;
    import com.google.gson.TypeAdapter;
    import com.google.gson.TypeAdapterFactory;
    import com.google.gson.internal.$Gson$Types;
    import com.google.gson.internal.ConstructorConstructor;
    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;
    
    /**
     * 自定义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();
            }
        }
    }

    注意上面的TypeAdapterRuntimeTypeWrapper类不是public的,所以也得拷贝出来写一个到本地。

    接下去是使用部分:

    
    import com.google.gson.Gson;
    import com.google.gson.GsonBuilder;
    import com.google.gson.InstanceCreator;
    import com.google.gson.JsonElement;
    import com.google.gson.JsonSerializationContext;
    import com.google.gson.JsonSerializer;
    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.reflect.TypeToken;
    import com.google.gson.stream.JsonReader;
    import com.google.gson.stream.JsonToken;
    import com.google.gson.stream.JsonWriter;
    
    import org.json.JSONArray;
    import org.json.JSONException;
    import org.json.JSONObject;
    
    import java.io.IOException;
    import java.lang.reflect.Field;
    import java.lang.reflect.Type;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    import java.util.Objects;
    
    /**
     * Created by linjizong on 15/7/20.
     */
    public class GsonUtils {
    
        public static Gson gson;
    
    
        /**
         * 自定义TypeAdapter ,null对象将被解析成空字符串
         */
        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();
                }
            }
        };
    
        /**
         * 自定义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();
                    return (int) i;
                } catch (NumberFormatException e) {
                    throw new JsonSyntaxException(e);
                }
            }
    
            @Override
            public void write(JsonWriter out, Number value) throws IOException {
                out.value(value);
            }
        };
    
        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();
        }
    
     /**
         * Json字符串 转为指定对象
         *
         * @param json json字符串
         * @param type 对象类型
         * @param <T>  对象类型
         * @return
         * @throws JsonSyntaxException
         */
        public static <T> T toBean(String json, Class<T> type) throws JsonSyntaxException {
            T obj = gson.fromJson(json, type);
            return obj;
        }
    
    }

    通过GsonBuilder的registerTypeAdapter方法可以直接注册TypeAdapter。而CollectionTypeAdapterFactory方法需要使用到GsonBuidler的instanceCreators字段,只好通过反射来获取了。接下去只要使用GsonUtils.toBean()就行了。

  • 相关阅读:
    使用HtmlAgilityPack将HtmlTable填入DataTable
    用EXCEL的VBA将PHPCMS的备份文件转换成HTML的一次尝试
    从微观角度看到宏观世界
    洛克菲特:如何管好你的钱包
    论永生_基因编辑
    如何隐藏自己的应用程序在服务器上不被发现?
    检视阅读
    改变了我对英语理解的语法课
    Rick And Morty使命必达与毁灭--------英语笔记
    文件太大,网速太慢,如何高效的传递到服务器上运行
  • 原文地址:https://www.cnblogs.com/linjzong/p/5201565.html
Copyright © 2020-2023  润新知