From beafa77abaf26f06fc02a10eb5b61a2f1b716d2a Mon Sep 17 00:00:00 2001 From: Carsten Wickner Date: Fri, 17 Mar 2017 22:22:16 +0100 Subject: [PATCH] Introduce config override for includeAsProperty --- .../jackson/databind/BeanProperty.java | 2 +- .../databind/DeserializationConfig.java | 40 ++++++++++ .../jackson/databind/SerializationConfig.java | 40 ++++++++++ .../jackson/databind/cfg/ConfigOverride.java | 7 ++ .../jackson/databind/cfg/MapperConfig.java | 25 ++++++ .../databind/cfg/MutableConfigOverride.java | 5 ++ .../jackson/databind/ser/PropertyBuilder.java | 20 ++++- .../databind/filter/JsonIncludeTest.java | 76 ++++++++++++++++++- 8 files changed, 210 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/BeanProperty.java b/src/main/java/com/fasterxml/jackson/databind/BeanProperty.java index 525aa11e2a..6095abc819 100644 --- a/src/main/java/com/fasterxml/jackson/databind/BeanProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/BeanProperty.java @@ -279,7 +279,7 @@ public JsonFormat.Value findPropertyFormat(MapperConfig config, Class base @Override public JsonInclude.Value findPropertyInclusion(MapperConfig config, Class baseType) { - JsonInclude.Value v0 = config.getDefaultPropertyInclusion(baseType); + JsonInclude.Value v0 = config.getDefaultPropertyInclusion(baseType, _type.getRawClass()); AnnotationIntrospector intr = config.getAnnotationIntrospector(); if ((intr == null) || (_member == null)) { return v0; diff --git a/src/main/java/com/fasterxml/jackson/databind/DeserializationConfig.java b/src/main/java/com/fasterxml/jackson/databind/DeserializationConfig.java index 68556adee7..67e8d205cd 100644 --- a/src/main/java/com/fasterxml/jackson/databind/DeserializationConfig.java +++ b/src/main/java/com/fasterxml/jackson/databind/DeserializationConfig.java @@ -796,6 +796,26 @@ public JsonInclude.Value getDefaultPropertyInclusion(Class baseType) { return EMPTY_INCLUDE; } + @Override + public JsonInclude.Value getDefaultPropertyInclusion(Class baseType, + Class propertyType) { + ConfigOverride propertyTypeOverrides = findConfigOverride(propertyType); + if (propertyTypeOverrides != null) { + JsonInclude.Value v0 = propertyTypeOverrides.getIncludeAsProperty(); + if (v0 != null) { + return v0; + } + } + ConfigOverride baseTypeOverrides = findConfigOverride(baseType); + if (baseTypeOverrides != null) { + JsonInclude.Value v1 = baseTypeOverrides.getInclude(); + if (v1 != null) { + return v1; + } + } + return EMPTY_INCLUDE; + } + @Override public JsonInclude.Value getDefaultPropertyInclusion(Class baseType, JsonInclude.Value defaultIncl) @@ -810,6 +830,26 @@ public JsonInclude.Value getDefaultPropertyInclusion(Class baseType, return defaultIncl; } + @Override + public JsonInclude.Value getDefaultPropertyInclusion(Class baseType, + Class propertyType, JsonInclude.Value defaultIncl) { + ConfigOverride propertyTypeOverrides = findConfigOverride(propertyType); + if (propertyTypeOverrides != null) { + JsonInclude.Value v0 = propertyTypeOverrides.getIncludeAsProperty(); + if (v0 != null) { + return v0; + } + } + ConfigOverride baseTypeOverrides = findConfigOverride(baseType); + if (baseTypeOverrides != null) { + JsonInclude.Value v1 = baseTypeOverrides.getInclude(); + if (v1 != null) { + return v1; + } + } + return defaultIncl; + } + /* /********************************************************** /* MapperConfig implementation/overrides: other diff --git a/src/main/java/com/fasterxml/jackson/databind/SerializationConfig.java b/src/main/java/com/fasterxml/jackson/databind/SerializationConfig.java index f57590f680..2838ba5e5f 100644 --- a/src/main/java/com/fasterxml/jackson/databind/SerializationConfig.java +++ b/src/main/java/com/fasterxml/jackson/databind/SerializationConfig.java @@ -899,6 +899,26 @@ public JsonInclude.Value getDefaultPropertyInclusion(Class baseType) { return _serializationInclusion; } + @Override + public JsonInclude.Value getDefaultPropertyInclusion(Class baseType, + Class propertyType) { + ConfigOverride propertyTypeOverrides = findConfigOverride(propertyType); + if (propertyTypeOverrides != null) { + JsonInclude.Value v0 = propertyTypeOverrides.getIncludeAsProperty(); + if (v0 != null) { + return v0; + } + } + ConfigOverride baseTypeOverrides = findConfigOverride(baseType); + if (baseTypeOverrides != null) { + JsonInclude.Value v1 = baseTypeOverrides.getInclude(); + if (v1 != null) { + return v1; + } + } + return _serializationInclusion; + } + @Override public JsonInclude.Value getDefaultPropertyInclusion(Class baseType, JsonInclude.Value defaultIncl) @@ -913,6 +933,26 @@ public JsonInclude.Value getDefaultPropertyInclusion(Class baseType, return defaultIncl; } + @Override + public JsonInclude.Value getDefaultPropertyInclusion(Class baseType, + Class propertyType, JsonInclude.Value defaultIncl) { + ConfigOverride propertyTypeOverrides = findConfigOverride(propertyType); + if (propertyTypeOverrides != null) { + JsonInclude.Value v0 = propertyTypeOverrides.getIncludeAsProperty(); + if (v0 != null) { + return v0; + } + } + ConfigOverride baseTypeOverrides = findConfigOverride(baseType); + if (baseTypeOverrides != null) { + JsonInclude.Value v1 = baseTypeOverrides.getInclude(); + if (v1 != null) { + return v1; + } + } + return defaultIncl; + } + /* /********************************************************** /* Configuration: other diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/ConfigOverride.java b/src/main/java/com/fasterxml/jackson/databind/cfg/ConfigOverride.java index 18e6304fd6..1ef3d504e1 100644 --- a/src/main/java/com/fasterxml/jackson/databind/cfg/ConfigOverride.java +++ b/src/main/java/com/fasterxml/jackson/databind/cfg/ConfigOverride.java @@ -25,6 +25,11 @@ public abstract class ConfigOverride */ protected JsonInclude.Value _include; + /** + * Definitions of inclusion overrides for properties of configured type, if any. + */ + protected JsonInclude.Value _includeAsProperty; + /** * Definitions of property ignoral (whether to serialize, deserialize * given logical property) overrides, if any. @@ -43,12 +48,14 @@ protected ConfigOverride() { } protected ConfigOverride(ConfigOverride src) { _format = src._format; _include = src._include; + _includeAsProperty = src._includeAsProperty; _ignorals = src._ignorals; _isIgnoredType = src._isIgnoredType; } public JsonFormat.Value getFormat() { return _format; } public JsonInclude.Value getInclude() { return _include; } + public JsonInclude.Value getIncludeAsProperty() { return _includeAsProperty; } public JsonIgnoreProperties.Value getIgnorals() { return _ignorals; } public Boolean getIsIgnoredType() { diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfig.java b/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfig.java index 1ae5732d84..7d88b3ae99 100644 --- a/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfig.java +++ b/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfig.java @@ -365,6 +365,18 @@ public BeanDescription introspectDirectClassAnnotations(Class cls) { */ public abstract JsonInclude.Value getDefaultPropertyInclusion(Class baseType); + /** + * Accessor for default property inclusion to use for serialization, + * considering possible per-type override for given base type and + * possible per-type override for given property type.
+ * NOTE: if no override found, defaults to value returned by + * {@link #getDefaultPropertyInclusion()}. + * + * @since 2.8.8 + */ + public abstract JsonInclude.Value getDefaultPropertyInclusion(Class baseType, + Class propertyType); + /** * Accessor for default property inclusion to use for serialization, * considering possible per-type override for given base type; but @@ -377,6 +389,19 @@ public BeanDescription introspectDirectClassAnnotations(Class cls) { public abstract JsonInclude.Value getDefaultPropertyInclusion(Class baseType, JsonInclude.Value defaultIncl); + /** + * Accessor for default property inclusion to use for serialization, + * considering possible per-type override for given base type and + * possible per-type override for given property type; but + * if none found, returning given defaultIncl + * + * @param defaultIncl Inclusion setting to return if no overrides found. + * + * @since 2.8.8 + */ + public abstract JsonInclude.Value getDefaultPropertyInclusion(Class baseType, + Class propertyType, JsonInclude.Value defaultIncl); + /** * Accessor for default format settings to use for serialization (and, to a degree * deserialization), considering baseline settings and per-type defaults diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/MutableConfigOverride.java b/src/main/java/com/fasterxml/jackson/databind/cfg/MutableConfigOverride.java index 902e18b531..afaeff407f 100644 --- a/src/main/java/com/fasterxml/jackson/databind/cfg/MutableConfigOverride.java +++ b/src/main/java/com/fasterxml/jackson/databind/cfg/MutableConfigOverride.java @@ -39,6 +39,11 @@ public MutableConfigOverride setInclude(JsonInclude.Value v) { return this; } + public MutableConfigOverride setIncludeAsProperty(JsonInclude.Value v) { + _includeAsProperty = v; + return this; + } + public MutableConfigOverride setIgnorals(JsonIgnoreProperties.Value v) { _ignorals = v; return this; 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 6363421ef7..a3b574817c 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/PropertyBuilder.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/PropertyBuilder.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.cfg.ConfigOverride; import com.fasterxml.jackson.databind.introspect.*; import com.fasterxml.jackson.databind.jsontype.TypeSerializer; import com.fasterxml.jackson.databind.util.*; @@ -56,8 +57,9 @@ public PropertyBuilder(SerializationConfig config, BeanDescription beanDesc) // 08-Sep-2016, tatu: This gets tricky, with 3 levels of definitions: // (a) global default inclusion // (b) per-type default inclusion (from annotation or config overrides; - // latter having precedence - // Cc) per-property override + // config override having precedence) + // (c) per-property override (from annotation or config overrides; + // annotation having precedence) // // and not only requiring merging, but also considering special handling // for NON_DEFAULT in case of (b) (vs (a) or (c)) @@ -127,11 +129,23 @@ protected BeanPropertyWriter buildWriter(SerializerProvider prov, // 12-Jul-2016, tatu: [databind#1256] Need to make sure we consider type refinement JavaType actualType = (serializationType == null) ? declaredType : serializationType; + // 17-Mar-2017: [databind#1522] Allow config override per property type + Class rawPropertyType; + if (propDef.hasField()) { + rawPropertyType = propDef.getField().getRawType(); + } else if (propDef.hasGetter()) { + rawPropertyType = propDef.getGetter().getRawReturnType(); + } else { + // neither Setter nor ConstructorParameter are expected here + return prov.reportBadPropertyDefinition(_beanDesc, propDef, + "could not determine property type"); + } + // 17-Aug-2016, tatu: Default inclusion covers global default (for all types), as well // as type-default for enclosing POJO. What we need, then, is per-type default (if any) // for declared property type... and finally property annotation overrides JsonInclude.Value inclV = _config.getDefaultPropertyInclusion(actualType.getRawClass(), - _defaultInclusion); + rawPropertyType, _defaultInclusion); // property annotation override diff --git a/src/test/java/com/fasterxml/jackson/databind/filter/JsonIncludeTest.java b/src/test/java/com/fasterxml/jackson/databind/filter/JsonIncludeTest.java index a5aa82e6b6..33b45852bf 100644 --- a/src/test/java/com/fasterxml/jackson/databind/filter/JsonIncludeTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/filter/JsonIncludeTest.java @@ -129,6 +129,32 @@ static class EmptyListMapBean public Map map = Collections.emptyMap(); } + @JsonInclude(JsonInclude.Include.ALWAYS) + @JsonPropertyOrder({"num", "annotated", "plain"}) + static class MixedTypeBean + { + @JsonInclude(JsonInclude.Include.USE_DEFAULTS) + public Integer num = null; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String annotated = null; + + public String plain = null; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonPropertyOrder({"num", "annotated", "plain"}) + static class MixedTypeNonNullBean + { + @JsonInclude(JsonInclude.Include.USE_DEFAULTS) + public Integer num = null; + + @JsonInclude(JsonInclude.Include.ALWAYS) + public String annotated = null; + + public String plain = null; + } + // [databind#1351] static class Issue1351Bean @@ -221,7 +247,7 @@ public void testNonDefaultByClassNoCtor() throws IOException String json = MAPPER.writeValueAsString(bean); assertEquals(aposToQuotes("{'x':1,'y':2}"), json); } - + public void testMixedMethod() throws IOException { MixedBean bean = new MixedBean(); @@ -309,6 +335,54 @@ public void testPropConfigOverridesForInclude() throws IOException mapper.writeValueAsString(empty)); } + public void testPropConfigOverrideForIncludeAsPropertyNonNull() throws Exception + { + // First, with defaults, all but NON_NULL annotated included + MixedTypeBean nullValues = new MixedTypeBean(); + assertEquals(aposToQuotes("{'num':null,'plain':null}"), + MAPPER.writeValueAsString(nullValues)); + ObjectMapper mapper; + + // and then change inclusion as property criteria for either + mapper = new ObjectMapper(); + mapper.configOverride(String.class) + .setIncludeAsProperty(JsonInclude.Value + .construct(JsonInclude.Include.NON_NULL, null)); + assertEquals("{\"num\":null}", + mapper.writeValueAsString(nullValues)); + + mapper = new ObjectMapper(); + mapper.configOverride(Integer.class) + .setIncludeAsProperty(JsonInclude.Value + .construct(JsonInclude.Include.NON_NULL, null)); + assertEquals("{\"plain\":null}", + mapper.writeValueAsString(nullValues)); + } + + public void testPropConfigOverrideForIncludeAsPropertyAlways() throws Exception + { + // First, with defaults, only ALWAYS annotated included + MixedTypeNonNullBean nullValues = new MixedTypeNonNullBean(); + assertEquals("{\"annotated\":null}", + MAPPER.writeValueAsString(nullValues)); + ObjectMapper mapper; + + // and then change inclusion as property criteria for either + mapper = new ObjectMapper(); + mapper.configOverride(String.class) + .setIncludeAsProperty(JsonInclude.Value + .construct(JsonInclude.Include.ALWAYS, null)); + assertEquals(aposToQuotes("{'annotated':null,'plain':null}"), + mapper.writeValueAsString(nullValues)); + + mapper = new ObjectMapper(); + mapper.configOverride(Integer.class) + .setIncludeAsProperty(JsonInclude.Value + .construct(JsonInclude.Include.ALWAYS, null)); + assertEquals(aposToQuotes("{'num':null,'annotated':null}"), + mapper.writeValueAsString(nullValues)); + } + // [databind#1351], [databind#1417] public void testIssue1351() throws Exception {