Gson:自定义TypeAdapter
当前项目解析json用的工具是google的gson,原因嘛,因为有GsonFormat插件,可以直接把服务端传回的json字符串转成Bean对象。不过在实际使用中出现了以下两个问题:
- 传回的字符串或者数组为null,使用时若不加空指针判断,容易出现空指针异常。
- 测试用的数值为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()就行了。