package com.fasterxml.jackson.databind.ser.impl; import java.io.IOException; import java.util.Map; import java.util.Map.Entry; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; import com.fasterxml.jackson.databind.introspect.AnnotatedMember; import com.fasterxml.jackson.databind.jsontype.TypeSerializer; import com.fasterxml.jackson.databind.ser.ContainerSerializer; import com.fasterxml.jackson.databind.ser.ContextualSerializer; import com.fasterxml.jackson.databind.util.ArrayBuilders; import com.fasterxml.jackson.databind.util.BeanUtil; /** * @since 2.5 */ @SuppressWarnings("serial") @JacksonStdImpl public class MapEntrySerializer extends ContainerSerializer<Map.Entry<?,?>> implements ContextualSerializer { /** * @since 2.9 */ public final static Object MARKER_FOR_EMPTY = JsonInclude.Include.NON_EMPTY; /** * Map-valued property being serialized with this instance */ protected final BeanProperty _property; /** * Whether static types should be used for serialization of values * or not (if not, dynamic runtime type is used) */ protected final boolean _valueTypeIsStatic; protected final JavaType _entryType, _keyType, _valueType; /* /********************************************************** /* Serializers used /********************************************************** */ /** * Key serializer to use, if it can be statically determined */ protected JsonSerializer<Object> _keySerializer; /** * Value serializer to use, if it can be statically determined */ protected JsonSerializer<Object> _valueSerializer; /** * Type identifier serializer used for values, if any. */ protected final TypeSerializer _valueTypeSerializer; /** * If value type can not be statically determined, mapping from * runtime value types to serializers are stored in this object. */ protected PropertySerializerMap _dynamicValueSerializers; /* /********************************************************** /* Config settings, filtering /********************************************************** */ /** * Value that indicates suppression mechanism to use for <b>values contained</b>; * either "filter" (of which <code>equals()</code> is called), or marker * value of {@link #MARKER_FOR_EMPTY}, or null to indicate no filtering for * non-null values. * Note that inclusion value for Map instance itself is handled by caller (POJO * property that refers to the Map value). * * @since 2.5 */ protected final Object _suppressableValue; /** * Flag that indicates what to do with `null` values, distinct from * handling of {@link #_suppressableValue} * * @since 2.9 */ protected final boolean _suppressNulls; /* /********************************************************** /* Construction, initialization /********************************************************** */ public MapEntrySerializer(JavaType type, JavaType keyType, JavaType valueType, boolean staticTyping, TypeSerializer vts, BeanProperty property) { super(type); _entryType = type; _keyType = keyType; _valueType = valueType; _valueTypeIsStatic = staticTyping; _valueTypeSerializer = vts; _property = property; _dynamicValueSerializers = PropertySerializerMap.emptyForProperties(); _suppressableValue = null; _suppressNulls = false; } @Deprecated // since 2.9 protected MapEntrySerializer(MapEntrySerializer src, BeanProperty property, TypeSerializer vts, JsonSerializer<?> keySer, JsonSerializer<?> valueSer) { this(src, property, vts, keySer, valueSer, src._suppressableValue, src._suppressNulls); } @SuppressWarnings("unchecked") protected MapEntrySerializer(MapEntrySerializer src, BeanProperty property, TypeSerializer vts, JsonSerializer<?> keySer, JsonSerializer<?> valueSer, Object suppressableValue, boolean suppressNulls) { super(Map.class, false); _entryType = src._entryType; _keyType = src._keyType; _valueType = src._valueType; _valueTypeIsStatic = src._valueTypeIsStatic; _valueTypeSerializer = src._valueTypeSerializer; _keySerializer = (JsonSerializer<Object>) keySer; _valueSerializer = (JsonSerializer<Object>) valueSer; _dynamicValueSerializers = src._dynamicValueSerializers; _property = src._property; _suppressableValue = suppressableValue; _suppressNulls = suppressNulls; } @Override public ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) { return new MapEntrySerializer(this, _property, vts, _keySerializer, _valueSerializer, _suppressableValue, _suppressNulls); } /** * @since 2.9 */ public MapEntrySerializer withResolved(BeanProperty property, JsonSerializer<?> keySerializer, JsonSerializer<?> valueSerializer, Object suppressableValue, boolean suppressNulls) { return new MapEntrySerializer(this, property, _valueTypeSerializer, keySerializer, valueSerializer, suppressableValue, suppressNulls); } /** * @since 2.9 */ public MapEntrySerializer withContentInclusion(Object suppressableValue, boolean suppressNulls) { if ((_suppressableValue == suppressableValue) && (_suppressNulls == suppressNulls)) { return this; } return new MapEntrySerializer(this, _property, _valueTypeSerializer, _keySerializer, _valueSerializer, suppressableValue, suppressNulls); } @Override public JsonSerializer<?> createContextual(SerializerProvider provider, BeanProperty property) throws JsonMappingException { JsonSerializer<?> ser = null; JsonSerializer<?> keySer = null; final AnnotationIntrospector intr = provider.getAnnotationIntrospector(); final AnnotatedMember propertyAcc = (property == null) ? null : property.getMember(); // First: if we have a property, may have property-annotation overrides if (propertyAcc != null && intr != null) { Object serDef = intr.findKeySerializer(propertyAcc); if (serDef != null) { keySer = provider.serializerInstance(propertyAcc, serDef); } serDef = intr.findContentSerializer(propertyAcc); if (serDef != null) { ser = provider.serializerInstance(propertyAcc, serDef); } } if (ser == null) { ser = _valueSerializer; } // [databind#124]: May have a content converter ser = findContextualConvertingSerializer(provider, property, ser); if (ser == null) { // 30-Sep-2012, tatu: One more thing -- if explicit content type is annotated, // we can consider it a static case as well. // 20-Aug-2013, tatu: Need to avoid trying to access serializer for java.lang.Object tho if (_valueTypeIsStatic && !_valueType.isJavaLangObject()) { ser = provider.findValueSerializer(_valueType, property); } } if (keySer == null) { keySer = _keySerializer; } if (keySer == null) { keySer = provider.findKeySerializer(_keyType, property); } else { keySer = provider.handleSecondaryContextualization(keySer, property); } Object valueToSuppress = _suppressableValue; boolean suppressNulls = _suppressNulls; if (property != null) { JsonInclude.Value inclV = property.findPropertyInclusion(provider.getConfig(), null); if (inclV != null) { JsonInclude.Include incl = inclV.getContentInclusion(); if (incl != JsonInclude.Include.USE_DEFAULTS) { switch (incl) { case NON_DEFAULT: valueToSuppress = BeanUtil.getDefaultValue(_valueType); suppressNulls = true; if (valueToSuppress != null) { if (valueToSuppress.getClass().isArray()) { valueToSuppress = ArrayBuilders.getArrayComparator(valueToSuppress); } } break; case NON_ABSENT: suppressNulls = true; valueToSuppress = _valueType.isReferenceType() ? MARKER_FOR_EMPTY : null; break; case NON_EMPTY: suppressNulls = true; valueToSuppress = MARKER_FOR_EMPTY; break; case CUSTOM: valueToSuppress = provider.includeFilterInstance(null, inclV.getContentFilter()); if (valueToSuppress == null) { // is this legal? suppressNulls = true; } else { suppressNulls = provider.includeFilterSuppressNulls(valueToSuppress); } break; case NON_NULL: valueToSuppress = null; suppressNulls = true; break; case ALWAYS: // default default: valueToSuppress = null; // 30-Sep-2016, tatu: Should not need to check global flags here, // if inclusion forced to be ALWAYS suppressNulls = false; break; } } } } MapEntrySerializer mser = withResolved(property, keySer, ser, valueToSuppress, suppressNulls); // but note: no (full) filtering or sorting (unlike Maps) return mser; } /* /********************************************************** /* Accessors /********************************************************** */ @Override public JavaType getContentType() { return _valueType; } @Override public JsonSerializer<?> getContentSerializer() { return _valueSerializer; } @Override public boolean hasSingleElement(Map.Entry<?,?> value) { return true; } @Override public boolean isEmpty(SerializerProvider prov, Entry<?, ?> entry) { Object value = entry.getValue(); if (value == null) { return _suppressNulls; } if (_suppressableValue == null) { return false; } JsonSerializer<Object> valueSer = _valueSerializer; if (valueSer == null) { // Let's not worry about generic types here, actually; // unlikely to make any difference, but does add significant overhead Class<?> cc = value.getClass(); valueSer = _dynamicValueSerializers.serializerFor(cc.getClass()); if (valueSer == null) { try { valueSer = _findAndAddDynamic(_dynamicValueSerializers, cc, prov); } catch (JsonMappingException e) { // Ugh... can not just throw as-is, so... return false; } } } if (_suppressableValue == MARKER_FOR_EMPTY) { return valueSer.isEmpty(prov, value); } return _suppressableValue.equals(value); } /* /********************************************************** /* Serialization methods /********************************************************** */ @Override public void serialize(Map.Entry<?, ?> value, JsonGenerator gen, SerializerProvider provider) throws IOException { gen.writeStartObject(value); serializeDynamic(value, gen, provider); gen.writeEndObject(); } @Override public void serializeWithType(Map.Entry<?, ?> value, JsonGenerator gen, SerializerProvider provider, TypeSerializer typeSer) throws IOException { typeSer.writeTypePrefixForObject(value, gen); // [databind#631]: Assign current value, to be accessible by custom serializers gen.setCurrentValue(value); serializeDynamic(value, gen, provider); typeSer.writeTypeSuffixForObject(value, gen); } protected void serializeDynamic(Map.Entry<?, ?> value, JsonGenerator gen, SerializerProvider provider) throws IOException { final TypeSerializer vts = _valueTypeSerializer; final Object keyElem = value.getKey(); JsonSerializer<Object> keySerializer; if (keyElem == null) { keySerializer = provider.findNullKeySerializer(_keyType, _property); } else { keySerializer = _keySerializer; } // or by value; nulls often suppressed final Object valueElem = value.getValue(); JsonSerializer<Object> valueSer; // And then value if (valueElem == null) { if (_suppressNulls) { return; } valueSer = provider.getDefaultNullValueSerializer(); } else { valueSer = _valueSerializer; if (valueSer == null) { Class<?> cc = valueElem.getClass(); valueSer = _dynamicValueSerializers.serializerFor(cc); if (valueSer == null) { if (_valueType.hasGenericTypes()) { valueSer = _findAndAddDynamic(_dynamicValueSerializers, provider.constructSpecializedType(_valueType, cc), provider); } else { valueSer = _findAndAddDynamic(_dynamicValueSerializers, cc, provider); } } } // also may need to skip non-empty values: if (_suppressableValue != null) { if (_suppressableValue == MARKER_FOR_EMPTY) { if (valueSer.isEmpty(provider, valueElem)) { return; } } if (_suppressableValue.equals(valueElem)) { return; } } } keySerializer.serialize(keyElem, gen, provider); try { if (vts == null) { valueSer.serialize(valueElem, gen, provider); } else { valueSer.serializeWithType(valueElem, gen, provider, vts); } } catch (Exception e) { String keyDesc = ""+keyElem; wrapAndThrow(provider, e, value, keyDesc); } } /* /********************************************************** /* Internal helper methods /********************************************************** */ protected final JsonSerializer<Object> _findAndAddDynamic(PropertySerializerMap map, Class<?> type, SerializerProvider provider) throws JsonMappingException { PropertySerializerMap.SerializerAndMapResult result = map.findAndAddSecondarySerializer(type, provider, _property); if (map != result.map) { _dynamicValueSerializers = result.map; } return result.serializer; } protected final JsonSerializer<Object> _findAndAddDynamic(PropertySerializerMap map, JavaType type, SerializerProvider provider) throws JsonMappingException { PropertySerializerMap.SerializerAndMapResult result = map.findAndAddSecondarySerializer(type, provider, _property); if (map != result.map) { _dynamicValueSerializers = result.map; } return result.serializer; } }