Merge branch '2.5'
diff --git a/release-notes/VERSION b/release-notes/VERSION
index adc94b5..850bbb3 100644
--- a/release-notes/VERSION
+++ b/release-notes/VERSION
@@ -18,6 +18,8 @@
(reported by Antibrumm@github)
#703: Multiple calls to ObjectMapper#canSerialize(Object.class) returns different values
(reported by flexfrank@github)
+#705: JsonAnyGetter doesn't work with JsonSerialize (except with keyUsing)
+ (reported by natnan@github)
2.5.1 (06-Feb-2015)
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/AnyGetterWriter.java b/src/main/java/com/fasterxml/jackson/databind/ser/AnyGetterWriter.java
index 3bfa7b2..9fe4077 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/AnyGetterWriter.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/AnyGetterWriter.java
@@ -20,18 +20,24 @@
* Method (or field) that represents the "any getter"
*/
protected final AnnotatedMember _accessor;
+
+ protected JsonSerializer<Object> _serializer;
+
+ protected MapSerializer _mapSerializer;
- protected MapSerializer _serializer;
-
+ @SuppressWarnings("unchecked")
public AnyGetterWriter(BeanProperty property,
- AnnotatedMember accessor, MapSerializer serializer)
+ AnnotatedMember accessor, JsonSerializer<?> serializer)
{
_accessor = accessor;
_property = property;
- _serializer = serializer;
+ _serializer = (JsonSerializer<Object>) serializer;
+ if (serializer instanceof MapSerializer) {
+ _mapSerializer = (MapSerializer) serializer;
+ }
}
- public void getAndSerialize(Object bean, JsonGenerator jgen, SerializerProvider provider)
+ public void getAndSerialize(Object bean, JsonGenerator gen, SerializerProvider provider)
throws Exception
{
Object value = _accessor.getValue(bean);
@@ -42,32 +48,49 @@
throw new JsonMappingException("Value returned by 'any-getter' ("
+_accessor.getName()+"()) not java.util.Map but "+value.getClass().getName());
}
- _serializer.serializeFields((Map<?,?>) value, jgen, provider);
+ // 23-Feb-2015, tatu: Nasty, but has to do (for now)
+ if (_mapSerializer != null) {
+ _mapSerializer.serializeFields((Map<?,?>) value, gen, provider);
+ return;
+ }
+ _serializer.serialize(value, gen, provider);
}
/**
* @since 2.3
*/
- public void getAndFilter(Object bean, JsonGenerator jgen, SerializerProvider provider,
+ public void getAndFilter(Object bean, JsonGenerator gen, SerializerProvider provider,
PropertyFilter filter)
- throws Exception
- {
- Object value = _accessor.getValue(bean);
- if (value == null) {
- return;
- }
- if (!(value instanceof Map<?,?>)) {
- throw new JsonMappingException("Value returned by 'any-getter' ("
- +_accessor.getName()+"()) not java.util.Map but "+value.getClass().getName());
- }
- // 19-Oct-2014, tatu: Should we try to support @JsonInclude options here?
- _serializer.serializeFilteredFields((Map<?,?>) value, jgen, provider, filter, null);
+ throws Exception
+ {
+ Object value = _accessor.getValue(bean);
+ if (value == null) {
+ return;
}
+ if (!(value instanceof Map<?,?>)) {
+ throw new JsonMappingException("Value returned by 'any-getter' ("
+ +_accessor.getName()+"()) not java.util.Map but "+value.getClass().getName());
+ }
+ // 19-Oct-2014, tatu: Should we try to support @JsonInclude options here?
+ if (_mapSerializer != null) {
+ _mapSerializer.serializeFilteredFields((Map<?,?>) value, gen, provider, filter, null);
+ return;
+ }
+ // ... not sure how custom handler would do it
+ _serializer.serialize(value, gen, provider);
+ }
// Note: NOT part of ResolvableSerializer...
+ @SuppressWarnings("unchecked")
public void resolve(SerializerProvider provider) throws JsonMappingException
{
// 05-Sep-2013, tatu: I _think_ this can be considered a primary property...
- _serializer = (MapSerializer) provider.handlePrimaryContextualization(_serializer, _property);
+ if (_serializer instanceof ContextualSerializer) {
+ JsonSerializer<?> ser = provider.handlePrimaryContextualization(_serializer, _property);
+ _serializer = (JsonSerializer<Object>) ser;
+ if (ser instanceof MapSerializer) {
+ _mapSerializer = (MapSerializer) ser;
+ }
+ }
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerFactory.java
index 7049d59..83e7373 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerFactory.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerFactory.java
@@ -391,18 +391,22 @@
JavaType valueType = type.getContentType();
TypeSerializer typeSer = createTypeSerializer(config, valueType);
// last 2 nulls; don't know key, value serializers (yet)
- // TODO: support '@JsonIgnoreProperties' with any setter?
- MapSerializer mapSer = MapSerializer.construct(/* ignored props*/ null, type, staticTyping,
- typeSer, null, null, /*filterId*/ null);
+ // 23-Feb-2015, tatu: As per [#705], need to support custom serializers
+ JsonSerializer<?> anySer = findSerializerFromAnnotation(prov, anyGetter);
+ if (anySer == null) {
+ // TODO: support '@JsonIgnoreProperties' with any setter?
+ anySer = MapSerializer.construct(/* ignored props*/ null, type, staticTyping,
+ typeSer, null, null, /*filterId*/ null);
+ }
// TODO: can we find full PropertyName?
PropertyName name = PropertyName.construct(anyGetter.getName());
BeanProperty.Std anyProp = new BeanProperty.Std(name, valueType, null,
beanDesc.getClassAnnotations(), anyGetter, PropertyMetadata.STD_OPTIONAL);
- builder.setAnyGetter(new AnyGetterWriter(anyProp, anyGetter, mapSer));
+ builder.setAnyGetter(new AnyGetterWriter(anyProp, anyGetter, anySer));
}
// Next: need to gather view information, if any:
processViews(config, builder);
-
+
// Finally: let interested parties mess with the result bit more...
if (_factoryConfig.hasSerializerModifiers()) {
for (BeanSerializerModifier mod : _factoryConfig.serializerModifiers()) {
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerFactory.java.orig b/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerFactory.java.orig
new file mode 100644
index 0000000..1712a1a
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerFactory.java.orig
@@ -0,0 +1,735 @@
+package com.fasterxml.jackson.databind.ser;
+
+import java.util.*;
+
+import com.fasterxml.jackson.annotation.ObjectIdGenerator;
+import com.fasterxml.jackson.annotation.ObjectIdGenerators;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.cfg.SerializerFactoryConfig;
+import com.fasterxml.jackson.databind.introspect.*;
+import com.fasterxml.jackson.databind.jsontype.NamedType;
+import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
+import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
+import com.fasterxml.jackson.databind.ser.impl.FilteredBeanPropertyWriter;
+import com.fasterxml.jackson.databind.ser.impl.ObjectIdWriter;
+import com.fasterxml.jackson.databind.ser.impl.PropertyBasedObjectIdGenerator;
+import com.fasterxml.jackson.databind.ser.std.MapSerializer;
+import com.fasterxml.jackson.databind.ser.std.StdDelegatingSerializer;
+import com.fasterxml.jackson.databind.type.*;
+import com.fasterxml.jackson.databind.util.ArrayBuilders;
+import com.fasterxml.jackson.databind.util.ClassUtil;
+import com.fasterxml.jackson.databind.util.Converter;
+
+/**
+ * Factory class that can provide serializers for any regular Java beans
+ * (as defined by "having at least one get method recognizable as bean
+ * accessor" -- where {@link Object#getClass} does not count);
+ * as well as for "standard" JDK types. Latter is achieved
+ * by delegating calls to {@link BasicSerializerFactory}
+ * to find serializers both for "standard" JDK types (and in some cases,
+ * sub-classes as is the case for collection classes like
+ * {@link java.util.List}s and {@link java.util.Map}s) and bean (value)
+ * classes.
+ *<p>
+ * Note about delegating calls to {@link BasicSerializerFactory}:
+ * although it would be nicer to use linear delegation
+ * for construction (to essentially dispatch all calls first to the
+ * underlying {@link BasicSerializerFactory}; or alternatively after
+ * failing to provide bean-based serializer}, there is a problem:
+ * priority levels for detecting standard types are mixed. That is,
+ * we want to check if a type is a bean after some of "standard" JDK
+ * types, but before the rest.
+ * As a result, "mixed" delegation used, and calls are NOT done using
+ * regular {@link SerializerFactory} interface but rather via
+ * direct calls to {@link BasicSerializerFactory}.
+ *<p>
+ * Finally, since all caching is handled by the serializer provider
+ * (not factory) and there is no configurability, this
+ * factory is stateless.
+ * This means that a global singleton instance can be used.
+ */
+public class BeanSerializerFactory
+ extends BasicSerializerFactory
+ implements java.io.Serializable // since 2.1
+{
+ private static final long serialVersionUID = 1;
+
+ /**
+ * Like {@link BasicSerializerFactory}, this factory is stateless, and
+ * thus a single shared global (== singleton) instance can be used
+ * without thread-safety issues.
+ */
+ public final static BeanSerializerFactory instance = new BeanSerializerFactory(null);
+
+ /*
+ /**********************************************************
+ /* Life-cycle: creation, configuration
+ /**********************************************************
+ */
+
+ /**
+ * Constructor for creating instances with specified configuration.
+ */
+ protected BeanSerializerFactory(SerializerFactoryConfig config)
+ {
+ super(config);
+ }
+
+ /**
+ * Method used by module registration functionality, to attach additional
+ * serializer providers into this serializer factory. This is typically
+ * handled by constructing a new instance with additional serializers,
+ * to ensure thread-safe access.
+ */
+ @Override
+ public SerializerFactory withConfig(SerializerFactoryConfig config)
+ {
+ if (_factoryConfig == config) {
+ return this;
+ }
+ /* 22-Nov-2010, tatu: Handling of subtypes is tricky if we do immutable-with-copy-ctor;
+ * and we pretty much have to here either choose between losing subtype instance
+ * when registering additional serializers, or losing serializers.
+ * Instead, let's actually just throw an error if this method is called when subtype
+ * has not properly overridden this method; this to indicate problem as soon as possible.
+ */
+ if (getClass() != BeanSerializerFactory.class) {
+ throw new IllegalStateException("Subtype of BeanSerializerFactory ("+getClass().getName()
+ +") has not properly overridden method 'withAdditionalSerializers': can not instantiate subtype with "
+ +"additional serializer definitions");
+ }
+ return new BeanSerializerFactory(config);
+ }
+
+ @Override
+ protected Iterable<Serializers> customSerializers() {
+ return _factoryConfig.serializers();
+ }
+
+ /*
+ /**********************************************************
+ /* SerializerFactory impl
+ /**********************************************************
+ */
+
+ /**
+ * Main serializer constructor method. We will have to be careful
+ * with respect to ordering of various method calls: essentially
+ * we want to reliably figure out which classes are standard types,
+ * and which are beans. The problem is that some bean Classes may
+ * implement standard interfaces (say, {@link java.lang.Iterable}.
+ *<p>
+ * Note: sub-classes may choose to complete replace implementation,
+ * if they want to alter priority of serializer lookups.
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public JsonSerializer<Object> createSerializer(SerializerProvider prov,
+ JavaType origType)
+ throws JsonMappingException
+ {
+ // Very first thing, let's check if there is explicit serializer annotation:
+ final SerializationConfig config = prov.getConfig();
+ BeanDescription beanDesc = config.introspect(origType);
+ JsonSerializer<?> ser = findSerializerFromAnnotation(prov, beanDesc.getClassInfo());
+ if (ser != null) {
+ return (JsonSerializer<Object>) ser;
+ }
+ boolean staticTyping;
+ // Next: we may have annotations that further define types to use...
+ JavaType type = modifyTypeByAnnotation(config, beanDesc.getClassInfo(), origType);
+ if (type == origType) { // no changes, won't force static typing
+ staticTyping = false;
+ } else { // changes; assume static typing; plus, need to re-introspect if class differs
+ staticTyping = true;
+ if (!type.hasRawClass(origType.getRawClass())) {
+ beanDesc = config.introspect(type);
+ }
+ }
+ // Slight detour: do we have a Converter to consider?
+ Converter<Object,Object> conv = beanDesc.findSerializationConverter();
+ if (conv == null) { // no, simple
+ return (JsonSerializer<Object>) _createSerializer2(prov, type, beanDesc, staticTyping);
+ }
+ JavaType delegateType = conv.getOutputType(prov.getTypeFactory());
+
+ // One more twist, as per [Issue#288]; probably need to get new BeanDesc
+ if (!delegateType.hasRawClass(type.getRawClass())) {
+ beanDesc = config.introspect(delegateType);
+ // [#359]: explicitly check (again) for @JsonSerializer...
+ ser = findSerializerFromAnnotation(prov, beanDesc.getClassInfo());
+ }
+ if (ser == null) {
+ ser = _createSerializer2(prov, delegateType, beanDesc, true);
+ }
+ return new StdDelegatingSerializer(conv, delegateType, ser);
+ }
+
+ protected JsonSerializer<?> _createSerializer2(SerializerProvider prov,
+ JavaType type, BeanDescription beanDesc, boolean staticTyping)
+ throws JsonMappingException
+ {
+ // Then JsonSerializable, @JsonValue etc:
+ JsonSerializer<?> ser = findSerializerByAnnotations(prov, type, beanDesc);
+ if (ser != null) {
+ return ser;
+ }
+ final SerializationConfig config = prov.getConfig();
+
+ // Container types differ from non-container types
+ // (note: called method checks for module-provided serializers)
+ if (type.isContainerType()) {
+ if (!staticTyping) {
+ staticTyping = usesStaticTyping(config, beanDesc, null);
+ // [Issue#23]: Need to figure out how to force passed parameterization
+ // to stick...
+ /*
+ if (property == null) {
+ JavaType t = origType.getContentType();
+ if (t != null && !t.hasRawClass(Object.class)) {
+ staticTyping = true;
+ }
+ }
+ */
+ }
+ // 03-Aug-2012, tatu: As per [Issue#40], may require POJO serializer...
+ ser = buildContainerSerializer(prov, type, beanDesc, staticTyping);
+ // Will return right away, since called method does post-processing:
+ if (ser != null) {
+ return ser;
+ }
+ } else {
+ // Modules may provide serializers of POJO types:
+ for (Serializers serializers : customSerializers()) {
+ ser = serializers.findSerializer(config, type, beanDesc);
+ if (ser != null) {
+ break;
+ }
+ }
+ }
+
+ // Otherwise, we will check "primary types"; both marker types that
+ // indicate specific handling (JsonSerializable), or main types that have
+ // precedence over container types
+ if (ser == null) {
+ ser = findSerializerByLookup(type, config, beanDesc, staticTyping);
+ if (ser == null) {
+ ser = findSerializerByPrimaryType(prov, type, beanDesc, staticTyping);
+ if (ser == null) {
+ // And this is where this class comes in: if type is not a
+ // known "primary JDK type", perhaps it's a bean? We can still
+ // get a null, if we can't find a single suitable bean property.
+ ser = findBeanSerializer(prov, type, beanDesc);
+ // Finally: maybe we can still deal with it as an implementation of some basic JDK interface?
+ if (ser == null) {
+ ser = findSerializerByAddonType(config, type, beanDesc, staticTyping);
+ // 18-Sep-2014, tatu: Actually, as per [jackson-databind#539], need to get
+ // 'unknown' serializer assigned earlier, here, so that it gets properly
+ // post-processed
+ if (ser == null) {
+ ser = prov.getUnknownTypeSerializer(beanDesc.getBeanClass());
+ }
+ }
+ }
+ }
+ }
+ if (ser != null) {
+ // [Issue#120]: Allow post-processing
+ if (_factoryConfig.hasSerializerModifiers()) {
+ for (BeanSerializerModifier mod : _factoryConfig.serializerModifiers()) {
+ ser = mod.modifySerializer(config, beanDesc, ser);
+ }
+ }
+ }
+ return ser;
+ }
+
+ /*
+ /**********************************************************
+ /* Other public methods that are not part of
+ /* JsonSerializerFactory API
+ /**********************************************************
+ */
+
+ /**
+ * Method that will try to construct a {@link BeanSerializer} for
+ * given class. Returns null if no properties are found.
+ */
+ public JsonSerializer<Object> findBeanSerializer(SerializerProvider prov, JavaType type, BeanDescription beanDesc)
+ throws JsonMappingException
+ {
+ // First things first: we know some types are not beans...
+ if (!isPotentialBeanType(type.getRawClass())) {
+ // 03-Aug-2012, tatu: Except we do need to allow serializers for Enums,
+ // as per [Issue#24]
+ if (!type.isEnumType()) {
+ return null;
+ }
+ }
+ return constructBeanSerializer(prov, beanDesc);
+ }
+
+ /**
+ * Method called to create a type information serializer for values of given
+ * non-container property
+ * if one is needed. If not needed (no polymorphic handling configured), should
+ * return null.
+ *
+ * @param baseType Declared type to use as the base type for type information serializer
+ *
+ * @return Type serializer to use for property values, if one is needed; null if not.
+ */
+ public TypeSerializer findPropertyTypeSerializer(JavaType baseType,
+ SerializationConfig config, AnnotatedMember accessor)
+ throws JsonMappingException
+ {
+ AnnotationIntrospector ai = config.getAnnotationIntrospector();
+ TypeResolverBuilder<?> b = ai.findPropertyTypeResolver(config, accessor, baseType);
+ // Defaulting: if no annotations on member, check value class
+ if (b == null) {
+ return createTypeSerializer(config, baseType);
+ }
+ Collection<NamedType> subtypes = config.getSubtypeResolver().collectAndResolveSubtypes(
+ accessor, config, ai, baseType);
+ return b.buildTypeSerializer(config, baseType, subtypes);
+ }
+
+ /**
+ * Method called to create a type information serializer for values of given
+ * container property
+ * if one is needed. If not needed (no polymorphic handling configured), should
+ * return null.
+ *
+ * @param containerType Declared type of the container to use as the base type for type information serializer
+ *
+ * @return Type serializer to use for property value contents, if one is needed; null if not.
+ */
+ public TypeSerializer findPropertyContentTypeSerializer(JavaType containerType,
+ SerializationConfig config, AnnotatedMember accessor)
+ throws JsonMappingException
+ {
+ JavaType contentType = containerType.getContentType();
+ AnnotationIntrospector ai = config.getAnnotationIntrospector();
+ TypeResolverBuilder<?> b = ai.findPropertyContentTypeResolver(config, accessor, containerType);
+ // Defaulting: if no annotations on member, check value class
+ if (b == null) {
+ return createTypeSerializer(config, contentType);
+ }
+ Collection<NamedType> subtypes = config.getSubtypeResolver().collectAndResolveSubtypes(accessor,
+ config, ai, contentType);
+ return b.buildTypeSerializer(config, contentType, subtypes);
+ }
+
+ /*
+ /**********************************************************
+ /* Overridable non-public factory methods
+ /**********************************************************
+ */
+
+ /**
+ * Method called to construct serializer for serializing specified bean type.
+ *
+ * @since 2.1
+ */
+ @SuppressWarnings("unchecked")
+ protected JsonSerializer<Object> constructBeanSerializer(SerializerProvider prov,
+ BeanDescription beanDesc)
+ throws JsonMappingException
+ {
+ // 13-Oct-2010, tatu: quick sanity check: never try to create bean serializer for plain Object
+ // 05-Jul-2012, tatu: ... but we should be able to just return "unknown type" serializer, right?
+ if (beanDesc.getBeanClass() == Object.class) {
+ return prov.getUnknownTypeSerializer(Object.class);
+// throw new IllegalArgumentException("Can not create bean serializer for Object.class");
+ }
+ final SerializationConfig config = prov.getConfig();
+ BeanSerializerBuilder builder = constructBeanSerializerBuilder(beanDesc);
+ builder.setConfig(config);
+
+ // First: any detectable (auto-detect, annotations) properties to serialize?
+ List<BeanPropertyWriter> props = findBeanProperties(prov, beanDesc, builder);
+ if (props == null) {
+ props = new ArrayList<BeanPropertyWriter>();
+ }
+ // [databind#638]: Allow injection of "virtual" properties:
+ prov.getAnnotationIntrospector().findAndAddVirtualProperties(config, beanDesc.getClassInfo(), props);
+
+ // [JACKSON-440] Need to allow modification bean properties to serialize:
+ if (_factoryConfig.hasSerializerModifiers()) {
+ for (BeanSerializerModifier mod : _factoryConfig.serializerModifiers()) {
+ props = mod.changeProperties(config, beanDesc, props);
+ }
+ }
+
+ // Any properties to suppress?
+ props = filterBeanProperties(config, beanDesc, props);
+
+ // [JACKSON-440] Need to allow reordering of properties to serialize
+ if (_factoryConfig.hasSerializerModifiers()) {
+ for (BeanSerializerModifier mod : _factoryConfig.serializerModifiers()) {
+ props = mod.orderProperties(config, beanDesc, props);
+ }
+ }
+
+ /* And if Object Id is needed, some preparation for that as well: better
+ * do before view handling, mostly for the custom id case which needs
+ * access to a property
+ */
+ builder.setObjectIdWriter(constructObjectIdHandler(prov, beanDesc, props));
+
+ builder.setProperties(props);
+ builder.setFilterId(findFilterId(config, beanDesc));
+
+ AnnotatedMember anyGetter = beanDesc.findAnyGetter();
+ if (anyGetter != null) {
+ if (config.canOverrideAccessModifiers()) {
+ anyGetter.fixAccess();
+ }
+ JavaType type = anyGetter.getType(beanDesc.bindingsForBeanType());
+ // copied from BasicSerializerFactory.buildMapSerializer():
+ boolean staticTyping = config.isEnabled(MapperFeature.USE_STATIC_TYPING);
+ JavaType valueType = type.getContentType();
+ TypeSerializer typeSer = createTypeSerializer(config, valueType);
+ // last 2 nulls; don't know key, value serializers (yet)
+ // TODO: support '@JsonIgnoreProperties' with any setter?
+ MapSerializer mapSer = MapSerializer.construct(/* ignored props*/ null, type, staticTyping,
+ typeSer, null, null, /*filterId*/ null);
+ // TODO: can we find full PropertyName?
+ PropertyName name = new PropertyName(anyGetter.getName());
+ BeanProperty.Std anyProp = new BeanProperty.Std(name, valueType, null,
+ beanDesc.getClassAnnotations(), anyGetter, PropertyMetadata.STD_OPTIONAL);
+ builder.setAnyGetter(new AnyGetterWriter(anyProp, anyGetter, mapSer));
+ }
+ // Next: need to gather view information, if any:
+ processViews(config, builder);
+
+ // Finally: let interested parties mess with the result bit more...
+ if (_factoryConfig.hasSerializerModifiers()) {
+ for (BeanSerializerModifier mod : _factoryConfig.serializerModifiers()) {
+ builder = mod.updateBuilder(config, beanDesc, builder);
+ }
+ }
+
+ JsonSerializer<Object> ser = (JsonSerializer<Object>) builder.build();
+
+ if (ser == null) {
+ // If we get this far, there were no properties found, so no regular BeanSerializer
+ // would be constructed. But, couple of exceptions.
+ // First: if there are known annotations, just create 'empty bean' serializer
+ if (beanDesc.hasKnownClassAnnotations()) {
+ return builder.createDummy();
+ }
+ }
+ return ser;
+ }
+
+ protected ObjectIdWriter constructObjectIdHandler(SerializerProvider prov,
+ BeanDescription beanDesc, List<BeanPropertyWriter> props)
+ throws JsonMappingException
+ {
+ ObjectIdInfo objectIdInfo = beanDesc.getObjectIdInfo();
+ if (objectIdInfo == null) {
+ return null;
+ }
+ ObjectIdGenerator<?> gen;
+ Class<?> implClass = objectIdInfo.getGeneratorType();
+
+ // Just one special case: Property-based generator is trickier
+ if (implClass == ObjectIdGenerators.PropertyGenerator.class) { // most special one, needs extra work
+ String propName = objectIdInfo.getPropertyName().getSimpleName();
+ BeanPropertyWriter idProp = null;
+
+ for (int i = 0, len = props.size() ;; ++i) {
+ if (i == len) {
+ throw new IllegalArgumentException("Invalid Object Id definition for "+beanDesc.getBeanClass().getName()
+ +": can not find property with name '"+propName+"'");
+ }
+ BeanPropertyWriter prop = props.get(i);
+ if (propName.equals(prop.getName())) {
+ idProp = prop;
+ /* Let's force it to be the first property to output
+ * (although it may still get rearranged etc)
+ */
+ if (i > 0) {
+ props.remove(i);
+ props.add(0, idProp);
+ }
+ break;
+ }
+ }
+ JavaType idType = idProp.getType();
+ gen = new PropertyBasedObjectIdGenerator(objectIdInfo, idProp);
+ // one more thing: must ensure that ObjectIdWriter does not actually write the value:
+ return ObjectIdWriter.construct(idType, (PropertyName) null, gen, objectIdInfo.getAlwaysAsId());
+
+ }
+ // other types are simpler
+ JavaType type = prov.constructType(implClass);
+ // Could require type to be passed explicitly, but we should be able to find it too:
+ JavaType idType = prov.getTypeFactory().findTypeParameters(type, ObjectIdGenerator.class)[0];
+ gen = prov.objectIdGeneratorInstance(beanDesc.getClassInfo(), objectIdInfo);
+ return ObjectIdWriter.construct(idType, objectIdInfo.getPropertyName(), gen,
+ objectIdInfo.getAlwaysAsId());
+ }
+
+ /**
+ * Method called to construct a filtered writer, for given view
+ * definitions. Default implementation constructs filter that checks
+ * active view type to views property is to be included in.
+ */
+ protected BeanPropertyWriter constructFilteredBeanWriter(BeanPropertyWriter writer,
+ Class<?>[] inViews)
+ {
+ return FilteredBeanPropertyWriter.constructViewBased(writer, inViews);
+ }
+
+ protected PropertyBuilder constructPropertyBuilder(SerializationConfig config,
+ BeanDescription beanDesc)
+ {
+ return new PropertyBuilder(config, beanDesc);
+ }
+
+ protected BeanSerializerBuilder constructBeanSerializerBuilder(BeanDescription beanDesc) {
+ return new BeanSerializerBuilder(beanDesc);
+ }
+
+ /*
+ /**********************************************************
+ /* Overridable non-public introspection methods
+ /**********************************************************
+ */
+
+ /**
+ * Helper method used to skip processing for types that we know
+ * can not be (i.e. are never consider to be) beans:
+ * things like primitives, Arrays, Enums, and proxy types.
+ *<p>
+ * Note that usually we shouldn't really be getting these sort of
+ * types anyway; but better safe than sorry.
+ */
+ protected boolean isPotentialBeanType(Class<?> type)
+ {
+ return (ClassUtil.canBeABeanType(type) == null) && !ClassUtil.isProxyType(type);
+ }
+
+ /**
+ * Method used to collect all actual serializable properties.
+ * Can be overridden to implement custom detection schemes.
+ */
+ protected List<BeanPropertyWriter> findBeanProperties(SerializerProvider prov,
+ BeanDescription beanDesc, BeanSerializerBuilder builder)
+ throws JsonMappingException
+ {
+ List<BeanPropertyDefinition> properties = beanDesc.findProperties();
+ final SerializationConfig config = prov.getConfig();
+
+ // [JACKSON-429]: ignore specified types
+ removeIgnorableTypes(config, beanDesc, properties);
+
+ // and possibly remove ones without matching mutator...
+ if (config.isEnabled(MapperFeature.REQUIRE_SETTERS_FOR_GETTERS)) {
+ removeSetterlessGetters(config, beanDesc, properties);
+ }
+
+ // nothing? can't proceed (caller may or may not throw an exception)
+ if (properties.isEmpty()) {
+ return null;
+ }
+ // null is for value type serializer, which we don't have access to from here (ditto for bean prop)
+ boolean staticTyping = usesStaticTyping(config, beanDesc, null);
+ PropertyBuilder pb = constructPropertyBuilder(config, beanDesc);
+
+ ArrayList<BeanPropertyWriter> result = new ArrayList<BeanPropertyWriter>(properties.size());
+ TypeBindings typeBind = beanDesc.bindingsForBeanType();
+ for (BeanPropertyDefinition property : properties) {
+ final AnnotatedMember accessor = property.getAccessor();
+ // [JACKSON-762]: type id? Requires special handling:
+ if (property.isTypeId()) {
+ if (accessor != null) { // only add if we can access... but otherwise?
+ if (config.canOverrideAccessModifiers()) {
+ accessor.fixAccess();
+ }
+ builder.setTypeId(accessor);
+ }
+ continue;
+ }
+ // [JACKSON-235]: suppress writing of back references
+ AnnotationIntrospector.ReferenceProperty refType = property.findReferenceType();
+ if (refType != null && refType.isBackReference()) {
+ continue;
+ }
+ if (accessor instanceof AnnotatedMethod) {
+ result.add(_constructWriter(prov, property, typeBind, pb, staticTyping, (AnnotatedMethod) accessor));
+ } else {
+ result.add(_constructWriter(prov, property, typeBind, pb, staticTyping, (AnnotatedField) accessor));
+ }
+ }
+ return result;
+ }
+
+ /*
+ /**********************************************************
+ /* Overridable non-public methods for manipulating bean properties
+ /**********************************************************
+ */
+
+ /**
+ * Overridable method that can filter out properties. Default implementation
+ * checks annotations class may have.
+ */
+ protected List<BeanPropertyWriter> filterBeanProperties(SerializationConfig config,
+ BeanDescription beanDesc, List<BeanPropertyWriter> props)
+ {
+ AnnotationIntrospector intr = config.getAnnotationIntrospector();
+ AnnotatedClass ac = beanDesc.getClassInfo();
+ String[] ignored = intr.findPropertiesToIgnore(ac);
+ if (ignored != null && ignored.length > 0) {
+ HashSet<String> ignoredSet = ArrayBuilders.arrayToSet(ignored);
+ Iterator<BeanPropertyWriter> it = props.iterator();
+ while (it.hasNext()) {
+ if (ignoredSet.contains(it.next().getName())) {
+ it.remove();
+ }
+ }
+ }
+ return props;
+ }
+
+ /**
+ * Method called to handle view information for constructed serializer,
+ * based on bean property writers.
+ *<p>
+ * Note that this method is designed to be overridden by sub-classes
+ * if they want to provide custom view handling. As such it is not
+ * considered an internal implementation detail, and will be supported
+ * as part of API going forward.
+ */
+ protected void processViews(SerializationConfig config, BeanSerializerBuilder builder)
+ {
+ // [JACKSON-232]: whether non-annotated fields are included by default or not is configurable
+ List<BeanPropertyWriter> props = builder.getProperties();
+ boolean includeByDefault = config.isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION);
+ final int propCount = props.size();
+ int viewsFound = 0;
+ BeanPropertyWriter[] filtered = new BeanPropertyWriter[propCount];
+ // Simple: view information is stored within individual writers, need to combine:
+ for (int i = 0; i < propCount; ++i) {
+ BeanPropertyWriter bpw = props.get(i);
+ Class<?>[] views = bpw.getViews();
+ if (views == null) { // no view info? include or exclude by default?
+ if (includeByDefault) {
+ filtered[i] = bpw;
+ }
+ } else {
+ ++viewsFound;
+ filtered[i] = constructFilteredBeanWriter(bpw, views);
+ }
+ }
+ // minor optimization: if no view info, include-by-default, can leave out filtering info altogether:
+ if (includeByDefault && viewsFound == 0) {
+ return;
+ }
+ builder.setFilteredProperties(filtered);
+ }
+
+ /**
+ * Method that will apply by-type limitations (as per [JACKSON-429]);
+ * by default this is based on {@link com.fasterxml.jackson.annotation.JsonIgnoreType}
+ * annotation but can be supplied by module-provided introspectors too.
+ */
+ protected void removeIgnorableTypes(SerializationConfig config, BeanDescription beanDesc,
+ List<BeanPropertyDefinition> properties)
+ {
+ AnnotationIntrospector intr = config.getAnnotationIntrospector();
+ HashMap<Class<?>,Boolean> ignores = new HashMap<Class<?>,Boolean>();
+ Iterator<BeanPropertyDefinition> it = properties.iterator();
+ while (it.hasNext()) {
+ BeanPropertyDefinition property = it.next();
+ AnnotatedMember accessor = property.getAccessor();
+ if (accessor == null) {
+ it.remove();
+ continue;
+ }
+ Class<?> type = accessor.getRawType();
+ Boolean result = ignores.get(type);
+ if (result == null) {
+ BeanDescription desc = config.introspectClassAnnotations(type);
+ AnnotatedClass ac = desc.getClassInfo();
+ result = intr.isIgnorableType(ac);
+ // default to false, non-ignorable
+ if (result == null) {
+ result = Boolean.FALSE;
+ }
+ ignores.put(type, result);
+ }
+ // lotsa work, and yes, it is ignorable type, so:
+ if (result.booleanValue()) {
+ it.remove();
+ }
+ }
+ }
+
+ /**
+ * Helper method that will remove all properties that do not have a mutator.
+ */
+ protected void removeSetterlessGetters(SerializationConfig config, BeanDescription beanDesc,
+ List<BeanPropertyDefinition> properties)
+ {
+ Iterator<BeanPropertyDefinition> it = properties.iterator();
+ while (it.hasNext()) {
+ BeanPropertyDefinition property = it.next();
+ // one caveat: as per [JACKSON-806], only remove implicit properties;
+ // explicitly annotated ones should remain
+ if (!property.couldDeserialize() && !property.isExplicitlyIncluded()) {
+ it.remove();
+ }
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Internal helper methods
+ /**********************************************************
+ */
+
+ /**
+ * Secondary helper method for constructing {@link BeanPropertyWriter} for
+ * given member (field or method).
+ */
+ protected BeanPropertyWriter _constructWriter(SerializerProvider prov,
+ BeanPropertyDefinition propDef, TypeBindings typeContext,
+ PropertyBuilder pb, boolean staticTyping, AnnotatedMember accessor)
+ throws JsonMappingException
+ {
+ final PropertyName name = propDef.getFullName();
+ if (prov.canOverrideAccessModifiers()) {
+ accessor.fixAccess();
+ }
+ JavaType type = accessor.getType(typeContext);
+ BeanProperty.Std property = new BeanProperty.Std(name, type, propDef.getWrapperName(),
+ pb.getClassAnnotations(), accessor, propDef.getMetadata());
+
+ // Does member specify a serializer? If so, let's use it.
+ JsonSerializer<?> annotatedSerializer = findSerializerFromAnnotation(prov,
+ accessor);
+ /* 02-Feb-2012, tatu: Unlike most other code paths, serializer produced
+ * here will NOT be resolved or contextualized, unless done here, so:
+ */
+ if (annotatedSerializer instanceof ResolvableSerializer) {
+ ((ResolvableSerializer) annotatedSerializer).resolve(prov);
+ }
+ // 05-Sep-2013, tatu: should be primary property serializer so:
+ annotatedSerializer = prov.handlePrimaryContextualization(annotatedSerializer, property);
+ // And how about polymorphic typing? First special to cover JAXB per-field settings:
+ TypeSerializer contentTypeSer = null;
+ // 16-Feb-2014, cgc: contentType serializers for collection-like and map-like types
+ if (ClassUtil.isCollectionMapOrArray(type.getRawClass()) || type.isCollectionLikeType() || type.isMapLikeType()) {
+ contentTypeSer = findPropertyContentTypeSerializer(type, prov.getConfig(), accessor);
+ }
+ // and if not JAXB collection/array with annotations, maybe regular type info?
+ TypeSerializer typeSer = findPropertyTypeSerializer(type, prov.getConfig(), accessor);
+ BeanPropertyWriter pbw = pb.buildWriter(prov, propDef, type, annotatedSerializer,
+ typeSer, contentTypeSer, accessor, staticTyping);
+ return pbw;
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerFactory.java.rej b/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerFactory.java.rej
new file mode 100644
index 0000000..c243c36
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerFactory.java.rej
@@ -0,0 +1,36 @@
+***************
+*** 391,404 ****
+ JavaType valueType = type.getContentType();
+ TypeSerializer typeSer = createTypeSerializer(config, valueType);
+ // last 2 nulls; don't know key, value serializers (yet)
+- // TODO: support '@JsonIgnoreProperties' with any setter?
+- MapSerializer mapSer = MapSerializer.construct(/* ignored props*/ null, type, staticTyping,
+- typeSer, null, null, /*filterId*/ null);
+ // TODO: can we find full PropertyName?
+ PropertyName name = PropertyName.construct(anyGetter.getName());
+ BeanProperty.Std anyProp = new BeanProperty.Std(name, valueType, null,
+ beanDesc.getClassAnnotations(), anyGetter, PropertyMetadata.STD_OPTIONAL);
+- builder.setAnyGetter(new AnyGetterWriter(anyProp, anyGetter, mapSer));
+ }
+ // Next: need to gather view information, if any:
+ processViews(config, builder);
+--- 391,409 ----
+ JavaType valueType = type.getContentType();
+ TypeSerializer typeSer = createTypeSerializer(config, valueType);
+ // last 2 nulls; don't know key, value serializers (yet)
++
++ // 23-Feb-2015, tatu: As per [#705], need to support custom serializers
++ JsonSerializer<?> anySer = findSerializerFromAnnotation(prov, anyGetter);
++ if (anySer == null) {
++ // TODO: support '@JsonIgnoreProperties' with any setter?
++ anySer = MapSerializer.construct(/* ignored props*/ null, type, staticTyping,
++ typeSer, null, null, /*filterId*/ null);
++ }
+ // TODO: can we find full PropertyName?
+ PropertyName name = PropertyName.construct(anyGetter.getName());
+ BeanProperty.Std anyProp = new BeanProperty.Std(name, valueType, null,
+ beanDesc.getClassAnnotations(), anyGetter, PropertyMetadata.STD_OPTIONAL);
++ builder.setAnyGetter(new AnyGetterWriter(anyProp, anyGetter, anySer));
+ }
+ // Next: need to gather view information, if any:
+ processViews(config, builder);
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/BeanSerializerBase.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/BeanSerializerBase.java
index fec2318..ee8735c 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/BeanSerializerBase.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/BeanSerializerBase.java
@@ -342,6 +342,7 @@
// also, any-getter may need to be resolved
if (_anyGetterWriter != null) {
+ // 23-Feb-2015, tatu: Misleading, as this actually triggers call to contextualization...
_anyGetterWriter.resolve(provider);
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/TestAnyGetter.java b/src/test/java/com/fasterxml/jackson/databind/ser/TestAnyGetter.java
index 1b855da..cc9998f 100644
--- a/src/test/java/com/fasterxml/jackson/databind/ser/TestAnyGetter.java
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/TestAnyGetter.java
@@ -1,12 +1,17 @@
package com.fasterxml.jackson.databind.ser;
+import java.io.IOException;
import java.util.*;
import com.fasterxml.jackson.annotation.*;
-
+import com.fasterxml.jackson.core.JsonGenerationException;
+import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BaseMapTest;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
public class TestAnyGetter extends BaseMapTest
{
@@ -46,7 +51,43 @@
stuff.put(key, value);
}
}
-
+
+ static class Issue705Bean
+ {
+ protected Map<String,String> stuff;
+
+ public Issue705Bean(String key, String value) {
+ stuff = new LinkedHashMap<String,String>();
+ stuff.put(key, value);
+ }
+
+ @JsonSerialize(using = Issue705Serializer.class)
+// @JsonSerialize(converter = MyConverter.class)
+ @JsonAnyGetter
+ public Map<String, String> getParameters(){
+ return stuff;
+ }
+ }
+
+ @SuppressWarnings("serial")
+ static class Issue705Serializer extends StdSerializer<Object>
+ {
+ public Issue705Serializer() {
+ super(Map.class, false);
+ }
+
+ @Override
+ public void serialize(Object value, JsonGenerator jgen,
+ SerializerProvider provider) throws IOException
+ {
+ StringBuilder sb = new StringBuilder();
+ for (Map.Entry<?,?> entry : ((Map<?,?>) value).entrySet()) {
+ sb.append('[').append(entry.getKey()).append('/').append(entry.getValue()).append(']');
+ }
+ jgen.writeStringField("stuff", sb.toString());
+ }
+ }
+
/*
/**********************************************************
/* Test cases
@@ -90,4 +131,11 @@
assertEquals(aposToQuotes("{'bar':null}"),
MAPPER.writeValueAsString(input));
}
+
+ public void testIssue705() throws Exception
+ {
+ Issue705Bean input = new Issue705Bean("key", "value");
+ String json = MAPPER.writeValueAsString(input);
+ assertEquals("{\"stuff\":\"[key/value]\"}", json);
+ }
}