【问题标题】:Can not find a (Map) Key deserializer for type [simple type, class ...]找不到类型 [简单类型,类 ...] 的(映射)密钥反序列化器
【发布时间】:2011-09-16 07:09:37
【问题描述】:

我有一个包含地图的域对象:

private Map<AutoHandlingSlotKey, LinkedHashSet<AutoFunction>> autoHandling;

当我序列化对象时,我得到了这个:

"autoHandling" : [ "java.util.HashMap", {
} ],

这个地图的键是一个自定义对象:

public class AutoHandlingSlotKey implements Serializable {
    private FunctionalArea slot; // ENUM
    private String returnView;   // ENUM

所以,我不确定如何纠正反序列化对象时不断出现的这个异常:

org.codehaus.jackson.map.JsonMappingException: Can not find a (Map) Key deserializer for type [simple type, class com.comcast.ivr.core.domain.AutoHandlingSlotKey]

如何解决这个问题?我确实没有有权访问要修改的域对象。

【问题讨论】:

标签: java json jackson deserialization


【解决方案1】:

这是一个通用的 Map 序列化器和反序列化器,它使用键值对列表,而不是 JSON 键值对。

[
    {
        "key": Object,
        "value": Object
    }...
]

package default;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.impl.MapEntrySerializer;
import com.fasterxml.jackson.databind.ser.std.MapSerializer;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;

/**
 * Simple Map Serializer<br>
 * <br>
 * Serializes the map as a list of key-value pairs, instead of as a list of JSON
 * key-value pairs (using the default serializer {@link MapSerializer}).
 *
 * @param <K> the type of keys maintained by the map
 * @param <V> the type of mapped values
 * @author Gitesh Agarwal (gagarwa)
 */
public class SimpleMapSerializer<K, V> extends StdSerializer<Map<K, V>> {

    private static final long serialVersionUID = 1L;

    /**
     * Default Constructor
     */
    public SimpleMapSerializer() {
        super(Map.class, true);
    }

    @Override
    public void serialize(Map<K, V> value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        List<SimpleEntry<K, V>> listValues = value.entrySet()
                .stream()
                .map(SimpleEntry::new)
                .collect(Collectors.toList());

        provider.defaultSerializeValue(listValues, gen);
    }

    /**
     * Simple Entry<br>
     * <br>
     * Intentionally does not implement the {@link Map.Entry} interface, so as not
     * to invoke the default serializer {@link MapEntrySerializer}.
     *
     * @author Gitesh Agarwal (gagarwa)
     */
    protected static class SimpleEntry<K, V> {

        private K key;

        private V value;

        /**
         * Default Constructor
         * 
         * @param entry the map entry
         */
        public SimpleEntry(Map.Entry<K, V> entry) {
            key = entry.getKey();
            value = entry.getValue();
        }

        /**
         * @return the key
         */
        public K getKey() {
            return key;
        }

        /**
         * @return the value
         */
        public V getValue() {
            return value;
        }

    }

}

如果您不想每次都定义自定义序列化程序。

package default;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.fasterxml.jackson.databind.ser.std.StdKeySerializers;
import com.fasterxml.jackson.databind.type.MapType;
import com.ibm.response.SimpleMapSerializer;

/**
 * Map Serializer Modifier
 *
 * @author Gitesh Agarwal (gagarwa)
 */
@Configuration
public class MapSerializerModifier extends BeanSerializerModifier {

    @Override
    @SuppressWarnings("rawtypes")
    public JsonSerializer<?> modifyMapSerializer(SerializationConfig config, MapType valueType,
            BeanDescription beanDesc, JsonSerializer<?> serializer) {

        JsonSerializer keySerializer = StdKeySerializers.getStdKeySerializer(config,
                valueType.getKeyType().getRawClass(), false);

        if (keySerializer == null)
            return new SimpleMapSerializer();

        return serializer;
    }

    /**
     * Simple Module Builder, including the map serializer modifier.
     * 
     * @return the module
     */
    @Bean
    public Module module() {
        SimpleModule module = new SimpleModule();
        module.setSerializerModifier(new MapSerializerModifier());
        return module;
    }

}


反序列化器有点棘手,因为您需要维护通用版本的类型信息。

package default;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.KeyDeserializer;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.type.MapType;
import com.ibm.request.action.SimpleMapDeserializer;

/**
 * Map Deserializer Modifier
 *
 * @author Gitesh Agarwal (gagarwa)
 */
@Configuration
public class MapDeserializerModifier extends BeanDeserializerModifier {

    @Override
    @SuppressWarnings("rawtypes")
    public JsonDeserializer<?> modifyMapDeserializer(DeserializationConfig config, MapType type,
            BeanDescription beanDesc, JsonDeserializer<?> deserializer) {

        KeyDeserializer keyDeserializer = StdKeyDeserializer.forType(type.getKeyType().getRawClass());

        if (keyDeserializer == null)
            return new SimpleMapDeserializer(type, config.getTypeFactory());

        return deserializer;
    }

    /**
     * Simple Module Builder, including the map deserializer modifier.
     * 
     * @return the module
     */
    @Bean
    public Module module() {
        SimpleModule module = new SimpleModule();
        module.setDeserializerModifier(new MapDeserializerModifier());
        return module;
    }

}

package default;

package com.ibm.request.action;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.MapDeserializer;
import com.fasterxml.jackson.databind.deser.std.MapEntryDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.databind.type.MapType;
import com.fasterxml.jackson.databind.type.TypeFactory;

/**
 * Simple Map Deserializer<br>
 * <br>
 * Deserializes the map from a list of key-value pairs, instead of from a list
 * of JSON key-value pairs (using the default deserializer
 * {@link MapDeserializer}).
 *
 * @param <K> the type of keys maintained by the map
 * @param <V> the type of mapped values
 * @author Gitesh Agarwal (gagarwa)
 */
public class SimpleMapDeserializer<K, V> extends StdDeserializer<Map<K, V>> {

    private static final long serialVersionUID = 1L;

    private final CollectionType type;

    /**
     * Default Constructor
     * 
     * @param type    the map type (key, value)
     * @param factory the type factory, to create the collection type
     */
    public SimpleMapDeserializer(MapType type, TypeFactory factory) {
        super(Map.class);
        this.type = factory.constructCollectionType(List.class,
                factory.constructParametricType(SimpleEntry.class, type.getKeyType(), type.getContentType()));
    }

    @Override
    public Map<K, V> deserialize(JsonParser p, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {

        List<SimpleEntry<K, V>> listValues = ctxt.readValue(p, type);
        HashMap<K, V> value = new HashMap<>();

        listValues.forEach(e -> value.put(e.key, e.value));
        return value;
    }

    /**
     * Simple Entry<br>
     * <br>
     * Intentionally does not implement the {@link Map.Entry} interface, so as not
     * to invoke the default deserializer {@link MapEntryDeserializer}.
     *
     * @author Gitesh Agarwal (gagarwa)
     */
    protected static class SimpleEntry<K, V> {

        private K key;

        private V value;

        /**
         * Default Constructor
         */
        public SimpleEntry() {

        }

        /**
         * @param key the key
         */
        public void setKey(K key) {
            this.key = key;
        }

        /**
         * @param value the value
         */
        public void setValue(V value) {
            this.value = value;
        }

    }

}

【讨论】:

    【解决方案2】:

    这是很久以前问的,是查找错误时的第一个谷歌结果,但接受的答案没有代码,可能会让杰克逊初学者(我)感到困惑。我最终找到了有帮助的 this answer

    因此,正如接受的答案中所述,实施和注册“密钥反序列化器”是要走的路。你可以这样做。

    SimpleModule simpleModule = new SimpleModule();
    simpleModule.addKeyDeserializer(YourClass.class, new YourClassKeyDeserializer());
    objectMapper.registerModule(simpleModule);
    

    对于班级,你所要做的就是:

    class YourClassKeyDeserializer extends KeyDeserializer
    {
        @Override
        public Object deserializeKey(final String key, final DeserializationContext ctxt ) throws IOException, JsonProcessingException
        {
            return null; // replace null with your logic
        }
    }
    

    就是这样!类上没有注释,地图的自定义反序列化器等没有。

    【讨论】:

    • 你知道如何使用通过 Spring 加载的 Resteasy 上下文注册模块吗?我不知道如何访问自动构建的objectMapper
    • @Fractaliste 这是另一个问题。但简而言之:您用自己的 bean 覆盖它。 @Bean fun myMapper(): ObjectMapper { ... }
    • 如果key是另一个存储为JSON的实体怎么办?我可以安全地使用在我的密钥反序列化器中创建的映射器吗?
    【解决方案3】:

    默认情况下,Jackson 尝试将 Java Maps 序列化为 JSON 对象(键/值对),因此 Map 键对象必须以某种方式序列化为 String;并且必须有匹配(和注册)的密钥反序列化器。默认配置仅支持一小组 JDK 类型(字符串、数字、枚举)。 所以 mapper 不知道如何获取 String 并从中创建 AutoHandlingSlotKey。 (事实上​​我很惊讶序列化程序没有因为同样的原因而失败)

    两种明显的解决方法是:

    • 实现并注册“密钥反序列化器”
    • 为地图实现并注册自定义反序列化程序。

    在您的情况下,前者可能更容易。您可能还想实现自定义密钥序列化程序,以确保密钥是正确格式的序列化程序。

    注册序列化器和反序列化器的最简单方法是Module interface,它是在 Jackson 1.7 中添加的(并在 1.8 中扩展以支持关键序列化器/反序列化器)。

    【讨论】:

    • 经过快速测试,我认为键是使用它们的 toString 方法序列化的,这可能通常不是您想要的。创建密钥序列化程序似乎是一个不错的选择。
    • 如果没有找到其他东西,那是后备。还有其他选择:例如,如果该类型具有公共的单字符串参数构造函数,则将使用该构造函数。或者使用@JsonCreator 注释的单字符串参数工厂方法。这将消除编写和注册自定义密钥反序列化程序的需要。
    • 很好 - 但你描述了反序列化器(我会尝试)。序列化器呢?在我的情况下,使用了 toString(用于 Scala 案例类)。除了编写我自己的序列化器之外,还有哪些其他选择?
    • 对,对于序列化程序,使用@JsonValue 应该可以工作,虽然我还没有测试过;在支持带有键(反)序列化器的注释方面存在一些差距。除此之外,自定义密钥序列化器是一个选项,可以全局注册或使用@JsonSerialize(keyUsing=MyKeySerializer.class 用于类或属性。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-19
    • 1970-01-01
    • 1970-01-01
    • 2010-10-18
    • 2013-05-29
    • 1970-01-01
    相关资源
    最近更新 更多