Gson转化的类包含list变量时,如果直接使用new Gson().toJson(MyClass); 进行转化为Json 字符串内容,list对象的内容会为空。
1、解决方法一:
new Gson().toJsonTree(MyClass).toString();
2、解决方法二:
getGson().toJson(MyClass).toString();
本次修改也可以彻底解决 Gson 将 int 转换为 double 的问题。
Gson 中默认处理数值转换的类为 com.google.gson.internal.bind.ObjectTypeAdapter,我们只需要在内存中将其替换即可。
ObjectTypeAdapter 在创建的 Gson 对象中是不存在的,其使用了内部的工厂对象(FACTORY)动态创建。
而 FACTORY 会在 Gson 的构造函数中加入 factories 对象中。
最终,factories 对象通过 Collections 的方法变为不可变列表后保存为成功变量。
我们只需要把 Gson 实例中的 factories 对象内部的工厂对象取代即可。
2.1、实现过程
第一步,创建自定义的 ObjectTypeAdapter 对象,并实现其工厂方法。
public final class MapTypeAdapter extends TypeAdapter<Object> {
public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
@SuppressWarnings("unchecked")
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (type.getRawType() == Object.class) {
return (TypeAdapter<T>) new MapTypeAdapter(gson);
}
return null;
}
};
private final Gson gson;
private MapTypeAdapter(Gson gson) {
this.gson = gson;
}
@Override
public Object read(JsonReader in) throws IOException {
JsonToken token = in.peek();
//判断字符串的实际类型
switch (token) {
case BEGIN_ARRAY:
List<Object> list = new ArrayList<>();
in.beginArray();
while (in.hasNext()) {
list.add(read(in));
}
in.endArray();
return list;
case BEGIN_OBJECT:
Map<String, Object> map = new LinkedTreeMap<>();
in.beginObject();
while (in.hasNext()) {
map.put(in.nextName(), read(in));
}
in.endObject();
return map;
case STRING:
return in.nextString();
case NUMBER:
String s = in.nextString();
if (s.contains(".")) {
return Double.valueOf(s);
} else {
try {
return Integer.valueOf(s);
} catch (Exception e) {
return Long.valueOf(s);
}
}
case BOOLEAN:
return in.nextBoolean();
case NULL:
in.nextNull();
return null;
default:
throw new IllegalStateException();
}
}
@Override
public void write(JsonWriter out, Object value) throws IOException {
if (value == null) {
out.nullValue();
return;
}
//noinspection unchecked
TypeAdapter<Object> typeAdapter = (TypeAdapter<Object>) gson.getAdapter(value.getClass());
if (typeAdapter instanceof ObjectTypeAdapter) {
out.beginObject();
out.endObject();
return;
}
typeAdapter.write(out, value);
}
}
其实改动部分只有对 NUMBER 分支的细化,将原始数据是否包含小数点来作为其是否为整数与小数的依据。 我认为原始数据中字面值为 1.0 的数是小数,而字面值为 1 的数为整数。你也可以有自己的实现方式。
第二步,使用自定义工厂方法取代 Gson 实例中的工厂方法。
public Gson getGson() {
Gson gson = new GsonBuilder().create();
try {
Field factories = Gson.class.getDeclaredField("factories");
factories.setAccessible(true);
Object o = factories.get(gson);
Class<?>[] declaredClasses = Collections.class.getDeclaredClasses();
for (Class c : declaredClasses) {
if ("java.util.Collections$UnmodifiableList".equals(c.getName())) {
Field listField = c.getDeclaredField("list");
listField.setAccessible(true);
List<TypeAdapterFactory> list = (List<TypeAdapterFactory>) listField.get(o);
int i = list.indexOf(ObjectTypeAdapter.FACTORY);
list.set(i, MapTypeAdapter.FACTORY);
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return gson;
}
代码中,首先获得 gson 实例的 factories 属性,将属性设置为 public 访问权限,然后获得其属性 o。
因为在 gson 的创建过程中,factories 通过 Collections 的方法变为了不可修改对象,所以我们需要将其真实属性获得才能进行修改。
通过 Collections 的字节码对象获得其声明的所有内部类,遍历内部类获得 UnmodifiableList 类的字节码对象,最后获得其进行包装之前的真实列表数据 listField,并设置其访问权限为 public。
最终获得了真实的 factories 列表 list。
最后一步,得到 ObjectTypeAdapter.FACTORY 在列表中的位置,并用自定义的工厂对象取代之。需要注意的是,必须要将工厂对象同位置替换,因为解析优先级是和列表中的位置有关的。