diff options
author | Baptiste Pernet <pernetmu@gmail.com> | 2020-07-23 19:27:54 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-23 19:27:54 -0700 |
commit | d9c0332e5ad76d7e4f35d9906f0b8e94a5237627 (patch) | |
tree | 7079a25712fd088846e3c81b30ea87f21032f93a /src | |
parent | 5758c6bce6773108beeadd8090641f21778ba5e0 (diff) | |
download | jackson-databind-d9c0332e5ad76d7e4f35d9906f0b8e94a5237627.tar.gz |
FasterXML/jackson-databind#1296 @JsonIncludeProperties (#2771)
Implement #1296 (add and support `@JsonIncludeProperties`)
Diffstat (limited to 'src')
25 files changed, 931 insertions, 80 deletions
diff --git a/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java index d8035f031..0cf03fb23 100644 --- a/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java @@ -279,6 +279,18 @@ public abstract class AnnotationIntrospector 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 64afde82e..0b22a22ca 100644 --- a/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfig.java +++ b/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfig.java @@ -499,6 +499,17 @@ public abstract class MapperConfig<T extends MapperConfig<T>> 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 * their visibility (access modifiers). Can be changed to allow 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 2514e8eb7..7a2e7b0d7 100644 --- a/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfigBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfigBase.java @@ -666,6 +666,14 @@ public abstract class MapperConfigBase<CFG extends ConfigFeature, } @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() { VisibilityChecker<?> vchecker = _configOverrides.getDefaultVisibility(); 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 7890921dc..bd572a3fd 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -10,6 +10,7 @@ import java.util.concurrent.atomic.AtomicReference; 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; @@ -1385,6 +1386,10 @@ nonAnnotatedParamIndex, ctor); Set<String> ignored = (ignorals == null) ? null : ignorals.findIgnoredForDeserialization(); md.setIgnorableProperties(ignored); + JsonIncludeProperties.Value inclusions = config.getDefaultPropertyInclusions(Map.class, + beanDesc.getClassInfo()); + Set<String> 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 ced28f0a9..81dbc97d5 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.*; 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<String, SettableBeanProperty> backRefs, HashSet<String> 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<String, SettableBeanProperty> backRefs, + HashSet<String> ignorableProps, boolean ignoreAllUnknown, Set<String> includableProps, + boolean hasViews) + { + super(builder, beanDesc, properties, backRefs, + ignorableProps, ignoreAllUnknown, includableProps, hasViews); } /** @@ -86,10 +104,21 @@ public class BeanDeserializer super(src, oir); } + /** + * @deprecated in 2.12, remove from 3.0 + */ + @Deprecated public BeanDeserializer(BeanDeserializerBase src, Set<String> ignorableProps) { super(src, ignorableProps); } + /** + * @since 2.12 + */ + public BeanDeserializer(BeanDeserializerBase src, Set<String> ignorableProps, Set<String> includableProps) { + super(src, ignorableProps, includableProps); + } + public BeanDeserializer(BeanDeserializerBase src, BeanPropertyMap props) { super(src, props); } @@ -119,8 +148,8 @@ public class BeanDeserializer } @Override - public BeanDeserializer withIgnorableProperties(Set<String> ignorableProps) { - return new BeanDeserializer(this, ignorableProps); + public BeanDeserializer withIgnorableProperties(Set<String> ignorableProps, Set<String> includableProps) { + return new BeanDeserializer(this, ignorableProps, includableProps); } @Override @@ -464,7 +493,7 @@ public class BeanDeserializer 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 @@ public class BeanDeserializer 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 @@ public class BeanDeserializer } continue; } - if (_ignorableProps != null && _ignorableProps.contains(propName)) { + if (IgnorePropertiesUtil.shouldIgnore(propName, _ignorableProps, _includableProps)) { handleIgnoredProperty(p, ctxt, bean, propName); continue; } @@ -850,7 +879,7 @@ public class BeanDeserializer 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 @@ public class BeanDeserializer 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 @@ public class BeanDeserializer 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 b059782e9..4a0fb1f14 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java @@ -136,6 +136,11 @@ public abstract class BeanDeserializerBase final protected Set<String> _ignorableProps; /** + * Keep track of the the properties that needs to be specifically included. + */ + final protected Set<String> _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 @@ public abstract class BeanDeserializerBase BeanDescription beanDesc, BeanPropertyMap properties, Map<String, SettableBeanProperty> backRefs, Set<String> ignorableProps, boolean ignoreAllUnknown, + Set<String> includableProps, boolean hasViews) { super(beanDesc.getType()); @@ -211,6 +217,7 @@ public abstract class BeanDeserializerBase _backRefs = backRefs; _ignorableProps = ignorableProps; _ignoreAllUnknown = ignoreAllUnknown; + _includableProps = includableProps; _anySetter = builder.getAnySetter(); List<ValueInjector> injectables = builder.getInjectables(); @@ -263,6 +270,7 @@ public abstract class BeanDeserializerBase _backRefs = src._backRefs; _ignorableProps = src._ignorableProps; _ignoreAllUnknown = ignoreAllUnknown; + _includableProps = src._includableProps; _anySetter = src._anySetter; _injectables = src._injectables; _objectIdReader = src._objectIdReader; @@ -288,6 +296,7 @@ public abstract class BeanDeserializerBase _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 abstract class BeanDeserializerBase _backRefs = src._backRefs; _ignorableProps = src._ignorableProps; _ignoreAllUnknown = src._ignoreAllUnknown; + _includableProps = src._includableProps; _anySetter = src._anySetter; _injectables = src._injectables; @@ -352,6 +362,14 @@ public abstract class BeanDeserializerBase public BeanDeserializerBase(BeanDeserializerBase src, Set<String> ignorableProps) { + this(src, ignorableProps, src._includableProps); + } + + /** + * @since 2.12 + */ + public BeanDeserializerBase(BeanDeserializerBase src, Set<String> ignorableProps, Set<String> includableProps) + { super(src._beanType); _beanType = src._beanType; @@ -362,6 +380,7 @@ public abstract class BeanDeserializerBase _backRefs = src._backRefs; _ignorableProps = ignorableProps; _ignoreAllUnknown = src._ignoreAllUnknown; + _includableProps = includableProps; _anySetter = src._anySetter; _injectables = src._injectables; @@ -375,9 +394,10 @@ public abstract class BeanDeserializerBase // 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 @@ public abstract class BeanDeserializerBase _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 @@ public abstract class BeanDeserializerBase public abstract BeanDeserializerBase withObjectIdReader(ObjectIdReader oir); - public abstract BeanDeserializerBase withIgnorableProperties(Set<String> ignorableProps); + public BeanDeserializerBase withIgnorableProperties(Set<String> ignorableProps) { + return withIgnorableProperties(ignorableProps, _includableProps); + } + + /** + * @since 2.12 + */ + public abstract BeanDeserializerBase withIgnorableProperties(Set<String> ignorableProps, Set<String> includableProps); + + /** + * @since 2.12 + */ + public BeanDeserializerBase withIncludableProperties(Set<String> includableProperties) { + return withIgnorableProperties(_ignorableProps, includableProperties); + } // NOTE! To be made `abstract` in 2.12 or later /** @@ -422,7 +457,7 @@ public abstract class BeanDeserializerBase if (ignoreUnknown == _ignoreAllUnknown) { return this; } - return withIgnorableProperties(_ignorableProps); + return withIgnorableProperties(_ignorableProps, _includableProps); } /** @@ -469,10 +504,10 @@ public abstract class BeanDeserializerBase // 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 abstract class BeanDeserializerBase contextual = contextual.withIgnoreAllUnknown(true); } } + JsonIncludeProperties.Value inclusions = intr.findPropertyInclusions(accessor); + if (inclusions != null) { + Set<String> included = inclusions.getIncluded(); + Set<String> prev = contextual._includableProps; + if (prev != null && included != null) { + Set<String> 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? @@ -1601,7 +1651,8 @@ public abstract class BeanDeserializerBase 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 { @@ -1629,7 +1680,7 @@ public abstract class BeanDeserializerBase 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 702edd847..5f5f493c4 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.deser.impl.ValueInjector; 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 @@ -73,6 +74,12 @@ public class BeanDeserializerBuilder protected HashSet<String> _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<String> _includableProps; + + /** * Object that will handle value instantiation for the bean type. */ protected ValueInstantiator _valueInstantiator; @@ -136,7 +143,8 @@ public class BeanDeserializerBuilder _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; @@ -236,6 +244,19 @@ public class BeanDeserializerBuilder } /** + * 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; * instead of setter or field). @@ -333,7 +354,7 @@ public class BeanDeserializerBuilder * @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 class BeanDeserializerBuilder } return new BeanDeserializer(this, - _beanDesc, propertyMap, _backRefProperties, _ignorableProps, _ignoreAllUnknown, + _beanDesc, propertyMap, _backRefProperties, _ignorableProps, _ignoreAllUnknown, _includableProps, anyViews); } @@ -463,7 +484,7 @@ public class BeanDeserializerBuilder 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 fcf93b1fa..c9f6e597e 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java @@ -14,6 +14,7 @@ import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; import com.fasterxml.jackson.databind.jsontype.impl.SubTypeValidator; import com.fasterxml.jackson.databind.util.BeanUtil; import com.fasterxml.jackson.databind.util.ClassUtil; +import com.fasterxml.jackson.databind.util.IgnorePropertiesUtil; import com.fasterxml.jackson.databind.util.SimpleBeanPropertyDefinition; /** @@ -510,6 +511,18 @@ public class BeanDeserializerFactory } else { ignored = Collections.emptySet(); } + JsonIncludeProperties.Value inclusions = ctxt.getConfig() + .getDefaultPropertyInclusions(beanDesc.getBeanClass(), + beanDesc.getClassInfo()); + Set<String> 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(); @@ -532,7 +545,7 @@ public class BeanDeserializerFactory // Ok: let's then filter out property definitions List<BeanPropertyDefinition> 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()) { @@ -644,20 +657,41 @@ public class BeanDeserializerFactory * 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<BeanPropertyDefinition> filterBeanProps(DeserializationContext ctxt, BeanDescription beanDesc, BeanDeserializerBuilder builder, List<BeanPropertyDefinition> propDefsIn, Set<String> 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<BeanPropertyDefinition> filterBeanProps(DeserializationContext ctxt, + BeanDescription beanDesc, BeanDeserializerBuilder builder, + List<BeanPropertyDefinition> propDefsIn, + Set<String> ignored, + Set<String> included) + { ArrayList<BeanPropertyDefinition> result = new ArrayList<BeanPropertyDefinition>( Math.max(4, propDefsIn.size())); HashMap<Class<?>,Boolean> ignoredTypes = new HashMap<Class<?>,Boolean>(); // 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 fa15ed319..44bff9d3c 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.*; 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; @@ -51,8 +52,20 @@ public class BuilderBasedDeserializer Set<String> 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<String, SettableBeanProperty> backRefs, + Set<String> ignorableProps, boolean ignoreAllUnknown, Set<String> 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 class BuilderBasedDeserializer } public BuilderBasedDeserializer(BuilderBasedDeserializer src, Set<String> ignorableProps) { - super(src, ignorableProps); + this(src, ignorableProps, src._includableProps); + } + + public BuilderBasedDeserializer(BuilderBasedDeserializer src, Set<String> ignorableProps, Set<String> includableProps) { + super(src, ignorableProps, includableProps); _buildMethod = src._buildMethod; _targetType = src._targetType; } @@ -133,8 +150,8 @@ public class BuilderBasedDeserializer } @Override - public BeanDeserializerBase withIgnorableProperties(Set<String> ignorableProps) { - return new BuilderBasedDeserializer(this, ignorableProps); + public BeanDeserializerBase withIgnorableProperties(Set<String> ignorableProps, Set<String> includableProps) { + return new BuilderBasedDeserializer(this, ignorableProps, includableProps); } @Override @@ -396,7 +413,7 @@ public class BuilderBasedDeserializer } // 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 @@ public class BuilderBasedDeserializer 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 @@ public class BuilderBasedDeserializer 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 @@ public class BuilderBasedDeserializer } continue; } - if (_ignorableProps != null && _ignorableProps.contains(propName)) { + if (IgnorePropertiesUtil.shouldIgnore(propName, _ignorableProps, _includableProps)) { handleIgnoredProperty(p, ctxt, builder, propName); continue; } @@ -770,7 +787,7 @@ public class BuilderBasedDeserializer 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 3c9cf8b07..73ab84871 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 class BeanAsArrayBuilderDeserializer } @Override - public BeanDeserializerBase withIgnorableProperties(Set<String> ignorableProps) { - return new BeanAsArrayBuilderDeserializer(_delegate.withIgnorableProperties(ignorableProps), + public BeanDeserializerBase withIgnorableProperties(Set<String> ignorableProps, Set<String> 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 f0116727b..aad5b1f52 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 class BeanAsArrayDeserializer } @Override - public BeanDeserializerBase withIgnorableProperties(Set<String> ignorableProps) { - return new BeanAsArrayDeserializer(_delegate.withIgnorableProperties(ignorableProps), + public BeanDeserializerBase withIgnorableProperties(Set<String> ignorableProps, Set<String> 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 68e17ad0b..50248c600 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.PropertyName; 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 class BeanPropertyMap */ public BeanPropertyMap withoutProperties(Collection<String> 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<String> toExclude, Collection<String> toInclude) + { + if ((toExclude == null || toExclude.isEmpty()) && toInclude == null) { return this; } final int len = _propsInOrder.length; @@ -394,7 +408,7 @@ public class BeanPropertyMap // 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 cba73fb4a..8216cd1cc 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 java.util.*; 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.introspect.AnnotatedMember; 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<String> _ignorableProperties; + protected Set<String> _includableProperties; /* /********************************************************** @@ -124,6 +127,7 @@ public class MapDeserializer _hasDefaultCreator = src._hasDefaultCreator; // should we make a copy here? _ignorableProperties = src._ignorableProperties; + _includableProperties = src._includableProperties; _standardStringKey = src._standardStringKey; } @@ -134,6 +138,19 @@ public class MapDeserializer NullValueProvider nuller, Set<String> ignorable) { + this(src, keyDeser,valueDeser, valueTypeDeser, nuller, ignorable, null); + } + + /** + * @since 2.12 + */ + protected MapDeserializer(MapDeserializer src, + KeyDeserializer keyDeser, JsonDeserializer<Object> valueDeser, + TypeDeserializer valueTypeDeser, + NullValueProvider nuller, + Set<String> ignorable, + Set<String> includable) + { super(src, nuller, src._unwrapSingle); _keyDeserializer = keyDeser; _valueDeserializer = valueDeser; @@ -143,6 +160,7 @@ public class MapDeserializer _delegateDeserializer = src._delegateDeserializer; _hasDefaultCreator = src._hasDefaultCreator; _ignorableProperties = ignorable; + _includableProperties = includable; _standardStringKey = _isStdKeyDeser(_containerType, keyDeser); } @@ -157,15 +175,26 @@ public class MapDeserializer NullValueProvider nuller, Set<String> ignorable) { - + return withResolved(keyDeser, valueTypeDeser, valueDeser, nuller, ignorable, _includableProperties); + } + + /** + * @since 2.12 + */ + protected MapDeserializer withResolved(KeyDeserializer keyDeser, + TypeDeserializer valueTypeDeser, JsonDeserializer<?> valueDeser, + NullValueProvider nuller, + Set<String> ignorable, Set<String> includable) + { + if ((_keyDeserializer == keyDeser) && (_valueDeserializer == valueDeser) && (_valueTypeDeserializer == valueTypeDeser) && (_nullProvider == nuller) - && (_ignorableProperties == ignorable)) { + && (_ignorableProperties == ignorable) && (_includableProperties == includable)) { return this; } return new MapDeserializer(this, keyDeser, (JsonDeserializer<Object>) valueDeser, valueTypeDeser, - nuller, ignorable); + nuller, ignorable, includable); } /** @@ -186,6 +215,10 @@ public class MapDeserializer && 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 class MapDeserializer null : ignorable; } + public void setIncludableProperties(Set<String> includable) { + _includableProperties = includable; + } + /* /********************************************************** /* Validation, post-processing (ResolvableDeserializer) @@ -269,6 +306,7 @@ public class MapDeserializer vtd = vtd.forProperty(property); } Set<String> ignored = _ignorableProperties; + Set<String> included = _includableProperties; AnnotationIntrospector intr = ctxt.getAnnotationIntrospector(); if (_neitherNull(intr, property)) { AnnotatedMember member = property.getMember(); @@ -283,10 +321,27 @@ public class MapDeserializer } } } + JsonIncludeProperties.Value inclusions = intr.findPropertyInclusions(member); + if (inclusions != null) { + Set<String> includedToAdd = inclusions.getIncluded(); + if (includedToAdd != null) { + Set<String> 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 class MapDeserializer return (_valueDeserializer == null) && (_keyDeserializer == null) && (_valueTypeDeserializer == null) - && (_ignorableProperties == null); + && (_ignorableProperties == null) + && (_includableProperties == null); } @Override // since 2.12 @@ -458,7 +514,7 @@ public class MapDeserializer 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 @@ public class MapDeserializer 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 class MapDeserializer 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 @@ public class MapDeserializer 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 @@ public class MapDeserializer 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 d69bbd3cd..3ba0ab9a5 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.JsonCreator; 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; @@ -126,6 +127,15 @@ public class AnnotationIntrospectorPair } @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) { Boolean result = _primary.isIgnorableType(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 731102df8..268ee7f49 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 class JacksonAnnotationIntrospector 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 889a72470..d37ef546c 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 java.util.concurrent.atomic.AtomicReference; 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 @@ public abstract class BasicSerializerFactory beanDesc.getClassInfo()); Set<String> ignored = (ignorals == null) ? null : ignorals.findIgnoredForSerialization(); - MapSerializer mapSer = MapSerializer.construct(ignored, + JsonIncludeProperties.Value inclusions = config.getDefaultPropertyInclusions(Map.class, + beanDesc.getClassInfo()); + Set<String> 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 501f16d7f..b202b47f6 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 @@ public class BeanSerializer super(src, toIgnore); } + protected BeanSerializer(BeanSerializerBase src, Set<String> toIgnore, Set<String> toInclude) { + super(src, toIgnore, toInclude); + } + // @since 2.11.1 protected BeanSerializer(BeanSerializerBase src, BeanPropertyWriter[] properties, BeanPropertyWriter[] filteredProperties) { @@ -120,6 +124,11 @@ public class BeanSerializer return new BeanSerializer(this, toIgnore); } + @Override + protected BeanSerializerBase withIgnorals(Set<String> toIgnore, Set<String> 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 07a969208..81c494dee 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 @@ package com.fasterxml.jackson.databind.ser; 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; @@ -23,6 +24,7 @@ import com.fasterxml.jackson.databind.type.ReferenceType; import com.fasterxml.jackson.databind.util.BeanUtil; 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 @@ -635,17 +637,25 @@ public class BeanSerializerFactory // just use it as is. JsonIgnoreProperties.Value ignorals = config.getDefaultPropertyIgnorals(beanDesc.getBeanClass(), beanDesc.getClassInfo()); + Set<String> ignored = null; if (ignorals != null) { - Set<String> ignored = ignorals.findIgnoredForSerialization(); - if (!ignored.isEmpty()) { - Iterator<BeanPropertyWriter> 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<String> included = null; + if (inclusions != null) { + included = inclusions.getIncluded(); + } + if (included != null || (ignored != null && !ignored.isEmpty())) { + Iterator<BeanPropertyWriter> 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 2abf7649d..86b16366d 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 class BeanAsArraySerializer } protected BeanAsArraySerializer(BeanSerializerBase src, Set<String> toIgnore) { - super(src, toIgnore); + this(src, toIgnore, null); + } + + protected BeanAsArraySerializer(BeanSerializerBase src, Set<String> toIgnore, Set<String> toInclude) { + super(src, toIgnore, toInclude); _defaultSerializer = src; } @@ -112,6 +116,11 @@ public class BeanAsArraySerializer return new BeanAsArraySerializer(this, toIgnore); } + @Override + protected BeanAsArraySerializer withIgnorals(Set<String> toIgnore, Set<String> 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 32418233f..dd50a6b19 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 class UnwrappingBeanSerializer } protected UnwrappingBeanSerializer(UnwrappingBeanSerializer src, Set<String> toIgnore) { - super(src, toIgnore); + this(src, toIgnore, null); + } + + protected UnwrappingBeanSerializer(UnwrappingBeanSerializer src, Set<String> toIgnore, Set<String> toInclude) { + super(src, toIgnore, toInclude); _nameTransformer = src._nameTransformer; } @@ -94,6 +98,11 @@ public class UnwrappingBeanSerializer return new UnwrappingBeanSerializer(this, toIgnore); } + @Override + protected BeanSerializerBase withIgnorals(Set<String> toIgnore, Set<String> 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 95621c9a5..afadc8a22 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.PropertyBasedObjectIdGenerator; 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 @@ public abstract class BeanSerializerBase @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<String> toIgnore) + + protected BeanSerializerBase(BeanSerializerBase src, Set<String> toIgnore) { + this(src, toIgnore, null); + } + + protected BeanSerializerBase(BeanSerializerBase src, Set<String> toIgnore, Set<String> toInclude) { super(src._handledType); @@ -193,7 +198,7 @@ public abstract class BeanSerializerBase 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 @@ public abstract class BeanSerializerBase * @since 2.8 */ protected abstract BeanSerializerBase withIgnorals(Set<String> 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<String> toIgnore, Set<String> 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<String> toInclude) { + return withIgnorals(Collections.<String>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 abstract class BeanSerializerBase // at a later point int idPropOrigIndex = 0; Set<String> ignoredProps = null; + Set<String> includedProps = null; Object newFilterId = null; // Then we may have an override for Object Id @@ -484,6 +508,10 @@ public abstract class BeanSerializerBase 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 abstract class BeanSerializerBase } } // 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 66056ff7a..34992f4f2 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 java.util.*; 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.introspect.AnnotatedMember; 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.type.TypeFactory; 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. @@ -110,6 +113,11 @@ public class MapSerializer protected final Set<String> _ignoredEntries; /** + * Set of entries to include during serialization, if null, it is ignored, empty will include nothing. + */ + protected final Set<String> _includedEntries; + + /** * Id of the property filter to use, if any; null if none. * * @since 2.3 @@ -157,10 +165,10 @@ public class MapSerializer */ /** - * @since 2.5 + * @since 2.12 */ @SuppressWarnings("unchecked") - protected MapSerializer(Set<String> ignoredEntries, + protected MapSerializer(Set<String> ignoredEntries, Set<String> includedEntries, JavaType keyType, JavaType valueType, boolean valueTypeIsStatic, TypeSerializer vts, JsonSerializer<?> keySerializer, JsonSerializer<?> valueSerializer) @@ -168,6 +176,7 @@ public class MapSerializer super(Map.class, false); _ignoredEntries = ((ignoredEntries == null) || ignoredEntries.isEmpty()) ? null : ignoredEntries; + _includedEntries = includedEntries; _keyType = keyType; _valueType = valueType; _valueTypeIsStatic = valueTypeIsStatic; @@ -182,14 +191,34 @@ public class MapSerializer _suppressNulls = false; } + /** + * @since 2.5 + * @deprecated in 2.12, remove from 3.0 + */ + @Deprecated + protected MapSerializer(Set<String> 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<String> ignoredEntries) + Set<String> ignoredEntries, Set<String> includedEntries) { super(Map.class, false); _ignoredEntries = ((ignoredEntries == null) || ignoredEntries.isEmpty()) ? null : ignoredEntries; + _includedEntries = includedEntries; _keyType = src._keyType; _valueType = src._valueType; _valueTypeIsStatic = src._valueTypeIsStatic; @@ -206,6 +235,18 @@ public class MapSerializer } /** + * @deprecated in 2.12, remove from 3.0 + */ + @SuppressWarnings("unchecked") + @Deprecated + protected MapSerializer(MapSerializer src, BeanProperty property, + JsonSerializer<?> keySerializer, JsonSerializer<?> valueSerializer, + Set<String> ignoredEntries) + { + this(src, property, keySerializer, valueSerializer, ignoredEntries, null); + } + + /** * @since 2.9 */ protected MapSerializer(MapSerializer src, TypeSerializer vts, @@ -213,6 +254,7 @@ public class MapSerializer { super(Map.class, false); _ignoredEntries = src._ignoredEntries; + _includedEntries = src._includedEntries; _keyType = src._keyType; _valueType = src._valueType; _valueTypeIsStatic = src._valueTypeIsStatic; @@ -233,6 +275,7 @@ public class MapSerializer { super(Map.class, false); _ignoredEntries = src._ignoredEntries; + _includedEntries = src._includedEntries; _keyType = src._keyType; _valueType = src._valueType; _valueTypeIsStatic = src._valueTypeIsStatic; @@ -258,20 +301,30 @@ public class MapSerializer } /** - * @since 2.4 + * @since 2.12 */ public MapSerializer withResolved(BeanProperty property, - JsonSerializer<?> keySerializer, JsonSerializer<?> valueSerializer, - Set<String> ignored, boolean sortKeys) + JsonSerializer<?> keySerializer, JsonSerializer<?> valueSerializer, + Set<String> ignored, Set<String> 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<String> 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 class MapSerializer } /** - * @since 2.8 + * @since 2.12 */ - public static MapSerializer construct(Set<String> ignoredEntries, JavaType mapType, + public static MapSerializer construct(Set<String> ignoredEntries, Set<String> includedEntries, JavaType mapType, boolean staticValueType, TypeSerializer vts, JsonSerializer<Object> keySerializer, JsonSerializer<Object> valueSerializer, Object filterId) @@ -326,7 +379,7 @@ public class MapSerializer 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); @@ -335,6 +388,17 @@ public class MapSerializer } /** + * @since 2.8 + */ + public static MapSerializer construct(Set<String> ignoredEntries, JavaType mapType, + boolean staticValueType, TypeSerializer vts, + JsonSerializer<Object> keySerializer, JsonSerializer<Object> valueSerializer, + Object filterId) + { + return construct(ignoredEntries, null, mapType, staticValueType, vts, keySerializer, valueSerializer, filterId); + } + + /** * @since 2.9 */ protected void _ensureOverride(String method) { @@ -439,8 +503,10 @@ public class MapSerializer keySer = provider.handleSecondaryContextualization(keySer, property); } Set<String> ignored = _ignoredEntries; + Set<String> included = _includedEntries; boolean sortKeys = false; if (_neitherNull(propertyAcc, intr)) { + // ignorals JsonIgnoreProperties.Value ignorals = intr.findPropertyIgnorals(propertyAcc); if (ignorals != null){ Set<String> newIgnored = ignorals.findIgnoredForSerialization(); @@ -451,6 +517,18 @@ public class MapSerializer } } } + // inclusions + JsonIncludeProperties.Value inclusions = intr.findPropertyInclusions(propertyAcc); + if (inclusions != null) { + Set<String> newIncluded = inclusions.getIncluded(); + if (newIncluded != null) { + included = (included == null) ? new HashSet<String>() : new HashSet<String>(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 class MapSerializer 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 class MapSerializer } final JsonSerializer<Object> keySerializer = _keySerializer; final Set<String> ignored = _ignoredEntries; + final Set<String> included = _includedEntries; Object keyElem = null; try { @@ -709,7 +788,7 @@ public class MapSerializer 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 class MapSerializer return; } final Set<String> ignored = _ignoredEntries; + final Set<String> included = _includedEntries; final boolean checkEmpty = (MARKER_FOR_EMPTY == suppressableValue); for (Map.Entry<?,?> entry : value.entrySet()) { @@ -752,7 +832,7 @@ public class MapSerializer 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 class MapSerializer { final JsonSerializer<Object> keySerializer = _keySerializer; final Set<String> ignored = _ignoredEntries; + final Set<String> 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 class MapSerializer throws IOException { final Set<String> ignored = _ignoredEntries; + final Set<String> 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<Object> keySerializer; if (keyElem == null) { @@ -899,6 +981,7 @@ public class MapSerializer throws IOException { final Set<String> ignored = _ignoredEntries; + final Set<String> included = _includedEntries; final boolean checkEmpty = (MARKER_FOR_EMPTY == suppressableValue); for (Map.Entry<?,?> entry : value.entrySet()) { @@ -908,7 +991,7 @@ public class MapSerializer 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 class MapSerializer throws IOException { final Set<String> ignored = _ignoredEntries; + final Set<String> 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<Object> 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 000000000..75766150d --- /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<String> toIgnore, Collection<String> 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 000000000..0d10e8b07 --- /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<OnlyYAndZ> onlyYs; + + public IncludeForListValuesY() + { + onlyYs = Arrays.asList(new OnlyYAndZ()); + } + } + + @JsonIncludeProperties({"@class", "a"}) + static class MyMap extends HashMap<String, String> + { + } + + static class MapWrapper + { + @JsonIncludeProperties({"a"}) + public final HashMap<String, Integer> value = new HashMap<String, Integer>(); + } + + @JsonIncludeProperties({"foo", "bar"}) + @JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class) + static class AnySetterObjectId + { + protected Map<String, AnySetterObjectId> values = new HashMap<String, AnySetterObjectId>(); + + @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 000000000..952f1eb3c --- /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<String, String> { } + + //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<String, Integer> value = new HashMap<String, Integer>(); + + { + value.put("a", 1); + value.put("b", 2); + } + } + + // for [databind#1060] + static class IncludeForListValuesXY + { + @JsonIncludeProperties({"x"}) + public List<XY> coordinates; + + public IncludeForListValuesXY() + { + coordinates = Arrays.asList(new XY()); + } + } + + static class IncludeForListValuesXYZ + { + @JsonIncludeProperties({"x"}) + public List<XYZ> 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<String, Object> 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<String, Object> 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())); + } +} |