package com.fasterxml.jackson.databind.deser.std; import java.io.IOException; import java.util.*; import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.deser.ContextualDeserializer; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; /** * Deserializer for {@link EnumMap} values. * <p> * Note: casting within this class is all messed up -- just could not figure out a way * to properly deal with recursive definition of "EnumMap<K extends Enum<K>, V> * * @author tsaloranta */ @SuppressWarnings({ "unchecked", "rawtypes" }) public class EnumMapDeserializer extends StdDeserializer<EnumMap<?,?>> implements ContextualDeserializer { protected final JavaType _mapType; protected final Class<?> _enumClass; protected JsonDeserializer<Enum<?>> _keyDeserializer; protected JsonDeserializer<Object> _valueDeserializer; /* /********************************************************** /* Life-cycle /********************************************************** */ public EnumMapDeserializer(JavaType mapType, JsonDeserializer<?> keyDeserializer, JsonDeserializer<?> valueDeser) { super(EnumMap.class); _mapType = mapType; _enumClass = mapType.getKeyType().getRawClass(); _keyDeserializer = (JsonDeserializer<Enum<?>>) keyDeserializer; _valueDeserializer = (JsonDeserializer<Object>) valueDeser; } public EnumMapDeserializer withResolved(JsonDeserializer<?> keyDeserializer, JsonDeserializer<?> valueDeserializer) { if ((keyDeserializer == _keyDeserializer) && valueDeserializer == _valueDeserializer) { return this; } return new EnumMapDeserializer(_mapType, keyDeserializer, valueDeserializer); } /** * Method called to finalize setup of this deserializer, * when it is known for which property deserializer is needed for. */ // @Override public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException { // note: instead of finding key deserializer, with enums we actually // work with regular deserializers (less code duplication; but not // quite as clean as it ought to be) JsonDeserializer<?> kd = _keyDeserializer; if (kd == null) { kd = ctxt.findContextualValueDeserializer(_mapType.getKeyType(), property); } JsonDeserializer<?> vd = _valueDeserializer; if (vd == null) { vd = ctxt.findContextualValueDeserializer(_mapType.getContentType(), property); } else { // if directly assigned, probably not yet contextual, so: if (vd instanceof ContextualDeserializer) { vd = ((ContextualDeserializer) vd).createContextual(ctxt, property); } } return withResolved(kd, vd); } /** * Because of costs associated with constructing Enum resolvers, * let's cache instances by default. */ @Override public boolean isCachable() { return true; } /* /********************************************************** /* Actual deserialization /********************************************************** */ @Override public EnumMap<?,?> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { // Ok: must point to START_OBJECT if (jp.getCurrentToken() != JsonToken.START_OBJECT) { throw ctxt.mappingException(EnumMap.class); } EnumMap result = constructMap(); while ((jp.nextToken()) != JsonToken.END_OBJECT) { Enum<?> key = _keyDeserializer.deserialize(jp, ctxt); if (key == null) { if (!ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) { String value = null; try { // bit ugly, but will have to do; works with usual scalars if (jp.hasCurrentToken()) { value = jp.getText(); } } catch (Exception e) { } throw ctxt.weirdStringException(value, _enumClass, "value not one of declared Enum instance names"); } /* 24-Mar-2012, tatu: Null won't work as a key anyway, so let's * just skip the entry then. But we must skip the value then. */ jp.nextToken(); jp.skipChildren(); continue; } // And then the value... JsonToken t = jp.nextToken(); /* note: MUST check for nulls separately: deserializers will * not handle them (and maybe fail or return bogus data) */ Object value = (t == JsonToken.VALUE_NULL) ? null : _valueDeserializer.deserialize(jp, ctxt); result.put(key, value); } return result; } @Override public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException, JsonProcessingException { // In future could check current token... for now this should be enough: return typeDeserializer.deserializeTypedFromObject(jp, ctxt); } private EnumMap<?,?> constructMap() { return new EnumMap(_enumClass); } }