【问题标题】:How to (de)serialize an EnumMap using Jackson and default typing?如何使用 Jackson 和默认类型(反)序列化 EnumMap?
【发布时间】:2014-10-06 10:04:26
【问题描述】:

我有一个对象,其中一个属性是Map<MyEnum, Object>

由于我的应用程序很大,所以我启用了默认类型:

    ObjectMapper jsonMapper = new ObjectMapper()
        .enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_OBJECT)
        .configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);

总的来说,这相当不错。

但是,由于 Javascript 在使用对象作为哈希时不支持对象键,所以当我从 javascript 端将一些数据放入该映射时,对象会转换为字符串。

因此,我收到的 JSON 包含

     "MyClass": {
        "contextElements": {
          "userCredentials": {
            "UserCredentials": {
              "login": "admin",
              "password": "admin",
              }
            }
          }
        },

反序列化时,Jackson 失败并出现以下异常

java.lang.IllegalArgumentException: Invalid type id 'userCredentials' (for id type 'Id.class'): no such class found
    at org.codehaus.jackson.map.jsontype.impl.ClassNameIdResolver.typeFromId(ClassNameIdResolver.java:72)
    at org.codehaus.jackson.map.jsontype.impl.TypeDeserializerBase._findDeserializer(TypeDeserializerBase.java:61)
    at org.codehaus.jackson.map.jsontype.impl.AsWrapperTypeDeserializer._deserialize(AsWrapperTypeDeserializer.java:87)
    at org.codehaus.jackson.map.jsontype.impl.AsWrapperTypeDeserializer.deserializeTypedFromObject(AsWrapperTypeDeserializer.java:39)
    at org.codehaus.jackson.map.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:133)
    at org.codehaus.jackson.map.deser.SettableBeanProperty$MethodProperty.deserializeAndSet(SettableBeanProperty.java:221)

我非常了解:Jackson 不理解我班级中的 Map<MyEnum, Object> 声明,尽管 MyEnum 是最后一班,但希望添加一些类型元数据(嘿,也许这是一个错误?!)。

我可以做些什么来使代码正常工作?

我正在使用 Jackson 1.5.2

【问题讨论】:

  • 这里可能无关紧要,但您确实应该升级到更高的 Jackson 版本。如果您想继续使用 1.x,则 1.9(.13) 是最新的 - 自 1.5 以来已修复了许多错误。
  • @StaxMan 好吧,我已经考虑过了,但还没有遇到任何真正的杰克逊错误。但我可能会改变主意......

标签: java javascript map enums jackson


【解决方案1】:

好的,所以,问题正确地说明了:不能使用键不是字符串的 JSON 映射。因此,要在 javascript 中模拟 Java 地图,必须走更长的路,这通常涉及将地图转换为……其他东西。

我选择的是非常常见的数组:

地图如

{
    a:b,
    c:d,
}

然后会被翻译成数组

[
    [a,b],
    [c,d],
]

获得该结果所需的详细步骤是什么

配置自定义(反)序列化

这是通过在对象映射器中设置一个序列化工厂来获得的,如Jackson doc clearly explains

/**
 * Associates all maps with our custom serialization mechanism, which will transform them into arrays of arrays
 * @see MapAsArraySerializer
 * @return
 */
@Produces
public SerializerFactory createSerializerFactory() {
    CustomSerializerFactory customized = new CustomSerializerFactory();
    customized.addGenericMapping(Map.class, new MapAsArraySerializer());
    return customized;
}

public @Produces ObjectMapper createMapper() {
    ObjectMapper jsonMapper = new ObjectMapper();
    // ....
    // now configure serializer
    jsonMapper.setSerializerFactory(createSerializerFactory());
    // ....
    return jsonMapper;
}

这个过程看起来很简单,主要是因为序列化在序列化中提供了相当正确的多态特性,这对于反序列化来说并不是那么好。事实上,正如doc also states,反序列化需要添加显式的类映射,这些映射不以任何面向对象的方式使用(那里支持继承)

/**
 * Defines a deserializer for each and any used map class, as there is no inheritence support ind eserialization
 * @return
 */
@Produces
public DeserializerProvider createDeserializationProvider() {
    // Yeah it's not even a standard Jackson class, it'll be explained why later
    CustomDeserializerFactory factory = new MapAsArrayDeserializerFactory();
    List<Class<? extends Map>> classesToHandle = new LinkedList<>();
    classesToHandle.add(HashMap.class);
    classesToHandle.add(LinkedHashMap.class);
    classesToHandle.add(TreeMap.class);
    for(Class<? extends Map> c : classesToHandle) {
        addClassMappingFor(c, c, factory);
    }
    // and don't forget interfaces !
    addClassMappingFor(Map.class, HashMap.class, factory);
    addClassMappingFor(SortedMap.class, TreeMap.class, factory);
    return new StdDeserializerProvider(factory);
}

private void addClassMappingFor(final Class<? extends Map> detected, final Class<? extends Map> created, CustomDeserializerFactory factory) {
    factory.addSpecificMapping(detected, new MapAsArrayDeserializer() {

        @Override
        protected Map createNewMap() throws Exception {
            return created.newInstance();
        }
    });
}

// It's the same createMapper() method that was described upper
public @Produces ObjectMapper createMapper() {
    ObjectMapper jsonMapper = new ObjectMapper();
    // ....
    // and deserializer
    jsonMapper.setDeserializerProvider(createDeserializationProvider());
    return jsonMapper;
}

现在我们已经正确定义了(反)序列化是如何定制的,还是我们有?事实上,不:MapAsArrayDeserializerFactory 值得自己解释。

经过一些调试,我发现DeserializerProvider 在类没有反序列化器的情况下委托给DeserializerFactory,这很酷。但是,DeserializerFactory 根据 obejct 的“种类”创建反序列化器:如果它是一个集合,那么将使用一个 CollectionDeserializer(将数组读入一个集合)。如果是 Map,则使用 MapDeserializer。

不幸的是,此解决方案使用 JSON 流中给出的 java 类(尤其是在使用 polymorphic deserialization 时,这是我的情况)。因此,配置自定义反序列化没有任何效果,除非 CustomDeserializerFactory 是自定义的......像这样:

public class MapAsArrayDeserializerFactory extends CustomDeserializerFactory {
    @Override
    public JsonDeserializer<?> createMapDeserializer(DeserializationConfig config, MapType type, DeserializerProvider p) throws JsonMappingException {
        return createBeanDeserializer(config, type, p);
    }
}

是的,我将所有地图反序列化为 bean。但是现在,我所有的反序列化器都被正确调用了。

序列化

现在,序列化是一项相当简单的任务:

public class MapAsArraySerializer extends JsonSerializer<Map> {

    @SuppressWarnings("unchecked")
    private Set asListOfLists(Map<?, ?> value) {
        Set returned = new HashSet<>();
        for(Map.Entry e : value.entrySet()) {
            returned.add(Arrays.asList(e.getKey(), e.getValue()));
        }
        return returned;
    }

    @Override
    public void serialize(Map value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
        Collection entries = asListOfLists(value);
        jgen.writeObjectField("entries", entries);
    }

    @Override
    public void serializeWithType(Map value, JsonGenerator jgen, SerializerProvider provider, TypeSerializer typeSer) throws IOException,
                    JsonProcessingException {
        Collection entries = asListOfLists(value);
        typeSer.writeTypePrefixForObject(value, jgen);
        jgen.writeObjectField("entries", entries);
        typeSer.writeTypeSuffixForObject(value, jgen);
    }
}

反序列化

而且反序列化并不复杂:

public abstract class MapAsArrayDeserializer<Type extends Map> extends JsonDeserializer<Type> {

    protected Type newMap(Collection c, Type returned) {
        for(Object o : c) {
            if (o instanceof List) {
                List l = (List) o;
                if(l.size()==2) {
                    Iterator i = l.iterator();
                    returned.put(i.next(), i.next());
                }
            }
        }
        return returned;
    }

    protected abstract Type createNewMap() throws Exception;

    @Override
    public Type deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        if(jp.getCurrentToken().equals(JsonToken.START_OBJECT)) {
            JsonToken nameToken = jp.nextToken();
            String name = jp.getCurrentName();
            if(name.equals("entries")) {
                jp.nextToken();
                Collection entries = jp.readValueAs(Collection.class);
                JsonToken endMap = jp.nextToken();
                try {
                    return newMap(entries, createNewMap());
                } catch(Exception e) {
                    throw new IOException("unable to create receiver map", e);
                }
            } else {
                throw new IOException("expected \"entries\", but field name was \""+name+"\"");
            }
        } else {
            throw new IOException("not startying an object ? Not possible");
        }
    }

    @Override
    public Type deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException,
                    JsonProcessingException {
        Object value = typeDeserializer.deserializeTypedFromObject(jp, ctxt);
        return (Type) value;
    }
}

好吧,预计类是左抽象的,每个声明的子类型都创建正确的地图实例。

现在

现在它可以在 Java 端无缝运行(因为 Javascript 必须有一个与地图等效的对象才能读取这些数据。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-10-22
    • 2014-04-05
    • 1970-01-01
    • 1970-01-01
    • 2012-10-18
    • 2018-07-15
    • 2012-07-24
    相关资源
    最近更新 更多