json 反序列化多层嵌套泛型类与 java 中的Type类型笔

作者: 光剑书架上的书 | 来源:发表于2019-07-10 03:12 被阅读15次

    json 反序列化多层嵌套泛型类与java中的Type类型笔记

            val typeRef = TypeRef()
            val result = JSON.parseObject(json, typeRef)
            return result
    

    其中,

    class TypeRef : TypeReference<ResultDTO<List<ProductDTO>>>()
    

    另外的一些写法:

    JSON.parseObject(json,new TypeReference<ResultDTO<List<Product>>>(){})
    gson.fromJson<ResultDTO<List<ProductDTO>>>(json, ResultDTO::class.java)
    

    另附

    How do I deserialize JSON into a List<SomeType> with Kotlin + Jackson [duplicate]

    What is the correct syntax to deserialize the following JSON:

    [ {
      "id" : "1",
      "name" : "Blues"
    }, {
      "id" : "0",
      "name" : "Rock"
    } ]
    

    I tried:

    //Works OK
    val dtos  = mapper.readValue(json, List::class.java)
    However I want:
    
    val dtos : List<GenreDTO>  = mapper.readValue(json, 
        List<GenreDTO>::class.java)
    

    The above syntax is not correct and gives: only classes are allowed on the left hand side of a class literal

    You should use the Jackson + Kotlin module or you will have other problems deserializing into Kotlin objects when you do no have a default constructor.

    Your first sample of the code:

    val dtos  = mapper.readValue(json, List::class.java)
    

    Is returning an inferred type of List<*> since you did not specify more type information, and it is actually a List<Map<String,Any>> which is not really "working OK" but is not producing any errors. It is unsafe, not typed.

    The second code should be:

    import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
    import com.fasterxml.jackson.module.kotlin.readValue
    
    val mapper = jacksonObjectMapper()
    // ...
    val genres: List<GenreDTO> = mapper.readValue(json)
    

    You do not need anything else on the right side of the assignment, the Kotlin module for Jackson will reify the generics and create the TypeReference for Jackson internally. Notice the readValue import, you need that or .* for the com.fasterxml.jackson.module.kotlin package to have the extension functions that do all of the magic.

    A slightly different alternative that also works:

    val genres = mapper.readValue<List<GenreDTO>>(json)
    

    There is no reason to NOT use the extension functions and the add-on module for Jackson. It is small and solves other issues that would require you to jump through hoops to make a default constructor, or use a bunch of annotations. With the module, your class can be normal Kotlin (optional to be data class):

    class GenreDTO(val id: Int, val name: String)
    

    Following code works well for me:

    import com.fasterxml.jackson.databind.ObjectMapper
    import com.fasterxml.jackson.module.kotlin.readValue
    import com.fasterxml.jackson.module.kotlin.registerKotlinModule
    
    val json = """[ {
      "id" : "1",
      "name" : "Blues"
    }, {
      "id" : "0",
      "name" : "Rock"
    } ]"""
    
    data class GenreDTO(val id: Int, val name: String)
    
    val mapper = ObjectMapper().registerKotlinModule()
    
    fun main(args: Array<String>) {
        val obj: List<GenreDTO> = mapper.readValue(json)
        obj.forEach {
            println(it)
        }
    }
    

    This work because of extension function defined inside jackson-kotlin-module (that used reifiedgenerics):

     public inline fun <reified T: Any> ObjectMapper.readValue(content: String): T = readValue(content, object: TypeReference<T>() {})
    

    Thanks @JaysonMinard for notify me about it.

    Output:

    GenreDTO(id=1, name=Blues)
    GenreDTO(id=0, name=Rock)
    

    参考文章

    https://www.cnblogs.com/liqipeng/p/9148545.html

    在使用springmvc时,我们通常会定义类似这样的通用类与前端进行交互,以便于前端可以做一些统一的处理:

    public class Result<T> {
        private int ret;
        private String msg;
        private T data;
        // 此处省略getter和setter方法
    }
    

    这样的类序列化为json后,js反序列化处理起来毫无压力。但是如果rest接口的消费端就是java呢,java泛型的类型擦除却容易引入一些障碍。

    一个反序列化的迭代

    先定义一个类,后面的例子会用到:

    public class Item {
        private String name;
        private String value;
        // 此处省略getter和setter方法
    }
    

    JSON数据:

    {
        "data":{
            "name":"username",
            "value":"root"
        },
        "msg":"Success",
        "ret":0
    }
    

    当拿到上面的数据时,我们想到其对应的类型是Result<Item>,所以得想办法将这个json数据反序列化为这个类型才行。

    v1

    JSONObject.parseObject(json, Result<Item>.class);,编译器就报错了Cannot select parameterized type

    v2

    JSONObject.parseObject(json, Result.class);,执行没问题。但是没有Item类型信息,fastjson不可能跟你心有灵犀一点通知道该把data转为Item类型,result.getData().getClass()结果是com.alibaba.fastjson.JSONObject,也算是个妥善处理吧。

    v3

    找了一下前人的经验,使用TypeReference来处理,JSONObject.parseObject(json, new TypeReference<Result<Item>>(){});,终于“完美”解决!

    v4

    有了v3的经验,以为找到了通用处理的捷径,遂封装了一个处理这种类型的工具方法:

    private static <T> Result<T> parseResultV1(String json) {
        return JSONObject.parseObject(json, new TypeReference<Result<T>>() {
        });
    }
    

    并且把采用v3的地方改用了此parseResult方法:

    Result<Item> result = parseResultV1(json);
    

    以为万事大吉,连测都没测试就把代码提交了。测都不测试,当然难以有好结果了:

    System.out.println(result.getData());
    // java.lang.ClassCastException: com.alibaba.fastjson.JSONObject cannot be cast to Item
    

    很显然parseResultV1把Item的类型信息丢掉了。

    {
        "data":"Hello,world!",
        "msg":"Success",
        "ret":0
    }
    

    试了一下Result<string style="margin: 0px; padding: 0px;">形式的,parseResultV1可以成功将其反序列化。推测(没有看fastjson具体实现)是fastjson刚好检测到data字段就是String类型,并将其赋值到data字段上了。仔细看parseObject并没有报错,而是在getData()时报错的,联系到java的泛型擦除,我们在用getData(),应该把data当作Object类型这么看:</string>

    String data = (String)result.getData();
    System.out.println(data);
    

    v5

    原来TypeReference的构造器是可以传入参数的,

    private static <T> Result<T> parseResultV2(String json, Class<T> clazz) {
        return JSONObject.parseObject(json, new TypeReference<Result<T>>(clazz) {
        });
    }
    

    这个可以真的可以完美反序列化Result<Item>了。

    v6

    后来发现parseResultV2无法处理类似Result<List<T>>,原来TypeReference无法处理嵌套的泛型(这里指的是类型参数未确定,而不是类似Result<List<Item>>类型参数已经确定)。借用Fastjson解析多级泛型的几种方式—使用class文件来解析多级泛型里的方法,新增加一个专门处理List类型的方法:

    private static <T> Result<List<T>> parseListResult(String json, Class<T> clazz) {
        return JSONObject.parseObject(json, buildType(Result.class, List.class, Item.class));
    }
    
    private static Type buildType(Type... types) {
        ParameterizedTypeImpl beforeType = null;
        if (types != null && types.length > 0) {
            for (int i = types.length - 1; i > 0; i--) {
                beforeType = new ParameterizedTypeImpl(new Type[]{beforeType == null ? types[i] : beforeType}, null, types[i - 1]);
            }
        }
        return beforeType;
    }
    

    或者根据这里只有两层,简单如下:

    private static <T> Result<List<T>> parseListResult(String json, Class<T> clazz) {
        ParameterizedTypeImpl inner = new ParameterizedTypeImpl(new Type[]{clazz}, null, List.class);
        ParameterizedTypeImpl outer = new ParameterizedTypeImpl(new Type[]{inner}, null, Result.class);
        return JSONObject.parseObject(json, outer);
    }
    

    v7

    todo: 上面两个方法已经可以满足现有需要,有时间再看看能否将两个方法统一为一个。

    com.alibaba.fastjson.TypeReference

    看看TypeReference的源码:

    protected TypeReference(Type... actualTypeArguments) {
        Class<?> thisClass = this.getClass();
        Type superClass = thisClass.getGenericSuperclass();
        ParameterizedType argType = (ParameterizedType)((ParameterizedType)superClass).getActualTypeArguments()[0];
        Type rawType = argType.getRawType();
        Type[] argTypes = argType.getActualTypeArguments();
        int actualIndex = 0;
    
        for(int i = 0; i < argTypes.length; ++i) {
            if (argTypes[i] instanceof TypeVariable) {
                argTypes[i] = actualTypeArguments[actualIndex++];
                if (actualIndex >= actualTypeArguments.length) {
                    break;
                }
            }
        }
    
        Type key = new ParameterizedTypeImpl(argTypes, thisClass, rawType);
        Type cachedType = (Type)classTypeCache.get(key);
        if (cachedType == null) {
            classTypeCache.putIfAbsent(key, key);
            cachedType = (Type)classTypeCache.get(key);
        }
    
        this.type = cachedType;
    }
    

    实际上它首先获取到了泛型的类型参数argTypes,然后遍历这些类型参数,如果遇到是TypeVariable类型的则用构造函数传入的Type将其替换,然后此处理后的argTypes基于ParameterizedTypeImpl构造出一个新的Type,这样的新的Type就可以具备我们期待的Type的各个泛型类型参数的信息了。所以fastjson就能够符合我们期望地反序列化出了Result<Item>

    正是由于这个处理逻辑,所以对于v6里的Result<List<T>>就无法处理了,它只能处理单层多类型参数的情况,而无法处理嵌套的泛型参数。

    没找到TypeReference的有参构造函数用法的比较正式的文档,但是基于源码的认识,我们应该这么使用TypeReference的有参构造函数:

    new TypeReference<Map<T1, T2>>(clazz1, clazz2){}
    new TypeReference<Xxx<T1, T2, T3>>(clazz1, clazz2, clazz3){}
    

    也就是构造器里的Type列表要与泛型类型参数一一对应。

    com.alibaba.fastjson.util.ParameterizedTypeImpl

    那至于ParameterizedTypeImpl怎么回事呢?

    import java.lang.reflect.ParameterizedType;
    // ...其他省略...
    
    public class ParameterizedTypeImpl implements ParameterizedType {
        public ParameterizedTypeImpl(Type[] actualTypeArguments, Type ownerType, Type rawType){
            this.actualTypeArguments = actualTypeArguments;
            this.ownerType = ownerType;
            this.rawType = rawType;
        }
        // ...其他省略...
    }
    

    以前也没了解过ParameterizedType,与它相关的还有

    Type
    所有已知子接口: 
    GenericArrayType, ParameterizedType, TypeVariable<D>, WildcardType 
    所有已知实现类: 
    Class
    

    先看看这次已经用到的ParameterizedType接口(下列注释是从jdk中文文档拷贝过来,不太好理解)

    public interface ParameterizedType extends Type {
        //返回表示此类型实际类型参数的 Type 对象的数组。
        //注意,在某些情况下,返回的数组为空。如果此类型表示嵌套在参数化类型中的非参数化类型,则会发生这种情况。 
        Type[] getActualTypeArguments();
        //返回 Type 对象,表示此类型是其成员之一的类型。
        Type getOwnerType();
        //返回 Type 对象,表示声明此类型的类或接口。
        Type getRawType();
    }
    

    结合ParameterizedTypeImpl(Type[] actualTypeArguments, Type ownerType, Type rawType)的例子来理解:
    new ParameterizedTypeImpl(new Type[]{clazz}, null, List.class)用于构造List<T>

    关于Type

    泛型是Java SE 1.5的新特性,Type也是1.5才有的。它是在java加入泛型之后为了扩充类型引入的。与Type相关的一些类或者接口来表示与Class类似但是又因泛型擦除丢失的一些类型信息。

    参考文章: https://www.jianshu.com/p/ca03c2fe36e3

    在Gson中:
    如果使用fromJson(String json, Class<T> classOfT)来反序列化Map的话,不会造成编译错误,返回的类型就会变化,Long类型变成了Double类型,使用的时候就会出现异常,例如在遍历Map的entrySet的时候就会出现异常。
    java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.Long

    因此:

    反序列化泛型对象如Map<K,V>等需要使用 fromJson(String json, Type typeOfT)

    一般对象使用fromJson(String json, Class<T> classOfT)
    在Jackson中:
    如果使用T readValue(String content, Class<T> valueType)来反序列化Map的话,返回的类型就会由Long类型变成了Integer类型。
    反序列化泛型对象如Map<K,V>等需要使用 T readValue(String content, TypeReference valueTypeRef)

    一般对象使用T readValue(String content, Class<T> valueType)


    Kotlin 开发者社区

    国内第一Kotlin 开发者社区公众号,主要分享、交流 Kotlin 编程语言、Spring Boot、Android、React.js/Node.js、函数式编程、编程思想等相关主题。

    Kotlin 开发者社区

    相关文章

      网友评论

        本文标题:json 反序列化多层嵌套泛型类与 java 中的Type类型笔

        本文链接:https://www.haomeiwen.com/subject/euwqkctx.html