Bootstrap

解决jackson反序列化对象嵌套List失败的问题

问题描述

当用feign client远程调用时,返回的复杂对象反序列化报错。
错误信息:

Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.util.ArrayList` out of START_OBJECT token

复杂对象为:

PageModel<T>{
	int pageSize;
	int pageNum;
	int total;
	List<T> data;
}

解决方法

在对应List属性上增加注解配置

PageModel<T>{
	int pageSize;
	int pageNum;
	int total;
	@JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
	List<T> data;
}

调试过程

通过DEBUG,找到几个关键类。
ObjectMapper解析类报错位置:

_readMapAndClose(JsonParser p0, JavaType valueType) {
	...
	result = deser.deserialize(p, ctxt);
	...
}

CollectionDeserializer报错位置:

boolean canWrap = (_unwrapSingle == Boolean.TRUE) ||
            ((_unwrapSingle == null) &&
                    ctxt.isEnabled(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY));

重点在于CollectionDeserializer类,可以从名称看出是一个集合解码类,而这个标志位返回false,导致异常抛出,这就是最根本的原因。

CollectionDeserializer相关完整函数如下:

/**
     * Helper method called when current token is no START_ARRAY. Will either
     * throw an exception, or try to handle value as if member of implicit
     * array, depending on configuration.
     */
    @SuppressWarnings("unchecked")
    protected final Collection<Object> handleNonArray(JsonParser p, DeserializationContext ctxt,
            Collection<Object> result)
        throws IOException
    {
        // Implicit arrays from single values?
        boolean canWrap = (_unwrapSingle == Boolean.TRUE) ||
                ((_unwrapSingle == null) &&
                        ctxt.isEnabled(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY));
        if (!canWrap) {
            return (Collection<Object>) ctxt.handleUnexpectedToken(_containerType.getRawClass(), p);
        }
        JsonDeserializer<Object> valueDes = _valueDeserializer;
        final TypeDeserializer typeDeser = _valueTypeDeserializer;
        JsonToken t = p.getCurrentToken();

        Object value;

        try {
            if (t == JsonToken.VALUE_NULL) {
                // 03-Feb-2017, tatu: Hmmh. I wonder... let's try skipping here, too
                if (_skipNullValues) {
                    return result;
                }
                value = _nullProvider.getNullValue(ctxt);
            } else if (typeDeser == null) {
                value = valueDes.deserialize(p, ctxt);
            } else {
                value = valueDes.deserializeWithType(p, ctxt, typeDeser);
            }
        } catch (Exception e) {
            // note: pass Object.class, not Object[].class, as we need element type for error info
            throw JsonMappingException.wrapWithPath(e, Object.class, result.size());
        }
        result.add(value);
        return result;
    }

结合注释,可以看出,不是不能解析,而是根据配置判断不解析。因此有希望通过配置,执行下面的解析逻辑。

然后一顿找关键字:如何配置ACCEPT_SINGLE_VALUE_AS_ARRAY,终于找到了:https://www.codercto.com/a/51454.html

总结

问题虽然简单,但是很关键,导致feign没法传复杂对象,限制很大。通过源码调试的方式,找到问题,并确认是否有解决方法,最终解决掉问题(另一种可能性:确认没有解决办法)。

;