概述
1、现状及原因
目前网络上的资料中有两种解决这个问题的办法。
第一种:修改源码。
第二种:添加指定类型的自定义解密工具。
所有能找到的资料中,都对第一种进行了忽略,认为修改源码改动太大,转而使用了第二种方式。
不论是注册Map的解析器,还是使用自定义的Bean对象,本质都是指定类型解析,也就是第二种,完全无法解决以下问题。
gson.fromJson(s,new TypeToken<List<Map<String,Object>>>(){}.getType());
在这段代码中,你可以测试网络中找到的其他解决方式,你会发现所有设置都失效了。
原因是,当你使用 Class 对象作为解析类型时,Gson 会转入你自定义的解析器,而使用 TypeToken 时,Gson 无法将传入的参数与绑定的 TypeToken 对应,最终使用默认解析器。
2、最终解决办法
由上面的讲解过程,你会明白,第二种方法无法实现,只有修改源码才能达到最终目标。
大多数人没有意识到,修改源码不一定要重新编译,反射一样可以实现。
Gson 中默认处理数值转换的类为 com.google.gson.internal.bind.ObjectTypeAdapter,我们只需要在内存中将其替换即可。
ObjectTypeAdapter 在创建的 Gson 对象中是不存在的,其使用了内部的工厂对象(FACTORY)动态创建。
而 FACTORY 会在 Gson 的构造函数中加入 factories 对象中。
最终,factories 对象通过 Collections 的方法变为不可变列表后保存为成功变量。
找来找去,发现,我们只需要把 Gson 实例中的 factories 对象内部的工厂对象取代即可。
3、实现过程
第一步,创建自定义的 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 在列表中的位置,并用自定义的工厂对象取代之。需要注意的是,必须要将工厂对象同位置替换,因为解析优先级是和列表中的位置有关的。
大功告成。
4、测试
测试代码
@Test
public void fromJson2() {
String s = "{"Integer":123,"Float":123.0,"String":"abc","Boolean":true,"Double":123.0}";
Gson gson1 = new GsonBuilder().create();
Gson gson2 = getGson();
System.out.println("gson1: "+gson1.fromJson(s,new TypeToken<Map<String,Object>>(){}.getType()));
System.out.println("gson2: "+gson2.fromJson(s,new TypeToken<Map<String,Object>>(){}.getType()));
}
测试结果
gson1: {Integer=123.0, Float=123.0, String=abc, Boolean=true, Double=123.0}
gson2: {Integer=123, Float=123.0, String=abc, Boolean=true, Double=123.0}
Process finished with exit code 0
最后
以上就是真实羊为你收集整理的彻底解决 Gson 将 int 转换为 double 的问题1、现状及原因2、最终解决办法3、实现过程4、测试的全部内容,希望文章能够帮你解决彻底解决 Gson 将 int 转换为 double 的问题1、现状及原因2、最终解决办法3、实现过程4、测试所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复