2014-10-06 40 views
1

我有一個對象,其中一個屬性是Map<MyEnum, Object>如何(德)使用傑克遜和默認打字序列化EnumMap?

我的應用程序是相當大的,我已經啓用默認打字像這樣:

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", 
       } 
      } 
      } 
     }, 

當反序列化,傑克遜失敗,出現以下異常

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) 

這一點我非常好理解:傑克遜不明白Map<MyEnum, Object>聲明在我的班級,雖然MyEnum是最後一堂課,但希望添加一些類型的元數據(嘿,也許這是一個錯誤?!)。

我該怎麼做才能讓代碼工作?

我使用的是傑克遜1.5.2

+1

它可能無所謂這裏,但你真的應該升級到一個後來的傑克遜版本,如果你想繼續使用1.x,1.9(.13)是最新版本 - 許多bug從1.5開始已經被修復了。 – StaxMan 2014-10-08 18:50:43

+0

@StaxMan好吧,我已經想過了,但沒有卻遇到了任何真正的傑克遜蟲,但我可能會改變主意...... – Riduidel 2014-10-09 08:29:50

回答

0

OK,所以,這個問題正確地指出的:這是不可能使用JSON的地圖,其中鍵不是字符串。因此,要在JavaScript中模擬Java Map,必須走更長的路徑,這通常會涉及將地圖轉換爲...其他東西。

我選擇什麼是陣列的很平常數組:

地圖如

{ 
    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(將該數組讀取到Collection中)。如果它是一個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); 
    } 
} 

啊,我反序列化所有地圖豆。但是現在,我所有的反序列化器都被正確地調用了。

序列化

現在,序列化是一個相當簡單的任務:

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; 
    } 
} 

好,預期類是左抽象的每個聲明的子類型創建正確的馬p實例。

現在

而現在它的工作原理上無縫Java端(造成的JavaScript必須有一個地圖,相當於對象讀取這些DATAS。