前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >解析JSON数组正常,却在获取数组元素时抛出了类型转换异常

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

作者头像
吴就业
发布2020-07-10 11:29:43
2K0
发布2020-07-10 11:29:43
举报
文章被收录于专栏:Java艺术Java艺术

1

BUG重现与原因分析

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

代码语言:javascript
复制
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类型,上面的代码编译后等价于:

代码语言:javascript
复制
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方法如下:

代码语言:javascript
复制
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类型了。

我们来看个例子:

代码语言:javascript
复制
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();
        }

}

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

代码语言:javascript
复制
java.util.List<java.lang.String>
java.util.List<T>

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

2

BUG修复

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

代码语言:javascript
复制
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
代码语言:javascript
复制
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方法
代码语言:javascript
复制
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方法修改后如下:

代码语言:javascript
复制
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方法修改后如下:

代码语言:javascript
复制
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方法
代码语言:javascript
复制
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);
    }

}
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-05-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java艺术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档