package org.codehaus.jackson.map.ser;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.*;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.*;
import org.codehaus.jackson.map.annotate.JacksonStdImpl;
import org.codehaus.jackson.map.ser.impl.PropertySerializerMap;
import org.codehaus.jackson.map.type.TypeFactory;
import org.codehaus.jackson.node.ObjectNode;
import org.codehaus.jackson.type.JavaType;
/**
* Standard serializer implementation for serializing {link java.util.Map} types.
*
* Note: about the only configurable setting currently is ability to filter out
* entries with specified names.
*/
@JacksonStdImpl
public class MapSerializer
extends ContainerSerializerBase>
implements ResolvableSerializer
{
protected final static JavaType UNSPECIFIED_TYPE = TypeFactory.unknownType();
/**
* Map-valued property being serialized with this instance
*
* @since 1.7
*/
protected final BeanProperty _property;
/**
* Set of entries to omit during serialization, if any
*/
protected final HashSet _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
*
* @since 1.7
*/
protected final JavaType _keyType;
/**
* Declared type of contained values
*/
protected final JavaType _valueType;
/**
* Key serializer to use, if it can be statically determined
*
* @since 1.7
*/
protected JsonSerializer _keySerializer;
/**
* Value serializer to use, if it can be statically determined
*
* @since 1.5
*/
protected JsonSerializer _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.
*
* @since 1.8
*/
protected PropertySerializerMap _dynamicValueSerializers;
protected MapSerializer() {
this((HashSet)null, null, null, false, null, null, null, null);
}
/**
* Legacy constructor (as of 1.7)
*
* @deprecated Use variant that takes Key type and property information
*/
@Deprecated
protected MapSerializer(HashSet ignoredEntries,
JavaType valueType, boolean valueTypeIsStatic,
TypeSerializer vts)
{
this(ignoredEntries, UNSPECIFIED_TYPE, valueType, valueTypeIsStatic, vts, null, null, null);
}
/**
* Legacy constructor (as of 1.8)
*
* @deprecated As of 1.8, use version that takes valueSerializer
*/
@Deprecated
protected MapSerializer(HashSet ignoredEntries,
JavaType keyType, JavaType valueType, boolean valueTypeIsStatic,
TypeSerializer vts, JsonSerializer keySerializer, BeanProperty property)
{
this(ignoredEntries, keyType, valueType, valueTypeIsStatic, vts, keySerializer, null, property);
}
protected MapSerializer(HashSet ignoredEntries,
JavaType keyType, JavaType valueType, boolean valueTypeIsStatic,
TypeSerializer vts,
JsonSerializer keySerializer, JsonSerializer valueSerializer,
BeanProperty property)
{
super(Map.class, false);
_property = property;
_ignoredEntries = ignoredEntries;
_keyType = keyType;
_valueType = valueType;
_valueTypeIsStatic = valueTypeIsStatic;
_valueTypeSerializer = vts;
_keySerializer = keySerializer;
_valueSerializer = valueSerializer;
_dynamicValueSerializers = PropertySerializerMap.emptyMap();
}
@Override
public ContainerSerializerBase> _withValueTypeSerializer(TypeSerializer vts)
{
MapSerializer ms = new MapSerializer(_ignoredEntries, _keyType, _valueType, _valueTypeIsStatic, vts,
_keySerializer, _valueSerializer, _property);
if (_valueSerializer != null) {
ms._valueSerializer = _valueSerializer;
}
return ms;
}
/**
* Factory method used to construct Map serializers.
*
* @param ignoredList Array of entry names that are to be filtered on
* serialization; null if none
* @param mapType Declared type information (needed for static typing)
* @param staticValueType Whether static typing should be used for the
* Map (which includes its contents)
* @param vts Type serializer to use for map entry values, if any
*
* @deprecated As of 1.8; use the variant with more arguments
*/
@Deprecated
public static MapSerializer construct(String[] ignoredList, JavaType mapType,
boolean staticValueType, TypeSerializer vts, BeanProperty property)
{
return construct(ignoredList, mapType, staticValueType, vts, property, null, null);
}
public static MapSerializer construct(String[] ignoredList, JavaType mapType,
boolean staticValueType, TypeSerializer vts, BeanProperty property,
JsonSerializer keySerializer, JsonSerializer valueSerializer)
{
HashSet 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, property);
}
private static HashSet toSet(String[] ignoredEntries) {
if (ignoredEntries == null || ignoredEntries.length == 0) {
return null;
}
HashSet result = new HashSet(ignoredEntries.length);
for (String prop : ignoredEntries) {
result.add(prop);
}
return result;
}
/*
/**********************************************************
/* JsonSerializer implementation
/**********************************************************
*/
@Override
public void serialize(Map,?> value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonGenerationException
{
jgen.writeStartObject();
if (!value.isEmpty()) {
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 (_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.
*/
protected 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 keySerializer = _keySerializer;
final HashSet ignored = _ignoredEntries;
final boolean skipNulls = !provider.isEnabled(SerializationConfig.Feature.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.getNullKeySerializer().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 serializer = serializers.serializerFor(cc);
if (serializer == null) {
if (_valueType.hasGenericTypes()) {
serializer = _findAndAddDynamic(serializers, _valueType.forcedNarrowBy(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 ser)
throws IOException, JsonGenerationException
{
final JsonSerializer keySerializer = _keySerializer;
final HashSet ignored = _ignoredEntries;
final TypeSerializer typeSer = _valueTypeSerializer;
final boolean skipNulls = !provider.isEnabled(SerializationConfig.Feature.WRITE_NULL_MAP_VALUES);
for (Map.Entry,?> entry : value.entrySet()) {
Object valueElem = entry.getValue();
Object keyElem = entry.getKey();
if (keyElem == null) {
provider.getNullKeySerializer().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 keySerializer = _keySerializer;
JsonSerializer prevValueSerializer = null;
Class> prevValueClass = null;
final HashSet ignored = _ignoredEntries;
final boolean skipNulls = !provider.isEnabled(SerializationConfig.Feature.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.getNullKeySerializer().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 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;
}
/**
* Need to get callback to resolve value serializer, if static typing
* is used (either being forced, or because value type is final)
*/
public void resolve(SerializerProvider provider)
throws JsonMappingException
{
if (_valueTypeIsStatic && _valueSerializer == null) {
_valueSerializer = provider.findValueSerializer(_valueType, _property);
}
/* 10-Dec-2010, tatu: Let's also fetch key serializer; and always assume we'll
* do that just by using static type information
*/
/* 25-Feb-2011, tatu: May need to reconsider this static checking (since it
* differs from value handling)... but for now, it's ok to ensure contextual
* aspects are handled; this is done by provider.
*/
if (_keySerializer == null) {
_keySerializer = provider.findKeySerializer(_keyType, _property);
}
}
/*
/**********************************************************
/* Internal methods
/**********************************************************
*/
protected final JsonSerializer _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 _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;
}
}