2014-10-06 2 views
1

У меня есть объект, в котором одним из свойств является Map<MyEnum, Object>.Как (де) сериализовать EnumMap с помощью Jackson и по умолчанию?

Поскольку мое приложение довольно большой, я позволил печатать по умолчанию, как так:

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) - последнее - многие ошибки были исправлены с 1.5. – StaxMan

+0

@StaxMan хорошо, я думал об этом, но не имею но встретил любую реальную ошибку Джексона, но я могу передумать ... – Riduidel

ответ

0

OK, поэтому, вопрос гласит правильно: это не представляется возможным использовать карты в формате JSON, в котором ключи не являются строками. Как следствие, чтобы эмулировать Java-карту в javascript, нужно пройти более длинный путь, который обычно включает преобразование карты в ... что-то еще.

То, что я выбрал был вполне обычный массив массивов:

карта, такие как

{ 
    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 (чтобы прочитать массив в коллекции). Если это карта, то будет использоваться MapDeserializer.

К сожалению, это разрешение использует класс java, указанный в потоке JSON (особенно при использовании 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; 
    } 
} 

Ну, ожидал, что класс левый абстрактный t, каждый объявленный подтип создает правильную малую p instance.

А теперь

И теперь он работает плавно на Java стороны (вызвать Javascript должно иметь карту-эквивалентный объект для чтения этих ДАННЫХ.

Смежные вопросы