专栏首页Java艺术解析JSON数组正常,却在获取数组元素时抛出了类型转换异常

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

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艺术(javaskill),作者:wujiuye

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-05-27

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 我的开源项目|可扩展、可自由玩耍的miniexcel,不需要考虑OOM

    去年我也写过一篇文章介绍miniexcel,不过现在加了点新功能,并且现在可以通过依赖配置使用啦。

    wujiuye
  • 访问者模式在ASM框架中的使用

    访问者模式的定义是:封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。

    wujiuye
  • 类加载阶段之准备阶段

    准备阶段是为类中定义的静态变量分配内存并设置初始化值的阶段,这里的初始值通常情况下指的是对应类型的零值,比如int类型的零值为0。而给静态字段赋值通常是在编译器...

    wujiuye
  • 探秘Java并发模块:容器与工具类

    在谈及同步容器之前,必须要说说他们的老前辈同步容器类。同步容器类的代表就是Vector和HashTable,这是早期JDK中提供的类。此外Collections...

    lyb-geek
  • Java设计模式精讲(一):责任链模式

    这篇分享作为一个系列分享的第一篇,主要和大家一起学习一下java设计模式方面的基础,我们现在的安卓开发主要还是基于java语言,所以掌握java...

    open
  • SpringBoot整合Redis

    Spring Data Redis 为我们提供 RedisTemplate 和 StringRedisTemplate 两个模板进行数据操作,它们主要 的访问方...

    崔笑颜
  • Spark Streaming 中使用 zookeeper 保存 offset 并重用 Java版

    最近中使用spark Streaming +kafka,由于涉及到金额,所以需要保证at only one, 而网上关于java版的kafka offset...

    shengjk1
  • C#笔记:RC4算法实现

    超级大猪
  • Java面试题整理

    Java面向对象 19. super()与this()的区别? This():当前类的对象,super父类对象。 Super():在子类访问父类的成员和行...

    葆宁
  • 漫谈可视化Prefuse(三)---Prefuse API数据结构阅读有感

      前篇回顾:上篇《漫谈可视化Prefuse(二)---一分钟学会Prefuse》主要通过一个Prefuse的具体实例了解了构建一个Prefuse applic...

    JackieZheng

扫码关注云+社区

领取腾讯云代金券