解析JSON数组正常,却在获取数组元素时抛出了类型转换异常

关注“Java艺术”一起来充电吧!

1

BUG重现与原因分析

下面这段代码会抛出类型转换异常(ClassCastException),JVM给出的解释是:不能将Double类型对象转换String类型 (java.lang.Double connot be cast to java.lang.String)。

public class JsonUtilTest{
    @Test
    public void testToObjectArray() {
        String jsonArray = "[222.22,11.22,12.24]";
        List<String> list = JsonUtils.fromJsonArray(jsonArray, String.class);
        String item = list.get(0);
    }
}

根据异常栈信息得知类型转换异常发生在String item = list.get(0);这行代码。

可是解析都正常,为什么调用Listget方法却抛出类型转换异常呢?

这就不得不提泛型的"类型擦除"了。

List<String>经过类型擦除后变为裸类型List, 而List存储的元素类型变为Object类型,上面的代码编译后等价于:

public class JsonUtilTest{
    @Test
    public void testToObjectArray() {
        String jsonArray = "[222.22,11.22,12.24]";
        List list = JsonUtils.fromJsonArray(jsonArray, String.class);
        String item = (String)list.get(0);
    }
}

由此可以定位到问题就出在JsonUtilsfromJsonArray方法。fromJsonArrayjson解析为Double类型的数组了, 所以会抛出ClassCastException异常,Double类型对象强制转为String类型失败。

JsonUtils工具类是笔者为项目封装的一个Json解析工具类,目的是适配多个json解析框架。

例子中调用 JsonUtilsfromJsonArray方法可能是调用GsonParserfromJsonArray方法,也可能是调用 JacksonParserfromJsonArray方法,会根据项目中依赖了哪个json解析框架决定。

假设我们项目中使用的是Gson,那么调用JsonUtilsfromJsonArray方法最终会调用GsonParserfromJsonArray方法, GsonParser实现的fromJsonArray方法如下:

public class GsonParser implements JsonParser{
    
    @Override
    public <T> List<T> fromJsonArray(String jsonStr, Class<T> tClass) {
        GsonBuilder gsonBuilder = new GsonBuilder();
                //.registerTypeAdapter(Date.class, new DateTypeAdapter(null))
                //.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeTypeAdapter(null))
                //.addDeserializationExclusionStrategy(new GsonExclusionStrategy());
        return gsonBuilder.create().fromJson(jsonStr, new TypeToken<List<T>>(){}.getType());
    }

}

问题就出现在new TypeToken<List<T>>(){}.getType()这行,这行代码编译后会生成一个继承TypeToken的匿名内部类, 但由于TypeToken指定的参数化类型为List<T>,将getType()方法返回的Type对象传给Gson框架, Gson框架是不知道List<T>的参数化类型T是什么的。Gson框架只知道将json解析为一个List,但不知道 List的参数化类型T是什么,所以就根据json的信息将其转换为Double类型了。

我们来看个例子:

public class GsonTypeTokenTest{

        private <T> void getTypeToken2() {
            Type type = new TypeToken<List<T>>() {}.getType();
            System.out.println(type);
        }
    
        private void getTypeToken1() {
            Type type = new TypeToken<List<String>>() {}.getType();
            System.out.println(type);
        }
    
        @Test
        public void testTypeToken() {
            getTypeToken1();
            getTypeToken2();
        }

}

上面代码输出的结果如下:

java.util.List<java.lang.String>
java.util.List<T>

从结果可以看出,getTypeToken2方法我们无法获取到List的参数化类型T的实际类型,而getTypeToken1方法中指定了List的参数化类型为String, 因此能够获取到。


2

BUG修复

如果只是使用Gson解析框架,修改该BUG的办法很简单,将GsonParserfromJsonArray方法改为如下即可:

public <T> List<T> fromJsonArray(String jsonStr, TypeToken<List<T>> type){
    .....
    return gsonBuilder.create().fromJson(jsonStr, type.getType());
}

因为笔者写的JsonUtils工具类要适配多种解析框架,因此我们不能使用Gson框架的TypeToken, 也不能使用Jackson框架的TypeReference,而是抽象出一个中间类。

  • 1、自定义TypeReference

public abstract class TypeReference<T> {

    protected final Type _type;

    protected TypeReference() {
        Type superClass = this.getClass().getGenericSuperclass();
        if (superClass instanceof Class) {
            throw new IllegalArgumentException("TypeReference constructed without actual type information");
        } else {
            this._type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
        }
    }

    public Type getType() {
        return this._type;
    }

}
  • 2、修改JsonParser接口的fromJsonArray方法

public interface JsonParser {

    <T> String toJsonString(T obj, boolean serializeNulls, String pattern);

    <T> T fromJson(String jsonStr, Class<T> tClass);

    <T> List<T> fromJsonArray(String jsonStr, TypeReference<List<T>> typeReference);

}
  • 3、修改GsonParserJacksonParserfromJsonArray方法

GsonParserfromJsonArray方法修改后如下:

public class GsonParser implements JsonParser {

    @Override
    public <T> List<T> fromJsonArray(String jsonStr, TypeReference<List<T>> typeReference) {
        GsonBuilder gsonBuilder = new GsonBuilder();
                //.registerTypeAdapter(Date.class, new DateTypeAdapter(null))
                //.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeTypeAdapter(null))
                //.addDeserializationExclusionStrategy(new GsonExclusionStrategy());
        return gsonBuilder.create().fromJson(jsonStr, typeReference.getType());
    }

}

JacksonParserfromJsonArray方法修改后如下:

public class JacksonParser implements JsonParser {
    
    @Override
    public <T> List<T> fromJsonArray(String jsonStr, TypeReference<List<T>> typeReference) {
        ObjectMapper objectMapper = new ObjectMapper();
        // ......
        try {
            return objectMapper.readValue(jsonStr, new com.fasterxml.jackson.core.type.TypeReference() {
                @Override
                public Type getType() {
                    return typeReference.getType();
                }
            });
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

}
  • 4、修改JsonUtilsfromJsonArray方法

public class JsonUtils {

    private static JsonParser chooseJsonParser;

    static {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        try {
            classLoader.loadClass("com.google.gson.GsonBuilder");
            chooseJsonParser = new GsonParser();
        } catch (ClassNotFoundException e) {
            try {
                classLoader.loadClass("com.fasterxml.jackson.databind.ObjectMapper");
                chooseJsonParser = new JacksonParser();
            } catch (ClassNotFoundException ex) {
                throw new RuntimeException("未找到任务json包,请先在当前项目的依赖配置文件中加入 gson或fackson");
            }
        }
    }

    public static <T> String toJsonString(T obj) {
        return toJsonString(obj, false, null);
    }

    public static <T> String toJsonString(T obj, boolean serializeNulls) {
        return toJsonString(obj, serializeNulls, null);
    }

    public static <T> String toJsonString(T obj, boolean serializeNulls, String datePattern) {
        return chooseJsonParser.toJsonString(obj, serializeNulls, datePattern);
    }

    public static <T> T fromJson(String jsonStr, Class<T> tClass) {
        return chooseJsonParser.fromJson(jsonStr, tClass);
    }

    // 修改后的fromJsonArray方法
    public static <T> List<T> fromJsonArray(String jsonStr, TypeReference<List<T>> typeReference) {
        return chooseJsonParser.fromJsonArray(jsonStr, typeReference);
    }

}

公众号:Java艺术

扫码关注最新动态

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章