【java8】如何为泛型类自定义jackson反序列化器JsonDeserializer

缘起:如何避免在Jackson的反序列化语句中显式使用new TypeReference语句

在Jackson对字符串的反序列化测试中,发现传入的参数new TypeReference<Map<String, Integer>>() {} 使得反序列化得到的map泛型的到了保障

        JsonMap<Object> dummy = JsonUtils.createJsonMap(Collections.singletonMap("0", "1"));
        String s = dummy.toJSONString();
        Map<String, Integer> integerMap = mapper.readValue(s, new TypeReference<Map<String, Integer>>() {
        });
        // 这里输出的integerMap 是 {"0":1}而不是{"0":"1"}

不难发现,上面这个式子将“1”解析为1,也就是说泛型限制起了作用,jackson底层在反序列化时接受到了在new TypeReference过程中传递的信息。

但是这个过程我们总是要new一个匿名类出来,这无疑增加了许多的代码工作量,于是有了这样的尝试:

    public static <T> JsonMap<T> createJsonMap(String jsonStr) throws JsonProcessingException {
        return createJsonMap(mapper.readValue(jsonStr, new TypeReference<Map<String, T>>() {
        }));
    }

    public static <T> JsonMap<T> createJsonMap(String jsonStr, Class<T> tClass) throws JsonProcessingException {
        return createJsonMap(mapper.readValue(jsonStr, new TypeReference<Map<String, T>>() {
        }));
    }

不论是试图通过返回值来传T的信息,还是试图通过传Class<T>来传递信息,最终这两个实现里T都会当Object使用,泛型信息在方法内部丢失,这是擦除导致的。

jackson的 objectmapper的readValue这个方法里头根本得不到泛型限制信息。同样的问题也存在于自定义序列化器的过程中:

package com.fidt.fate.utils;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;

import java.io.IOException;

public class JsonMapDeserializer<T> extends JsonDeserializer<JsonMap<T>> {
    public static final JsonMapDeserializer INSTANCE = new JsonMapDeserializer();

    public JsonMapDeserializer() {
    }
    @Override
    public <T> JsonMap<T> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
        JsonNode treeNode = jsonParser.getCodec().readTree(jsonParser);
        return JsonUtils.createJsonMap(treeNode.get("map").asText(), new TypeReference<Map<String,T>>(){});
    }
}

在上面这个代码中,我们为JsonMap这个泛型类定义了一个自定义序列化器,妄图通过类上的标识,
使得在解析的时候new TypeReference<Map<String,T>>(){}能够传下去实际运行中的类型,
例如我希望反序列化 JsonMap<Integer>,而里头自动识别为new TypeReference<Map<String,Integer>>(){},这也是痴人说梦。
当然,手动传class信息来保证反序列化时的类型,就这件事而言,可不可以呢?当然是可以的。

    /**
     * 该思路参考自:https://www.jianshu.com/p/b1ad2f1d3e3e
     */
    public static class ParameterizedTypeImpl implements ParameterizedType {

        private final Class raw;
        private final Type[] args;
        private final Type owner;

        public ParameterizedTypeImpl(Class raw, Type[] args, Type owner) {
            this.raw = raw;
            this.args = args != null ? args : new Type[0];
            this.owner = owner;
        }

        @Override
        public Type[] getActualTypeArguments() {
            return args;
        }

        @Override
        public Type getRawType() {
            return raw;
        }

        @Override
        public Type getOwnerType() {
            return owner;
        }
    }

    JsonMap(String jsonStr, Class<T> dataType) throws JsonProcessingException {
        ParameterizedType p = new ParameterizedTypeImpl(Map.class, new Type[]{String.class, dataType}, Map.class);
        this.map = createJsonMap(mapper.readValue(jsonStr, new TypeReference<Map<String, T>>() {
            @Override
            public Type getType() {
                return p;
            }
        }));
    }

根据对jackson底层代码的剖析,我们可以看到这个过程实际上还有更为简洁的方式。因为构建TypeReference的目的也是为了在底层得到一个JavaType,我们不妨直击本质:

    JsonMap(@NotBlank String jsonStr, @NotNull Class<T> dataType) throws JsonProcessingException {
        JavaType javaType = JsonUtils.mapper.getTypeFactory().constructParametricType(Map.class, String.class , dataType);
        this.map = JsonUtils.mapper.readValue(jsonStr, javaType);
    }

这里的class虽然因为泛型而失去了类型装载的信息,但是通过动态方法getRawClass或者getClassName能够得到动态信息,最终被保存在JavaType当中。

在写JsonMap的反序列化方法时,我们会思考对于泛型类Container<T>的泛型方法,如何不显式传入class<T>而在内部的逻辑处理中捕获class<T>类型,

例如通过在实例化时通过new JsonMap<Integer>这样的指定,我们在JsonMap这个类内部就能够得到Integer.class并顺利使用。

这是一个参考:

java获取泛型参数(T.class)_徐乙的博客-CSDN博客_获取泛型t的参数class

其核心部分在于这样的调用方式

    Type tp0 = ((ParameterizedType)subclassInstance.getClass().getGenericSuperclass()).getActualTypeArguments()[0];

其中理论上tp0就是你想要的Map里头装载的泛型内容。

但仔细看下来,这种方法仅在两种特定情况下有效:

  1. Sub extends Super<Integer>,则在父类方法里可以得到子类的泛型信息(Integer.Class)。
  2. Sub <T> extends Super<T>时,仅当用匿名类作为子类继承了父类时,才能在父类的方法中获得子类实例化时的泛型信息
    // 第一个情况无法得到泛型信息
    @Test
    public void testIfMapCanSaveGeneric() throws JsonProcessingException {
        Map<String, Integer> map = new HashMap<>();
        ParameterizedType superclass = ((ParameterizedType)map.getClass().getGenericSuperclass());
        Type tp0 = ((ParameterizedType)map.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        return;
    }

    // 匿名实现类可以
    @Test
    public void testIfAnonymousAbcCanSaveGeneric() throws JsonProcessingException {
        Map<String, Integer> tr0 = new AbstractMap<String, Integer>() {
            @Override
            public int size() {
                return 0;
            }

            @Override
            public boolean isEmpty() {
                return false;
            }

            @Override
            public boolean containsKey(Object key) {
                return false;
            }

            @Override
            public boolean containsValue(Object value) {
                return false;
            }

            @Override
            public Integer get(Object key) {
                return null;
            }

            @Nullable
            @Override
            public Integer put(String key, Integer value) {
                return null;
            }

            @Override
            public Integer remove(Object key) {
                return null;
            }

            @Override
            public void putAll(@NotNull Map<? extends String, ? extends Integer> m) {

            }

            @Override
            public void clear() {

            }

            @NotNull
            @Override
            public Set<String> keySet() {
                return null;
            }

            @NotNull
            @Override
            public Collection<Integer> values() {
                return null;
            }

            @NotNull
            @Override
            public Set<Entry<String, Integer>> entrySet() {
                return null;
            }
        };
        Type tp0 = ((ParameterizedType)tr0.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        return;
    }

因此Sub<T> extends Super<T>时,没有真正意义上的在Sub<T>内部得到运行期实际类型的方法。

反序列化器里的泛型反思:

既然不可能通过类上的标识符传递类型信息来为类内部所用,

那么Map又是如何实现在反序列化过程中保证了值受到泛型符号上实际传递的值的限制的呢?

jackson里头,map的反序列化过程在:

MapDeserializer.class

public class MapDeserializer extends ContainerDeserializerBase<Map<Object, Object>> 声明可以看到其实走的过程非常的尴尬

里头没有对<T>进行限制,而是主动传了Object下去

    public Map<Object, Object> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        if (this._propertyBasedCreator != null) {
            return this._deserializeUsingCreator(p, ctxt);
        } else if (this._delegateDeserializer != null) {
            return (Map)this._valueInstantiator.createUsingDelegate(ctxt, this._delegateDeserializer.deserialize(p, ctxt));
        } else if (!this._hasDefaultCreator) {
            return (Map)ctxt.handleMissingInstantiator(this.getMapClass(), this.getValueInstantiator(), p, "no default constructor found", new Object[0]);
        } else {
            switch(p.currentTokenId()) {
            case 1:
            case 2:
            case 5:
                Map<Object, Object> result = (Map)this._valueInstantiator.createUsingDefault(ctxt);
                if (this._standardStringKey) {
                    this._readAndBindStringKeyMap(p, ctxt, result);
                    return result;
                }

                this._readAndBind(p, ctxt, result);
                return result;
            case 3:
                return (Map)this._deserializeFromArray(p, ctxt);
            case 4:
            default:
                return (Map)ctxt.handleUnexpectedToken(this.getValueType(ctxt), p);
            case 6:
                return (Map)this._deserializeFromString(p, ctxt);
            }
        }
    }

Map<Object, Object> result 这样的返回类型更是表明了在回传过程中其实根本没有确定类型

实际上,最底层new Map的实例时,是没有使用泛型信息的,而在json逐字段解析值的时候,用上了JavaType带来的泛型信息。

在这个过程中,contex里头会定义一个ConcurrentHashMap,里面为各个JavaType规定了可能用到的解析器的Deserializer

当规定Value取Integer时,ConcurrentHashMap里头只对其提供了对Integer的解析器,因此反序列化时作为值的"1"也会被解析为1而不是字符串1。

泛型的类型限制是通过解析时的控制来保证的,而不是通过声明时的标识符<T>类型来保证的。

而主要的泛型信息,是通过DeserializationContext ctxt得到的。

这里参考一下
https://stackoverflow.com/questions/36159677/how-to-create-a-custom-deserializer-in-jackson-for-a-generic-type
这里面详细讲述了怎么通过ctxt得到泛型。

最终,综合上面的各种内容,最后可以得到最终的方案:

public final class JsonMapDeserializer extends JsonDeserializer<JsonMap<?>> implements ContextualDeserializer {

    public JsonMapDeserializer() {
    }

    private JavaType valueType;

    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
        if (property == null) { //  context is generic
            JsonMapDeserializer parser = new JsonMapDeserializer();
            parser.valueType = ctxt.getContextualType().containedType(0);
            return parser;
        } else {  //  property is generic
            JavaType wrapperType = property.getType();
            JavaType valueType = wrapperType.containedType(0);
            JsonMapDeserializer parser = new JsonMapDeserializer();
            parser.valueType = valueType;
            return parser;
        }
    }

    @Override
    public JsonMap<?> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
        JavaType javaType = JsonUtils.mapper.getTypeFactory().constructParametricType(Map.class, String.class, valueType.getRawClass());
        Map<String, ?> innerMap = deserializationContext.readValue(jsonParser, javaType);
        return new JsonMap<>(innerMap);
    }
}

结论一目了然:泛型类没有方法在不手动传泛型类型的情况下知道自己装了什么类型
于是就有各种丑陋的 new JsonMap<Integer> (string, Integer.class)
而jackson的反序列化实现也没有例外,他用的是上面的这种方式。


版权声明:本文为aa250071173原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。