aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTatu Saloranta <tatu.saloranta@iki.fi>2016-09-30 17:26:12 -0700
committerTatu Saloranta <tatu.saloranta@iki.fi>2016-09-30 17:26:12 -0700
commitcdf8041a29df98179f47f51747df1ce930c0e6c3 (patch)
treeb26a9e6c7ca620b985303b1bf0d466753aa645c1
parent0c4e156cea2a4823bff884d1a4a3010a127ff96f (diff)
downloadjackson-databind-cdf8041a29df98179f47f51747df1ce930c0e6c3.tar.gz
More work on #888; rewriting Map-include handling, adding temporarily test failures
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java24
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java110
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/ser/DefaultSerializerProvider.java19
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/ser/PropertyBuilder.java49
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/ser/std/BeanSerializerBase.java2
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/ser/std/MapSerializer.java351
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/util/BeanUtil.java42
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/filter/JsonIncludeCustomTest.java24
8 files changed, 424 insertions, 197 deletions
diff --git a/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java b/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java
index e8d6cd4db..698aeab24 100644
--- a/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java
+++ b/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java
@@ -930,12 +930,24 @@ public abstract class SerializerProvider
* filter instance,
* given a {@link Class} to instantiate (with default constructor, by default).
*
+ * @param forProperty (optional) If filter is created for a property, that property;
+ * `null` if filter created via defaulting, global or per-type.
+ *
* @since 2.9
*/
public abstract Object includeFilterInstance(BeanPropertyDefinition forProperty,
Class<?> filterClass)
throws JsonMappingException;
+ /**
+ * Follow-up method that may be called after calling {@link #includeFilterInstance},
+ * to check handling of `null` values by the filter.
+ *
+ * @since 2.9
+ */
+ public abstract boolean includeFilterSuppressNulls(Object filter)
+ throws JsonMappingException;
+
/*
/**********************************************************
/* Support for contextualization
@@ -1180,7 +1192,17 @@ public abstract class SerializerProvider
e.initCause(cause);
throw e;
}
-
+
+ /**
+ * @since 2.9
+ */
+ public <T> T reportBadDefinition(Class<?> raw, String msg, Throwable cause)
+ throws JsonMappingException {
+ InvalidDefinitionException e = InvalidDefinitionException.from(getGenerator(), msg, constructType(raw));
+ e.initCause(cause);
+ throw e;
+ }
+
/**
* Helper method called to indicate problem; default behavior is to construct and
* throw a {@link JsonMappingException}, but in future may collect more than one
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java
index 29c4a4900..b44f4dd18 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java
@@ -774,12 +774,7 @@ public abstract class BasicSerializerFactory
MapSerializer mapSer = MapSerializer.construct(ignored,
type, staticTyping, elementTypeSerializer,
keySerializer, elementValueSerializer, filterId);
- Object suppressableValue = findSuppressableContentValue(config,
- type.getContentType(), beanDesc);
- if (suppressableValue != null) {
- mapSer = mapSer.withContentInclusion(suppressableValue);
- }
- ser = mapSer;
+ ser = _checkMapContentInclusion(prov, beanDesc, mapSer);
}
}
// [databind#120]: Allow post-processing
@@ -792,42 +787,95 @@ public abstract class BasicSerializerFactory
}
/**
- *<p>
- * NOTE: although return type is left opaque, it really needs to be
- * <code>JsonInclude.Include</code> for things to work as expected.
+ * Helper method that does figures out content inclusion value to use, if any,
+ * and construct re-configured {@link MapSerializer} appropriately.
*
- * @since 2.5
+ * @since 2.9
*/
- protected Object findSuppressableContentValue(SerializationConfig config,
- JavaType contentType, BeanDescription beanDesc)
+ protected MapSerializer _checkMapContentInclusion(SerializerProvider prov,
+ BeanDescription beanDesc, MapSerializer mapSer)
throws JsonMappingException
{
- /* 16-Apr-2016, tatu: Should this consider possible property-config overrides?
- * Quite possibly yes, but would need to carefully check that content type being
- * used is appropriate.
- */
- JsonInclude.Value inclV = beanDesc.findPropertyInclusion(config.getDefaultPropertyInclusion());
+ final SerializationConfig config = prov.getConfig();
- if (inclV == null) {
- return null;
+ // 30-Sep-2016, tatu: Defaulting gets complicated because we might have two distinct
+ // axis to consider: Map itself, and then value type.
+ // Start with Map-defaults, then use more-specific value override, if any
+
+ // Start by getting global setting, overridden by Map-type-override
+ JsonInclude.Value inclV = config.getDefaultPropertyInclusion(Map.class,
+ config.getDefaultPropertyInclusion());
+ // and then merge content-type overrides, if any. But note that there's
+ // content-to-value inclusion shift we have to do
+ final JavaType contentType = mapSer.getContentType();
+ JsonInclude.Value valueIncl = config.getDefaultPropertyInclusion(contentType.getRawClass(), null);
+
+ if (valueIncl != null) {
+ switch (valueIncl.getValueInclusion()) {
+ case USE_DEFAULTS:
+ break;
+ case CUSTOM:
+ inclV = inclV.withContentFilter(valueIncl.getContentFilter());
+ break;
+ default:
+ inclV = inclV.withContentInclusion(valueIncl.getValueInclusion());
+ }
}
- JsonInclude.Include incl = inclV.getContentInclusion();
- switch (incl) {
- case USE_DEFAULTS: // means "dunno"
- return null;
+ JsonInclude.Include incl = (inclV == null) ? JsonInclude.Include.USE_DEFAULTS : inclV.getContentInclusion();
+ if (incl == JsonInclude.Include.USE_DEFAULTS) {
+ if (!config.isEnabled(SerializationFeature.WRITE_NULL_MAP_VALUES)) {
+ return mapSer.withContentInclusion(null, true);
+ }
+ return mapSer;
+ }
+ // NOTE: mostly copied from `PropertyBuilder`; would be nice to refactor
+ // but code is not identical nor are these types related
+ Object valueToSuppress;
+ boolean suppressNulls;
+
+ switch (inclV.getContentInclusion()) {
case NON_DEFAULT:
- // 19-Oct-2014, tatu: Not sure what this'd mean; so take it to mean "NON_EMPTY"...
- // 11-Nov-2015, tatu: With 2.6, we did indeed revert to "NON_EMPTY", but that did
- // not go well, so with 2.7, we'll do this instead...
- // But not 100% sure if we ought to call new `JsonSerializer.findDefaultValue()`;
- // to do that, would need to locate said serializer
-// incl = JsonInclude.Include.NON_EMPTY;
+ valueToSuppress = BeanUtil.getDefaultValue(contentType);
+ suppressNulls = true;
+ if (valueToSuppress != null) {
+ if (valueToSuppress.getClass().isArray()) {
+ valueToSuppress = ArrayBuilders.getArrayComparator(valueToSuppress);
+ }
+ }
break;
+ case NON_ABSENT: // new with 2.6, to support Guava/JDK8 Optionals
+ // always suppress nulls
+ suppressNulls = true;
+ // and for referential types, also "empty", which in their case means "absent"
+ valueToSuppress = contentType.isReferenceType()
+ ? MapSerializer.MARKER_FOR_EMPTY : null;
+ break;
+ case NON_EMPTY:
+ // always suppress nulls
+ suppressNulls = true;
+ // but possibly also 'empty' values:
+ valueToSuppress = MapSerializer.MARKER_FOR_EMPTY;
+ break;
+ case CUSTOM: // new with 2.9
+ valueToSuppress = prov.includeFilterInstance(null, inclV.getContentFilter());
+ if (valueToSuppress == null) { // is this legal?
+ suppressNulls = true;
+ } else {
+ suppressNulls = prov.includeFilterSuppressNulls(valueToSuppress);
+ }
+ break;
+ case NON_NULL:
+ valueToSuppress = null;
+ suppressNulls = true;
+ // fall through
+ case ALWAYS: // default
default:
- // all other modes actually good as is, unless we'll find better ways
+ valueToSuppress = null;
+ suppressNulls = !prov.isEnabled(SerializationFeature.WRITE_NULL_MAP_VALUES);
break;
}
- return incl;
+
+ return mapSer.withContentInclusion(valueToSuppress, suppressNulls);
}
/*
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/DefaultSerializerProvider.java b/src/main/java/com/fasterxml/jackson/databind/ser/DefaultSerializerProvider.java
index d89cb052c..118a82b3a 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/DefaultSerializerProvider.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/DefaultSerializerProvider.java
@@ -155,6 +155,25 @@ public abstract class DefaultSerializerProvider
return filter;
}
+ @Override
+ public boolean includeFilterSuppressNulls(Object filter) throws JsonMappingException
+ {
+ if (filter == null) {
+ return true;
+ }
+ // should let filter decide what to do with nulls:
+ // But just case, let's handle unexpected (from our perspective) problems explicitly
+ try {
+ return filter.equals(null);
+ } catch (Throwable t) {
+ String msg = String.format(
+"Problem determining whether filter of type '%s' should filter out `null` values: (%s) %s",
+filter.getClass().getName(), t.getClass().getName(), t.getMessage());
+ reportBadDefinition(filter.getClass(), msg, t);
+ return false; // never gets here
+ }
+ }
+
/*
/**********************************************************
/* Object Id handling
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/PropertyBuilder.java b/src/main/java/com/fasterxml/jackson/databind/ser/PropertyBuilder.java
index 95997509c..7638438db 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/PropertyBuilder.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/PropertyBuilder.java
@@ -143,7 +143,6 @@ public class PropertyBuilder
if (inclusion == JsonInclude.Include.USE_DEFAULTS) { // should not occur but...
inclusion = JsonInclude.Include.ALWAYS;
}
-
switch (inclusion) {
case NON_DEFAULT:
// 11-Nov-2015, tatu: This is tricky because semantics differ between cases,
@@ -158,7 +157,7 @@ public class PropertyBuilder
}
valueToSuppress = getPropertyDefaultValue(propDef.getName(), am, actualType);
} else {
- valueToSuppress = getDefaultValue(actualType);
+ valueToSuppress = BeanUtil.getDefaultValue(actualType);
suppressNulls = true;
}
if (valueToSuppress == null) {
@@ -185,20 +184,10 @@ public class PropertyBuilder
break;
case CUSTOM: // new with 2.9
valueToSuppress = prov.includeFilterInstance(propDef, inclV.getValueFilter());
-
if (valueToSuppress == null) { // is this legal?
suppressNulls = true;
} else {
- // should let filter decide what to do with nulls:
- // But just case, let's handle unexpected (from our perspective) problems explicitly
- try {
- suppressNulls = valueToSuppress.equals(null);
- } catch (Throwable t) {
- String msg = String.format(
-"Problem determining whether filter of type '%s' should filter out `null` values: (%s) %s",
-valueToSuppress.getClass().getName(), t.getClass().getName(), t.getMessage());
- prov.reportBadDefinition(_beanDesc.getType(), msg, t);
- }
+ suppressNulls = prov.includeFilterSuppressNulls(valueToSuppress);
}
break;
case NON_NULL:
@@ -342,37 +331,13 @@ valueToSuppress.getClass().getName(), t.getClass().getName(), t.getMessage());
}
/**
- * Accessor used to find out "default value" to use for comparing values to
- * serialize, to determine whether to exclude value from serialization with
- * inclusion type of {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_DEFAULT}.
- *<p>
- * Default logic is such that for primitives and wrapper types for primitives, expected
- * defaults (0 for `int` and `java.lang.Integer`) are returned; for Strings, empty String,
- * and for structured (Maps, Collections, arrays) and reference types, criteria
- * {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_DEFAULT}
- * is used.
- *
- * @since 2.7
+ * @deprecated Since 2.9
*/
- protected Object getDefaultValue(JavaType type)
- {
- // 06-Nov-2015, tatu: Returning null is fine for Object types; but need special
- // handling for primitives since they are never passed as nulls.
- Class<?> cls = type.getRawClass();
-
- Class<?> prim = ClassUtil.primitiveType(cls);
- if (prim != null) {
- return ClassUtil.defaultValue(prim);
- }
- if (type.isContainerType() || type.isReferenceType()) {
- return JsonInclude.Include.NON_EMPTY;
- }
- if (cls == String.class) {
- return "";
- }
- return null;
+ @Deprecated // since 2.9
+ protected Object getDefaultValue(JavaType type) {
+ return BeanUtil.getDefaultValue(type);
}
-
+
/*
/**********************************************************
/* Helper methods for exception handling
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 b1a36a514..389c2e0ef 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
@@ -494,7 +494,6 @@ public abstract class BeanSerializerBase
objectIdInfo.getAlwaysAsId());
}
}
-
// Or change Filter Id in use?
Object filterId = intr.findFilterId(accessor);
if (filterId != null) {
@@ -523,6 +522,7 @@ public abstract class BeanSerializerBase
if (shape == null) {
shape = _serializationShape;
}
+ // last but not least; may need to transmute into as-array serialization
if (shape == JsonFormat.Shape.ARRAY) {
return contextual.asArraySerializer();
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/MapSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/MapSerializer.java
index 9915fb133..3da9858b5 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/MapSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/MapSerializer.java
@@ -20,6 +20,7 @@ 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;
/**
* Standard serializer implementation for serializing {link java.util.Map} types.
@@ -37,14 +38,20 @@ public class MapSerializer
protected final static JavaType UNSPECIFIED_TYPE = TypeFactory.unknownType();
/**
- * Map-valued property being serialized with this instance
+ * @since 2.9
*/
- protected final BeanProperty _property;
+ public final static Object MARKER_FOR_EMPTY = JsonInclude.Include.NON_EMPTY;
+ /*
+ /**********************************************************
+ /* Basic information about referring property, type
+ /**********************************************************
+ */
+
/**
- * Set of entries to omit during serialization, if any
+ * Map-valued property being serialized with this instance
*/
- protected final Set<String> _ignoredEntries;
+ protected final BeanProperty _property;
/**
* Whether static types should be used for serialization of values
@@ -62,6 +69,12 @@ public class MapSerializer
*/
protected final JavaType _valueType;
+ /*
+ /**********************************************************
+ /* Serializers used
+ /**********************************************************
+ */
+
/**
* Key serializer to use, if it can be statically determined
*/
@@ -83,6 +96,17 @@ public class MapSerializer
*/
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.
*
@@ -91,14 +115,6 @@ public class MapSerializer
protected final Object _filterId;
/**
- * Flag set if output is forced to be sorted by keys (usually due
- * to annotation).
- *
- * @since 2.4
- */
- protected final boolean _sortKeys;
-
- /**
* Value that indicates suppression mechanism to use for <b>values contained</b>;
* either one of values of {@link com.fasterxml.jackson.annotation.JsonInclude.Include},
* or actual object to compare against ("default value").
@@ -109,6 +125,28 @@ public class MapSerializer
*/
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
@@ -138,17 +176,9 @@ public class MapSerializer
_filterId = null;
_sortKeys = false;
_suppressableValue = null;
+ _suppressNulls = false;
}
- /**
- * @since 2.5
- */
- protected void _ensureOverride() {
- if (getClass() != MapSerializer.class) {
- throw new IllegalStateException("Missing override in class "+getClass().getName());
- }
- }
-
@SuppressWarnings("unchecked")
protected MapSerializer(MapSerializer src, BeanProperty property,
JsonSerializer<?> keySerializer, JsonSerializer<?> valueSerializer,
@@ -168,18 +198,14 @@ public class MapSerializer
_filterId = src._filterId;
_sortKeys = src._sortKeys;
_suppressableValue = src._suppressableValue;
- }
-
- @Deprecated // since 2.5
- protected MapSerializer(MapSerializer src, TypeSerializer vts) {
- this(src, vts, src._suppressableValue);
+ _suppressNulls = src._suppressNulls;
}
/**
- * @since 2.5
+ * @since 2.9
*/
protected MapSerializer(MapSerializer src, TypeSerializer vts,
- Object suppressableValue)
+ Object suppressableValue, boolean suppressNulls)
{
super(Map.class, false);
_ignoredEntries = src._ignoredEntries;
@@ -193,12 +219,8 @@ public class MapSerializer
_property = src._property;
_filterId = src._filterId;
_sortKeys = src._sortKeys;
- // 05-Jun-2015, tatu: For referential, this is same as NON_EMPTY; for others, NON_NULL, so:
- if (suppressableValue == JsonInclude.Include.NON_ABSENT) {
- suppressableValue = _valueType.isReferenceType() ?
- JsonInclude.Include.NON_EMPTY : JsonInclude.Include.NON_NULL;
- }
_suppressableValue = suppressableValue;
+ _suppressNulls = suppressNulls;
}
protected MapSerializer(MapSerializer src, Object filterId, boolean sortKeys)
@@ -216,6 +238,7 @@ public class MapSerializer
_filterId = filterId;
_sortKeys = sortKeys;
_suppressableValue = src._suppressableValue;
+ _suppressNulls = src._suppressNulls;
}
@Override
@@ -224,7 +247,7 @@ public class MapSerializer
return this;
}
_ensureOverride();
- return new MapSerializer(this, vts, null);
+ return new MapSerializer(this, vts, _suppressableValue, _suppressNulls);
}
/**
@@ -255,32 +278,15 @@ public class MapSerializer
* Mutant factory for constructing an instance with different inclusion strategy
* for content (Map values).
*
- * @since 2.5
+ * @since 2.9
*/
- public MapSerializer withContentInclusion(Object suppressableValue) {
- if (suppressableValue == _suppressableValue) {
+ public MapSerializer withContentInclusion(Object suppressableValue, boolean suppressNulls) {
+ if ((suppressableValue == _suppressableValue) && (_suppressNulls == suppressNulls)) {
return this;
}
_ensureOverride();
- return new MapSerializer(this, _valueTypeSerializer, 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 = (ignoredList == null || ignoredList.length == 0)
- ? null : ArrayBuilders.arrayToSet(ignoredList);
- return construct(ignoredEntries, mapType, staticValueType, vts,
- keySerializer, valueSerializer, filterId);
- }
/**
* @since 2.8
@@ -315,6 +321,61 @@ public class MapSerializer
return ser;
}
+ /**
+ * @since 2.5
+ */
+ protected void _ensureOverride() {
+ if (getClass() != MapSerializer.class) {
+ throw new IllegalStateException("Missing override in class "+getClass().getName());
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* 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) {
+ if (suppressableValue == _suppressableValue) {
+ return this;
+ }
+ _ensureOverride();
+ 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 = (ignoredList == null || ignoredList.length == 0)
+ ? null : ArrayBuilders.arrayToSet(ignoredList);
+ return construct(ignoredEntries, mapType, staticValueType, vts,
+ keySerializer, valueSerializer, filterId);
+ }
+
/*
/**********************************************************
/* Post-processing (contextualization)
@@ -330,7 +391,6 @@ public class MapSerializer
JsonSerializer<?> keySer = null;
final AnnotationIntrospector intr = provider.getAnnotationIntrospector();
final AnnotatedMember propertyAcc = (property == null) ? null : property.getMember();
- Object suppressableValue = _suppressableValue;
// First: if we have a property, may have property-annotation overrides
if ((propertyAcc != null) && (intr != null)) {
@@ -343,12 +403,6 @@ public class MapSerializer
ser = provider.serializerInstance(propertyAcc, serDef);
}
}
-
- JsonInclude.Value inclV = findIncludeOverrides(provider, property, Map.class);
- JsonInclude.Include incl = inclV.getContentInclusion();
- if ((incl != null) && (incl != JsonInclude.Include.USE_DEFAULTS)) {
- suppressableValue = incl;
- }
if (ser == null) {
ser = _valueSerializer;
}
@@ -396,9 +450,6 @@ public class MapSerializer
}
}
MapSerializer mser = withResolved(property, keySer, ser, ignored, sortKeys);
- if (suppressableValue != _suppressableValue) {
- mser = mser.withContentInclusion(suppressableValue);
- }
// [databind#307]: allow filtering
if (property != null) {
@@ -409,10 +460,58 @@ public class MapSerializer
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 = MapSerializer.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;
+ // fall through
+ case ALWAYS: // default
+ default:
+ valueToSuppress = null;
+ // 30-Sep-2016, tatu: Should not check global flags here, if inclusion forced
+ // to be ALWAYS
+ suppressNulls = false;
+ break;
+ }
+ mser = mser.withContentInclusion(valueToSuppress, suppressNulls);
+ }
+ }
}
return mser;
}
-
+
/*
/**********************************************************
/* Accessors
@@ -437,16 +536,27 @@ public class MapSerializer
}
// 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 = this._suppressableValue;
+ Object supp = _suppressableValue;
- if ((supp == null) || (supp == JsonInclude.Include.ALWAYS)) {
+ 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) && !valueSer.isEmpty(prov, elemValue)) {
- return false;
+ if (elemValue == null) {
+ if (!_suppressNulls) {
+ return false;
+ }
+ } else if (checkEmpty) {
+ if (!valueSer.isEmpty(prov, elemValue)) {
+ return false;
+ }
+ } else if (supp != null) {
+ if (!supp.equals(value)) {
+ return false;
+ }
}
}
return true;
@@ -455,6 +565,9 @@ public class MapSerializer
PropertySerializerMap serializers = _dynamicValueSerializers;
for (Object elemValue : value.values()) {
if (elemValue == null) {
+ if (!_suppressNulls) {
+ return false;
+ }
continue;
}
Class<?> cc = elemValue.getClass();
@@ -470,8 +583,14 @@ public class MapSerializer
}
serializers = _dynamicValueSerializers;
}
- if (!valueSer.isEmpty(prov, elemValue)) {
- return false;
+ if (checkEmpty) {
+ if (!valueSer.isEmpty(prov, elemValue)) {
+ return false;
+ }
+ } else if (supp != null) {
+ if (!supp.equals(value)) {
+ return false;
+ }
}
}
return true;
@@ -481,7 +600,7 @@ public class MapSerializer
public boolean hasSingleElement(Map<?,?> value) {
return (value.size() == 1);
}
-
+
/*
/**********************************************************
/* Extended API
@@ -514,22 +633,14 @@ public class MapSerializer
{
gen.writeStartObject(value);
if (!value.isEmpty()) {
- Object suppressableValue = _suppressableValue;
- if (suppressableValue == JsonInclude.Include.ALWAYS) {
- suppressableValue = null;
- } else if (suppressableValue == null) {
- if (!provider.isEnabled(SerializationFeature.WRITE_NULL_MAP_VALUES)) {
- suppressableValue = JsonInclude.Include.NON_NULL;
- }
- }
if (_sortKeys || provider.isEnabled(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS)) {
value = _orderEntries(value);
}
if (_filterId != null) {
serializeFilteredFields(value, gen, provider,
- findPropertyFilter(provider, _filterId, value), suppressableValue);
- } else if (suppressableValue != null) {
- serializeOptionalFields(value, gen, provider, suppressableValue);
+ findPropertyFilter(provider, _filterId, value), _suppressableValue);
+ } else if ((_suppressableValue != null) || _suppressNulls) {
+ serializeOptionalFields(value, gen, provider, _suppressableValue);
} else if (_valueSerializer != null) {
serializeFieldsUsing(value, gen, provider, _valueSerializer);
} else {
@@ -548,22 +659,14 @@ public class MapSerializer
// [databind#631]: Assign current value, to be accessible by custom serializers
gen.setCurrentValue(value);
if (!value.isEmpty()) {
- Object suppressableValue = _suppressableValue;
- if (suppressableValue == JsonInclude.Include.ALWAYS) {
- suppressableValue = null;
- } else if (suppressableValue == null) {
- if (!provider.isEnabled(SerializationFeature.WRITE_NULL_MAP_VALUES)) {
- suppressableValue = JsonInclude.Include.NON_NULL;
- }
- }
if (_sortKeys || provider.isEnabled(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS)) {
value = _orderEntries(value);
}
if (_filterId != null) {
serializeFilteredFields(value, gen, provider,
- findPropertyFilter(provider, _filterId, value), suppressableValue);
- } else if (suppressableValue != null) {
- serializeOptionalFields(value, gen, provider, suppressableValue);
+ findPropertyFilter(provider, _filterId, value), _suppressableValue);
+ } else if ((_suppressableValue != null) || _suppressNulls) {
+ serializeOptionalFields(value, gen, provider, _suppressableValue);
} else if (_valueSerializer != null) {
serializeFieldsUsing(value, gen, provider, _valueSerializer);
} else {
@@ -653,6 +756,7 @@ public class MapSerializer
}
final Set<String> ignored = _ignoredEntries;
PropertySerializerMap serializers = _dynamicValueSerializers;
+ final boolean checkEmpty = (MARKER_FOR_EMPTY == suppressableValue);
for (Map.Entry<?,?> entry : value.entrySet()) {
// First find key serializer
@@ -669,7 +773,7 @@ public class MapSerializer
final Object valueElem = entry.getValue();
JsonSerializer<Object> valueSer;
if (valueElem == null) {
- if (suppressableValue != null) { // all suppressions include null-suppression
+ if (_suppressNulls) { // all suppressions include null-suppression
continue;
}
valueSer = provider.getDefaultNullValueSerializer();
@@ -689,9 +793,14 @@ public class MapSerializer
}
}
// also may need to skip non-empty values:
- if ((suppressableValue == JsonInclude.Include.NON_EMPTY)
- && valueSer.isEmpty(provider, valueElem)) {
- continue;
+ if (checkEmpty) {
+ if (valueSer.isEmpty(provider, valueElem)) {
+ continue;
+ }
+ } else if (suppressableValue != null) {
+ if (suppressableValue.equals(valueElem)) {
+ continue;
+ }
}
}
// and then serialize, if all went well
@@ -760,7 +869,8 @@ public class MapSerializer
PropertySerializerMap serializers = _dynamicValueSerializers;
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();
@@ -778,7 +888,7 @@ public class MapSerializer
JsonSerializer<Object> valueSer;
// And then value
if (valueElem == null) {
- if (suppressableValue != null) { // all suppressions include null-suppression
+ if (_suppressNulls) {
continue;
}
valueSer = provider.getDefaultNullValueSerializer();
@@ -798,9 +908,14 @@ public class MapSerializer
}
}
// also may need to skip non-empty values:
- if ((suppressableValue == JsonInclude.Include.NON_EMPTY)
- && valueSer.isEmpty(provider, valueElem)) {
- continue;
+ 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
@@ -814,13 +929,6 @@ public class MapSerializer
}
}
- @Deprecated // since 2.5
- public void serializeFilteredFields(Map<?,?> value, JsonGenerator gen, SerializerProvider provider,
- PropertyFilter filter) throws IOException {
- serializeFilteredFields(value, gen, provider, filter,
- provider.isEnabled(SerializationFeature.WRITE_NULL_MAP_VALUES) ? null : JsonInclude.Include.NON_NULL);
- }
-
/**
* @since 2.5
*/
@@ -830,6 +938,7 @@ public class MapSerializer
{
final Set<String> ignored = _ignoredEntries;
PropertySerializerMap serializers = _dynamicValueSerializers;
+ final boolean checkEmpty = (MARKER_FOR_EMPTY == suppressableValue);
for (Map.Entry<?,?> entry : value.entrySet()) {
Object keyElem = entry.getKey();
@@ -846,7 +955,7 @@ public class MapSerializer
// And then value
JsonSerializer<Object> valueSer;
if (valueElem == null) {
- if (suppressableValue != null) { // all suppression include null suppression
+ if (_suppressNulls) { // all suppression include null suppression
continue;
}
valueSer = provider.getDefaultNullValueSerializer();
@@ -864,9 +973,14 @@ public class MapSerializer
serializers = _dynamicValueSerializers;
}
// also may need to skip non-empty values:
- if ((suppressableValue == JsonInclude.Include.NON_EMPTY)
- && valueSer.isEmpty(provider, valueElem)) {
- continue;
+ if (checkEmpty) {
+ if (valueSer.isEmpty(provider, valueElem)) {
+ continue;
+ }
+ } else if (suppressableValue != null) {
+ if (suppressableValue.equals(valueElem)) {
+ continue;
+ }
}
}
keySerializer.serialize(keyElem, gen, provider);
@@ -879,13 +993,6 @@ public class MapSerializer
}
}
- @Deprecated // since 2.5
- protected void serializeTypedFields(Map<?,?> value, JsonGenerator gen, SerializerProvider provider)
- throws IOException {
- serializeTypedFields(value, gen, provider,
- provider.isEnabled(SerializationFeature.WRITE_NULL_MAP_VALUES) ? null : JsonInclude.Include.NON_NULL);
- }
-
/*
/**********************************************************
/* Schema related functionality
diff --git a/src/main/java/com/fasterxml/jackson/databind/util/BeanUtil.java b/src/main/java/com/fasterxml/jackson/databind/util/BeanUtil.java
index 5fcaa8d6a..9b9fb928b 100644
--- a/src/main/java/com/fasterxml/jackson/databind/util/BeanUtil.java
+++ b/src/main/java/com/fasterxml/jackson/databind/util/BeanUtil.java
@@ -1,5 +1,7 @@
package com.fasterxml.jackson.databind.util;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
/**
@@ -103,6 +105,46 @@ public class BeanUtil
/*
/**********************************************************
+ /* Value defaulting helpers
+ /**********************************************************
+ */
+
+ /**
+ * Accessor used to find out "default value" to use for comparing values to
+ * serialize, to determine whether to exclude value from serialization with
+ * inclusion type of {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_DEFAULT}.
+ *<p>
+ * Default logic is such that for primitives and wrapper types for primitives, expected
+ * defaults (0 for `int` and `java.lang.Integer`) are returned; for Strings, empty String,
+ * and for structured (Maps, Collections, arrays) and reference types, criteria
+ * {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_DEFAULT}
+ * is used.
+ *
+ * @since 2.7
+ */
+ public static Object getDefaultValue(JavaType type)
+ {
+ // 06-Nov-2015, tatu: Returning null is fine for Object types; but need special
+ // handling for primitives since they are never passed as nulls.
+ Class<?> cls = type.getRawClass();
+
+ // 30-Sep-2016, tatu: Also works for Wrappers, so both `Integer.TYPE` and `Integer.class`
+ // would return `Integer.TYPE`
+ Class<?> prim = ClassUtil.primitiveType(cls);
+ if (prim != null) {
+ return ClassUtil.defaultValue(prim);
+ }
+ if (type.isContainerType() || type.isReferenceType()) {
+ return JsonInclude.Include.NON_EMPTY;
+ }
+ if (cls == String.class) {
+ return "";
+ }
+ return null;
+ }
+
+ /*
+ /**********************************************************
/* Handling property names, deprecated methods
/**********************************************************
*/
diff --git a/src/test/java/com/fasterxml/jackson/databind/filter/JsonIncludeCustomTest.java b/src/test/java/com/fasterxml/jackson/databind/filter/JsonIncludeCustomTest.java
index 31a8f4f0a..21b0433cc 100644
--- a/src/test/java/com/fasterxml/jackson/databind/filter/JsonIncludeCustomTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/filter/JsonIncludeCustomTest.java
@@ -1,5 +1,8 @@
package com.fasterxml.jackson.databind.filter;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.BaseMapTest;
@@ -37,6 +40,17 @@ public class JsonIncludeCustomTest extends BaseMapTest
public FooBean(String v) { value = v; }
}
+ static class FooMapBean {
+ @JsonInclude(content=JsonInclude.Include.CUSTOM,
+ contentFilter=FooFilter.class)
+ public Map<String,String> stuff = new LinkedHashMap<String,String>();
+
+ public FooMapBean add(String key, String value) {
+ stuff.put(key, value);
+ return this;
+ }
+ }
+
static class BrokenBean {
@JsonInclude(value=JsonInclude.Include.CUSTOM,
valueFilter=BrokenFilter.class)
@@ -59,6 +73,16 @@ public class JsonIncludeCustomTest extends BaseMapTest
assertEquals("{}", MAPPER.writeValueAsString(new FooBean("foo")));
}
+ public void testCustomFilterWithMap() throws Exception
+ {
+ FooMapBean input = new FooMapBean()
+ .add("a", "1")
+ .add("b", "foo")
+ .add("c", "2");
+
+ assertEquals(aposToQuotes("{'stuff':{'a':'1','c':'2'}}"), MAPPER.writeValueAsString(input));
+ }
+
/*
/**********************************************************
/* Test methods, fail handling