blob: a51aee3b07daac6cfc18daabad32ba29f6f41a7c [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.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.JsonFormatTypes;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitable;
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.node.ObjectNode;
import com.fasterxml.jackson.databind.ser.ContainerSerializer;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap;
import com.fasterxml.jackson.databind.type.TypeFactory;
/**
* 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
{
protected final static JavaType UNSPECIFIED_TYPE = TypeFactory.unknownType();
/**
* Map-valued property being serialized with this instance
*/
protected final BeanProperty _property;
/**
* Set of entries to omit during serialization, if any
*/
protected final HashSet<String> _ignoredEntries;
/**
* 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;
/**
* 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;
/*
/**********************************************************
/* Life-cycle
/**********************************************************
*/
@SuppressWarnings("unchecked")
protected MapSerializer(HashSet<String> ignoredEntries,
JavaType keyType, JavaType valueType, boolean valueTypeIsStatic,
TypeSerializer vts,
JsonSerializer<?> keySerializer, JsonSerializer<?> valueSerializer)
{
super(Map.class, false);
_ignoredEntries = ignoredEntries;
_keyType = keyType;
_valueType = valueType;
_valueTypeIsStatic = valueTypeIsStatic;
_valueTypeSerializer = vts;
_keySerializer = (JsonSerializer<Object>) keySerializer;
_valueSerializer = (JsonSerializer<Object>) valueSerializer;
_dynamicValueSerializers = PropertySerializerMap.emptyMap();
_property = null;
}
@SuppressWarnings("unchecked")
protected MapSerializer(MapSerializer src, BeanProperty property,
JsonSerializer<?> keySerializer, JsonSerializer<?> valueSerializer,
HashSet<String> ignored)
{
super(Map.class, false);
_ignoredEntries = ignored;
_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;
}
protected MapSerializer(MapSerializer src, TypeSerializer vts)
{
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;
}
@Override
public MapSerializer _withValueTypeSerializer(TypeSerializer vts)
{
return new MapSerializer(this, vts);
}
public MapSerializer withResolved(BeanProperty property,
JsonSerializer<?> keySerializer, JsonSerializer<?> valueSerializer,
HashSet<String> ignored)
{
return new MapSerializer(this, property, keySerializer, valueSerializer, ignored);
}
public static MapSerializer construct(String[] ignoredList, JavaType mapType,
boolean staticValueType, TypeSerializer vts,
JsonSerializer<Object> keySerializer, JsonSerializer<Object> valueSerializer)
{
HashSet<String> ignoredEntries = toSet(ignoredList);
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());
}
return new MapSerializer(ignoredEntries, keyType, valueType, staticValueType, vts,
keySerializer, valueSerializer);
}
private static HashSet<String> toSet(String[] ignoredEntries) {
if (ignoredEntries == null || ignoredEntries.length == 0) {
return null;
}
HashSet<String> result = new HashSet<String>(ignoredEntries.length);
for (String prop : ignoredEntries) {
result.add(prop);
}
return result;
}
/*
/**********************************************************
/* Post-processing (contextualization)
/**********************************************************
*/
// @Override
public JsonSerializer<?> createContextual(SerializerProvider provider,
BeanProperty property)
throws JsonMappingException
{
/* 29-Sep-2012, tatu: Actually, we need to do much more contextual
* checking here since we finally know for sure the property,
* and it may have overrides
*/
JsonSerializer<?> ser = null;
JsonSerializer<?> keySer = null;
// First: if we have a property, may have property-annotation overrides
if (property != null) {
AnnotatedMember m = property.getMember();
if (m != null) {
Object serDef;
final AnnotationIntrospector intr = provider.getAnnotationIntrospector();
serDef = intr.findKeySerializer(m);
if (serDef != null) {
keySer = provider.serializerInstance(m, serDef);
}
serDef = intr.findContentSerializer(m);
if (serDef != null) {
ser = provider.serializerInstance(m, serDef);
}
}
}
if (ser == null) {
ser = _valueSerializer;
}
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.
if (_valueTypeIsStatic || hasContentTypeAnnotation(provider, property)) {
ser = provider.findValueSerializer(_valueType, property);
}
} else if (ser instanceof ContextualSerializer) {
ser = ((ContextualSerializer) ser).createContextual(provider, property);
}
if (keySer == null) {
keySer = _keySerializer;
}
if (keySer == null) {
keySer = provider.findKeySerializer(_keyType, property);
} else if (keySer instanceof ContextualSerializer) {
keySer = ((ContextualSerializer) keySer).createContextual(provider, property);
}
HashSet<String> ignored = this._ignoredEntries;
AnnotationIntrospector intr = provider.getAnnotationIntrospector();
if (intr != null && property != null) {
String[] moreToIgnore = intr.findPropertiesToIgnore(property.getMember());
if (moreToIgnore != null) {
ignored = (ignored == null) ? new HashSet<String>() : new HashSet<String>(ignored);
for (String str : moreToIgnore) {
ignored.add(str);
}
}
}
return withResolved(property, keySer, ser, ignored);
}
/*
/**********************************************************
/* Accessors
/**********************************************************
*/
@Override
public JavaType getContentType() {
return _valueType;
}
@Override
public JsonSerializer<?> getContentSerializer() {
return _valueSerializer;
}
@Override
public boolean isEmpty(Map<?,?> value) {
return (value == null) || value.isEmpty();
}
@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 jgen, SerializerProvider provider)
throws IOException, JsonGenerationException
{
jgen.writeStartObject();
if (!value.isEmpty()) {
if (provider.isEnabled(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS)) {
value = _orderEntries(value);
}
if (_valueSerializer != null) {
serializeFieldsUsing(value, jgen, provider, _valueSerializer);
} else {
serializeFields(value, jgen, provider);
}
}
jgen.writeEndObject();
}
@Override
public void serializeWithType(Map<?,?> value, JsonGenerator jgen, SerializerProvider provider,
TypeSerializer typeSer)
throws IOException, JsonGenerationException
{
typeSer.writeTypePrefixForObject(value, jgen);
if (!value.isEmpty()) {
if (provider.isEnabled(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS)) {
value = _orderEntries(value);
}
if (_valueSerializer != null) {
serializeFieldsUsing(value, jgen, provider, _valueSerializer);
} else {
serializeFields(value, jgen, provider);
}
}
typeSer.writeTypeSuffixForObject(value, jgen);
}
/*
/**********************************************************
/* JsonSerializer implementation
/**********************************************************
*/
/**
* Method called to serialize fields, when the value type is not statically known.
*/
public void serializeFields(Map<?,?> value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonGenerationException
{
// If value type needs polymorphic type handling, some more work needed:
if (_valueTypeSerializer != null) {
serializeTypedFields(value, jgen, provider);
return;
}
final JsonSerializer<Object> keySerializer = _keySerializer;
final HashSet<String> ignored = _ignoredEntries;
final boolean skipNulls = !provider.isEnabled(SerializationFeature.WRITE_NULL_MAP_VALUES);
PropertySerializerMap serializers = _dynamicValueSerializers;
for (Map.Entry<?,?> entry : value.entrySet()) {
Object valueElem = entry.getValue();
// First, serialize key
Object keyElem = entry.getKey();
if (keyElem == null) {
provider.findNullKeySerializer(_keyType, _property).serialize(null, jgen, provider);
} else {
// [JACKSON-314] skip entries with null values?
if (skipNulls && valueElem == null) continue;
// One twist: is entry ignorable? If so, skip
if (ignored != null && ignored.contains(keyElem)) continue;
keySerializer.serialize(keyElem, jgen, provider);
}
// And then value
if (valueElem == null) {
provider.defaultSerializeNull(jgen);
} else {
Class<?> cc = valueElem.getClass();
JsonSerializer<Object> serializer = serializers.serializerFor(cc);
if (serializer == null) {
if (_valueType.hasGenericTypes()) {
serializer = _findAndAddDynamic(serializers,
provider.constructSpecializedType(_valueType, cc), provider);
} else {
serializer = _findAndAddDynamic(serializers, cc, provider);
}
serializers = _dynamicValueSerializers;
}
try {
serializer.serialize(valueElem, jgen, provider);
} catch (Exception e) {
// [JACKSON-55] Need to add reference information
String keyDesc = ""+keyElem;
wrapAndThrow(provider, e, value, keyDesc);
}
}
}
}
/**
* 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.
*/
protected void serializeFieldsUsing(Map<?,?> value, JsonGenerator jgen, SerializerProvider provider,
JsonSerializer<Object> ser)
throws IOException, JsonGenerationException
{
final JsonSerializer<Object> keySerializer = _keySerializer;
final HashSet<String> ignored = _ignoredEntries;
final TypeSerializer typeSer = _valueTypeSerializer;
final boolean skipNulls = !provider.isEnabled(SerializationFeature.WRITE_NULL_MAP_VALUES);
for (Map.Entry<?,?> entry : value.entrySet()) {
Object valueElem = entry.getValue();
Object keyElem = entry.getKey();
if (keyElem == null) {
provider.findNullKeySerializer(_keyType, _property).serialize(null, jgen, provider);
} else {
// [JACKSON-314] also may need to skip entries with null values
if (skipNulls && valueElem == null) continue;
if (ignored != null && ignored.contains(keyElem)) continue;
keySerializer.serialize(keyElem, jgen, provider);
}
if (valueElem == null) {
provider.defaultSerializeNull(jgen);
} else {
try {
if (typeSer == null) {
ser.serialize(valueElem, jgen, provider);
} else {
ser.serializeWithType(valueElem, jgen, provider, typeSer);
}
} catch (Exception e) {
// [JACKSON-55] Need to add reference information
String keyDesc = ""+keyElem;
wrapAndThrow(provider, e, value, keyDesc);
}
}
}
}
protected void serializeTypedFields(Map<?,?> value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonGenerationException
{
final JsonSerializer<Object> keySerializer = _keySerializer;
JsonSerializer<Object> prevValueSerializer = null;
Class<?> prevValueClass = null;
final HashSet<String> ignored = _ignoredEntries;
final boolean skipNulls = !provider.isEnabled(SerializationFeature.WRITE_NULL_MAP_VALUES);
for (Map.Entry<?,?> entry : value.entrySet()) {
Object valueElem = entry.getValue();
// First, serialize key
Object keyElem = entry.getKey();
if (keyElem == null) {
provider.findNullKeySerializer(_keyType, _property).serialize(null, jgen, provider);
} else {
// [JACKSON-314] also may need to skip entries with null values
if (skipNulls && valueElem == null) continue;
// One twist: is entry ignorable? If so, skip
if (ignored != null && ignored.contains(keyElem)) continue;
keySerializer.serialize(keyElem, jgen, provider);
}
// And then value
if (valueElem == null) {
provider.defaultSerializeNull(jgen);
} else {
Class<?> cc = valueElem.getClass();
JsonSerializer<Object> currSerializer;
if (cc == prevValueClass) {
currSerializer = prevValueSerializer;
} else {
currSerializer = provider.findValueSerializer(cc, _property);
prevValueSerializer = currSerializer;
prevValueClass = cc;
}
try {
currSerializer.serializeWithType(valueElem, jgen, provider, _valueTypeSerializer);
} catch (Exception e) {
// [JACKSON-55] Need to add reference information
String keyDesc = ""+keyElem;
wrapAndThrow(provider, e, value, keyDesc);
}
}
}
}
@Override
public JsonNode getSchema(SerializerProvider provider, Type typeHint)
{
ObjectNode o = createSchemaNode("object", true);
//(ryan) 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 o;
}
@Override
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
throws JsonMappingException
{
JsonMapFormatVisitor v2 = (visitor == null) ? null : 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.findAndAddSerializer(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.findAndAddSerializer(type, provider, _property);
if (map != result.map) {
_dynamicValueSerializers = result.map;
}
return result.serializer;
}
protected Map<?,?> _orderEntries(Map<?,?> input)
{
// minor optimization: may already be sorted?
if (input instanceof SortedMap<?,?>) {
return input;
}
return new TreeMap<Object,Object>(input);
}
}