Fix [JACKSON-700]
diff --git a/release-notes/CREDITS b/release-notes/CREDITS index 5be9249..f518c77 100644 --- a/release-notes/CREDITS +++ b/release-notes/CREDITS
@@ -620,7 +620,7 @@ * [JACKSON-508] Type information lost when serializing List<List<X>> [1.7.4] -Pascal Glinas: +Pascal Glinas: * Suggested [JACKSON-541] with patch: Remove the need for @JsonCreator on multi-arg constructor iff all parameters have @JsonProperty [1.7.5] @@ -782,3 +782,8 @@ * Suggested [JACKSON-650] Allow dealing with missing filter ids, by adding 'SimpleFilterProvider.setFailOnUnknownId()' to specify if exception is thrown or not. [1.9.0] + +Ben Hale: + * Reported [JACKSON-700] Type problems with properties that have different + types for constructor property, setter and/or field + [1.9.1]
diff --git a/release-notes/VERSION b/release-notes/VERSION index 446e10d..9a85901 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION
@@ -7,6 +7,10 @@ The second patch release for 1.9. Fixes: + + * [JACKSON-700] Type problems with properties that have different types + for constructor property, setter and/or field + (reported by Ben H) ------------------------------------------------------------------------ === History: ===
diff --git a/src/mapper/java/org/codehaus/jackson/map/deser/BeanDeserializerBuilder.java b/src/mapper/java/org/codehaus/jackson/map/deser/BeanDeserializerBuilder.java index 505aa68..5c1030d 100644 --- a/src/mapper/java/org/codehaus/jackson/map/deser/BeanDeserializerBuilder.java +++ b/src/mapper/java/org/codehaus/jackson/map/deser/BeanDeserializerBuilder.java
@@ -81,7 +81,7 @@ /* /********************************************************** - /* Construction and setters + /* Life-cycle: construction /********************************************************** */ @@ -118,6 +118,12 @@ return new HashMap<String, SettableBeanProperty>(src); } + /* + /********************************************************** + /* Life-cycle: state modification (adders, setters) + /********************************************************** + */ + /** * Method for adding a new property or replacing a property. */ @@ -157,6 +163,20 @@ } /** + * @since 1.9 + */ + public void addInjectable(String propertyName, JavaType propertyType, + Annotations contextAnnotations, AnnotatedMember member, + Object valueId) + { + if (_injectables == null) { + _injectables = new ArrayList<ValueInjector>(); + } + _injectables.add(new ValueInjector(propertyName, propertyType, + contextAnnotations, member, valueId)); + } + + /** * Method that will add property name as one of properties that can * be ignored if not recognized. */ @@ -169,6 +189,48 @@ } /** + * Method called by deserializer factory, when a "creator property" + * (something that is passed via constructor- or factory method argument; + * instead of setter or field). + *<p> + * Default implementation does not do anything; we may need to revisit this + * decision if these properties need to be available through accessors. + * For now, however, we just have to ensure that we don't try to resolve + * types that masked setter/field has (see [JACKSON-700] for details). + * + * @since 1.9.2 + */ + public void addCreatorProperty(BeanPropertyDefinition propDef) + { + // do nothing + } + + public void setAnySetter(SettableAnyProperty s) + { + if (_anySetter != null && s != null) { + throw new IllegalStateException("_anySetter already set to non-null"); + } + _anySetter = s; + } + + public void setIgnoreUnknownProperties(boolean ignore) { + _ignoreAllUnknown = ignore; + } + + /** + * @since 1.9 + */ + public void setValueInstantiator(ValueInstantiator inst) { + _valueInstantiator = inst; + } + + /* + /********************************************************** + /* Public accessors + /********************************************************** + */ + + /** * Method that allows accessing all properties that this * builder currently contains. *<p> @@ -191,45 +253,12 @@ return _properties.remove(name); } - public void setAnySetter(SettableAnyProperty s) - { - if (_anySetter != null && s != null) { - throw new IllegalStateException("_anySetter already set to non-null"); - } - _anySetter = s; - } - - public void setIgnoreUnknownProperties(boolean ignore) { - _ignoreAllUnknown = ignore; - } - - /** - * @since 1.9 - */ - public void setValueInstantiator(ValueInstantiator inst) { - _valueInstantiator = inst; - } - /** * @since 1.9 */ public ValueInstantiator getValueInstantiator() { return _valueInstantiator; } - - /** - * @since 1.9 - */ - public void addInjectable(String propertyName, JavaType propertyType, - Annotations contextAnnotations, AnnotatedMember member, - Object valueId) - { - if (_injectables == null) { - _injectables = new ArrayList<ValueInjector>(); - } - _injectables.add(new ValueInjector(propertyName, propertyType, - contextAnnotations, member, valueId)); - } /* /**********************************************************
diff --git a/src/mapper/java/org/codehaus/jackson/map/deser/BeanDeserializerFactory.java b/src/mapper/java/org/codehaus/jackson/map/deser/BeanDeserializerFactory.java index 9bb39ed..36d6207 100644 --- a/src/mapper/java/org/codehaus/jackson/map/deser/BeanDeserializerFactory.java +++ b/src/mapper/java/org/codehaus/jackson/map/deser/BeanDeserializerFactory.java
@@ -1051,7 +1051,16 @@ if (ignored.contains(name)) { // explicit ignoral using @JsonIgnoreProperties needs to block entries continue; } - // primary: have a getter? + /* [JACKSON-700] If property as passed via constructor parameter, we must + * handle things in special way. Not sure what is the most optimal way... + * for now, let's just call a (new) method in builder, which does nothing. + */ + if (property.hasConstructorParameter()) { + // but let's call a method just to allow custom builders to be aware... + builder.addCreatorProperty(property); + continue; + } + // primary: have a setter? if (property.hasSetter()) { AnnotatedMethod setter = property.getSetter(); // [JACKSON-429] Some types are declared as ignorable as well
diff --git a/src/test/org/codehaus/jackson/map/deser/TestCreators2.java b/src/test/org/codehaus/jackson/map/deser/TestCreators2.java index 92a6f5c..9eda865 100644 --- a/src/test/org/codehaus/jackson/map/deser/TestCreators2.java +++ b/src/test/org/codehaus/jackson/map/deser/TestCreators2.java
@@ -167,6 +167,18 @@ } } + static interface Issue700Set extends java.util.Set<Object> { } + + static class Issue700Bean + { + protected Issue700Set item; + + @JsonCreator + public Issue700Bean(@JsonProperty("item") String item) { } + + public String getItem() { return null; } + } + /* /********************************************************** /* Unit tests @@ -318,4 +330,12 @@ bb = m.readValue(quote("true"), BooleanBean.class); assertEquals(Boolean.TRUE, bb.value); } + + // [JACKSON-700] + public void testCreatorProperties() throws Exception + { + ObjectMapper mapper = new ObjectMapper(); + Issue700Bean value = mapper.readValue("{ \"item\" : \"foo\" }", Issue700Bean.class); + assertNotNull(value); + } }
diff --git a/src/test/org/codehaus/jackson/map/introspect/TestPOJOPropertiesCollector.java b/src/test/org/codehaus/jackson/map/introspect/TestPOJOPropertiesCollector.java index 1fddf00..5cedb3b 100644 --- a/src/test/org/codehaus/jackson/map/introspect/TestPOJOPropertiesCollector.java +++ b/src/test/org/codehaus/jackson/map/introspect/TestPOJOPropertiesCollector.java
@@ -103,6 +103,18 @@ public void setD(int value) { } public void setA(int value) { } } + + // [JACKSON-700]: test property type detection, selection + static class TypeTestBean + { + protected Long value; + + @JsonCreator + public TypeTestBean(@JsonProperty("value") String value) { } + + // If you remove this method, the test will pass + public Integer getValue() { return 0; } + } /* /********************************************************** @@ -254,7 +266,29 @@ assertEquals("c", props.get(2).getName()); assertEquals("d", props.get(3).getName()); } - + + public void testSimpleWithType() + { + ObjectMapper mapper = new ObjectMapper(); + // first for serialization; should base choice on getter + POJOPropertiesCollector coll = collector(mapper, TypeTestBean.class, true); + List<BeanPropertyDefinition> props = coll.getProperties(); + assertEquals(1, props.size()); + assertEquals("value", props.get(0).getName()); + AnnotatedMember m = props.get(0).getAccessor(); + assertTrue(m instanceof AnnotatedMethod); + assertEquals(Integer.class, m.getRawType()); + + // then for deserialization; prefer ctor param + coll = collector(mapper, TypeTestBean.class, false); + props = coll.getProperties(); + assertEquals(1, props.size()); + assertEquals("value", props.get(0).getName()); + m = props.get(0).getMutator(); + assertEquals(AnnotatedParameter.class, m.getClass()); + assertEquals(String.class, m.getRawType()); + } + /* /********************************************************** /* Helper methods @@ -271,7 +305,11 @@ { BasicClassIntrospector bci = new BasicClassIntrospector(); // no real difference between serialization, deserialization, at least here - return bci.collectProperties(mapper.getSerializationConfig(), - mapper.constructType(cls), null, forSerialization); + if (forSerialization) { + return bci.collectProperties(mapper.getSerializationConfig(), + mapper.constructType(cls), null, true); + } + return bci.collectProperties(mapper.getDeserializationConfig(), + mapper.constructType(cls), null, false); } }