blob: e30987cbbe3c7227aeb67d4ab1d36063fa145923 [file] [log] [blame]
package com.fasterxml.jackson.databind.ser.std;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonMapFormatVisitor;
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.ser.PropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.util.ArrayBuilders;
import com.fasterxml.jackson.databind.util.BeanUtil;
import com.fasterxml.jackson.databind.util.ClassUtil;
/**
* Standard serializer implementation for serializing {link java.util.Map} types.
*<p>
* Note: about the only configurable setting currently is ability to filter out
* entries with specified names.
*/
@JacksonStdImpl
public class MapSerializer
extends ContainerSerializer<Map<?,?>>
implements ContextualSerializer
{
private static final long serialVersionUID = 1L;
protected final static JavaType UNSPECIFIED_TYPE = TypeFactory.unknownType();
/**
* @since 2.9
*/
public final static Object MARKER_FOR_EMPTY = JsonInclude.Include.NON_EMPTY;
/*
/**********************************************************
/* Basic information about referring property, type
/**********************************************************
*/
/**
* 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;
/**
* Declared type of keys
*/
protected final JavaType _keyType;
/**
* Declared type of contained values
*/
protected final JavaType _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
/**********************************************************
*/
/**
* Set of entries to omit during serialization, if any
*/
protected final Set<String> _ignoredEntries;
/**
* Id of the property filter to use, if any; null if none.
*
* @since 2.3
*/
protected final Object _filterId;
/**
* 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;
/*
/**********************************************************
/* Config settings, other
/**********************************************************
*/
/**
* Flag set if output is forced to be sorted by keys (usually due
* to annotation).
*
* @since 2.4
*/
protected final boolean _sortKeys;
/*
/**********************************************************
/* Life-cycle
/**********************************************************
*/
/**
* @since 2.5
*/
@SuppressWarnings("unchecked")
protected MapSerializer(Set<String> ignoredEntries,
JavaType keyType, JavaType valueType, boolean valueTypeIsStatic,
TypeSerializer vts,
JsonSerializer<?> keySerializer, JsonSerializer<?> valueSerializer)
{
super(Map.class, false);
_ignoredEntries = ((ignoredEntries == null) || ignoredEntries.isEmpty())
? null : ignoredEntries;
_keyType = keyType;
_valueType = valueType;
_valueTypeIsStatic = valueTypeIsStatic;
_valueTypeSerializer = vts;
_keySerializer = (JsonSerializer<Object>) keySerializer;
_valueSerializer = (JsonSerializer<Object>) valueSerializer;
_dynamicValueSerializers = PropertySerializerMap.emptyForProperties();
_property = null;
_filterId = null;
_sortKeys = false;
_suppressableValue = null;
_suppressNulls = false;
}
@SuppressWarnings("unchecked")
protected MapSerializer(MapSerializer src, BeanProperty property,
JsonSerializer<?> keySerializer, JsonSerializer<?> valueSerializer,
Set<String> ignoredEntries)
{
super(Map.class, false);
_ignoredEntries = ((ignoredEntries == null) || ignoredEntries.isEmpty())
? null : ignoredEntries;
_keyType = src._keyType;
_valueType = src._valueType;
_valueTypeIsStatic = src._valueTypeIsStatic;
_valueTypeSerializer = src._valueTypeSerializer;
_keySerializer = (JsonSerializer<Object>) keySerializer;
_valueSerializer = (JsonSerializer<Object>) valueSerializer;
_dynamicValueSerializers = src._dynamicValueSerializers;
_property = property;
_filterId = src._filterId;
_sortKeys = src._sortKeys;
_suppressableValue = src._suppressableValue;
_suppressNulls = src._suppressNulls;
}
/**
* @since 2.9
*/
protected MapSerializer(MapSerializer src, TypeSerializer vts,
Object suppressableValue, boolean suppressNulls)
{
super(Map.class, false);
_ignoredEntries = src._ignoredEntries;
_keyType = src._keyType;
_valueType = src._valueType;
_valueTypeIsStatic = src._valueTypeIsStatic;
_valueTypeSerializer = vts;
_keySerializer = src._keySerializer;
_valueSerializer = src._valueSerializer;
_dynamicValueSerializers = src._dynamicValueSerializers;
_property = src._property;
_filterId = src._filterId;
_sortKeys = src._sortKeys;
_suppressableValue = suppressableValue;
_suppressNulls = suppressNulls;
}
protected MapSerializer(MapSerializer src, Object filterId, boolean sortKeys)
{
super(Map.class, false);
_ignoredEntries = src._ignoredEntries;
_keyType = src._keyType;
_valueType = src._valueType;
_valueTypeIsStatic = src._valueTypeIsStatic;
_valueTypeSerializer = src._valueTypeSerializer;
_keySerializer = src._keySerializer;
_valueSerializer = src._valueSerializer;
_dynamicValueSerializers = src._dynamicValueSerializers;
_property = src._property;
_filterId = filterId;
_sortKeys = sortKeys;
_suppressableValue = src._suppressableValue;
_suppressNulls = src._suppressNulls;
}
@Override
public MapSerializer _withValueTypeSerializer(TypeSerializer vts) {
if (_valueTypeSerializer == vts) {
return this;
}
_ensureOverride("_withValueTypeSerializer");
return new MapSerializer(this, vts, _suppressableValue, _suppressNulls);
}
/**
* @since 2.4
*/
public MapSerializer withResolved(BeanProperty property,
JsonSerializer<?> keySerializer, JsonSerializer<?> valueSerializer,
Set<String> ignored, boolean sortKeys)
{
_ensureOverride("withResolved");
MapSerializer ser = new MapSerializer(this, property, keySerializer, valueSerializer, ignored);
if (sortKeys != ser._sortKeys) {
ser = new MapSerializer(ser, _filterId, sortKeys);
}
return ser;
}
@Override
public MapSerializer withFilterId(Object filterId) {
if (_filterId == filterId) {
return this;
}
_ensureOverride("withFilterId");
return new MapSerializer(this, filterId, _sortKeys);
}
/**
* Mutant factory for constructing an instance with different inclusion strategy
* for content (Map values).
*
* @since 2.9
*/
public MapSerializer withContentInclusion(Object suppressableValue, boolean suppressNulls) {
if ((suppressableValue == _suppressableValue) && (suppressNulls == _suppressNulls)) {
return this;
}
_ensureOverride("withContentInclusion");
return new MapSerializer(this, _valueTypeSerializer, suppressableValue, suppressNulls);
}
/**
* @since 2.8
*/
public static MapSerializer construct(Set<String> ignoredEntries, JavaType mapType,
boolean staticValueType, TypeSerializer vts,
JsonSerializer<Object> keySerializer, JsonSerializer<Object> valueSerializer,
Object filterId)
{
JavaType keyType, valueType;
if (mapType == null) {
keyType = valueType = UNSPECIFIED_TYPE;
} else {
keyType = mapType.getKeyType();
valueType = mapType.getContentType();
}
// If value type is final, it's same as forcing static value typing:
if (!staticValueType) {
staticValueType = (valueType != null && valueType.isFinal());
} else {
// also: Object.class can not be handled as static, ever
if (valueType.getRawClass() == Object.class) {
staticValueType = false;
}
}
MapSerializer ser = new MapSerializer(ignoredEntries, keyType, valueType, staticValueType, vts,
keySerializer, valueSerializer);
if (filterId != null) {
ser = ser.withFilterId(filterId);
}
return ser;
}
/**
* @since 2.9
*/
protected void _ensureOverride(String method) {
ClassUtil.verifyMustOverride(MapSerializer.class, this, method);
}
/**
* @since 2.5
*/
@Deprecated // since 2.9
protected void _ensureOverride() {
_ensureOverride("N/A");
}
/*
/**********************************************************
/* Deprecated creators
/**********************************************************
*/
/**
* @since 2.5
* @deprecated // since 2.9
*/
@Deprecated // since 2.9
protected MapSerializer(MapSerializer src, TypeSerializer vts,
Object suppressableValue)
{
this(src, vts, suppressableValue, false);
}
/**
* @deprecated since 2.9
*/
@Deprecated // since 2.9
public MapSerializer withContentInclusion(Object suppressableValue) {
return new MapSerializer(this, _valueTypeSerializer, suppressableValue, _suppressNulls);
}
/**
* @since 2.3
*
* @deprecated Since 2.8 use the other overload
*/
@Deprecated // since 2.8
public static MapSerializer construct(String[] ignoredList, JavaType mapType,
boolean staticValueType, TypeSerializer vts,
JsonSerializer<Object> keySerializer, JsonSerializer<Object> valueSerializer,
Object filterId)
{
Set<String> ignoredEntries = ArrayBuilders.arrayToSet(ignoredList);
return construct(ignoredEntries, mapType, staticValueType, vts,
keySerializer, valueSerializer, filterId);
}
/*
/**********************************************************
/* Post-processing (contextualization)
/**********************************************************
*/
@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 (_neitherNull(propertyAcc, intr)) {
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);
}
Set<String> ignored = _ignoredEntries;
boolean sortKeys = false;
if (_neitherNull(propertyAcc, intr)) {
JsonIgnoreProperties.Value ignorals = intr.findPropertyIgnorals(propertyAcc);
if (ignorals != null){
Set<String> newIgnored = ignorals.findIgnoredForSerialization();
if (_nonEmpty(newIgnored)) {
ignored = (ignored == null) ? new HashSet<String>() : new HashSet<String>(ignored);
for (String str : newIgnored) {
ignored.add(str);
}
}
}
Boolean b = intr.findSerializationSortAlphabetically(propertyAcc);
sortKeys = Boolean.TRUE.equals(b);
}
JsonFormat.Value format = findFormatOverrides(provider, property, Map.class);
if (format != null) {
Boolean B = format.getFeature(JsonFormat.Feature.WRITE_SORTED_MAP_ENTRIES);
if (B != null) {
sortKeys = B.booleanValue();
}
}
MapSerializer mser = withResolved(property, keySer, ser, ignored, sortKeys);
// [databind#307]: allow filtering
if (property != null) {
AnnotatedMember m = property.getMember();
if (m != null) {
Object filterId = intr.findFilterId(m);
if (filterId != null) {
mser = mser.withFilterId(filterId);
}
}
JsonInclude.Value inclV = property.findPropertyInclusion(provider.getConfig(), null);
if (inclV != null) {
JsonInclude.Include incl = inclV.getContentInclusion();
if (incl != JsonInclude.Include.USE_DEFAULTS) {
Object valueToSuppress;
boolean suppressNulls;
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;
}
mser = mser.withContentInclusion(valueToSuppress, suppressNulls);
}
}
}
return mser;
}
/*
/**********************************************************
/* Accessors
/**********************************************************
*/
@Override
public JavaType getContentType() {
return _valueType;
}
@Override
public JsonSerializer<?> getContentSerializer() {
return _valueSerializer;
}
@Override
public boolean isEmpty(SerializerProvider prov, Map<?,?> value)
{
if (value.isEmpty()) {
return true;
}
// 05-Nove-2015, tatu: Simple cases are cheap, but for recursive
// emptiness checking we actually need to see if values are empty as well.
Object supp = _suppressableValue;
if ((supp == null) && !_suppressNulls) {
return false;
}
JsonSerializer<Object> valueSer = _valueSerializer;
final boolean checkEmpty = (MARKER_FOR_EMPTY == supp);
if (valueSer != null) {
for (Object elemValue : value.values()) {
if (elemValue == null) {
if (_suppressNulls) {
continue;
}
return false;
}
if (checkEmpty) {
if (!valueSer.isEmpty(prov, elemValue)) {
return false;
}
} else if ((supp == null) || !supp.equals(value)) {
return false;
}
}
return true;
}
// But if not statically known, try this:
for (Object elemValue : value.values()) {
if (elemValue == null) {
if (_suppressNulls) {
continue;
}
return false;
}
try {
valueSer = _findSerializer(prov, elemValue);
} catch (JsonMappingException e) { // Ugh... can not just throw as-is, so...
// 05-Nov-2015, tatu: For now, probably best not to assume empty then
return false;
}
if (checkEmpty) {
if (!valueSer.isEmpty(prov, elemValue)) {
return false;
}
} else if ((supp == null) || !supp.equals(value)) {
return false;
}
}
return true;
}
@Override
public boolean hasSingleElement(Map<?,?> value) {
return (value.size() == 1);
}
/*
/**********************************************************
/* Extended API
/**********************************************************
*/
/**
* Accessor for currently assigned key serializer. Note that
* this may return null during construction of <code>MapSerializer</code>:
* depedencies are resolved during {@link #createContextual} method
* (which can be overridden by custom implementations), but for some
* dynamic types, it is possible that serializer is only resolved
* during actual serialization.
*
* @since 2.0
*/
public JsonSerializer<?> getKeySerializer() {
return _keySerializer;
}
/*
/**********************************************************
/* JsonSerializer implementation
/**********************************************************
*/
@Override
public void serialize(Map<?,?> value, JsonGenerator gen, SerializerProvider provider)
throws IOException
{
gen.writeStartObject(value);
if (!value.isEmpty()) {
if (_sortKeys || provider.isEnabled(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS)) {
value = _orderEntries(value, gen, provider);
}
PropertyFilter pf;
if ((_filterId != null) && (pf = findPropertyFilter(provider, _filterId, value)) != null) {
serializeFilteredFields(value, gen, provider, pf, _suppressableValue);
} else if ((_suppressableValue != null) || _suppressNulls) {
serializeOptionalFields(value, gen, provider, _suppressableValue);
} else if (_valueSerializer != null) {
serializeFieldsUsing(value, gen, provider, _valueSerializer);
} else {
serializeFields(value, gen, provider);
}
}
gen.writeEndObject();
}
@Override
public void serializeWithType(Map<?,?> 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);
if (!value.isEmpty()) {
if (_sortKeys || provider.isEnabled(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS)) {
value = _orderEntries(value, gen, provider);
}
PropertyFilter pf;
if ((_filterId != null) && (pf = findPropertyFilter(provider, _filterId, value)) != null) {
serializeFilteredFields(value, gen, provider, pf, _suppressableValue);
} else if ((_suppressableValue != null) || _suppressNulls) {
serializeOptionalFields(value, gen, provider, _suppressableValue);
} else if (_valueSerializer != null) {
serializeFieldsUsing(value, gen, provider, _valueSerializer);
} else {
serializeFields(value, gen, provider);
}
}
typeSer.writeTypeSuffixForObject(value, gen);
}
/*
/**********************************************************
/* Secondary serialization methods
/**********************************************************
*/
/**
* General-purpose serialization for contents, where we do not necessarily know
* the value serialization, but
* we do know that no value suppression is needed (which simplifies processing a bit)
*/
public void serializeFields(Map<?,?> value, JsonGenerator gen, SerializerProvider provider)
throws IOException
{
// If value type needs polymorphic type handling, some more work needed:
if (_valueTypeSerializer != null) {
serializeTypedFields(value, gen, provider, null);
return;
}
final JsonSerializer<Object> keySerializer = _keySerializer;
final Set<String> ignored = _ignoredEntries;
Object keyElem = null;
try {
for (Map.Entry<?,?> entry : value.entrySet()) {
Object valueElem = entry.getValue();
// First, serialize key
keyElem = entry.getKey();
if (keyElem == null) {
provider.findNullKeySerializer(_keyType, _property).serialize(null, gen, provider);
} else {
// One twist: is entry ignorable? If so, skip
if ((ignored != null) && ignored.contains(keyElem)) {
continue;
}
keySerializer.serialize(keyElem, gen, provider);
}
// And then value
if (valueElem == null) {
provider.defaultSerializeNull(gen);
continue;
}
JsonSerializer<Object> serializer = _valueSerializer;
if (serializer == null) {
serializer = _findSerializer(provider, valueElem);
}
serializer.serialize(valueElem, gen, provider);
}
} catch (Exception e) { // Add reference information
wrapAndThrow(provider, e, value, String.valueOf(keyElem));
}
}
/**
* Serialization method called when exclusion filtering needs to be applied.
*/
public void serializeOptionalFields(Map<?,?> value, JsonGenerator gen, SerializerProvider provider,
Object suppressableValue)
throws IOException
{
// If value type needs polymorphic type handling, some more work needed:
if (_valueTypeSerializer != null) {
serializeTypedFields(value, gen, provider, suppressableValue);
return;
}
final Set<String> ignored = _ignoredEntries;
final boolean checkEmpty = (MARKER_FOR_EMPTY == suppressableValue);
for (Map.Entry<?,?> entry : value.entrySet()) {
// First find key serializer
final Object keyElem = entry.getKey();
JsonSerializer<Object> keySerializer;
if (keyElem == null) {
keySerializer = provider.findNullKeySerializer(_keyType, _property);
} else {
if (ignored != null && ignored.contains(keyElem)) continue;
keySerializer = _keySerializer;
}
// Then value serializer
final Object valueElem = entry.getValue();
JsonSerializer<Object> valueSer;
if (valueElem == null) {
if (_suppressNulls) { // all suppressions include null-suppression
continue;
}
valueSer = provider.getDefaultNullValueSerializer();
} else {
valueSer = _valueSerializer;
if (valueSer == null) {
valueSer = _findSerializer(provider, valueElem);
}
// also may need to skip non-empty values:
if (checkEmpty) {
if (valueSer.isEmpty(provider, valueElem)) {
continue;
}
} else if (suppressableValue != null) {
if (suppressableValue.equals(valueElem)) {
continue;
}
}
}
// and then serialize, if all went well
try {
keySerializer.serialize(keyElem, gen, provider);
valueSer.serialize(valueElem, gen, provider);
} catch (Exception e) {
wrapAndThrow(provider, e, value, String.valueOf(keyElem));
}
}
}
/**
* Method called to serialize fields, when the value type is statically known,
* so that value serializer is passed and does not need to be fetched from
* provider.
*/
public void serializeFieldsUsing(Map<?,?> value, JsonGenerator gen, SerializerProvider provider,
JsonSerializer<Object> ser)
throws IOException
{
final JsonSerializer<Object> keySerializer = _keySerializer;
final Set<String> ignored = _ignoredEntries;
final TypeSerializer typeSer = _valueTypeSerializer;
for (Map.Entry<?,?> entry : value.entrySet()) {
Object keyElem = entry.getKey();
if (ignored != null && ignored.contains(keyElem)) continue;
if (keyElem == null) {
provider.findNullKeySerializer(_keyType, _property).serialize(null, gen, provider);
} else {
keySerializer.serialize(keyElem, gen, provider);
}
final Object valueElem = entry.getValue();
if (valueElem == null) {
provider.defaultSerializeNull(gen);
} else {
try {
if (typeSer == null) {
ser.serialize(valueElem, gen, provider);
} else {
ser.serializeWithType(valueElem, gen, provider, typeSer);
}
} catch (Exception e) {
wrapAndThrow(provider, e, value, String.valueOf(keyElem));
}
}
}
}
/**
* Helper method used when we have a JSON Filter to use for potentially
* filtering out Map entries.
*
* @since 2.5
*/
public void serializeFilteredFields(Map<?,?> value, JsonGenerator gen, SerializerProvider provider,
PropertyFilter filter,
Object suppressableValue) // since 2.5
throws IOException
{
final Set<String> ignored = _ignoredEntries;
final MapProperty prop = new MapProperty(_valueTypeSerializer, _property);
final boolean checkEmpty = (MARKER_FOR_EMPTY == suppressableValue);
for (Map.Entry<?,?> entry : value.entrySet()) {
// First, serialize key; unless ignorable by key
final Object keyElem = entry.getKey();
if (ignored != null && ignored.contains(keyElem)) continue;
JsonSerializer<Object> keySerializer;
if (keyElem == null) {
keySerializer = provider.findNullKeySerializer(_keyType, _property);
} else {
keySerializer = _keySerializer;
}
// or by value; nulls often suppressed
final Object valueElem = entry.getValue();
JsonSerializer<Object> valueSer;
// And then value
if (valueElem == null) {
if (_suppressNulls) {
continue;
}
valueSer = provider.getDefaultNullValueSerializer();
} else {
valueSer = _valueSerializer;
if (valueSer == null) {
valueSer = _findSerializer(provider, valueElem);
}
// also may need to skip non-empty values:
if (checkEmpty) {
if (valueSer.isEmpty(provider, valueElem)) {
continue;
}
} else if (suppressableValue != null) {
if (suppressableValue.equals(valueElem)) {
continue;
}
}
}
// and with that, ask filter to handle it
prop.reset(keyElem, valueElem, keySerializer, valueSer);
try {
filter.serializeAsField(value, gen, provider, prop);
} catch (Exception e) {
wrapAndThrow(provider, e, value, String.valueOf(keyElem));
}
}
}
/**
* @since 2.5
*/
public void serializeTypedFields(Map<?,?> value, JsonGenerator gen, SerializerProvider provider,
Object suppressableValue) // since 2.5
throws IOException
{
final Set<String> ignored = _ignoredEntries;
final boolean checkEmpty = (MARKER_FOR_EMPTY == suppressableValue);
for (Map.Entry<?,?> entry : value.entrySet()) {
Object keyElem = entry.getKey();
JsonSerializer<Object> keySerializer;
if (keyElem == null) {
keySerializer = provider.findNullKeySerializer(_keyType, _property);
} else {
// One twist: is entry ignorable? If so, skip
if (ignored != null && ignored.contains(keyElem)) continue;
keySerializer = _keySerializer;
}
final Object valueElem = entry.getValue();
// And then value
JsonSerializer<Object> valueSer;
if (valueElem == null) {
if (_suppressNulls) { // all suppression include null suppression
continue;
}
valueSer = provider.getDefaultNullValueSerializer();
} else {
valueSer = _valueSerializer;
if (valueSer == null) {
valueSer = _findSerializer(provider, valueElem);
}
// also may need to skip non-empty values:
if (checkEmpty) {
if (valueSer.isEmpty(provider, valueElem)) {
continue;
}
} else if (suppressableValue != null) {
if (suppressableValue.equals(valueElem)) {
continue;
}
}
}
keySerializer.serialize(keyElem, gen, provider);
try {
valueSer.serializeWithType(valueElem, gen, provider, _valueTypeSerializer);
} catch (Exception e) {
wrapAndThrow(provider, e, value, String.valueOf(keyElem));
}
}
}
/**
* Helper method used when we have a JSON Filter to use AND contents are
* "any properties" of a POJO.
*
* @param bean Enclosing POJO that has any-getter used to obtain "any properties"
*
* @since 2.9
*/
public void serializeFilteredAnyProperties(SerializerProvider provider, JsonGenerator gen,
Object bean, Map<?,?> value, PropertyFilter filter,
Object suppressableValue)
throws IOException
{
final Set<String> ignored = _ignoredEntries;
final MapProperty prop = new MapProperty(_valueTypeSerializer, _property);
final boolean checkEmpty = (MARKER_FOR_EMPTY == suppressableValue);
for (Map.Entry<?,?> entry : value.entrySet()) {
// First, serialize key; unless ignorable by key
final Object keyElem = entry.getKey();
if (ignored != null && ignored.contains(keyElem)) continue;
JsonSerializer<Object> keySerializer;
if (keyElem == null) {
keySerializer = provider.findNullKeySerializer(_keyType, _property);
} else {
keySerializer = _keySerializer;
}
// or by value; nulls often suppressed
final Object valueElem = entry.getValue();
JsonSerializer<Object> valueSer;
// And then value
if (valueElem == null) {
if (_suppressNulls) {
continue;
}
valueSer = provider.getDefaultNullValueSerializer();
} else {
valueSer = _valueSerializer;
if (valueSer == null) {
valueSer = _findSerializer(provider, valueElem);
}
// also may need to skip non-empty values:
if (checkEmpty) {
if (valueSer.isEmpty(provider, valueElem)) {
continue;
}
} else if (suppressableValue != null) {
if (suppressableValue.equals(valueElem)) {
continue;
}
}
}
// and with that, ask filter to handle it
prop.reset(keyElem, valueElem, keySerializer, valueSer);
try {
filter.serializeAsField(bean, gen, provider, prop);
} catch (Exception e) {
wrapAndThrow(provider, e, value, String.valueOf(keyElem));
}
}
}
/*
/**********************************************************
/* Schema related functionality
/**********************************************************
*/
@Override
public JsonNode getSchema(SerializerProvider provider, Type typeHint)
{
// even though it's possible to statically determine the "value" type of the map,
// there's no way to statically determine the keys, so the "Entries" can't be determined.
return createSchemaNode("object", true);
}
@Override
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
throws JsonMappingException
{
JsonMapFormatVisitor v2 = visitor.expectMapFormat(typeHint);
if (v2 != null) {
v2.keyFormat(_keySerializer, _keyType);
JsonSerializer<?> valueSer = _valueSerializer;
if (valueSer == null) {
valueSer = _findAndAddDynamic(_dynamicValueSerializers,
_valueType, visitor.getProvider());
}
v2.valueFormat(valueSer, _valueType);
}
}
/*
/**********************************************************
/* Internal helper methods
/**********************************************************
*/
protected final JsonSerializer<Object> _findAndAddDynamic(PropertySerializerMap map,
Class<?> type, SerializerProvider provider) throws JsonMappingException
{
PropertySerializerMap.SerializerAndMapResult result = map.findAndAddSecondarySerializer(type, provider, _property);
// did we get a new map of serializers? If so, start using it
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;
}
protected Map<?,?> _orderEntries(Map<?,?> input, JsonGenerator gen,
SerializerProvider provider) throws IOException
{
// minor optimization: may already be sorted?
if (input instanceof SortedMap<?,?>) {
return input;
}
// [databind#1411]: TreeMap does not like null key... (although note that
// check above should prevent this code from being called in that case)
// [databind#153]: but, apparently, some custom Maps do manage hit this
// problem.
if (_hasNullKey(input)) {
TreeMap<Object,Object> result = new TreeMap<Object,Object>();
for (Map.Entry<?,?> entry : input.entrySet()) {
Object key = entry.getKey();
if (key == null) {
_writeNullKeyedEntry(gen, provider, entry.getValue());
continue;
}
result.put(key, entry.getValue());
}
return result;
}
return new TreeMap<Object,Object>(input);
}
/**
* @since 2.8.7
*/
protected boolean _hasNullKey(Map<?,?> input) {
// 19-Feb-2017, tatu: As per [databind#1513] there are many cases where `null`
// keys are not allowed, and even attempt to check for presence can cause
// problems. Without resorting to external sorting (and internal API change),
// or custom sortable Map implementation (more code) we can try black- or
// white-listing (that is; either skip known problem cases; or only apply for
// known good cases).
// While my first instinct was to do black-listing (remove Hashtable and ConcurrentHashMap),
// all in all it is probably better to just white list `HashMap` (and its sub-classes).
return (input instanceof HashMap) && input.containsKey(null);
}
protected void _writeNullKeyedEntry(JsonGenerator gen, SerializerProvider provider,
Object value) throws IOException
{
JsonSerializer<Object> keySerializer = provider.findNullKeySerializer(_keyType, _property);
JsonSerializer<Object> valueSer;
if (value == null) {
if (_suppressNulls) {
return;
}
valueSer = provider.getDefaultNullValueSerializer();
} else {
valueSer = _valueSerializer;
if (valueSer == null) {
valueSer = _findSerializer(provider, value);
}
if (_suppressableValue == MARKER_FOR_EMPTY) {
if (valueSer.isEmpty(provider, value)) {
return;
}
} else if ((_suppressableValue != null)
&& (_suppressableValue.equals(value))) {
return;
}
}
try {
keySerializer.serialize(null, gen, provider);
valueSer.serialize(value, gen, provider);
} catch (Exception e) {
wrapAndThrow(provider, e, value, "");
}
}
private final JsonSerializer<Object> _findSerializer(SerializerProvider provider,
Object value) throws JsonMappingException
{
final Class<?> cc = value.getClass();
JsonSerializer<Object> valueSer = _dynamicValueSerializers.serializerFor(cc);
if (valueSer != null) {
return valueSer;
}
if (_valueType.hasGenericTypes()) {
return _findAndAddDynamic(_dynamicValueSerializers,
provider.constructSpecializedType(_valueType, cc), provider);
}
return _findAndAddDynamic(_dynamicValueSerializers, cc, provider);
}
}