From 0b3e175da441949b1a779c94132b2735cfbc700f Mon Sep 17 00:00:00 2001 From: Baptiste Pernet Date: Wed, 17 Jun 2020 18:34:12 -0700 Subject: [PATCH 01/11] FasterXML/jackson-databind#1296 Serialization --- pom.xml | 2 +- .../databind/AnnotationIntrospector.java | 13 ++ .../jackson/databind/cfg/MapperConfig.java | 11 ++ .../databind/cfg/MapperConfigBase.java | 8 + .../JacksonAnnotationIntrospector.java | 10 ++ .../databind/ser/BasicSerializerFactory.java | 7 +- .../jackson/databind/ser/BeanSerializer.java | 9 + .../databind/ser/BeanSerializerFactory.java | 24 ++- .../ser/impl/BeanAsArraySerializer.java | 11 +- .../ser/impl/UnwrappingBeanSerializer.java | 11 +- .../databind/ser/std/BeanSerializerBase.java | 43 ++++- .../databind/ser/std/MapSerializer.java | 120 ++++++++++--- .../databind/util/IgnorePropertiesUtil.java | 30 ++++ .../databind/deser/IncludeWithDeserTest.java | 84 +++++++++ .../ser/filter/IncludePropsForSerTest.java | 168 ++++++++++++++++++ 15 files changed, 512 insertions(+), 39 deletions(-) create mode 100644 src/main/java/com/fasterxml/jackson/databind/util/IgnorePropertiesUtil.java create mode 100644 src/test/java/com/fasterxml/jackson/databind/deser/IncludeWithDeserTest.java create mode 100644 src/test/java/com/fasterxml/jackson/databind/ser/filter/IncludePropsForSerTest.java diff --git a/pom.xml b/pom.xml index 38e2506cce..32f92835be 100644 --- a/pom.xml +++ b/pom.xml @@ -70,7 +70,7 @@ usage seems to benefit from actually specifying version here in case it is dependent on transitively --> - ${jackson.version.annotations} + 2.12.0-SNAPSHOT com.fasterxml.jackson.core diff --git a/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java index d8035f0316..c5b8fec0d6 100644 --- a/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java @@ -278,6 +278,19 @@ public JsonIgnoreProperties.Value findPropertyIgnorals(Annotated ac) */ public Boolean isIgnorableType(AnnotatedClass ac) { return null; } + /** + * Method for finding information about properties to include either by + * name, or by more general specification ("include all null"). + * + * @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..3e3c8c469d 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.8 + */ + 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/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 25c7c85529..6c8614e361 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); @@ -210,7 +215,7 @@ protected BeanSerializerBase(BeanSerializerBase src, Set toIgnore) _propertyFilterId = src._propertyFilterId; _serializationShape = src._serializationShape; } - + /** * Mutant factory used for creating a new instance with different * {@link ObjectIdWriter}. @@ -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) @@ -474,6 +497,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 @@ -482,6 +506,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? @@ -567,8 +595,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..33f81ac7cb 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. * @@ -155,19 +163,20 @@ public class MapSerializer /* Life-cycle /********************************************************** */ - + /** - * @since 2.5 + * @since 2.12 */ @SuppressWarnings("unchecked") - protected MapSerializer(Set ignoredEntries, - JavaType keyType, JavaType valueType, boolean valueTypeIsStatic, - TypeSerializer vts, - JsonSerializer keySerializer, JsonSerializer valueSerializer) + protected MapSerializer(Set ignoredEntries, Set includedEntries, + JavaType keyType, JavaType valueType, boolean valueTypeIsStatic, + TypeSerializer vts, + JsonSerializer keySerializer, JsonSerializer valueSerializer) { super(Map.class, false); _ignoredEntries = ((ignoredEntries == null) || ignoredEntries.isEmpty()) ? null : ignoredEntries; + _includedEntries = includedEntries; _keyType = keyType; _valueType = valueType; _valueTypeIsStatic = valueTypeIsStatic; @@ -182,14 +191,32 @@ protected MapSerializer(Set ignoredEntries, _suppressNulls = false; } + /** + * @since 2.5 + */ + 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 +232,14 @@ protected MapSerializer(MapSerializer src, BeanProperty property, _suppressNulls = src._suppressNulls; } + @SuppressWarnings("unchecked") + protected MapSerializer(MapSerializer src, BeanProperty property, + JsonSerializer keySerializer, JsonSerializer valueSerializer, + Set ignoredEntries) + { + this(src, property, keySerializer, valueSerializer, ignoredEntries, null); + } + /** * @since 2.9 */ @@ -213,6 +248,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 +269,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 +295,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 +343,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 +373,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 +381,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 +497,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 +511,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 +533,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 +770,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 +782,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 +816,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 +826,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 +875,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 +916,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 +975,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 +985,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 +1036,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..13161d19e5 --- /dev/null +++ b/src/main/java/com/fasterxml/jackson/databind/util/IgnorePropertiesUtil.java @@ -0,0 +1,30 @@ +package com.fasterxml.jackson.databind.util; + +import com.fasterxml.jackson.databind.ser.BeanPropertyWriter; + +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, Set toIgnore, Set 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..458a5b5d5c --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/deser/IncludeWithDeserTest.java @@ -0,0 +1,84 @@ +package com.fasterxml.jackson.databind.deser; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonIncludeProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.*; + +/** + * This unit test suite that tests use of {@link com.fasterxml.jackson.annotation.JsonIncludeProperties} + * annotation with deserialization. + */ +public class IncludeWithDeserTest + extends BaseMapTest +{ + // Class for testing {@link JsonIgnoreProperties} annotations with setters + final static class SizeClassIgnore + { + int _x = 0; + int _y = 0; + + public void setX(int value) { _x = value; } + @JsonIgnore public void setY(int value) { _y = value; } + + /* Just igoring won't help a lot here; let's define a replacement + * so that we won't get an exception for "unknown field" + */ + @JsonProperty("y") void foobar(int value) { + ; // nop + } + } + + @JsonIncludeProperties({ "y", "z" }) + final static class OnlyYOrZ + { + public int x; + + public int y = 1; + } + + /* + /********************************************************** + /* Test methods + /********************************************************** + */ + + private final ObjectMapper MAPPER = objectMapper(); + + public void testSimpleIgnore() throws Exception + { + SizeClassIgnore result = MAPPER.readValue("{ \"x\":1, \"y\" : 2 }", + SizeClassIgnore.class); + // x should be set, y not + assertEquals(1, result._x); + assertEquals(0, result._y); + } + + public void testIncludeOneField() throws Exception + { + ObjectReader r = MAPPER.readerFor(OnlyYOrZ.class); + + // First, fine to get "y": + OnlyYOrZ result = r.readValue(aposToQuotes("{'x':3, 'y': 4}")); + assertEquals(4, result.y); + assertEquals(0, result.x); + + // but not 'y' + r = r.with(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES); + try { + result = r.readValue(aposToQuotes("{'x':3, 'y': 4, 'z': 5}")); + fail("Should fail"); + } catch (JsonMappingException e) { + verifyException(e, "Ignored field"); + } + + // or 'z' + try { + result = r.readValue(aposToQuotes("{'z':2 }")); + fail("Should fail"); + } catch (JsonMappingException e) { + verifyException(e, "Ignored field"); + } + } +} 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..3f12edc9c0 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/ser/filter/IncludePropsForSerTest.java @@ -0,0 +1,168 @@ +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 @JsonIgnoreProperties 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 WrapperWithPropIgnore2 + { + @JsonIncludeProperties("x") + 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({"y"}) + 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 IgnoreForListValuesXY { + @JsonIncludeProperties({ "x" }) + public List coordinates; + + public IgnoreForListValuesXY() { + coordinates = Arrays.asList(new XY()); + } + } + + static class IgnoreForListValuesXYZ { + @JsonIncludeProperties({ "x" }) + public List coordinates; + + public IgnoreForListValuesXYZ() { + 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\":{\"y\":2}}", + 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 WrapperWithPropIgnore2())); + } + + public void testIgnoreViaConfigOverride() throws Exception + { + ObjectMapper mapper = new ObjectMapper(); + mapper.configOverride(Point.class) + .setIgnorals(JsonIgnoreProperties.Value.forIgnoredProperties("x")); + assertEquals("{\"y\":3}", mapper.writeValueAsString(new Point(2, 3))); + } + + // for [databind#1060] + // Ensure that `@JsonIgnoreProperties` applies to POJOs within lists, too + public void testIncludeForListValues() throws Exception + { + // should apply to elements + assertEquals(aposToQuotes("{'coordinates':[{'x':1}]}"), + MAPPER.writeValueAsString(new IgnoreForListValuesXY())); + + // and combine values too + assertEquals(aposToQuotes("{'coordinates':[{'x':1}]}"), + MAPPER.writeValueAsString(new IgnoreForListValuesXYZ())); + } +} From 41911711f882316845a6f512c612efb19d6919a0 Mon Sep 17 00:00:00 2001 From: Baptiste Pernet Date: Fri, 19 Jun 2020 11:32:55 -0700 Subject: [PATCH 02/11] Add more unit test with @JsonIgnoreProperties as well --- .../databind/AnnotationIntrospector.java | 3 +- .../ser/filter/IncludePropsForSerTest.java | 93 +++++++++++-------- 2 files changed, 56 insertions(+), 40 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java index c5b8fec0d6..0cf03fb234 100644 --- a/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java @@ -279,8 +279,7 @@ public JsonIgnoreProperties.Value findPropertyIgnorals(Annotated ac) public Boolean isIgnorableType(AnnotatedClass ac) { return null; } /** - * Method for finding information about properties to include either by - * name, or by more general specification ("include all null"). + * Method for finding information about properties to include. * * @param ac Annotated class to introspect * 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 index 3f12edc9c0..952f1eb3ce 100644 --- a/src/test/java/com/fasterxml/jackson/databind/ser/filter/IncludePropsForSerTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/ser/filter/IncludePropsForSerTest.java @@ -10,8 +10,7 @@ import java.util.List; import java.util.Map; -public class IncludePropsForSerTest - extends BaseMapTest +public class IncludePropsForSerTest extends BaseMapTest { @JsonIncludeProperties({"a", "d"}) static class IncludeSome @@ -19,34 +18,49 @@ static class IncludeSome public int a = 3; public String b = "x"; - public int getC() { return -6; } - public String getD() { return "abc"; } + public int getC() + { + return -6; + } + + public String getD() + { + return "abc"; + } } @SuppressWarnings("serial") @JsonIncludeProperties({"@class", "a"}) - static class MyMap extends HashMap { } + static class MyMap extends HashMap { } - //allow use of @JsonIgnoreProperties for properties + //allow use of @JsonIncludeProperties for properties static class WrapperWithPropInclude { @JsonIncludeProperties({"y"}) public XY value = new XY(); } - - static class XY { + + static class XY + { public int x = 1; public int y = 2; } - static class WrapperWithPropIgnore2 + 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 { + static class XYZ + { public int x = 1; public int y = 2; public int z = 3; @@ -55,13 +69,15 @@ static class XYZ { // also ought to work without full typing? static class WrapperWithPropIncludeUntyped { - @JsonIncludeProperties({"y"}) + @JsonIncludeProperties({"x"}) public Object value = new XYZ(); } - static class MapWrapper { + static class MapWrapper + { @JsonIncludeProperties({"a"}) - public final HashMap value = new HashMap(); + public final HashMap value = new HashMap(); + { value.put("a", 1); value.put("b", 2); @@ -69,23 +85,27 @@ static class MapWrapper { } // for [databind#1060] - static class IgnoreForListValuesXY { - @JsonIncludeProperties({ "x" }) + static class IncludeForListValuesXY + { + @JsonIncludeProperties({"x"}) public List coordinates; - public IgnoreForListValuesXY() { + public IncludeForListValuesXY() + { coordinates = Arrays.asList(new XY()); } } - static class IgnoreForListValuesXYZ { - @JsonIncludeProperties({ "x" }) + static class IncludeForListValuesXYZ + { + @JsonIncludeProperties({"x"}) public List coordinates; - public IgnoreForListValuesXYZ() { + public IncludeForListValuesXYZ() + { coordinates = Arrays.asList(new XYZ()); } - } + } /* /**************************************************************** @@ -94,11 +114,11 @@ public IgnoreForListValuesXYZ() { */ private final ObjectMapper MAPPER = objectMapper(); - + public void testExplicitIncludeWithBean() throws Exception { IncludeSome value = new IncludeSome(); - Map result = writeAndMap(MAPPER, value); + Map result = writeAndMap(MAPPER, value); assertEquals(2, result.size()); // verify that specified fields are ignored assertFalse(result.containsKey("b")); @@ -115,7 +135,7 @@ public void testExplicitIncludeWithMap() throws Exception value.put("a", "b"); value.put("c", "d"); value.put("@class", MyMap.class.getName()); - Map result = writeAndMap(MAPPER, value); + Map result = writeAndMap(MAPPER, value); assertEquals(2, result.size()); assertEquals(MyMap.class.getName(), result.get("@class")); assertEquals(value.get("a"), result.get("a")); @@ -130,39 +150,36 @@ public void testIncludeViaOnlyProps() throws Exception // Also: should be fine even if nominal type is `java.lang.Object` public void testIncludeViaPropForUntyped() throws Exception { - assertEquals("{\"value\":{\"y\":2}}", + 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 WrapperWithPropIgnore2())); - } - - public void testIgnoreViaConfigOverride() throws Exception - { - ObjectMapper mapper = new ObjectMapper(); - mapper.configOverride(Point.class) - .setIgnorals(JsonIgnoreProperties.Value.forIgnoredProperties("x")); - assertEquals("{\"y\":3}", mapper.writeValueAsString(new Point(2, 3))); + MAPPER.writeValueAsString(new WrapperWithPropInclude2())); } // for [databind#1060] - // Ensure that `@JsonIgnoreProperties` applies to POJOs within lists, too + // 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 IgnoreForListValuesXY())); + MAPPER.writeValueAsString(new IncludeForListValuesXY())); // and combine values too assertEquals(aposToQuotes("{'coordinates':[{'x':1}]}"), - MAPPER.writeValueAsString(new IgnoreForListValuesXYZ())); + MAPPER.writeValueAsString(new IncludeForListValuesXYZ())); + } + + public void testIgnoreWithInclude() throws Exception + { + assertEquals("{\"value\":{\"x\":1}}", MAPPER.writeValueAsString(new WrapperWithPropIgnore())); } } From 8e8671f55ed09820b3affc2a5fbb86d4745d5b0c Mon Sep 17 00:00:00 2001 From: Baptiste Pernet Date: Fri, 19 Jun 2020 15:06:42 -0700 Subject: [PATCH 03/11] Deserialization --- .../deser/BasicDeserializerFactory.java | 13 +-- .../databind/deser/BeanDeserializer.java | 37 ++++-- .../databind/deser/BeanDeserializerBase.java | 60 ++++++++-- .../deser/BeanDeserializerBuilder.java | 27 ++++- .../deser/BeanDeserializerFactory.java | 35 +++++- .../deser/BuilderBasedDeserializer.java | 35 ++++-- .../impl/BeanAsArrayBuilderDeserializer.java | 4 +- .../deser/impl/BeanAsArrayDeserializer.java | 4 +- .../databind/deser/impl/BeanPropertyMap.java | 2 +- .../databind/deser/std/MapDeserializer.java | 72 ++++++++++-- .../databind/deser/IncludeWithDeserTest.java | 108 +++++++++++++----- 11 files changed, 317 insertions(+), 80 deletions(-) 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..7a9875e3f1 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -5,11 +5,7 @@ import java.util.concurrent.*; 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.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.annotation.*; import com.fasterxml.jackson.annotation.JsonCreator.Mode; import com.fasterxml.jackson.core.JsonParser; @@ -1370,9 +1366,12 @@ public JsonDeserializer createMapDeserializer(DeserializationContext ctxt, MapDeserializer md = new MapDeserializer(type, inst, keyDes, contentDeser, contentTypeDeser); JsonIgnoreProperties.Value ignorals = config.getDefaultPropertyIgnorals(Map.class, beanDesc.getClassInfo()); - Set ignored = (ignorals == null) ? null - : ignorals.findIgnoredForDeserialization(); + 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..a53f9be9bf 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; @@ -63,7 +64,21 @@ public BeanDeserializer(BeanDeserializerBuilder builder, BeanDescription beanDes 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); } /** @@ -90,6 +105,10 @@ public BeanDeserializer(BeanDeserializerBase src, Set ignorableProps) { super(src, ignorableProps); } + public BeanDeserializer(BeanDeserializerBase src, Set ignorableProps, Set includableProps) { + super(src, ignorableProps, includableProps); + } + public BeanDeserializer(BeanDeserializerBase src, BeanPropertyMap props) { super(src, props); } @@ -119,8 +138,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 +483,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 +713,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 +770,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 +869,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 +961,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 +1052,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..24ed894e2a 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,10 +361,18 @@ 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; - + _valueInstantiator = src._valueInstantiator; _delegateDeserializer = src._delegateDeserializer; _propertyBasedCreator = src._propertyBasedCreator; @@ -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; @@ -378,6 +397,7 @@ public BeanDeserializerBase(BeanDeserializerBase src, Set ignorableProps _beanProperties = src._beanProperties.withoutProperties(ignorableProps); } + /** * @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,15 @@ 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); + } + + public abstract BeanDeserializerBase withIgnorableProperties(Set ignorableProps, Set includableProps); + + public BeanDeserializerBase withIncludableProperties(Set includableProperties) { + return withIgnorableProperties(_ignorableProps, includableProperties); + } // NOTE! To be made `abstract` in 2.12 or later /** @@ -422,7 +451,7 @@ public BeanDeserializerBase withIgnoreAllUnknown(boolean ignoreUnknown) { if (ignoreUnknown == _ignoreAllUnknown) { return this; } - return withIgnorableProperties(_ignorableProps); + return withIgnorableProperties(_ignorableProps, _includableProps); } /** @@ -469,10 +498,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(), _includableProps, _includableProps)) { creatorProps[i].markAsIgnorable(); } } @@ -773,6 +802,22 @@ 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) { + Set newIncluded = new HashSet<>(); + // Make the intersection with the previously included properties. + for(String prop : prev) { + if (included.contains(prop)) { + newIncluded.add(prop); + } + } + included = newIncluded; + } + contextual = contextual.withIncludableProperties(included); + } } // One more thing: are we asked to serialize POJO as array? @@ -1587,7 +1632,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 +1661,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..a6332f3c42 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 _includableProds; + /** * 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; + _includableProds = src._includableProds; _valueInstantiator = src._valueInstantiator; _objectIdReader = src._objectIdReader; @@ -235,6 +243,17 @@ public void addIgnorable(String propName) _ignorableProps.add(propName); } + /** + * Method that will add property name as one of the properties that will be included. + */ + public void addIncludable(String propName) + { + if (_includableProds == null) { + _includableProds = new HashSet<>(); + } + _includableProds.add(propName); + } + /** * Method called by deserializer factory, when a "creator property" * (something that is passed via constructor- or factory method argument; @@ -333,7 +352,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, _includableProds); } /* @@ -379,7 +398,7 @@ public JsonDeserializer build() } return new BeanDeserializer(this, - _beanDesc, propertyMap, _backRefProperties, _ignorableProps, _ignoreAllUnknown, + _beanDesc, propertyMap, _backRefProperties, _ignorableProps, _ignoreAllUnknown, _includableProds, anyViews); } @@ -463,7 +482,7 @@ protected JsonDeserializer createBuilderBasedDeserializer(JavaType valueType, BeanPropertyMap propertyMap, boolean anyViews) { return new BuilderBasedDeserializer(this, _beanDesc, valueType, propertyMap, _backRefProperties, _ignorableProps, _ignoreAllUnknown, - anyViews); + _includableProds, 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 f0a2648fa2..abb4f44a4b 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()) { @@ -652,6 +665,23 @@ protected List filterBeanProps(DeserializationContext ct 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 +689,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..ac129ca28a 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 @@ -382,7 +382,7 @@ public BeanPropertyMap renameAll(NameTransformer transformer) */ public BeanPropertyMap withoutProperties(Collection toExclude) { - if (toExclude.isEmpty()) { + if (toExclude == null || toExclude.isEmpty()) { return this; } final int len = _propsInOrder.length; 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..9fdc661956 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); } /** @@ -196,6 +225,10 @@ public void setIgnorableProperties(Set ignorable) { null : ignorable; } + public void setIncludableProperties(Set includable) { + _includableProperties = includable; + } + /* /********************************************************** /* Validation, post-processing (ResolvableDeserializer) @@ -269,6 +302,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 +317,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 +382,8 @@ public boolean isCachable() { return (_valueDeserializer == null) && (_keyDeserializer == null) && (_valueTypeDeserializer == null) - && (_ignorableProperties == null); + && (_ignorableProperties == null) + && (_includableProperties == null); } @Override // since 2.12 @@ -458,7 +510,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 +572,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 +624,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 +713,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 +780,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/test/java/com/fasterxml/jackson/databind/deser/IncludeWithDeserTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/IncludeWithDeserTest.java index 458a5b5d5c..f278bdd0f9 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/IncludeWithDeserTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/IncludeWithDeserTest.java @@ -1,42 +1,67 @@ package com.fasterxml.jackson.databind.deser; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIncludeProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.*; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + /** * This unit test suite that tests use of {@link com.fasterxml.jackson.annotation.JsonIncludeProperties} * annotation with deserialization. */ public class IncludeWithDeserTest - extends BaseMapTest + extends BaseMapTest { // Class for testing {@link JsonIgnoreProperties} annotations with setters - final static class SizeClassIgnore + @JsonIncludeProperties({"y", "z"}) + final static class SizeClassInclude { int _x = 0; int _y = 0; + int _z = 0; public void setX(int value) { _x = value; } - @JsonIgnore public void setY(int value) { _y = value; } - /* Just igoring won't help a lot here; let's define a replacement - * so that we won't get an exception for "unknown field" - */ - @JsonProperty("y") void foobar(int value) { - ; // nop + public void setY(int value) { _y = value; } + + @JsonProperty("y") + void replacementForY(int value) + { + _y = value * 2; } } - @JsonIncludeProperties({ "y", "z" }) + @JsonIncludeProperties({"y", "z"}) final static class OnlyYOrZ { public int x; public int y = 1; } + + // for [databind#1060] + static class IncludeForListValuesY + { + @JsonIncludeProperties({"y"}) + public List coordinates; + + public IncludeForListValuesY() + { + coordinates = Arrays.asList(new SizeClassInclude()); + } + } + + @JsonIncludeProperties({"@class", "a"}) + static class MyMap extends HashMap { } + + static class MapWrapper + { + @JsonIncludeProperties({"a"}) + public final HashMap value = new HashMap(); + } /* /********************************************************** @@ -45,40 +70,69 @@ final static class OnlyYOrZ */ private final ObjectMapper MAPPER = objectMapper(); - - public void testSimpleIgnore() throws Exception + + public void testSimpleInclude() throws Exception { - SizeClassIgnore result = MAPPER.readValue("{ \"x\":1, \"y\" : 2 }", - SizeClassIgnore.class); - // x should be set, y not - assertEquals(1, result._x); - assertEquals(0, result._y); + SizeClassInclude result = MAPPER.readValue("{ \"x\":1, \"y\" : 2 }", + SizeClassInclude.class); + assertEquals(0, result._x); + assertEquals(4, result._y); } - public void testIncludeOneField() throws Exception + public void testIncludeIgnoredAndUnrecognizedField() throws Exception { ObjectReader r = MAPPER.readerFor(OnlyYOrZ.class); - - // First, fine to get "y": + + // First, fine to get "y" only: OnlyYOrZ result = r.readValue(aposToQuotes("{'x':3, 'y': 4}")); - assertEquals(4, result.y); assertEquals(0, result.x); + assertEquals(4, result.y); - // but not 'y' + // but fail on ignored properties. r = r.with(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES); try { - result = r.readValue(aposToQuotes("{'x':3, 'y': 4, 'z': 5}")); + r.readValue(aposToQuotes("{'x':3, 'y': 4, 'z': 5}")); fail("Should fail"); } catch (JsonMappingException e) { verifyException(e, "Ignored field"); } - // or 'z' + // or fail on unrecognized properties try { - result = r.readValue(aposToQuotes("{'z':2 }")); + r.readValue(aposToQuotes("{'y': 3, 'z':2 }")); fail("Should fail"); } catch (JsonMappingException e) { - verifyException(e, "Ignored field"); + 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 testListInclude() throws Exception + { + IncludeForListValuesY result = MAPPER.readValue( + "{\"coordinates\":[{ \"x\":1, \"y\" : 2, \"z\": 3 }]}", + IncludeForListValuesY.class); + assertEquals(0, result.coordinates.get(0)._x); + assertEquals(4, result.coordinates.get(0)._y); + assertEquals(0, result.coordinates.get(0)._z); + } + + public void testMapWrapper() throws Exception + { + MapWrapper result = MAPPER.readValue("{\"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("{\"a\": 2, \"b\": 3}", MyMap.class); + assertEquals("2", result.get("a")); + assertFalse(result.containsKey("b")); } } From 07e6bde606b1b72dccebe22b9b87ec4830690f83 Mon Sep 17 00:00:00 2001 From: Baptiste Pernet Date: Sat, 20 Jun 2020 17:26:08 -0700 Subject: [PATCH 04/11] Fix ignore prop bug --- .../fasterxml/jackson/databind/deser/BeanDeserializerBase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 24ed894e2a..50ea269a0e 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java @@ -501,7 +501,7 @@ public void resolve(DeserializationContext ctxt) throws JsonMappingException if (_ignorableProps != null || _includableProps != null) { for (int i = 0, end = creatorProps.length; i < end; ++i) { SettableBeanProperty prop = creatorProps[i]; - if (IgnorePropertiesUtil.shouldIgnore(prop.getName(), _includableProps, _includableProps)) { + if (IgnorePropertiesUtil.shouldIgnore(prop.getName(), _ignorableProps, _includableProps)) { creatorProps[i].markAsIgnorable(); } } From 35c1f60648368982330acc5af5bf80d90f3d393e Mon Sep 17 00:00:00 2001 From: Baptiste Pernet Date: Sat, 20 Jun 2020 19:24:08 -0700 Subject: [PATCH 05/11] Fix merging properties --- .../databind/deser/BeanDeserializerBase.java | 7 +- .../databind/deser/impl/BeanPropertyMap.java | 18 ++- .../databind/util/IgnorePropertiesUtil.java | 3 +- .../databind/deser/IncludeWithDeserTest.java | 104 ++++++++++++++---- 4 files changed, 105 insertions(+), 27 deletions(-) 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 50ea269a0e..e29da6ba82 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java @@ -394,7 +394,7 @@ 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); } @@ -806,7 +806,7 @@ public JsonDeserializer createContextual(DeserializationContext ctxt, if (inclusions != null) { Set included = inclusions.getIncluded(); Set prev = contextual._includableProps; - if (prev != null) { + if (prev != null && included != null) { Set newIncluded = new HashSet<>(); // Make the intersection with the previously included properties. for(String prop : prev) { @@ -814,9 +814,8 @@ public JsonDeserializer createContextual(DeserializationContext ctxt, newIncluded.add(prop); } } - included = newIncluded; + contextual = contextual.withIncludableProperties(newIncluded); } - contextual = contextual.withIncludableProperties(included); } } 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 ac129ca28a..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 == null || 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/util/IgnorePropertiesUtil.java b/src/main/java/com/fasterxml/jackson/databind/util/IgnorePropertiesUtil.java index 13161d19e5..75766150db 100644 --- a/src/main/java/com/fasterxml/jackson/databind/util/IgnorePropertiesUtil.java +++ b/src/main/java/com/fasterxml/jackson/databind/util/IgnorePropertiesUtil.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.ser.BeanPropertyWriter; +import java.util.Collection; import java.util.Set; public class IgnorePropertiesUtil @@ -11,7 +12,7 @@ public class IgnorePropertiesUtil * * @since 2.12 */ - public static boolean shouldIgnore(Object value, Set toIgnore, Set toInclude) { + public static boolean shouldIgnore(Object value, Collection toIgnore, Collection toInclude) { if (toIgnore == null && toInclude ==null) { return false; } diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/IncludeWithDeserTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/IncludeWithDeserTest.java index f278bdd0f9..0d10e8b07b 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/IncludeWithDeserTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/IncludeWithDeserTest.java @@ -1,12 +1,20 @@ 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.databind.*; +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} @@ -15,17 +23,27 @@ public class IncludeWithDeserTest extends BaseMapTest { - // Class for testing {@link JsonIgnoreProperties} annotations with setters @JsonIncludeProperties({"y", "z"}) - final static class SizeClassInclude + static class OnlyYAndZ { int _x = 0; int _y = 0; int _z = 0; - public void setX(int value) { _x = value; } + public void setX(int value) + { + _x = value; + } - public void setY(int value) { _y = value; } + public void setY(int value) + { + _y = value; + } + + public void setZ(int value) + { + _z = value; + } @JsonProperty("y") void replacementForY(int value) @@ -35,34 +53,58 @@ void replacementForY(int value) } @JsonIncludeProperties({"y", "z"}) - final static class OnlyYOrZ + 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"}) - public List coordinates; + //@JsonIgnoreProperties({"z"}) + public List onlyYs; public IncludeForListValuesY() { - coordinates = Arrays.asList(new SizeClassInclude()); + onlyYs = Arrays.asList(new OnlyYAndZ()); } } @JsonIncludeProperties({"@class", "a"}) - static class MyMap extends HashMap { } + 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 @@ -73,18 +115,20 @@ static class MapWrapper public void testSimpleInclude() throws Exception { - SizeClassInclude result = MAPPER.readValue("{ \"x\":1, \"y\" : 2 }", - SizeClassInclude.class); + 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(OnlyYOrZ.class); + ObjectReader r = MAPPER.readerFor(OnlyY.class); // First, fine to get "y" only: - OnlyYOrZ result = r.readValue(aposToQuotes("{'x':3, 'y': 4}")); + OnlyY result = r.readValue(aposToQuotes("{'x':3, 'y': 4}")); assertEquals(0, result.x); assertEquals(4, result.y); @@ -112,27 +156,47 @@ public void testIncludeIgnoredAndUnrecognizedField() throws Exception 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( - "{\"coordinates\":[{ \"x\":1, \"y\" : 2, \"z\": 3 }]}", + aposToQuotes("{'onlyYs':[{ 'x':1, 'y' : 2, 'z': 3 }]}"), IncludeForListValuesY.class); - assertEquals(0, result.coordinates.get(0)._x); - assertEquals(4, result.coordinates.get(0)._y); - assertEquals(0, result.coordinates.get(0)._z); + 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("{\"value\": {\"a\": 2, \"b\": 3}}", MapWrapper.class); + 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("{\"a\": 2, \"b\": 3}", MyMap.class); + 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")); + } } From a3db197c2734406b4f300ec044a3fa30112e4def Mon Sep 17 00:00:00 2001 From: Baptiste Pernet Date: Sat, 20 Jun 2020 19:29:11 -0700 Subject: [PATCH 06/11] Remove * import --- .../jackson/databind/deser/BasicDeserializerFactory.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 7a9875e3f1..e8074e80ab 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -5,7 +5,12 @@ import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; -import com.fasterxml.jackson.annotation.*; +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; import com.fasterxml.jackson.core.JsonParser; From d575e31bc8164df5b48439535f906428c3e99b7e Mon Sep 17 00:00:00 2001 From: Baptiste Pernet Date: Sat, 20 Jun 2020 19:31:41 -0700 Subject: [PATCH 07/11] Fix naming --- .../databind/deser/BeanDeserializerBuilder.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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 a6332f3c42..597d937e7e 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBuilder.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBuilder.java @@ -77,7 +77,7 @@ public class BeanDeserializerBuilder * 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 _includableProds; + protected HashSet _includableProps; /** * Object that will handle value instantiation for the bean type. @@ -144,7 +144,7 @@ protected BeanDeserializerBuilder(BeanDeserializerBuilder src) _backRefProperties = _copy(src._backRefProperties); // Hmmh. Should we create defensive copies here? For now, not yet _ignorableProps = src._ignorableProps; - _includableProds = src._includableProds; + _includableProps = src._includableProps; _valueInstantiator = src._valueInstantiator; _objectIdReader = src._objectIdReader; @@ -248,10 +248,10 @@ public void addIgnorable(String propName) */ public void addIncludable(String propName) { - if (_includableProds == null) { - _includableProds = new HashSet<>(); + if (_includableProps == null) { + _includableProps = new HashSet<>(); } - _includableProds.add(propName); + _includableProps.add(propName); } /** @@ -352,7 +352,7 @@ public JsonPOJOBuilder.Value getBuilderConfig() { * @since 2.9.4 */ public boolean hasIgnorable(String name) { - return IgnorePropertiesUtil.shouldIgnore(name, _ignorableProps, _includableProds); + return IgnorePropertiesUtil.shouldIgnore(name, _ignorableProps, _includableProps); } /* @@ -398,7 +398,7 @@ public JsonDeserializer build() } return new BeanDeserializer(this, - _beanDesc, propertyMap, _backRefProperties, _ignorableProps, _ignoreAllUnknown, _includableProds, + _beanDesc, propertyMap, _backRefProperties, _ignorableProps, _ignoreAllUnknown, _includableProps, anyViews); } @@ -482,7 +482,7 @@ protected JsonDeserializer createBuilderBasedDeserializer(JavaType valueType, BeanPropertyMap propertyMap, boolean anyViews) { return new BuilderBasedDeserializer(this, _beanDesc, valueType, propertyMap, _backRefProperties, _ignorableProps, _ignoreAllUnknown, - _includableProds, anyViews); + _includableProps, anyViews); } /* From b740511dcd2b8b3a0e3d79e1ad85820bd9bccc9f Mon Sep 17 00:00:00 2001 From: Baptiste Pernet Date: Sat, 20 Jun 2020 19:39:22 -0700 Subject: [PATCH 08/11] Revert pom.xml change --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 32f92835be..38e2506cce 100644 --- a/pom.xml +++ b/pom.xml @@ -70,7 +70,7 @@ usage seems to benefit from actually specifying version here in case it is dependent on transitively --> - 2.12.0-SNAPSHOT + ${jackson.version.annotations} com.fasterxml.jackson.core From f1d122d71f71fddfadccd7ce8f04768870fb8531 Mon Sep 17 00:00:00 2001 From: Baptiste Pernet Date: Thu, 25 Jun 2020 14:25:31 -0700 Subject: [PATCH 09/11] Remove indentation changes --- pom.xml | 2 +- .../databind/deser/BasicDeserializerFactory.java | 3 ++- .../jackson/databind/deser/BeanDeserializerBase.java | 2 +- .../jackson/databind/ser/std/BeanSerializerBase.java | 2 +- .../jackson/databind/ser/std/MapSerializer.java | 12 ++++++------ 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/pom.xml b/pom.xml index 38e2506cce..32f92835be 100644 --- a/pom.xml +++ b/pom.xml @@ -70,7 +70,7 @@ usage seems to benefit from actually specifying version here in case it is dependent on transitively --> - ${jackson.version.annotations} + 2.12.0-SNAPSHOT com.fasterxml.jackson.core 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 e8074e80ab..c28c32c3da 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -1371,7 +1371,8 @@ public JsonDeserializer createMapDeserializer(DeserializationContext ctxt, MapDeserializer md = new MapDeserializer(type, inst, keyDes, contentDeser, contentTypeDeser); JsonIgnoreProperties.Value ignorals = config.getDefaultPropertyIgnorals(Map.class, beanDesc.getClassInfo()); - Set ignored = (ignorals == null) ? null : ignorals.findIgnoredForDeserialization(); + Set ignored = (ignorals == null) ? null + : ignorals.findIgnoredForDeserialization(); md.setIgnorableProperties(ignored); JsonIncludeProperties.Value inclusions = config.getDefaultPropertyInclusions(Map.class, beanDesc.getClassInfo()); 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 e29da6ba82..b6eeb5be6d 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java @@ -372,7 +372,7 @@ public BeanDeserializerBase(BeanDeserializerBase src, Set ignorableProps { super(src._beanType); _beanType = src._beanType; - + _valueInstantiator = src._valueInstantiator; _delegateDeserializer = src._delegateDeserializer; _propertyBasedCreator = src._propertyBasedCreator; 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 7e4f0f0874..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 @@ -215,7 +215,7 @@ protected BeanSerializerBase(BeanSerializerBase src, Set toIgnore, Set ignoredEntries, Set includedEntries, - JavaType keyType, JavaType valueType, boolean valueTypeIsStatic, - TypeSerializer vts, - JsonSerializer keySerializer, JsonSerializer valueSerializer) + JavaType keyType, JavaType valueType, boolean valueTypeIsStatic, + TypeSerializer vts, + JsonSerializer keySerializer, JsonSerializer valueSerializer) { super(Map.class, false); _ignoredEntries = ((ignoredEntries == null) || ignoredEntries.isEmpty()) @@ -298,8 +298,8 @@ public MapSerializer _withValueTypeSerializer(TypeSerializer vts) { * @since 2.12 */ public MapSerializer withResolved(BeanProperty property, - JsonSerializer keySerializer, JsonSerializer valueSerializer, - Set ignored, Set included, boolean sortKeys) + JsonSerializer keySerializer, JsonSerializer valueSerializer, + Set ignored, Set included, boolean sortKeys) { _ensureOverride("withResolved"); MapSerializer ser = new MapSerializer(this, property, keySerializer, valueSerializer, ignored, included); From 602ecd9d8da5fe90bb31b65c1e2fcf79f96e29c4 Mon Sep 17 00:00:00 2001 From: Baptiste Pernet Date: Thu, 25 Jun 2020 15:09:28 -0700 Subject: [PATCH 10/11] Javadoc --- .../fasterxml/jackson/databind/cfg/MapperConfig.java | 2 +- .../jackson/databind/deser/BeanDeserializer.java | 10 ++++++++++ .../jackson/databind/deser/BeanDeserializerBase.java | 6 ++++++ .../databind/deser/BeanDeserializerBuilder.java | 2 ++ .../databind/deser/BeanDeserializerFactory.java | 3 +++ .../jackson/databind/deser/std/MapDeserializer.java | 4 ++++ .../jackson/databind/ser/std/MapSerializer.java | 6 ++++++ 7 files changed, 32 insertions(+), 1 deletion(-) 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 3e3c8c469d..0b22a22ca2 100644 --- a/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfig.java +++ b/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfig.java @@ -504,7 +504,7 @@ public abstract JsonIgnoreProperties.Value getDefaultPropertyIgnorals(Class b * * TODO: config override. * - * @since 2.8 + * @since 2.12 */ public abstract JsonIncludeProperties.Value getDefaultPropertyInclusions(Class baseType, AnnotatedClass actualClass); 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 a53f9be9bf..81dbc97d50 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java @@ -57,7 +57,10 @@ 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, @@ -101,10 +104,17 @@ 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); } 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 b6eeb5be6d..15f0855c72 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java @@ -436,8 +436,14 @@ 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); } 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 597d937e7e..5f5f493c4e 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBuilder.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBuilder.java @@ -245,6 +245,8 @@ public void addIgnorable(String propName) /** * Method that will add property name as one of the properties that will be included. + * + * @since 2.12 */ public void addIncludable(String propName) { 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 472bc5ca88..75289456c3 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java @@ -659,7 +659,10 @@ 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, 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 9fdc661956..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 @@ -215,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); 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 284474d6ee..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 @@ -193,7 +193,9 @@ protected MapSerializer(Set ignoredEntries, Set includedEntries, /** * @since 2.5 + * @deprecated in 2.12, remove from 3.0 */ + @Deprecated protected MapSerializer(Set ignoredEntries, JavaType keyType, JavaType valueType, boolean valueTypeIsStatic, TypeSerializer vts, @@ -232,7 +234,11 @@ 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) From fbf4c12acbae24b91aea92d35d08df07347081c3 Mon Sep 17 00:00:00 2001 From: Baptiste Pernet Date: Thu, 25 Jun 2020 15:42:17 -0700 Subject: [PATCH 11/11] AnnotationIntrospectorPair --- pom.xml | 2 +- .../introspect/AnnotationIntrospectorPair.java | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 32f92835be..38e2506cce 100644 --- a/pom.xml +++ b/pom.xml @@ -70,7 +70,7 @@ usage seems to benefit from actually specifying version here in case it is dependent on transitively --> - 2.12.0-SNAPSHOT + ${jackson.version.annotations} com.fasterxml.jackson.core 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) {