diff --git a/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java index d8035f0316..0cf03fb234 100644 --- a/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java @@ -278,6 +278,18 @@ public JsonIgnoreProperties.Value findPropertyIgnorals(Annotated ac) */ public Boolean isIgnorableType(AnnotatedClass ac) { return null; } + /** + * Method for finding information about properties to include. + * + * @param ac Annotated class to introspect + * + * @since 2.12 + */ + public JsonIncludeProperties.Value findPropertyInclusions(Annotated ac) + { + return JsonIncludeProperties.Value.all(); + } + /** * Method for finding if annotated class has associated filter; and if so, * to return id that is used to locate filter. 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 64afde82ec..0b22a22ca2 100644 --- a/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfig.java +++ b/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfig.java @@ -498,6 +498,17 @@ public JsonInclude.Value getDefaultInclusion(Class baseType, public abstract JsonIgnoreProperties.Value getDefaultPropertyIgnorals(Class baseType, AnnotatedClass actualClass); + /** + * Helper method that may be called to see if there are property inclusion + * definitions from annotations (via {@link AnnotatedClass}). + * + * TODO: config override. + * + * @since 2.12 + */ + public abstract JsonIncludeProperties.Value getDefaultPropertyInclusions(Class baseType, + AnnotatedClass actualClass); + /** * Accessor for object used for determining whether specific property elements * (method, constructors, fields) can be auto-detected based on diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfigBase.java b/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfigBase.java index 044d13bcfe..72125d7abc 100644 --- a/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfigBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfigBase.java @@ -665,6 +665,14 @@ public final JsonIgnoreProperties.Value getDefaultPropertyIgnorals(Class base return JsonIgnoreProperties.Value.merge(base, overrides); } + @Override + public final JsonIncludeProperties.Value getDefaultPropertyInclusions(Class baseType, + AnnotatedClass actualClass) + { + AnnotationIntrospector intr = getAnnotationIntrospector(); + return (intr == null) ? null : intr.findPropertyInclusions(actualClass); + } + @Override public final VisibilityChecker getDefaultVisibilityChecker() { diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java index 96866488bb..c28c32c3da 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonIncludeProperties; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.Nulls; import com.fasterxml.jackson.annotation.JsonCreator.Mode; @@ -1373,6 +1374,10 @@ public JsonDeserializer createMapDeserializer(DeserializationContext ctxt, Set ignored = (ignorals == null) ? null : ignorals.findIgnoredForDeserialization(); md.setIgnorableProperties(ignored); + JsonIncludeProperties.Value inclusions = config.getDefaultPropertyInclusions(Map.class, + beanDesc.getClassInfo()); + Set included = inclusions == null ? null : inclusions.getIncluded(); + md.setIncludableProperties(included); deser = md; } } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java index ced28f0a94..81dbc97d50 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.cfg.CoercionAction; import com.fasterxml.jackson.databind.deser.impl.*; import com.fasterxml.jackson.databind.deser.impl.ReadableObjectId.Referring; +import com.fasterxml.jackson.databind.util.IgnorePropertiesUtil; import com.fasterxml.jackson.databind.util.NameTransformer; import com.fasterxml.jackson.databind.util.TokenBuffer; @@ -56,14 +57,31 @@ public class BeanDeserializer /** * Constructor used by {@link BeanDeserializerBuilder}. + * + * @deprecated in 2.12, remove from 3.0 */ + @Deprecated public BeanDeserializer(BeanDeserializerBuilder builder, BeanDescription beanDesc, BeanPropertyMap properties, Map backRefs, HashSet ignorableProps, boolean ignoreAllUnknown, boolean hasViews) { super(builder, beanDesc, properties, backRefs, - ignorableProps, ignoreAllUnknown, hasViews); + ignorableProps, ignoreAllUnknown, null, hasViews); + } + + /** + * Constructor used by {@link BeanDeserializerBuilder}. + * + * @since 2.12 + */ + public BeanDeserializer(BeanDeserializerBuilder builder, BeanDescription beanDesc, + BeanPropertyMap properties, Map backRefs, + HashSet ignorableProps, boolean ignoreAllUnknown, Set includableProps, + boolean hasViews) + { + super(builder, beanDesc, properties, backRefs, + ignorableProps, ignoreAllUnknown, includableProps, hasViews); } /** @@ -86,10 +104,21 @@ public BeanDeserializer(BeanDeserializerBase src, ObjectIdReader oir) { super(src, oir); } + /** + * @deprecated in 2.12, remove from 3.0 + */ + @Deprecated public BeanDeserializer(BeanDeserializerBase src, Set ignorableProps) { super(src, ignorableProps); } + /** + * @since 2.12 + */ + public BeanDeserializer(BeanDeserializerBase src, Set ignorableProps, Set includableProps) { + super(src, ignorableProps, includableProps); + } + public BeanDeserializer(BeanDeserializerBase src, BeanPropertyMap props) { super(src, props); } @@ -119,8 +148,8 @@ public BeanDeserializer withObjectIdReader(ObjectIdReader oir) { } @Override - public BeanDeserializer withIgnorableProperties(Set ignorableProps) { - return new BeanDeserializer(this, ignorableProps); + public BeanDeserializer withIgnorableProperties(Set ignorableProps, Set includableProps) { + return new BeanDeserializer(this, ignorableProps, includableProps); } @Override @@ -464,7 +493,7 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri continue; } // Things marked as ignorable should not be passed to any setter - if (_ignorableProps != null && _ignorableProps.contains(propName)) { + if (IgnorePropertiesUtil.shouldIgnore(propName, _ignorableProps, _includableProps)) { handleIgnoredProperty(p, ctxt, handledType(), propName); continue; } @@ -694,7 +723,7 @@ protected Object deserializeWithUnwrapped(JsonParser p, DeserializationContext c continue; } // Things marked as ignorable should not be passed to any setter - if (_ignorableProps != null && _ignorableProps.contains(propName)) { + if (IgnorePropertiesUtil.shouldIgnore(propName, _ignorableProps, _includableProps)) { handleIgnoredProperty(p, ctxt, bean, propName); continue; } @@ -751,7 +780,7 @@ protected Object deserializeWithUnwrapped(JsonParser p, DeserializationContext c } continue; } - if (_ignorableProps != null && _ignorableProps.contains(propName)) { + if (IgnorePropertiesUtil.shouldIgnore(propName, _ignorableProps, _includableProps)) { handleIgnoredProperty(p, ctxt, bean, propName); continue; } @@ -850,7 +879,7 @@ protected Object deserializeUsingPropertyBasedWithUnwrapped(JsonParser p, Deseri continue; } // Things marked as ignorable should not be passed to any setter - if (_ignorableProps != null && _ignorableProps.contains(propName)) { + if (IgnorePropertiesUtil.shouldIgnore(propName, _ignorableProps, _includableProps)) { handleIgnoredProperty(p, ctxt, handledType(), propName); continue; } @@ -942,7 +971,7 @@ protected Object deserializeWithExternalTypeId(JsonParser p, DeserializationCont continue; } // ignorable things should be ignored - if (_ignorableProps != null && _ignorableProps.contains(propName)) { + if (IgnorePropertiesUtil.shouldIgnore(propName, _ignorableProps, _includableProps)) { handleIgnoredProperty(p, ctxt, bean, propName); continue; } @@ -1033,7 +1062,7 @@ protected Object deserializeUsingPropertyBasedWithExternalTypeId(JsonParser p, D continue; } // Things marked as ignorable should not be passed to any setter - if (_ignorableProps != null && _ignorableProps.contains(propName)) { + if (IgnorePropertiesUtil.shouldIgnore(propName, _ignorableProps, _includableProps)) { handleIgnoredProperty(p, ctxt, handledType(), propName); continue; } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java index fe1008577b..15f0855c72 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java @@ -135,6 +135,11 @@ public abstract class BeanDeserializerBase */ final protected Set _ignorableProps; + /** + * Keep track of the the properties that needs to be specifically included. + */ + final protected Set _includableProps; + /** * Flag that can be set to ignore and skip unknown properties. * If set, will not throw an exception for unknown properties. @@ -201,6 +206,7 @@ protected BeanDeserializerBase(BeanDeserializerBuilder builder, BeanDescription beanDesc, BeanPropertyMap properties, Map backRefs, Set ignorableProps, boolean ignoreAllUnknown, + Set includableProps, boolean hasViews) { super(beanDesc.getType()); @@ -211,6 +217,7 @@ protected BeanDeserializerBase(BeanDeserializerBuilder builder, _backRefs = backRefs; _ignorableProps = ignorableProps; _ignoreAllUnknown = ignoreAllUnknown; + _includableProps = includableProps; _anySetter = builder.getAnySetter(); List injectables = builder.getInjectables(); @@ -263,6 +270,7 @@ protected BeanDeserializerBase(BeanDeserializerBase src, boolean ignoreAllUnknow _backRefs = src._backRefs; _ignorableProps = src._ignorableProps; _ignoreAllUnknown = ignoreAllUnknown; + _includableProps = src._includableProps; _anySetter = src._anySetter; _injectables = src._injectables; _objectIdReader = src._objectIdReader; @@ -288,6 +296,7 @@ protected BeanDeserializerBase(BeanDeserializerBase src, NameTransformer unwrapp _backRefs = src._backRefs; _ignorableProps = src._ignorableProps; _ignoreAllUnknown = (unwrapper != null) || src._ignoreAllUnknown; + _includableProps = src._includableProps; _anySetter = src._anySetter; _injectables = src._injectables; _objectIdReader = src._objectIdReader; @@ -325,6 +334,7 @@ public BeanDeserializerBase(BeanDeserializerBase src, ObjectIdReader oir) _backRefs = src._backRefs; _ignorableProps = src._ignorableProps; _ignoreAllUnknown = src._ignoreAllUnknown; + _includableProps = src._includableProps; _anySetter = src._anySetter; _injectables = src._injectables; @@ -351,6 +361,14 @@ public BeanDeserializerBase(BeanDeserializerBase src, ObjectIdReader oir) } public BeanDeserializerBase(BeanDeserializerBase src, Set ignorableProps) + { + this(src, ignorableProps, src._includableProps); + } + + /** + * @since 2.12 + */ + public BeanDeserializerBase(BeanDeserializerBase src, Set ignorableProps, Set includableProps) { super(src._beanType); _beanType = src._beanType; @@ -362,6 +380,7 @@ public BeanDeserializerBase(BeanDeserializerBase src, Set ignorableProps _backRefs = src._backRefs; _ignorableProps = ignorableProps; _ignoreAllUnknown = src._ignoreAllUnknown; + _includableProps = includableProps; _anySetter = src._anySetter; _injectables = src._injectables; @@ -375,9 +394,10 @@ public BeanDeserializerBase(BeanDeserializerBase src, Set ignorableProps // 01-May-2016, tatu: [databind#1217]: Remove properties from mapping, // to avoid them being deserialized - _beanProperties = src._beanProperties.withoutProperties(ignorableProps); + _beanProperties = src._beanProperties.withoutProperties(ignorableProps, includableProps); } + /** * @since 2.8 */ @@ -394,6 +414,7 @@ protected BeanDeserializerBase(BeanDeserializerBase src, BeanPropertyMap beanPro _backRefs = src._backRefs; _ignorableProps = src._ignorableProps; _ignoreAllUnknown = src._ignoreAllUnknown; + _includableProps = src._includableProps; _anySetter = src._anySetter; _injectables = src._injectables; _objectIdReader = src._objectIdReader; @@ -411,7 +432,21 @@ protected BeanDeserializerBase(BeanDeserializerBase src, BeanPropertyMap beanPro public abstract BeanDeserializerBase withObjectIdReader(ObjectIdReader oir); - public abstract BeanDeserializerBase withIgnorableProperties(Set ignorableProps); + public BeanDeserializerBase withIgnorableProperties(Set ignorableProps) { + return withIgnorableProperties(ignorableProps, _includableProps); + } + + /** + * @since 2.12 + */ + public abstract BeanDeserializerBase withIgnorableProperties(Set ignorableProps, Set includableProps); + + /** + * @since 2.12 + */ + public BeanDeserializerBase withIncludableProperties(Set includableProperties) { + return withIgnorableProperties(_ignorableProps, includableProperties); + } // NOTE! To be made `abstract` in 2.12 or later /** @@ -422,7 +457,7 @@ public BeanDeserializerBase withIgnoreAllUnknown(boolean ignoreUnknown) { if (ignoreUnknown == _ignoreAllUnknown) { return this; } - return withIgnorableProperties(_ignorableProps); + return withIgnorableProperties(_ignorableProps, _includableProps); } /** @@ -469,10 +504,10 @@ public void resolve(DeserializationContext ctxt) throws JsonMappingException // 22-Jan-2018, tatu: May need to propagate "ignorable" status (from `Access.READ_ONLY` // or perhaps class-ignorables) into Creator properties too. Can not just delete, // at this point, but is needed for further processing down the line - if (_ignorableProps != null) { + if (_ignorableProps != null || _includableProps != null) { for (int i = 0, end = creatorProps.length; i < end; ++i) { SettableBeanProperty prop = creatorProps[i]; - if (_ignorableProps.contains(prop.getName())) { + if (IgnorePropertiesUtil.shouldIgnore(prop.getName(), _ignorableProps, _includableProps)) { creatorProps[i].markAsIgnorable(); } } @@ -773,6 +808,21 @@ public JsonDeserializer createContextual(DeserializationContext ctxt, contextual = contextual.withIgnoreAllUnknown(true); } } + JsonIncludeProperties.Value inclusions = intr.findPropertyInclusions(accessor); + if (inclusions != null) { + Set included = inclusions.getIncluded(); + Set prev = contextual._includableProps; + if (prev != null && included != null) { + Set newIncluded = new HashSet<>(); + // Make the intersection with the previously included properties. + for(String prop : prev) { + if (included.contains(prop)) { + newIncluded.add(prop); + } + } + contextual = contextual.withIncludableProperties(newIncluded); + } + } } // One more thing: are we asked to serialize POJO as array? @@ -1587,7 +1637,8 @@ protected void handleUnknownVanilla(JsonParser p, DeserializationContext ctxt, Object beanOrBuilder, String propName) throws IOException { - if (_ignorableProps != null && _ignorableProps.contains(propName)) { + + if (IgnorePropertiesUtil.shouldIgnore(propName, _ignorableProps, _includableProps)) { handleIgnoredProperty(p, ctxt, beanOrBuilder, propName); } else if (_anySetter != null) { try { @@ -1615,7 +1666,7 @@ protected void handleUnknownProperty(JsonParser p, DeserializationContext ctxt, p.skipChildren(); return; } - if (_ignorableProps != null && _ignorableProps.contains(propName)) { + if (IgnorePropertiesUtil.shouldIgnore(propName, _ignorableProps, _includableProps)) { handleIgnoredProperty(p, ctxt, beanOrClass, propName); } // Otherwise use default handling (call handler(s); if not diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBuilder.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBuilder.java index 702edd847d..5f5f493c4e 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBuilder.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBuilder.java @@ -12,6 +12,7 @@ import com.fasterxml.jackson.databind.introspect.*; import com.fasterxml.jackson.databind.util.Annotations; import com.fasterxml.jackson.databind.util.ClassUtil; +import com.fasterxml.jackson.databind.util.IgnorePropertiesUtil; /** * Builder class used for aggregating deserialization information about @@ -72,6 +73,12 @@ public class BeanDeserializerBuilder */ protected HashSet _ignorableProps; + /** + * Set of names of properties that are recognized and are set to be included for deserialization + * purposes (null deactivate this, empty includes nothing). + */ + protected HashSet _includableProps; + /** * Object that will handle value instantiation for the bean type. */ @@ -136,7 +143,8 @@ protected BeanDeserializerBuilder(BeanDeserializerBuilder src) _injectables = _copy(src._injectables); _backRefProperties = _copy(src._backRefProperties); // Hmmh. Should we create defensive copies here? For now, not yet - _ignorableProps = src._ignorableProps; + _ignorableProps = src._ignorableProps; + _includableProps = src._includableProps; _valueInstantiator = src._valueInstantiator; _objectIdReader = src._objectIdReader; @@ -235,6 +243,19 @@ public void addIgnorable(String propName) _ignorableProps.add(propName); } + /** + * Method that will add property name as one of the properties that will be included. + * + * @since 2.12 + */ + public void addIncludable(String propName) + { + if (_includableProps == null) { + _includableProps = new HashSet<>(); + } + _includableProps.add(propName); + } + /** * Method called by deserializer factory, when a "creator property" * (something that is passed via constructor- or factory method argument; @@ -333,7 +354,7 @@ public JsonPOJOBuilder.Value getBuilderConfig() { * @since 2.9.4 */ public boolean hasIgnorable(String name) { - return (_ignorableProps != null) && _ignorableProps.contains(name); + return IgnorePropertiesUtil.shouldIgnore(name, _ignorableProps, _includableProps); } /* @@ -379,7 +400,7 @@ public JsonDeserializer build() } return new BeanDeserializer(this, - _beanDesc, propertyMap, _backRefProperties, _ignorableProps, _ignoreAllUnknown, + _beanDesc, propertyMap, _backRefProperties, _ignorableProps, _ignoreAllUnknown, _includableProps, anyViews); } @@ -463,7 +484,7 @@ protected JsonDeserializer createBuilderBasedDeserializer(JavaType valueType, BeanPropertyMap propertyMap, boolean anyViews) { return new BuilderBasedDeserializer(this, _beanDesc, valueType, propertyMap, _backRefProperties, _ignorableProps, _ignoreAllUnknown, - anyViews); + _includableProps, anyViews); } /* diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java index 91e6ebc261..75289456c3 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java @@ -13,6 +13,7 @@ import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; import com.fasterxml.jackson.databind.jsontype.impl.SubTypeValidator; import com.fasterxml.jackson.databind.util.ClassUtil; +import com.fasterxml.jackson.databind.util.IgnorePropertiesUtil; import com.fasterxml.jackson.databind.util.SimpleBeanPropertyDefinition; /** @@ -512,6 +513,18 @@ protected void addBeanProps(DeserializationContext ctxt, } else { ignored = Collections.emptySet(); } + JsonIncludeProperties.Value inclusions = ctxt.getConfig() + .getDefaultPropertyInclusions(beanDesc.getBeanClass(), + beanDesc.getClassInfo()); + Set included = null; + if (inclusions != null) { + included = inclusions.getIncluded(); + if (included != null) { + for(String propName : included) { + builder.addIncludable(propName); + } + } + } // Also, do we have a fallback "any" setter? AnnotatedMember anySetter = beanDesc.findAnySetterAccessor(); @@ -534,7 +547,7 @@ protected void addBeanProps(DeserializationContext ctxt, // Ok: let's then filter out property definitions List propDefs = filterBeanProps(ctxt, - beanDesc, builder, beanDesc.findProperties(), ignored); + beanDesc, builder, beanDesc.findProperties(), ignored, included); // After which we can let custom code change the set if (_factoryConfig.hasDeserializerModifiers()) { for (BeanDeserializerModifier mod : _factoryConfig.deserializerModifiers()) { @@ -646,12 +659,32 @@ private boolean _isSetterlessType(Class rawType) { * as well as properties that have "ignorable types". * Note that this will not remove properties that have no * setters. + * + * @deprecated in 2.12, remove from 3.0 */ + @Deprecated protected List filterBeanProps(DeserializationContext ctxt, BeanDescription beanDesc, BeanDeserializerBuilder builder, List propDefsIn, Set ignored) throws JsonMappingException + { + return filterBeanProps(ctxt, beanDesc, builder, propDefsIn, ignored, null); + } + + /** + * Helper method called to filter out explicit ignored properties, + * as well as properties that have "ignorable types". + * Note that this will not remove properties that have no + * setters. + * + * @since 2.12 + */ + protected List filterBeanProps(DeserializationContext ctxt, + BeanDescription beanDesc, BeanDeserializerBuilder builder, + List propDefsIn, + Set ignored, + Set included) { ArrayList result = new ArrayList( Math.max(4, propDefsIn.size())); @@ -659,7 +692,8 @@ protected List filterBeanProps(DeserializationContext ct // These are all valid setters, but we do need to introspect bit more for (BeanPropertyDefinition property : propDefsIn) { String name = property.getName(); - if (ignored.contains(name)) { // explicit ignoral using @JsonIgnoreProperties needs to block entries + // explicit ignoral using @JsonIgnoreProperties of @JsonIncludeProperties needs to block entries + if (IgnorePropertiesUtil.shouldIgnore(name, ignored, included)) { continue; } if (!property.hasConstructorParameter()) { // never skip constructor params diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BuilderBasedDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/BuilderBasedDeserializer.java index fa15ed319f..44bff9d3c5 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BuilderBasedDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BuilderBasedDeserializer.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.cfg.CoercionAction; import com.fasterxml.jackson.databind.deser.impl.*; import com.fasterxml.jackson.databind.introspect.AnnotatedMethod; +import com.fasterxml.jackson.databind.util.IgnorePropertiesUtil; import com.fasterxml.jackson.databind.util.NameTransformer; import com.fasterxml.jackson.databind.util.TokenBuffer; @@ -50,9 +51,21 @@ public BuilderBasedDeserializer(BeanDeserializerBuilder builder, BeanPropertyMap properties, Map backRefs, Set ignorableProps, boolean ignoreAllUnknown, boolean hasViews) + { + this(builder, beanDesc, targetType, properties, backRefs, ignorableProps, ignoreAllUnknown, null, hasViews); + } + + /** + * @since 2.12 + */ + public BuilderBasedDeserializer(BeanDeserializerBuilder builder, + BeanDescription beanDesc, JavaType targetType, + BeanPropertyMap properties, Map backRefs, + Set ignorableProps, boolean ignoreAllUnknown, Set includableProps, + boolean hasViews) { super(builder, beanDesc, properties, backRefs, - ignorableProps, ignoreAllUnknown, hasViews); + ignorableProps, ignoreAllUnknown, includableProps, hasViews); _targetType = targetType; _buildMethod = builder.getBuildMethod(); // 05-Mar-2012, tatu: Cannot really make Object Ids work with builders, not yet anyway @@ -106,7 +119,11 @@ public BuilderBasedDeserializer(BuilderBasedDeserializer src, ObjectIdReader oir } public BuilderBasedDeserializer(BuilderBasedDeserializer src, Set ignorableProps) { - super(src, ignorableProps); + this(src, ignorableProps, src._includableProps); + } + + public BuilderBasedDeserializer(BuilderBasedDeserializer src, Set ignorableProps, Set includableProps) { + super(src, ignorableProps, includableProps); _buildMethod = src._buildMethod; _targetType = src._targetType; } @@ -133,8 +150,8 @@ public BeanDeserializerBase withObjectIdReader(ObjectIdReader oir) { } @Override - public BeanDeserializerBase withIgnorableProperties(Set ignorableProps) { - return new BuilderBasedDeserializer(this, ignorableProps); + public BeanDeserializerBase withIgnorableProperties(Set ignorableProps, Set includableProps) { + return new BuilderBasedDeserializer(this, ignorableProps, includableProps); } @Override @@ -396,7 +413,7 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, } // As per [JACKSON-313], things marked as ignorable should not be // passed to any setter - if (_ignorableProps != null && _ignorableProps.contains(propName)) { + if (IgnorePropertiesUtil.shouldIgnore(propName, _ignorableProps, _includableProps)) { handleIgnoredProperty(p, ctxt, handledType(), propName); continue; } @@ -599,7 +616,7 @@ protected Object deserializeWithUnwrapped(JsonParser p, DeserializationContext c continue; } // ignorable things should be ignored - if (_ignorableProps != null && _ignorableProps.contains(propName)) { + if (IgnorePropertiesUtil.shouldIgnore(propName, _ignorableProps, _includableProps)) { handleIgnoredProperty(p, ctxt, bean, propName); continue; } @@ -665,7 +682,7 @@ protected Object deserializeUsingPropertyBasedWithUnwrapped(JsonParser p, buffer.bufferProperty(prop, prop.deserialize(p, ctxt)); continue; } - if (_ignorableProps != null && _ignorableProps.contains(propName)) { + if (IgnorePropertiesUtil.shouldIgnore(propName, _ignorableProps, _includableProps)) { handleIgnoredProperty(p, ctxt, handledType(), propName); continue; } @@ -710,7 +727,7 @@ protected Object deserializeWithUnwrapped(JsonParser p, } continue; } - if (_ignorableProps != null && _ignorableProps.contains(propName)) { + if (IgnorePropertiesUtil.shouldIgnore(propName, _ignorableProps, _includableProps)) { handleIgnoredProperty(p, ctxt, builder, propName); continue; } @@ -770,7 +787,7 @@ protected Object deserializeWithExternalTypeId(JsonParser p, continue; } // ignorable things should be ignored - if (_ignorableProps != null && _ignorableProps.contains(propName)) { + if (IgnorePropertiesUtil.shouldIgnore(propName, _ignorableProps, _includableProps)) { handleIgnoredProperty(p, ctxt, bean, propName); continue; } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/BeanAsArrayBuilderDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/BeanAsArrayBuilderDeserializer.java index 3c9cf8b072..73ab84871c 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/BeanAsArrayBuilderDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/BeanAsArrayBuilderDeserializer.java @@ -77,8 +77,8 @@ public BeanDeserializerBase withObjectIdReader(ObjectIdReader oir) { } @Override - public BeanDeserializerBase withIgnorableProperties(Set ignorableProps) { - return new BeanAsArrayBuilderDeserializer(_delegate.withIgnorableProperties(ignorableProps), + public BeanDeserializerBase withIgnorableProperties(Set ignorableProps, Set includableProps) { + return new BeanAsArrayBuilderDeserializer(_delegate.withIgnorableProperties(ignorableProps, includableProps), _targetType, _orderedProperties, _buildMethod); } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/BeanAsArrayDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/BeanAsArrayDeserializer.java index f0116727b8..aad5b1f526 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/BeanAsArrayDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/BeanAsArrayDeserializer.java @@ -67,8 +67,8 @@ public BeanDeserializerBase withObjectIdReader(ObjectIdReader oir) { } @Override - public BeanDeserializerBase withIgnorableProperties(Set ignorableProps) { - return new BeanAsArrayDeserializer(_delegate.withIgnorableProperties(ignorableProps), + public BeanDeserializerBase withIgnorableProperties(Set ignorableProps, Set includableProps) { + return new BeanAsArrayDeserializer(_delegate.withIgnorableProperties(ignorableProps, includableProps), _orderedProperties); } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/BeanPropertyMap.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/BeanPropertyMap.java index 68e17ad0b3..50248c600a 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/BeanPropertyMap.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/BeanPropertyMap.java @@ -15,6 +15,7 @@ import com.fasterxml.jackson.databind.cfg.MapperConfig; import com.fasterxml.jackson.databind.deser.SettableBeanProperty; import com.fasterxml.jackson.databind.util.ClassUtil; +import com.fasterxml.jackson.databind.util.IgnorePropertiesUtil; import com.fasterxml.jackson.databind.util.NameTransformer; /** @@ -382,7 +383,20 @@ public BeanPropertyMap renameAll(NameTransformer transformer) */ public BeanPropertyMap withoutProperties(Collection toExclude) { - if (toExclude.isEmpty()) { + return withoutProperties(toExclude, null); + } + + /** + * Mutant factory method that will use this instance as the base, and + * construct an instance that is otherwise same except for excluding + * properties with specified names, or including only the one marked + * as included + * + * @since 2.12 + */ + public BeanPropertyMap withoutProperties(Collection toExclude, Collection toInclude) + { + if ((toExclude == null || toExclude.isEmpty()) && toInclude == null) { return this; } final int len = _propsInOrder.length; @@ -394,7 +408,7 @@ public BeanPropertyMap withoutProperties(Collection toExclude) // or, if entries to ignore should be retained as nulls. For now just // prune them out if (prop != null) { // may contain holes, too, check. - if (!toExclude.contains(prop.getName())) { + if (!IgnorePropertiesUtil.shouldIgnore(prop.getName(), toExclude, toInclude)) { newProps.add(prop); } } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/MapDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/MapDeserializer.java index cba73fb4a0..8216cd1cc0 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/MapDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/MapDeserializer.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonIncludeProperties; import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.databind.*; @@ -17,6 +18,7 @@ import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; import com.fasterxml.jackson.databind.type.LogicalType; import com.fasterxml.jackson.databind.util.ArrayBuilders; +import com.fasterxml.jackson.databind.util.IgnorePropertiesUtil; /** * Basic deserializer that can take JSON "Object" structure and @@ -86,6 +88,7 @@ public class MapDeserializer // // Any properties to ignore if seen? protected Set _ignorableProperties; + protected Set _includableProperties; /* /********************************************************** @@ -124,6 +127,7 @@ protected MapDeserializer(MapDeserializer src) _hasDefaultCreator = src._hasDefaultCreator; // should we make a copy here? _ignorableProperties = src._ignorableProperties; + _includableProperties = src._includableProperties; _standardStringKey = src._standardStringKey; } @@ -133,6 +137,19 @@ protected MapDeserializer(MapDeserializer src, TypeDeserializer valueTypeDeser, NullValueProvider nuller, Set ignorable) + { + this(src, keyDeser,valueDeser, valueTypeDeser, nuller, ignorable, null); + } + + /** + * @since 2.12 + */ + protected MapDeserializer(MapDeserializer src, + KeyDeserializer keyDeser, JsonDeserializer valueDeser, + TypeDeserializer valueTypeDeser, + NullValueProvider nuller, + Set ignorable, + Set includable) { super(src, nuller, src._unwrapSingle); _keyDeserializer = keyDeser; @@ -143,6 +160,7 @@ protected MapDeserializer(MapDeserializer src, _delegateDeserializer = src._delegateDeserializer; _hasDefaultCreator = src._hasDefaultCreator; _ignorableProperties = ignorable; + _includableProperties = includable; _standardStringKey = _isStdKeyDeser(_containerType, keyDeser); } @@ -157,15 +175,26 @@ protected MapDeserializer withResolved(KeyDeserializer keyDeser, NullValueProvider nuller, Set ignorable) { - + return withResolved(keyDeser, valueTypeDeser, valueDeser, nuller, ignorable, _includableProperties); + } + + /** + * @since 2.12 + */ + protected MapDeserializer withResolved(KeyDeserializer keyDeser, + TypeDeserializer valueTypeDeser, JsonDeserializer valueDeser, + NullValueProvider nuller, + Set ignorable, Set includable) + { + if ((_keyDeserializer == keyDeser) && (_valueDeserializer == valueDeser) && (_valueTypeDeserializer == valueTypeDeser) && (_nullProvider == nuller) - && (_ignorableProperties == ignorable)) { + && (_ignorableProperties == ignorable) && (_includableProperties == includable)) { return this; } return new MapDeserializer(this, keyDeser, (JsonDeserializer) valueDeser, valueTypeDeser, - nuller, ignorable); + nuller, ignorable, includable); } /** @@ -186,6 +215,10 @@ protected final boolean _isStdKeyDeser(JavaType mapType, KeyDeserializer keyDese && isDefaultKeyDeserializer(keyDeser)); } + /** + * @deprecated in 2.12, remove from 3.0 + */ + @Deprecated public void setIgnorableProperties(String[] ignorable) { _ignorableProperties = (ignorable == null || ignorable.length == 0) ? null : ArrayBuilders.arrayToSet(ignorable); @@ -196,6 +229,10 @@ public void setIgnorableProperties(Set ignorable) { null : ignorable; } + public void setIncludableProperties(Set includable) { + _includableProperties = includable; + } + /* /********************************************************** /* Validation, post-processing (ResolvableDeserializer) @@ -269,6 +306,7 @@ public JsonDeserializer createContextual(DeserializationContext ctxt, vtd = vtd.forProperty(property); } Set ignored = _ignorableProperties; + Set included = _includableProperties; AnnotationIntrospector intr = ctxt.getAnnotationIntrospector(); if (_neitherNull(intr, property)) { AnnotatedMember member = property.getMember(); @@ -283,10 +321,27 @@ public JsonDeserializer createContextual(DeserializationContext ctxt, } } } + JsonIncludeProperties.Value inclusions = intr.findPropertyInclusions(member); + if (inclusions != null) { + Set includedToAdd = inclusions.getIncluded(); + if (includedToAdd != null) { + Set newIncluded = new HashSet<>(); + if (included == null) { + newIncluded = new HashSet<>(includedToAdd); + } else { + for (String str : includedToAdd) { + if (included.contains(str)) { + newIncluded.add(str); + } + } + } + included = newIncluded; + } + } } } return withResolved(keyDeser, vtd, valueDeser, - findContentNullProvider(ctxt, property, valueDeser), ignored); + findContentNullProvider(ctxt, property, valueDeser), ignored, included); } /* @@ -331,7 +386,8 @@ public boolean isCachable() { return (_valueDeserializer == null) && (_keyDeserializer == null) && (_valueTypeDeserializer == null) - && (_ignorableProperties == null); + && (_ignorableProperties == null) + && (_includableProperties == null); } @Override // since 2.12 @@ -458,7 +514,7 @@ protected final void _readAndBind(JsonParser p, DeserializationContext ctxt, Object key = keyDes.deserializeKey(keyStr, ctxt); // And then the value... JsonToken t = p.nextToken(); - if (_ignorableProperties != null && _ignorableProperties.contains(keyStr)) { + if (IgnorePropertiesUtil.shouldIgnore(keyStr, _ignorableProperties, _includableProperties)) { p.skipChildren(); continue; } @@ -520,7 +576,7 @@ protected final void _readAndBindStringKeyMap(JsonParser p, DeserializationConte for (; key != null; key = p.nextFieldName()) { JsonToken t = p.nextToken(); - if (_ignorableProperties != null && _ignorableProperties.contains(key)) { + if (IgnorePropertiesUtil.shouldIgnore(key, _ignorableProperties, _includableProperties)) { p.skipChildren(); continue; } @@ -572,7 +628,7 @@ public Map _deserializeUsingCreator(JsonParser p, Deserialization for (; key != null; key = p.nextFieldName()) { JsonToken t = p.nextToken(); // to get to value - if (_ignorableProperties != null && _ignorableProperties.contains(key)) { + if (IgnorePropertiesUtil.shouldIgnore(key, _ignorableProperties, _includableProperties)) { p.skipChildren(); // and skip it (in case of array/object) continue; } @@ -661,7 +717,7 @@ protected final void _readAndUpdate(JsonParser p, DeserializationContext ctxt, Object key = keyDes.deserializeKey(keyStr, ctxt); // And then the value... JsonToken t = p.nextToken(); - if (_ignorableProperties != null && _ignorableProperties.contains(keyStr)) { + if (IgnorePropertiesUtil.shouldIgnore(keyStr, _ignorableProperties, _includableProperties)) { p.skipChildren(); continue; } @@ -728,7 +784,7 @@ protected final void _readAndUpdateStringKeyMap(JsonParser p, DeserializationCon for (; key != null; key = p.nextFieldName()) { JsonToken t = p.nextToken(); - if (_ignorableProperties != null && _ignorableProperties.contains(key)) { + if (IgnorePropertiesUtil.shouldIgnore(key, _ignorableProperties, _includableProperties)) { p.skipChildren(); continue; } diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationIntrospectorPair.java b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationIntrospectorPair.java index d69bbd3cd3..3ba0ab9a5c 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationIntrospectorPair.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationIntrospectorPair.java @@ -10,6 +10,7 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonIncludeProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.core.Version; @@ -125,6 +126,15 @@ public JsonIgnoreProperties.Value findPropertyIgnorals(Annotated a) ? v1 : v2.withOverrides(v1); } + @Override + public JsonIncludeProperties.Value findPropertyInclusions(Annotated a) + { + JsonIncludeProperties.Value v2 = _secondary.findPropertyInclusions(a); + JsonIncludeProperties.Value v1 = _primary.findPropertyInclusions(a); + return (v2 == null) // shouldn't occur but + ? v1 : v2.withOverrides(v1); + } + @Override public Boolean isIgnorableType(AnnotatedClass ac) { diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java index 747aeccb5e..175b90c9db 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java @@ -307,6 +307,16 @@ public Boolean isIgnorableType(AnnotatedClass ac) { JsonIgnoreType ignore = _findAnnotation(ac, JsonIgnoreType.class); return (ignore == null) ? null : ignore.value(); } + + @Override + public JsonIncludeProperties.Value findPropertyInclusions(Annotated a) + { + JsonIncludeProperties v = _findAnnotation(a, JsonIncludeProperties.class); + if (v == null) { + return JsonIncludeProperties.Value.all(); + } + return JsonIncludeProperties.Value.from(v); + } @Override public Object findFilterId(Annotated a) { 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 889a724702..d37ef546cc 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java @@ -11,6 +11,7 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonIncludeProperties; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.cfg.SerializerFactoryConfig; @@ -823,7 +824,11 @@ protected JsonSerializer buildMapSerializer(SerializerProvider prov, beanDesc.getClassInfo()); Set ignored = (ignorals == null) ? null : ignorals.findIgnoredForSerialization(); - MapSerializer mapSer = MapSerializer.construct(ignored, + JsonIncludeProperties.Value inclusions = config.getDefaultPropertyInclusions(Map.class, + beanDesc.getClassInfo()); + Set included = (inclusions == null) ? null + : inclusions.getIncluded(); + MapSerializer mapSer = MapSerializer.construct(ignored, included, type, staticTyping, elementTypeSerializer, keySerializer, elementValueSerializer, filterId); ser = _checkMapContentInclusion(prov, beanDesc, mapSer); diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializer.java index 501f16d7f2..b202b47f60 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializer.java @@ -68,6 +68,10 @@ protected BeanSerializer(BeanSerializerBase src, Set toIgnore) { super(src, toIgnore); } + protected BeanSerializer(BeanSerializerBase src, Set toIgnore, Set toInclude) { + super(src, toIgnore, toInclude); + } + // @since 2.11.1 protected BeanSerializer(BeanSerializerBase src, BeanPropertyWriter[] properties, BeanPropertyWriter[] filteredProperties) { @@ -120,6 +124,11 @@ protected BeanSerializerBase withIgnorals(Set toIgnore) { return new BeanSerializer(this, toIgnore); } + @Override + protected BeanSerializerBase withIgnorals(Set toIgnore, Set toInclude) { + return new BeanSerializer(this, toIgnore, toInclude); + } + @Override // @since 2.11.1 protected BeanSerializerBase withProperties(BeanPropertyWriter[] properties, BeanPropertyWriter[] filteredProperties) { 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 043485cc8f..3a87460af0 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerFactory.java @@ -3,6 +3,7 @@ import java.util.*; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonIncludeProperties; import com.fasterxml.jackson.annotation.ObjectIdGenerator; import com.fasterxml.jackson.annotation.ObjectIdGenerators; import com.fasterxml.jackson.annotation.JsonTypeInfo.As; @@ -22,6 +23,7 @@ import com.fasterxml.jackson.databind.type.ReferenceType; import com.fasterxml.jackson.databind.util.ClassUtil; import com.fasterxml.jackson.databind.util.Converter; +import com.fasterxml.jackson.databind.util.IgnorePropertiesUtil; /** * Factory class that can provide serializers for any regular Java beans @@ -634,17 +636,25 @@ protected List filterBeanProperties(SerializationConfig conf // just use it as is. JsonIgnoreProperties.Value ignorals = config.getDefaultPropertyIgnorals(beanDesc.getBeanClass(), beanDesc.getClassInfo()); + Set ignored = null; if (ignorals != null) { - Set ignored = ignorals.findIgnoredForSerialization(); - if (!ignored.isEmpty()) { - Iterator it = props.iterator(); - while (it.hasNext()) { - if (ignored.contains(it.next().getName())) { - it.remove(); - } + ignored = ignorals.findIgnoredForSerialization(); + } + JsonIncludeProperties.Value inclusions = config.getDefaultPropertyInclusions(beanDesc.getBeanClass(), + beanDesc.getClassInfo()); + Set included = null; + if (inclusions != null) { + included = inclusions.getIncluded(); + } + if (included != null || (ignored != null && !ignored.isEmpty())) { + Iterator it = props.iterator(); + while (it.hasNext()) { + if (IgnorePropertiesUtil.shouldIgnore(it.next().getName(), ignored, included)) { + it.remove(); } } } + return props; } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/BeanAsArraySerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/BeanAsArraySerializer.java index 2abf7649de..86b16366da 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/BeanAsArraySerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/BeanAsArraySerializer.java @@ -67,7 +67,11 @@ public BeanAsArraySerializer(BeanSerializerBase src) { } protected BeanAsArraySerializer(BeanSerializerBase src, Set toIgnore) { - super(src, toIgnore); + this(src, toIgnore, null); + } + + protected BeanAsArraySerializer(BeanSerializerBase src, Set toIgnore, Set toInclude) { + super(src, toIgnore, toInclude); _defaultSerializer = src; } @@ -112,6 +116,11 @@ protected BeanAsArraySerializer withIgnorals(Set toIgnore) { return new BeanAsArraySerializer(this, toIgnore); } + @Override + protected BeanAsArraySerializer withIgnorals(Set toIgnore, Set toInclude) { + return new BeanAsArraySerializer(this, toIgnore, toInclude); + } + @Override // @since 2.11.1 protected BeanSerializerBase withProperties(BeanPropertyWriter[] properties, BeanPropertyWriter[] filteredProperties) { diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/UnwrappingBeanSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/UnwrappingBeanSerializer.java index 32418233f4..dd50a6b191 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/UnwrappingBeanSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/UnwrappingBeanSerializer.java @@ -51,7 +51,11 @@ public UnwrappingBeanSerializer(UnwrappingBeanSerializer src, } protected UnwrappingBeanSerializer(UnwrappingBeanSerializer src, Set toIgnore) { - super(src, toIgnore); + this(src, toIgnore, null); + } + + protected UnwrappingBeanSerializer(UnwrappingBeanSerializer src, Set toIgnore, Set toInclude) { + super(src, toIgnore, toInclude); _nameTransformer = src._nameTransformer; } @@ -94,6 +98,11 @@ protected BeanSerializerBase withIgnorals(Set toIgnore) { return new UnwrappingBeanSerializer(this, toIgnore); } + @Override + protected BeanSerializerBase withIgnorals(Set toIgnore, Set toInclude) { + return new UnwrappingBeanSerializer(this, toIgnore, toInclude); + } + @Override // @since 2.11.1 protected BeanSerializerBase withProperties(BeanPropertyWriter[] properties, BeanPropertyWriter[] filteredProperties) { 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 95621c9a50..afadc8a221 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 @@ -26,6 +26,7 @@ import com.fasterxml.jackson.databind.ser.impl.WritableObjectId; import com.fasterxml.jackson.databind.util.ArrayBuilders; import com.fasterxml.jackson.databind.util.Converter; +import com.fasterxml.jackson.databind.util.IgnorePropertiesUtil; import com.fasterxml.jackson.databind.util.NameTransformer; /** @@ -175,10 +176,14 @@ protected BeanSerializerBase(BeanSerializerBase src, @Deprecated // since 2.8, remove soon protected BeanSerializerBase(BeanSerializerBase src, String[] toIgnore) { - this(src, ArrayBuilders.arrayToSet(toIgnore)); + this(src, ArrayBuilders.arrayToSet(toIgnore), null); } - - protected BeanSerializerBase(BeanSerializerBase src, Set toIgnore) + + protected BeanSerializerBase(BeanSerializerBase src, Set toIgnore) { + this(src, toIgnore, null); + } + + protected BeanSerializerBase(BeanSerializerBase src, Set toIgnore, Set toInclude) { super(src._handledType); @@ -193,7 +198,7 @@ protected BeanSerializerBase(BeanSerializerBase src, Set toIgnore) for (int i = 0; i < len; ++i) { BeanPropertyWriter bpw = propsIn[i]; // should be ignored? - if ((toIgnore != null) && toIgnore.contains(bpw.getName())) { + if (IgnorePropertiesUtil.shouldIgnore(bpw.getName(), toIgnore, toInclude)) { continue; } propsOut.add(bpw); @@ -226,7 +231,25 @@ protected BeanSerializerBase(BeanSerializerBase src, Set toIgnore) * @since 2.8 */ protected abstract BeanSerializerBase withIgnorals(Set toIgnore); - + + /** + * Mutant factory used for creating a new instance with additional + * set of properties to ignore or include (from properties this instance otherwise has) + * + * @since 2.12 + */ + protected abstract BeanSerializerBase withIgnorals(Set toIgnore, Set toInclude); + + /** + * Mutant factory used for creating a new instance with additional + * set of properties to ignore or include (from properties this instance otherwise has) + * + * @since 2.12 + */ + protected BeanSerializerBase withInclusions(Set toInclude) { + return withIgnorals(Collections.emptySet(), toInclude); + } + /** * Mutant factory used for creating a new instance with additional * set of properties to ignore (from properties this instance otherwise has) @@ -476,6 +499,7 @@ public JsonSerializer createContextual(SerializerProvider provider, // at a later point int idPropOrigIndex = 0; Set ignoredProps = null; + Set includedProps = null; Object newFilterId = null; // Then we may have an override for Object Id @@ -484,6 +508,10 @@ public JsonSerializer createContextual(SerializerProvider provider, if (ignorals != null) { ignoredProps = ignorals.findIgnoredForSerialization(); } + JsonIncludeProperties.Value inclusions = intr.findPropertyInclusions(accessor); + if (inclusions != null) { + includedProps = inclusions.getIncluded(); + } ObjectIdInfo objectIdInfo = intr.findObjectIdInfo(accessor); if (objectIdInfo == null) { // no ObjectId override, but maybe ObjectIdRef? @@ -569,8 +597,9 @@ public JsonSerializer createContextual(SerializerProvider provider, } } // And possibly add more properties to ignore + contextual = contextual.withInclusions(includedProps); if ((ignoredProps != null) && !ignoredProps.isEmpty()) { - contextual = contextual.withIgnorals(ignoredProps); + contextual = contextual.withIgnorals(ignoredProps, includedProps); } if (newFilterId != null) { contextual = contextual.withFilterId(newFilterId); 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 66056ff7ac..34992f4f24 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 @@ -7,6 +7,7 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonIncludeProperties; import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.core.type.WritableTypeId; import com.fasterxml.jackson.databind.*; @@ -15,6 +16,7 @@ import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper; import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonMapFormatVisitor; import com.fasterxml.jackson.databind.jsontype.TypeSerializer; +import com.fasterxml.jackson.databind.ser.BeanPropertyWriter; import com.fasterxml.jackson.databind.ser.ContainerSerializer; import com.fasterxml.jackson.databind.ser.ContextualSerializer; import com.fasterxml.jackson.databind.ser.PropertyFilter; @@ -23,6 +25,7 @@ import com.fasterxml.jackson.databind.util.ArrayBuilders; import com.fasterxml.jackson.databind.util.BeanUtil; import com.fasterxml.jackson.databind.util.ClassUtil; +import com.fasterxml.jackson.databind.util.IgnorePropertiesUtil; /** * Standard serializer implementation for serializing {link java.util.Map} types. @@ -109,6 +112,11 @@ public class MapSerializer */ protected final Set _ignoredEntries; + /** + * Set of entries to include during serialization, if null, it is ignored, empty will include nothing. + */ + protected final Set _includedEntries; + /** * Id of the property filter to use, if any; null if none. * @@ -157,10 +165,10 @@ public class MapSerializer */ /** - * @since 2.5 + * @since 2.12 */ @SuppressWarnings("unchecked") - protected MapSerializer(Set ignoredEntries, + protected MapSerializer(Set ignoredEntries, Set includedEntries, JavaType keyType, JavaType valueType, boolean valueTypeIsStatic, TypeSerializer vts, JsonSerializer keySerializer, JsonSerializer valueSerializer) @@ -168,6 +176,7 @@ protected MapSerializer(Set ignoredEntries, super(Map.class, false); _ignoredEntries = ((ignoredEntries == null) || ignoredEntries.isEmpty()) ? null : ignoredEntries; + _includedEntries = includedEntries; _keyType = keyType; _valueType = valueType; _valueTypeIsStatic = valueTypeIsStatic; @@ -182,14 +191,34 @@ protected MapSerializer(Set ignoredEntries, _suppressNulls = false; } + /** + * @since 2.5 + * @deprecated in 2.12, remove from 3.0 + */ + @Deprecated + protected MapSerializer(Set ignoredEntries, + JavaType keyType, JavaType valueType, boolean valueTypeIsStatic, + TypeSerializer vts, + JsonSerializer keySerializer, JsonSerializer valueSerializer) + { + this(ignoredEntries, null, + keyType, valueType, valueTypeIsStatic, + vts, + keySerializer, valueSerializer); + } + + /** + * @since 2.12 + */ @SuppressWarnings("unchecked") protected MapSerializer(MapSerializer src, BeanProperty property, JsonSerializer keySerializer, JsonSerializer valueSerializer, - Set ignoredEntries) + Set ignoredEntries, Set includedEntries) { super(Map.class, false); _ignoredEntries = ((ignoredEntries == null) || ignoredEntries.isEmpty()) ? null : ignoredEntries; + _includedEntries = includedEntries; _keyType = src._keyType; _valueType = src._valueType; _valueTypeIsStatic = src._valueTypeIsStatic; @@ -205,6 +234,18 @@ protected MapSerializer(MapSerializer src, BeanProperty property, _suppressNulls = src._suppressNulls; } + /** + * @deprecated in 2.12, remove from 3.0 + */ + @SuppressWarnings("unchecked") + @Deprecated + protected MapSerializer(MapSerializer src, BeanProperty property, + JsonSerializer keySerializer, JsonSerializer valueSerializer, + Set ignoredEntries) + { + this(src, property, keySerializer, valueSerializer, ignoredEntries, null); + } + /** * @since 2.9 */ @@ -213,6 +254,7 @@ protected MapSerializer(MapSerializer src, TypeSerializer vts, { super(Map.class, false); _ignoredEntries = src._ignoredEntries; + _includedEntries = src._includedEntries; _keyType = src._keyType; _valueType = src._valueType; _valueTypeIsStatic = src._valueTypeIsStatic; @@ -233,6 +275,7 @@ protected MapSerializer(MapSerializer src, Object filterId, boolean sortKeys) { super(Map.class, false); _ignoredEntries = src._ignoredEntries; + _includedEntries = src._includedEntries; _keyType = src._keyType; _valueType = src._valueType; _valueTypeIsStatic = src._valueTypeIsStatic; @@ -258,20 +301,30 @@ public MapSerializer _withValueTypeSerializer(TypeSerializer vts) { } /** - * @since 2.4 + * @since 2.12 */ public MapSerializer withResolved(BeanProperty property, - JsonSerializer keySerializer, JsonSerializer valueSerializer, - Set ignored, boolean sortKeys) + JsonSerializer keySerializer, JsonSerializer valueSerializer, + Set ignored, Set included, boolean sortKeys) { _ensureOverride("withResolved"); - MapSerializer ser = new MapSerializer(this, property, keySerializer, valueSerializer, ignored); + MapSerializer ser = new MapSerializer(this, property, keySerializer, valueSerializer, ignored, included); if (sortKeys != ser._sortKeys) { ser = new MapSerializer(ser, _filterId, sortKeys); } return ser; } + /** + * @since 2.4 + */ + public MapSerializer withResolved(BeanProperty property, + JsonSerializer keySerializer, JsonSerializer valueSerializer, + Set ignored, boolean sortKeys) + { + return withResolved(property, keySerializer, valueSerializer, ignored, null, sortKeys); + } + @Override public MapSerializer withFilterId(Object filterId) { if (_filterId == filterId) { @@ -296,9 +349,9 @@ public MapSerializer withContentInclusion(Object suppressableValue, boolean supp } /** - * @since 2.8 + * @since 2.12 */ - public static MapSerializer construct(Set ignoredEntries, JavaType mapType, + public static MapSerializer construct(Set ignoredEntries, Set includedEntries, JavaType mapType, boolean staticValueType, TypeSerializer vts, JsonSerializer keySerializer, JsonSerializer valueSerializer, Object filterId) @@ -326,7 +379,7 @@ public static MapSerializer construct(Set ignoredEntries, JavaType mapTy staticValueType = false; } } - MapSerializer ser = new MapSerializer(ignoredEntries, keyType, valueType, staticValueType, vts, + MapSerializer ser = new MapSerializer(ignoredEntries, includedEntries, keyType, valueType, staticValueType, vts, keySerializer, valueSerializer); if (filterId != null) { ser = ser.withFilterId(filterId); @@ -334,6 +387,17 @@ public static MapSerializer construct(Set ignoredEntries, JavaType mapTy return ser; } + /** + * @since 2.8 + */ + public static MapSerializer construct(Set ignoredEntries, JavaType mapType, + boolean staticValueType, TypeSerializer vts, + JsonSerializer keySerializer, JsonSerializer valueSerializer, + Object filterId) + { + return construct(ignoredEntries, null, mapType, staticValueType, vts, keySerializer, valueSerializer, filterId); + } + /** * @since 2.9 */ @@ -439,8 +503,10 @@ public JsonSerializer createContextual(SerializerProvider provider, keySer = provider.handleSecondaryContextualization(keySer, property); } Set ignored = _ignoredEntries; + Set included = _includedEntries; boolean sortKeys = false; if (_neitherNull(propertyAcc, intr)) { + // ignorals JsonIgnoreProperties.Value ignorals = intr.findPropertyIgnorals(propertyAcc); if (ignorals != null){ Set newIgnored = ignorals.findIgnoredForSerialization(); @@ -451,6 +517,18 @@ public JsonSerializer createContextual(SerializerProvider provider, } } } + // inclusions + JsonIncludeProperties.Value inclusions = intr.findPropertyInclusions(propertyAcc); + if (inclusions != null) { + Set newIncluded = inclusions.getIncluded(); + if (newIncluded != null) { + included = (included == null) ? new HashSet() : new HashSet(included); + for (String str : newIncluded) { + included.add(str); + } + } + } + // sort key Boolean b = intr.findSerializationSortAlphabetically(propertyAcc); sortKeys = Boolean.TRUE.equals(b); } @@ -461,7 +539,7 @@ public JsonSerializer createContextual(SerializerProvider provider, sortKeys = B.booleanValue(); } } - MapSerializer mser = withResolved(property, keySer, ser, ignored, sortKeys); + MapSerializer mser = withResolved(property, keySer, ser, ignored, included, sortKeys); // [databind#307]: allow filtering if (property != null) { @@ -698,6 +776,7 @@ public void serializeFields(Map value, JsonGenerator gen, SerializerProvide } final JsonSerializer keySerializer = _keySerializer; final Set ignored = _ignoredEntries; + final Set included = _includedEntries; Object keyElem = null; try { @@ -709,7 +788,7 @@ public void serializeFields(Map value, JsonGenerator gen, SerializerProvide provider.findNullKeySerializer(_keyType, _property).serialize(null, gen, provider); } else { // One twist: is entry ignorable? If so, skip - if ((ignored != null) && ignored.contains(keyElem)) { + if (IgnorePropertiesUtil.shouldIgnore(keyElem, ignored, included)) { continue; } keySerializer.serialize(keyElem, gen, provider); @@ -743,6 +822,7 @@ public void serializeOptionalFields(Map value, JsonGenerator gen, Serialize return; } final Set ignored = _ignoredEntries; + final Set included = _includedEntries; final boolean checkEmpty = (MARKER_FOR_EMPTY == suppressableValue); for (Map.Entry entry : value.entrySet()) { @@ -752,7 +832,7 @@ public void serializeOptionalFields(Map value, JsonGenerator gen, Serialize if (keyElem == null) { keySerializer = provider.findNullKeySerializer(_keyType, _property); } else { - if (ignored != null && ignored.contains(keyElem)) continue; + if (IgnorePropertiesUtil.shouldIgnore(keyElem, ignored, included)) continue; keySerializer = _keySerializer; } @@ -801,11 +881,12 @@ public void serializeFieldsUsing(Map value, JsonGenerator gen, SerializerPr { final JsonSerializer keySerializer = _keySerializer; final Set ignored = _ignoredEntries; + final Set included = _includedEntries; final TypeSerializer typeSer = _valueTypeSerializer; for (Map.Entry entry : value.entrySet()) { Object keyElem = entry.getKey(); - if (ignored != null && ignored.contains(keyElem)) continue; + if (IgnorePropertiesUtil.shouldIgnore(keyElem, ignored, included)) continue; if (keyElem == null) { provider.findNullKeySerializer(_keyType, _property).serialize(null, gen, provider); @@ -841,13 +922,14 @@ public void serializeFilteredFields(Map value, JsonGenerator gen, Serialize throws IOException { final Set ignored = _ignoredEntries; + final Set included = _includedEntries; 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(); - if (ignored != null && ignored.contains(keyElem)) continue; + if (IgnorePropertiesUtil.shouldIgnore(keyElem, ignored, included)) continue; JsonSerializer keySerializer; if (keyElem == null) { @@ -899,6 +981,7 @@ public void serializeTypedFields(Map value, JsonGenerator gen, SerializerPr throws IOException { final Set ignored = _ignoredEntries; + final Set included = _includedEntries; final boolean checkEmpty = (MARKER_FOR_EMPTY == suppressableValue); for (Map.Entry entry : value.entrySet()) { @@ -908,7 +991,7 @@ public void serializeTypedFields(Map value, JsonGenerator gen, SerializerPr keySerializer = provider.findNullKeySerializer(_keyType, _property); } else { // One twist: is entry ignorable? If so, skip - if (ignored != null && ignored.contains(keyElem)) continue; + if (IgnorePropertiesUtil.shouldIgnore(keyElem, ignored, included)) continue; keySerializer = _keySerializer; } final Object valueElem = entry.getValue(); @@ -959,13 +1042,14 @@ public void serializeFilteredAnyProperties(SerializerProvider provider, JsonGene throws IOException { final Set ignored = _ignoredEntries; + final Set included = _includedEntries; 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(); - if (ignored != null && ignored.contains(keyElem)) continue; + if (IgnorePropertiesUtil.shouldIgnore(keyElem, ignored, included)) continue; JsonSerializer keySerializer; if (keyElem == null) { diff --git a/src/main/java/com/fasterxml/jackson/databind/util/IgnorePropertiesUtil.java b/src/main/java/com/fasterxml/jackson/databind/util/IgnorePropertiesUtil.java new file mode 100644 index 0000000000..75766150db --- /dev/null +++ b/src/main/java/com/fasterxml/jackson/databind/util/IgnorePropertiesUtil.java @@ -0,0 +1,31 @@ +package com.fasterxml.jackson.databind.util; + +import com.fasterxml.jackson.databind.ser.BeanPropertyWriter; + +import java.util.Collection; +import java.util.Set; + +public class IgnorePropertiesUtil +{ + /** + * Decide if we need to ignore a property or not, given a set of field to ignore and a set of field to include. + * + * @since 2.12 + */ + public static boolean shouldIgnore(Object value, Collection toIgnore, Collection toInclude) { + if (toIgnore == null && toInclude ==null) { + return false; + } + + if (toInclude == null) { + return toIgnore.contains(value); + } + + if (toIgnore == null) { + return !toInclude.contains(value); + } + + // NOTE: conflict between both, JsonIncludeProperties will take priority. + return !toInclude.contains(value) || toIgnore.contains(value); + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/IncludeWithDeserTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/IncludeWithDeserTest.java new file mode 100644 index 0000000000..0d10e8b07b --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/deser/IncludeWithDeserTest.java @@ -0,0 +1,202 @@ +package com.fasterxml.jackson.databind.deser; + +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIdentityInfo; +import com.fasterxml.jackson.annotation.JsonIncludeProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.ObjectIdGenerators; +import com.fasterxml.jackson.databind.BaseMapTest; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This unit test suite that tests use of {@link com.fasterxml.jackson.annotation.JsonIncludeProperties} + * annotation with deserialization. + */ +public class IncludeWithDeserTest + extends BaseMapTest +{ + @JsonIncludeProperties({"y", "z"}) + static class OnlyYAndZ + { + int _x = 0; + int _y = 0; + int _z = 0; + + public void setX(int value) + { + _x = value; + } + + public void setY(int value) + { + _y = value; + } + + public void setZ(int value) + { + _z = value; + } + + @JsonProperty("y") + void replacementForY(int value) + { + _y = value * 2; + } + } + + @JsonIncludeProperties({"y", "z"}) + static class OnlyY + { + public int x; + + public int y = 1; + } + + static class OnlyYWrapperForOnlyYAndZ + { + @JsonIncludeProperties("y") + public OnlyYAndZ onlyY; + } + + // for [databind#1060] + static class IncludeForListValuesY + { + @JsonIncludeProperties({"y"}) + //@JsonIgnoreProperties({"z"}) + public List onlyYs; + + public IncludeForListValuesY() + { + onlyYs = Arrays.asList(new OnlyYAndZ()); + } + } + + @JsonIncludeProperties({"@class", "a"}) + static class MyMap extends HashMap + { + } + + static class MapWrapper + { + @JsonIncludeProperties({"a"}) + public final HashMap value = new HashMap(); + } + + @JsonIncludeProperties({"foo", "bar"}) + @JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class) + static class AnySetterObjectId + { + protected Map values = new HashMap(); + + @JsonAnySetter + public void anySet(String field, AnySetterObjectId value) + { + // Ensure that it is never called with null because of unresolved reference. + assertNotNull(value); + values.put(field, value); + } + } + + /* + /********************************************************** + /* Test methods + /********************************************************** + */ + + private final ObjectMapper MAPPER = objectMapper(); + + public void testSimpleInclude() throws Exception + { + OnlyYAndZ result = MAPPER.readValue( + aposToQuotes("{ 'x':1, '_x': 1, 'y':2, 'z':3 }"), + OnlyYAndZ.class); + assertEquals(0, result._x); + assertEquals(4, result._y); + assertEquals(3, result._z); + } + + public void testIncludeIgnoredAndUnrecognizedField() throws Exception + { + ObjectReader r = MAPPER.readerFor(OnlyY.class); + + // First, fine to get "y" only: + OnlyY result = r.readValue(aposToQuotes("{'x':3, 'y': 4}")); + assertEquals(0, result.x); + assertEquals(4, result.y); + + // but fail on ignored properties. + r = r.with(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES); + try { + r.readValue(aposToQuotes("{'x':3, 'y': 4, 'z': 5}")); + fail("Should fail"); + } catch (JsonMappingException e) { + verifyException(e, "Ignored field"); + } + + // or fail on unrecognized properties + try { + r.readValue(aposToQuotes("{'y': 3, 'z':2 }")); + fail("Should fail"); + } catch (JsonMappingException e) { + verifyException(e, "Unrecognized field"); + } + + // or success with the both settings disabled. + r = r.without(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + r = r.without(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES); + r.readValue(aposToQuotes("{'y': 3, 'z':2 }")); + assertEquals(4, result.y); + } + + + public void testMergeInclude() throws Exception + { + OnlyYWrapperForOnlyYAndZ onlyY = MAPPER.readValue( + aposToQuotes("{'onlyY': {'x': 2, 'y':3, 'z': 4}}"), + OnlyYWrapperForOnlyYAndZ.class + ); + assertEquals(0, onlyY.onlyY._x); + assertEquals(6, onlyY.onlyY._y); + assertEquals(0, onlyY.onlyY._z); + } + + public void testListInclude() throws Exception + { + IncludeForListValuesY result = MAPPER.readValue( + aposToQuotes("{'onlyYs':[{ 'x':1, 'y' : 2, 'z': 3 }]}"), + IncludeForListValuesY.class); + assertEquals(0, result.onlyYs.get(0)._x); + assertEquals(4, result.onlyYs.get(0)._y); + assertEquals(0, result.onlyYs.get(0)._z); + } + + public void testMapWrapper() throws Exception + { + MapWrapper result = MAPPER.readValue(aposToQuotes("{'value': {'a': 2, 'b': 3}}"), MapWrapper.class); + assertEquals(2, result.value.get("a").intValue()); + assertFalse(result.value.containsKey("b")); + } + + public void testMyMap() throws Exception + { + MyMap result = MAPPER.readValue(aposToQuotes("{'a': 2, 'b': 3}"), MyMap.class); + assertEquals("2", result.get("a")); + assertFalse(result.containsKey("b")); + } + + public void testForwardReferenceAnySetterComboWithInclude() throws Exception + { + String json = aposToQuotes("{'@id':1, 'foo':2, 'foo2':2, 'bar':{'@id':2, 'foo':1}}"); + AnySetterObjectId value = MAPPER.readValue(json, AnySetterObjectId.class); + assertSame(value.values.get("bar"), value.values.get("foo")); + assertNull(value.values.get("foo2")); + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/filter/IncludePropsForSerTest.java b/src/test/java/com/fasterxml/jackson/databind/ser/filter/IncludePropsForSerTest.java new file mode 100644 index 0000000000..952f1eb3ce --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/ser/filter/IncludePropsForSerTest.java @@ -0,0 +1,185 @@ +package com.fasterxml.jackson.databind.ser.filter; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonIncludeProperties; +import com.fasterxml.jackson.databind.BaseMapTest; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class IncludePropsForSerTest extends BaseMapTest +{ + @JsonIncludeProperties({"a", "d"}) + static class IncludeSome + { + public int a = 3; + public String b = "x"; + + public int getC() + { + return -6; + } + + public String getD() + { + return "abc"; + } + } + + @SuppressWarnings("serial") + @JsonIncludeProperties({"@class", "a"}) + static class MyMap extends HashMap { } + + //allow use of @JsonIncludeProperties for properties + static class WrapperWithPropInclude + { + @JsonIncludeProperties({"y"}) + public XY value = new XY(); + } + + static class XY + { + public int x = 1; + public int y = 2; + } + + static class WrapperWithPropInclude2 + { + @JsonIncludeProperties("x") + public XYZ value = new XYZ(); + } + + static class WrapperWithPropIgnore + { + @JsonIgnoreProperties("y") + public XYZ value = new XYZ(); + } + + @JsonIncludeProperties({"x", "y"}) + static class XYZ + { + public int x = 1; + public int y = 2; + public int z = 3; + } + + // also ought to work without full typing? + static class WrapperWithPropIncludeUntyped + { + @JsonIncludeProperties({"x"}) + public Object value = new XYZ(); + } + + static class MapWrapper + { + @JsonIncludeProperties({"a"}) + public final HashMap value = new HashMap(); + + { + value.put("a", 1); + value.put("b", 2); + } + } + + // for [databind#1060] + static class IncludeForListValuesXY + { + @JsonIncludeProperties({"x"}) + public List coordinates; + + public IncludeForListValuesXY() + { + coordinates = Arrays.asList(new XY()); + } + } + + static class IncludeForListValuesXYZ + { + @JsonIncludeProperties({"x"}) + public List coordinates; + + public IncludeForListValuesXYZ() + { + coordinates = Arrays.asList(new XYZ()); + } + } + + /* + /**************************************************************** + /* Unit tests + /**************************************************************** + */ + + private final ObjectMapper MAPPER = objectMapper(); + + public void testExplicitIncludeWithBean() throws Exception + { + IncludeSome value = new IncludeSome(); + Map result = writeAndMap(MAPPER, value); + assertEquals(2, result.size()); + // verify that specified fields are ignored + assertFalse(result.containsKey("b")); + assertFalse(result.containsKey("c")); + // and that others are not + assertEquals(Integer.valueOf(value.a), result.get("a")); + assertEquals(value.getD(), result.get("d")); + } + + public void testExplicitIncludeWithMap() throws Exception + { + // test simulating need to filter out metadata like class name + MyMap value = new MyMap(); + value.put("a", "b"); + value.put("c", "d"); + value.put("@class", MyMap.class.getName()); + Map result = writeAndMap(MAPPER, value); + assertEquals(2, result.size()); + assertEquals(MyMap.class.getName(), result.get("@class")); + assertEquals(value.get("a"), result.get("a")); + } + + public void testIncludeViaOnlyProps() throws Exception + { + assertEquals("{\"value\":{\"y\":2}}", + MAPPER.writeValueAsString(new WrapperWithPropInclude())); + } + + // Also: should be fine even if nominal type is `java.lang.Object` + public void testIncludeViaPropForUntyped() throws Exception + { + assertEquals("{\"value\":{\"x\":1}}", + MAPPER.writeValueAsString(new WrapperWithPropIncludeUntyped())); + } + + public void testIncludeWithMapProperty() throws Exception + { + assertEquals("{\"value\":{\"a\":1}}", MAPPER.writeValueAsString(new MapWrapper())); + } + + public void testIncludeViaPropsAndClass() throws Exception + { + assertEquals("{\"value\":{\"x\":1}}", + MAPPER.writeValueAsString(new WrapperWithPropInclude2())); + } + + // for [databind#1060] + // Ensure that `@JsonIncludeProperties` applies to POJOs within lists, too + public void testIncludeForListValues() throws Exception + { + // should apply to elements + assertEquals(aposToQuotes("{'coordinates':[{'x':1}]}"), + MAPPER.writeValueAsString(new IncludeForListValuesXY())); + + // and combine values too + assertEquals(aposToQuotes("{'coordinates':[{'x':1}]}"), + MAPPER.writeValueAsString(new IncludeForListValuesXYZ())); + } + + public void testIgnoreWithInclude() throws Exception + { + assertEquals("{\"value\":{\"x\":1}}", MAPPER.writeValueAsString(new WrapperWithPropIgnore())); + } +}