diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/BasicBeanDescription.java b/src/main/java/com/fasterxml/jackson/databind/introspect/BasicBeanDescription.java index aaaa584e2c..aef4cdcf31 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/BasicBeanDescription.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/BasicBeanDescription.java @@ -466,18 +466,34 @@ public JsonInclude.Value findPropertyInclusion(JsonInclude.Value defValue) { @Override public AnnotatedMember findAnyGetter() throws IllegalArgumentException { - AnnotatedMember anyGetter = (_propCollector == null) ? null - : _propCollector.getAnyGetter(); - if (anyGetter != null) { - /* For now let's require a Map; in future can add support for other - * types like perhaps Iterable? - */ - Class type = anyGetter.getRawType(); - if (!Map.class.isAssignableFrom(type)) { - throw new IllegalArgumentException("Invalid 'any-getter' annotation on method "+anyGetter.getName()+"(): return type is not instance of java.util.Map"); + if (_propCollector != null) { + AnnotatedMember anyGetter = _propCollector.getAnyGetterMethod(); + if (anyGetter != null) { + // For now let's require a Map; in future can add support for other + // types like perhaps Iterable? + Class type = anyGetter.getRawType(); + if (!Map.class.isAssignableFrom(type)) { + throw new IllegalArgumentException(String.format( + "Invalid 'any-getter' annotation on method %s(): return type is not instance of java.util.Map", + anyGetter.getName())); + } + return anyGetter; + } + + AnnotatedMember anyField = _propCollector.getAnyGetterField(); + if (anyField != null) { + // For now let's require a Map; in future can add support for other + // types like perhaps Iterable? + Class type = anyField.getRawType(); + if (!Map.class.isAssignableFrom(type)) { + throw new IllegalArgumentException(String.format( + "Invalid 'any-getter' annotation on field '%s': type is not instance of java.util.Map", + anyField.getName())); + } + return anyField; } } - return anyGetter; + return null; } @Override diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java index cf0b842883..708eacf10f 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java @@ -102,6 +102,11 @@ public class POJOPropertiesCollector protected LinkedList _anyGetters; + /** + * @since 2.12 + */ + protected LinkedList _anyGetterField; + protected LinkedList _anySetters; protected LinkedList _anySetterField; @@ -208,18 +213,36 @@ public AnnotatedMember getJsonValueAccessor() return null; } - public AnnotatedMember getAnyGetter() + /** + * @since 2.12 + */ + public AnnotatedMember getAnyGetterField() + { + if (!_collected) { + collectAll(); + } + if (_anyGetterField != null) { + if (_anyGetterField.size() > 1) { + reportProblem("Multiple 'any-getter' fields defined (%s vs %s)", + _anyGetterField.get(0), _anyGetterField.get(1)); + } + return _anyGetterField.getFirst(); + } + return null; + } + + public AnnotatedMember getAnyGetterMethod() { if (!_collected) { collectAll(); } if (_anyGetters != null) { if (_anyGetters.size() > 1) { - reportProblem("Multiple 'any-getters' defined (%s vs %s)", + reportProblem("Multiple 'any-getter' methods defined (%s vs %s)", _anyGetters.get(0), _anyGetters.get(1)); } return _anyGetters.getFirst(); - } + } return null; } @@ -392,12 +415,25 @@ protected void _addFields(Map props) _jsonValueAccessors.add(f); continue; } - // @JsonAnySetter? - if (Boolean.TRUE.equals(ai.hasAnySetter(f))) { - if (_anySetterField == null) { - _anySetterField = new LinkedList(); + // 12-October-2020, dominikrebhan: [databind#1458] Support @JsonAnyGetter on + // fields and allow @JsonAnySetter to be declared as well. + boolean anyGetter = Boolean.TRUE.equals(ai.hasAnyGetter(f)); + boolean anySetter = Boolean.TRUE.equals(ai.hasAnySetter(f)); + if (anyGetter || anySetter) { + // @JsonAnyGetter? + if (anyGetter) { + if (_anyGetterField == null) { + _anyGetterField = new LinkedList<>(); + } + _anyGetterField.add(f); + } + // @JsonAnySetter? + if (anySetter) { + if (_anySetterField == null) { + _anySetterField = new LinkedList<>(); + } + _anySetterField.add(f); } - _anySetterField.add(f); continue; } String implName = ai.findImplicitPropertyName(f); @@ -685,7 +721,7 @@ protected void _addSetterMethod(Map props, } // 27-Dec-2019, tatu: [databind#2527] may need to rename according to field implName = _checkRenameByField(implName); - boolean ignore = (ai == null) ? false : ai.hasIgnoreMarker(m); + boolean ignore = ai != null && ai.hasIgnoreMarker(m); _property(props, implName).addSetter(m, pn, nameExplicit, visible, ignore); } @@ -721,7 +757,7 @@ protected void _doAddInjectable(JacksonInject.Value injectable, AnnotatedMember if (prev.getClass() == m.getClass()) { String type = id.getClass().getName(); throw new IllegalArgumentException("Duplicate injectable value with id '" - +String.valueOf(id)+"' (of type "+type+")"); + + id +"' (of type "+type+")"); } } } @@ -1001,7 +1037,7 @@ protected void _sortProperties(Map props) { // Then how about explicit ordering? final AnnotationIntrospector intr = _annotationIntrospector; - Boolean alpha = intr.findSerializationSortAlphabetically((Annotated) _classDef); + Boolean alpha = intr.findSerializationSortAlphabetically(_classDef); final boolean sort = (alpha == null) ? _config.shouldSortPropertiesAlphabetically() : alpha.booleanValue(); diff --git a/src/test/java/com/fasterxml/jackson/databind/access/TestAnyGetterAccess.java b/src/test/java/com/fasterxml/jackson/databind/access/TestAnyGetterAccess.java index be141ca3af..8a08fbe80a 100644 --- a/src/test/java/com/fasterxml/jackson/databind/access/TestAnyGetterAccess.java +++ b/src/test/java/com/fasterxml/jackson/databind/access/TestAnyGetterAccess.java @@ -34,6 +34,22 @@ public void set(String name, String value) { } } + static class DynaFieldBean { + public int id; + + @JsonAnyGetter + @JsonAnySetter + protected HashMap other = new HashMap(); + + public Map any() { + return other; + } + + public void set(String name, String value) { + other.put(name, value); + } + } + static class PrivateThing { @JsonAnyGetter @@ -65,6 +81,18 @@ public void testDynaBean() throws Exception assertEquals("Joe", result.other.get("name")); } + public void testDynaFieldBean() throws Exception + { + DynaFieldBean b = new DynaFieldBean(); + b.id = 123; + b.set("name", "Billy"); + assertEquals("{\"id\":123,\"name\":\"Billy\"}", MAPPER.writeValueAsString(b)); + + DynaFieldBean result = MAPPER.readValue("{\"id\":2,\"name\":\"Joe\"}", DynaFieldBean.class); + assertEquals(2, result.id); + assertEquals("Joe", result.other.get("name")); + } + public void testPrivate() throws Exception { String json = MAPPER.writeValueAsString(new PrivateThing());