diff --git a/release-notes/CREDITS-2.x b/release-notes/CREDITS-2.x index b1ed415a51..f51d991205 100644 --- a/release-notes/CREDITS-2.x +++ b/release-notes/CREDITS-2.x @@ -1081,26 +1081,26 @@ Robert Diebels (RobertDiebels@github) Joseph Koshakow (jkosh44@github) * Contributed fix for #2515: `ObjectMapper.registerSubtypes(NamedType...)` doesn't allow registering the same POJO for two different type ids - (2.11.0) + (2.11.0) Haowei Wen (yushijinhun@github) * Reported #2565: Java 8 `Optional` not working with `@JsonUnwrapped` on unwrappable type - (2.11.0) + (2.11.0) Bartosz Baranowski (baranowb@github) * Reported #2589: `DOMDeserializer`: setExpandEntityReferences(false) may not prevent external entity expansion in all cases - (2.11.0) + (2.11.0) Oleksii Khomchenko (gagoman@github) * Reported, contributed fix for #2592: `ObjectMapper.setSerializationInclusion()` is ignored for `JsonAnyGetter` - (2.11.0) + (2.11.0) Oleksandr Poslavskyi (alevskyi@github) * Contributed fix for #1983: Polymorphic deserialization should handle case-insensitive Type Id property name if `MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES` is enabled - (2.11.0) + (2.11.0) Simone D'Avico (simonedavico@github) * Reported #2632: Failure to resolve generic type parameters on serialization @@ -1125,3 +1125,7 @@ Johannes Kuhn (DasBrain@github) deserializer properties (2.11.1) +Frank Schmager (fschmager@github) + * Reported #2757: "Conflicting setter definitions for property" exception for `Map` + subtype during deserialization + (2.11.1) diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index d9e94a5b0e..62b1378080 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -14,6 +14,9 @@ Project: jackson-databind #2755: `StdSubtypeResolver` is not thread safe (possibly due to copy not being made with `ObjectMapper.copy()`) (reported by tjwilson90@github) +#2757: "Conflicting setter definitions for property" exception for `Map` + subtype during deserialization + (reported by Frank S) #2759: Rearranging of props when property-based generator is in use leads to incorrect output (reported by Oleg C) diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/BeanPropertyDefinition.java b/src/main/java/com/fasterxml/jackson/databind/introspect/BeanPropertyDefinition.java index caa2663c69..075a87dbac 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/BeanPropertyDefinition.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/BeanPropertyDefinition.java @@ -224,12 +224,15 @@ public AnnotatedMember getNonConstructorMutator() { * the highest precedence in current context (getter method when serializing, * if available, and so forth), if any. *

+ * Note: may throw {@link IllegalArgumentException} in case problems are found + * trying to getter or setter info. + *

* Note: abstract since 2.5 * * @since 2.1 */ public abstract AnnotatedMember getPrimaryMember(); - + /* /********************************************************** /* More refined access to configuration features diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertyBuilder.java b/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertyBuilder.java index da593dc64e..a171f157de 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertyBuilder.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertyBuilder.java @@ -239,16 +239,20 @@ public PropertyMetadata getMetadata() { * Helper method that contains logic for accessing and merging all setter * information that we needed, regarding things like possible merging * of property value, and handling of incoming nulls. + * Only called for deserialization purposes. */ protected PropertyMetadata _getSetterInfo(PropertyMetadata metadata) { boolean needMerge = true; Nulls valueNulls = null; Nulls contentNulls = null; - + // Slightly confusing: first, annotations should be accessed via primary member - // (mutator); but accessor is needed for actual merge operation. So: - AnnotatedMember prim = getPrimaryMember(); + // (mutator); but accessor is needed for actual merge operation. So + + // 20-Jun-2020, tatu: Unfortunately strict checks lead to [databind#2757] + // so we will need to try to avoid them at this point + AnnotatedMember prim = getPrimaryMemberUnchecked(); AnnotatedMember acc = getAccessor(); if (prim != null) { @@ -272,7 +276,10 @@ protected PropertyMetadata _getSetterInfo(PropertyMetadata metadata) // If not, config override? // 25-Oct-2016, tatu: Either this, or type of accessor... if (needMerge || (valueNulls == null) || (contentNulls == null)) { - Class rawType = getRawPrimaryType(); + // 20-Jun-2020, tatu: Related to [databind#2757], need to find type + // but keeping mind that type for setters is trickier; and that + // generic typing gets tricky as well. + Class rawType = _rawTypeOf(prim); ConfigOverride co = _config.getConfigOverride(rawType); JsonSetter.Value setterInfo = co.getSetterInfo(); if (setterInfo != null) { @@ -330,7 +337,6 @@ public JavaType getPrimaryType() { // 09-Feb-2017, tatu: Not sure if this or `null` but... return TypeFactory.unknownType(); } - return m.getType(); } return m.getType(); } @@ -577,6 +583,45 @@ public AnnotatedMember getPrimaryMember() { return m; } + // Sometimes we need to actually by-pass failures related to conflicting + // getters or setters (see [databind#2757] for specific example); if so, + // this method is to be used instead of `getPrimaryMember()` + // @since 2.11.1 + protected AnnotatedMember getPrimaryMemberUnchecked() { + if (_forSerialization) { // Inlined `getAccessor()` logic: + // Inlined `getGetter()`: + if (_getters != null) { + return _getters.value; + } + // Inlined `getField()`: + if (_fields != null) { + return _fields.value; + } + return null; + } + + // Otherwise, inlined `getMutator()` logic: + + // Inlined `getConstructorParameter()`: + if (_ctorParameters != null) { + return _ctorParameters.value; + } + // Inlined `getSetter()`: + if (_setters != null) { + return _setters.value; + } + // Inlined `getField()`: + if (_fields != null) { + return _fields.value; + } + // but to support setterless-properties, also include part of + // `getAccessor()` not yet covered, `getGetter()`: + if (_getters != null) { + return _getters.value; + } + return null; + } + protected int _getterPriority(AnnotatedMethod m) { final String name = m.getName(); @@ -1220,6 +1265,26 @@ protected T fromMemberAnnotationsExcept(WithMember func, T defaultValue) return null; } + // Helper method needed to work around oddity in type access for + // `AnnotatedMethod`. + // + // @since 2.11.1 + protected Class _rawTypeOf(AnnotatedMember m) { + // AnnotatedMethod always returns return type, but for setters we + // actually need argument type + if (m instanceof AnnotatedMethod) { + AnnotatedMethod meh = (AnnotatedMethod) m; + if (meh.getParameterCount() > 0) { + // note: get raw type FROM full type since only that resolves + // generic types + return meh.getParameterType(0).getRawClass(); + } + } + // same as above, must get fully resolved type to handled generic typing + // of fields etc. + return m.getType().getRawClass(); + } + /* /********************************************************** /* Helper classes diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/MapDeser2757Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/MapDeser2757Test.java new file mode 100644 index 0000000000..fee3dd1a63 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/MapDeser2757Test.java @@ -0,0 +1,32 @@ +package com.fasterxml.jackson.databind.deser.jdk; + +import java.util.*; + +import com.fasterxml.jackson.databind.*; + +@SuppressWarnings("serial") +public class MapDeser2757Test extends BaseMapTest +{ + static class MyMap extends LinkedHashMap { + public MyMap() { } + + public void setValue(StringWrapper w) { } + public void setValue(IntWrapper w) { } + + public long getValue() { return 0L; } + } + + // [databind#2757]: should allow deserialization as Map despite conflicting setters + public void testCanDeserializeMap() throws Exception + { + final ObjectMapper mapper = jsonMapperBuilder() + .build(); + + MyMap input = new MyMap(); + input.put("a", "b"); + final String json = mapper.writeValueAsString(input); + MyMap x = mapper.readValue(json, MyMap.class); + assertEquals(1, x.size()); + assertEquals("b", input.get("a")); + } +} diff --git a/src/test/java/com/fasterxml/jackson/failing/ObjectMapper2757Test.java b/src/test/java/com/fasterxml/jackson/failing/ObjectMapper2757Test.java deleted file mode 100644 index 96556ff3a4..0000000000 --- a/src/test/java/com/fasterxml/jackson/failing/ObjectMapper2757Test.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.fasterxml.jackson.failing; - -import java.util.AbstractMap; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; - -import com.fasterxml.jackson.databind.*; - -public class ObjectMapper2757Test extends BaseMapTest -{ - interface MultiValueMap extends Map> { } - abstract static class MyMultiMap extends AbstractMap> - implements MultiValueMap { } - - static class MyMap extends MyMultiMap { - public MyMap() { } - - public void setValue(StringWrapper w) { } - public void setValue(IntWrapper w) { } - - public long getValue() { return 0L; } - - @Override - public Set>> entrySet() { - return Collections.emptySet(); - } - } - - private final ObjectMapper MAPPER = newJsonMapper(); - - // [databind#2757]: should allow deserialization as Map despite conflicting setters - public void testCanDeserializeMap() throws Exception - { -// final String json = MAPPER.writeValueAsString(new MyMap()); -// System.out.println("json: "+json); - // MyMap x = MAPPER.readValue(json, MyMap.class); - final AtomicReference ref = new AtomicReference<>(); - final JavaType type = MAPPER.constructType(MyMap.class); - - System.err.println("Type: "+type); - - boolean can = MAPPER.canDeserialize(MAPPER.constructType(type), - ref); -System.err.println(" Cause -> "+ref.get()); - if (!can) { - ref.get().printStackTrace(); - fail("canDeserialize() returned false; underlying failure: "+ref.get()); - } - } -}