Skip to content

Commit cdf8041

Browse files
committed
More work on #888; rewriting Map-include handling, adding temporarily test failures
1 parent 0c4e156 commit cdf8041

File tree

8 files changed

+424
-197
lines changed

8 files changed

+424
-197
lines changed

src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java

+23-1
Original file line numberDiff line numberDiff line change
@@ -930,12 +930,24 @@ public abstract JsonSerializer<Object> serializerInstance(Annotated annotated,
930930
* filter instance,
931931
* given a {@link Class} to instantiate (with default constructor, by default).
932932
*
933+
* @param forProperty (optional) If filter is created for a property, that property;
934+
* `null` if filter created via defaulting, global or per-type.
935+
*
933936
* @since 2.9
934937
*/
935938
public abstract Object includeFilterInstance(BeanPropertyDefinition forProperty,
936939
Class<?> filterClass)
937940
throws JsonMappingException;
938941

942+
/**
943+
* Follow-up method that may be called after calling {@link #includeFilterInstance},
944+
* to check handling of `null` values by the filter.
945+
*
946+
* @since 2.9
947+
*/
948+
public abstract boolean includeFilterSuppressNulls(Object filter)
949+
throws JsonMappingException;
950+
939951
/*
940952
/**********************************************************
941953
/* Support for contextualization
@@ -1180,7 +1192,17 @@ public <T> T reportBadDefinition(JavaType type, String msg, Throwable cause)
11801192
e.initCause(cause);
11811193
throw e;
11821194
}
1183-
1195+
1196+
/**
1197+
* @since 2.9
1198+
*/
1199+
public <T> T reportBadDefinition(Class<?> raw, String msg, Throwable cause)
1200+
throws JsonMappingException {
1201+
InvalidDefinitionException e = InvalidDefinitionException.from(getGenerator(), msg, constructType(raw));
1202+
e.initCause(cause);
1203+
throw e;
1204+
}
1205+
11841206
/**
11851207
* Helper method called to indicate problem; default behavior is to construct and
11861208
* throw a {@link JsonMappingException}, but in future may collect more than one

src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java

+79-31
Original file line numberDiff line numberDiff line change
@@ -774,12 +774,7 @@ protected JsonSerializer<?> buildMapSerializer(SerializerProvider prov,
774774
MapSerializer mapSer = MapSerializer.construct(ignored,
775775
type, staticTyping, elementTypeSerializer,
776776
keySerializer, elementValueSerializer, filterId);
777-
Object suppressableValue = findSuppressableContentValue(config,
778-
type.getContentType(), beanDesc);
779-
if (suppressableValue != null) {
780-
mapSer = mapSer.withContentInclusion(suppressableValue);
781-
}
782-
ser = mapSer;
777+
ser = _checkMapContentInclusion(prov, beanDesc, mapSer);
783778
}
784779
}
785780
// [databind#120]: Allow post-processing
@@ -792,42 +787,95 @@ protected JsonSerializer<?> buildMapSerializer(SerializerProvider prov,
792787
}
793788

794789
/**
795-
*<p>
796-
* NOTE: although return type is left opaque, it really needs to be
797-
* <code>JsonInclude.Include</code> for things to work as expected.
790+
* Helper method that does figures out content inclusion value to use, if any,
791+
* and construct re-configured {@link MapSerializer} appropriately.
798792
*
799-
* @since 2.5
793+
* @since 2.9
800794
*/
801-
protected Object findSuppressableContentValue(SerializationConfig config,
802-
JavaType contentType, BeanDescription beanDesc)
795+
protected MapSerializer _checkMapContentInclusion(SerializerProvider prov,
796+
BeanDescription beanDesc, MapSerializer mapSer)
803797
throws JsonMappingException
804798
{
805-
/* 16-Apr-2016, tatu: Should this consider possible property-config overrides?
806-
* Quite possibly yes, but would need to carefully check that content type being
807-
* used is appropriate.
808-
*/
809-
JsonInclude.Value inclV = beanDesc.findPropertyInclusion(config.getDefaultPropertyInclusion());
799+
final SerializationConfig config = prov.getConfig();
810800

811-
if (inclV == null) {
812-
return null;
801+
// 30-Sep-2016, tatu: Defaulting gets complicated because we might have two distinct
802+
// axis to consider: Map itself, and then value type.
803+
// Start with Map-defaults, then use more-specific value override, if any
804+
805+
// Start by getting global setting, overridden by Map-type-override
806+
JsonInclude.Value inclV = config.getDefaultPropertyInclusion(Map.class,
807+
config.getDefaultPropertyInclusion());
808+
// and then merge content-type overrides, if any. But note that there's
809+
// content-to-value inclusion shift we have to do
810+
final JavaType contentType = mapSer.getContentType();
811+
JsonInclude.Value valueIncl = config.getDefaultPropertyInclusion(contentType.getRawClass(), null);
812+
813+
if (valueIncl != null) {
814+
switch (valueIncl.getValueInclusion()) {
815+
case USE_DEFAULTS:
816+
break;
817+
case CUSTOM:
818+
inclV = inclV.withContentFilter(valueIncl.getContentFilter());
819+
break;
820+
default:
821+
inclV = inclV.withContentInclusion(valueIncl.getValueInclusion());
822+
}
813823
}
814-
JsonInclude.Include incl = inclV.getContentInclusion();
815-
switch (incl) {
816-
case USE_DEFAULTS: // means "dunno"
817-
return null;
824+
JsonInclude.Include incl = (inclV == null) ? JsonInclude.Include.USE_DEFAULTS : inclV.getContentInclusion();
825+
if (incl == JsonInclude.Include.USE_DEFAULTS) {
826+
if (!config.isEnabled(SerializationFeature.WRITE_NULL_MAP_VALUES)) {
827+
return mapSer.withContentInclusion(null, true);
828+
}
829+
return mapSer;
830+
}
831+
// NOTE: mostly copied from `PropertyBuilder`; would be nice to refactor
832+
// but code is not identical nor are these types related
833+
Object valueToSuppress;
834+
boolean suppressNulls;
835+
836+
switch (inclV.getContentInclusion()) {
818837
case NON_DEFAULT:
819-
// 19-Oct-2014, tatu: Not sure what this'd mean; so take it to mean "NON_EMPTY"...
820-
// 11-Nov-2015, tatu: With 2.6, we did indeed revert to "NON_EMPTY", but that did
821-
// not go well, so with 2.7, we'll do this instead...
822-
// But not 100% sure if we ought to call new `JsonSerializer.findDefaultValue()`;
823-
// to do that, would need to locate said serializer
824-
// incl = JsonInclude.Include.NON_EMPTY;
838+
valueToSuppress = BeanUtil.getDefaultValue(contentType);
839+
suppressNulls = true;
840+
if (valueToSuppress != null) {
841+
if (valueToSuppress.getClass().isArray()) {
842+
valueToSuppress = ArrayBuilders.getArrayComparator(valueToSuppress);
843+
}
844+
}
825845
break;
846+
case NON_ABSENT: // new with 2.6, to support Guava/JDK8 Optionals
847+
// always suppress nulls
848+
suppressNulls = true;
849+
// and for referential types, also "empty", which in their case means "absent"
850+
valueToSuppress = contentType.isReferenceType()
851+
? MapSerializer.MARKER_FOR_EMPTY : null;
852+
break;
853+
case NON_EMPTY:
854+
// always suppress nulls
855+
suppressNulls = true;
856+
// but possibly also 'empty' values:
857+
valueToSuppress = MapSerializer.MARKER_FOR_EMPTY;
858+
break;
859+
case CUSTOM: // new with 2.9
860+
valueToSuppress = prov.includeFilterInstance(null, inclV.getContentFilter());
861+
if (valueToSuppress == null) { // is this legal?
862+
suppressNulls = true;
863+
} else {
864+
suppressNulls = prov.includeFilterSuppressNulls(valueToSuppress);
865+
}
866+
break;
867+
case NON_NULL:
868+
valueToSuppress = null;
869+
suppressNulls = true;
870+
// fall through
871+
case ALWAYS: // default
826872
default:
827-
// all other modes actually good as is, unless we'll find better ways
873+
valueToSuppress = null;
874+
suppressNulls = !prov.isEnabled(SerializationFeature.WRITE_NULL_MAP_VALUES);
828875
break;
829876
}
830-
return incl;
877+
878+
return mapSer.withContentInclusion(valueToSuppress, suppressNulls);
831879
}
832880

833881
/*

src/main/java/com/fasterxml/jackson/databind/ser/DefaultSerializerProvider.java

+19
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,25 @@ public Object includeFilterInstance(BeanPropertyDefinition forProperty,
155155
return filter;
156156
}
157157

158+
@Override
159+
public boolean includeFilterSuppressNulls(Object filter) throws JsonMappingException
160+
{
161+
if (filter == null) {
162+
return true;
163+
}
164+
// should let filter decide what to do with nulls:
165+
// But just case, let's handle unexpected (from our perspective) problems explicitly
166+
try {
167+
return filter.equals(null);
168+
} catch (Throwable t) {
169+
String msg = String.format(
170+
"Problem determining whether filter of type '%s' should filter out `null` values: (%s) %s",
171+
filter.getClass().getName(), t.getClass().getName(), t.getMessage());
172+
reportBadDefinition(filter.getClass(), msg, t);
173+
return false; // never gets here
174+
}
175+
}
176+
158177
/*
159178
/**********************************************************
160179
/* Object Id handling

src/main/java/com/fasterxml/jackson/databind/ser/PropertyBuilder.java

+7-42
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,6 @@ protected BeanPropertyWriter buildWriter(SerializerProvider prov,
143143
if (inclusion == JsonInclude.Include.USE_DEFAULTS) { // should not occur but...
144144
inclusion = JsonInclude.Include.ALWAYS;
145145
}
146-
147146
switch (inclusion) {
148147
case NON_DEFAULT:
149148
// 11-Nov-2015, tatu: This is tricky because semantics differ between cases,
@@ -158,7 +157,7 @@ protected BeanPropertyWriter buildWriter(SerializerProvider prov,
158157
}
159158
valueToSuppress = getPropertyDefaultValue(propDef.getName(), am, actualType);
160159
} else {
161-
valueToSuppress = getDefaultValue(actualType);
160+
valueToSuppress = BeanUtil.getDefaultValue(actualType);
162161
suppressNulls = true;
163162
}
164163
if (valueToSuppress == null) {
@@ -185,20 +184,10 @@ protected BeanPropertyWriter buildWriter(SerializerProvider prov,
185184
break;
186185
case CUSTOM: // new with 2.9
187186
valueToSuppress = prov.includeFilterInstance(propDef, inclV.getValueFilter());
188-
189187
if (valueToSuppress == null) { // is this legal?
190188
suppressNulls = true;
191189
} else {
192-
// should let filter decide what to do with nulls:
193-
// But just case, let's handle unexpected (from our perspective) problems explicitly
194-
try {
195-
suppressNulls = valueToSuppress.equals(null);
196-
} catch (Throwable t) {
197-
String msg = String.format(
198-
"Problem determining whether filter of type '%s' should filter out `null` values: (%s) %s",
199-
valueToSuppress.getClass().getName(), t.getClass().getName(), t.getMessage());
200-
prov.reportBadDefinition(_beanDesc.getType(), msg, t);
201-
}
190+
suppressNulls = prov.includeFilterSuppressNulls(valueToSuppress);
202191
}
203192
break;
204193
case NON_NULL:
@@ -342,37 +331,13 @@ protected Object getPropertyDefaultValue(String name, AnnotatedMember member,
342331
}
343332

344333
/**
345-
* Accessor used to find out "default value" to use for comparing values to
346-
* serialize, to determine whether to exclude value from serialization with
347-
* inclusion type of {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_DEFAULT}.
348-
*<p>
349-
* Default logic is such that for primitives and wrapper types for primitives, expected
350-
* defaults (0 for `int` and `java.lang.Integer`) are returned; for Strings, empty String,
351-
* and for structured (Maps, Collections, arrays) and reference types, criteria
352-
* {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_DEFAULT}
353-
* is used.
354-
*
355-
* @since 2.7
334+
* @deprecated Since 2.9
356335
*/
357-
protected Object getDefaultValue(JavaType type)
358-
{
359-
// 06-Nov-2015, tatu: Returning null is fine for Object types; but need special
360-
// handling for primitives since they are never passed as nulls.
361-
Class<?> cls = type.getRawClass();
362-
363-
Class<?> prim = ClassUtil.primitiveType(cls);
364-
if (prim != null) {
365-
return ClassUtil.defaultValue(prim);
366-
}
367-
if (type.isContainerType() || type.isReferenceType()) {
368-
return JsonInclude.Include.NON_EMPTY;
369-
}
370-
if (cls == String.class) {
371-
return "";
372-
}
373-
return null;
336+
@Deprecated // since 2.9
337+
protected Object getDefaultValue(JavaType type) {
338+
return BeanUtil.getDefaultValue(type);
374339
}
375-
340+
376341
/*
377342
/**********************************************************
378343
/* Helper methods for exception handling

src/main/java/com/fasterxml/jackson/databind/ser/std/BeanSerializerBase.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,6 @@ public JsonSerializer<?> createContextual(SerializerProvider provider,
494494
objectIdInfo.getAlwaysAsId());
495495
}
496496
}
497-
498497
// Or change Filter Id in use?
499498
Object filterId = intr.findFilterId(accessor);
500499
if (filterId != null) {
@@ -523,6 +522,7 @@ public JsonSerializer<?> createContextual(SerializerProvider provider,
523522
if (shape == null) {
524523
shape = _serializationShape;
525524
}
525+
// last but not least; may need to transmute into as-array serialization
526526
if (shape == JsonFormat.Shape.ARRAY) {
527527
return contextual.asArraySerializer();
528528
}

0 commit comments

Comments
 (0)