Skip to content

Commit a28cded

Browse files
committed
Fix #2527
1 parent e1de974 commit a28cded

File tree

3 files changed

+88
-22
lines changed

3 files changed

+88
-22
lines changed

release-notes/VERSION-2.x

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ Project: jackson-databind
2424
#2522: `DeserializationContext.handleMissingInstantiator()` throws `MismatchedInputException`
2525
for non-static inner classes
2626
#2525: Incorrect `JsonStreamContext` for `TokenBuffer` and `TreeTraversingParser`
27+
#2527: Add `AnnotationIntrospector.findRenameByField()` to support Kotlin's "is-getter"
28+
naming convention
2729
#2555: Use `@JsonProperty(index)` for sorting properties on serialization
2830
#2565: Java 8 `Optional` not working with `@JsonUnwrapped` on unwrappable type
2931
(reported by Haowei W)

src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,22 @@ public class POJOPropertiesCollector
8989
protected LinkedHashMap<String, POJOPropertyBuilder> _properties;
9090

9191
protected LinkedList<POJOPropertyBuilder> _creatorProperties;
92+
93+
/**
94+
* A set of "field renamings" that have been discovered, indicating
95+
* intended renaming of other accesors: key is the implicit original
96+
* name and value intended name to use instead.
97+
*<p>
98+
* Note that these renamings are applied earlier than "regular" (explicit)
99+
* renamings and affect implicit name: their effect may be changed by
100+
* further renaming based on explicit indicators.
101+
* The main use case is to effectively relink accessors based on fields
102+
* discovered, and used to sort of correct otherwise missing linkage between
103+
* fields and other accessors.
104+
*
105+
* @since 2.11
106+
*/
107+
protected Map<PropertyName, PropertyName> _fieldRenameMappings;
92108

93109
protected LinkedList<AnnotatedMember> _anyGetters;
94110

@@ -305,7 +321,7 @@ protected void collectAll()
305321
LinkedHashMap<String, POJOPropertyBuilder> props = new LinkedHashMap<String, POJOPropertyBuilder>();
306322

307323
// First: gather basic data
308-
_addFields(props);
324+
_addFields(props); // note: populates _fieldRenameMappings
309325
_addMethods(props);
310326
// 25-Jan-2016, tatu: Avoid introspecting (constructor-)creators for non-static
311327
// inner classes, see [databind#1502]
@@ -314,9 +330,6 @@ protected void collectAll()
314330
}
315331
_addInjectables(props);
316332

317-
// 27-Dec-2019, tatu: [databind#2527] initial re-linking by Field needs to
318-
// be applied before other processing
319-
320333
// Remove ignored properties, first; this MUST precede annotation merging
321334
// since logic relies on knowing exactly which accessor has which annotation
322335
_removeUnwantedProperties(props);
@@ -397,15 +410,20 @@ protected void _addFields(Map<String, POJOPropertyBuilder> props)
397410
if (implName == null) {
398411
implName = f.getName();
399412
}
413+
final PropertyName implNameP = _propNameFromSimple(implName);
400414

401415
// [databind#2527: Field-based renaming can be applied early (here),
402416
// or at a later point, but probably must be done before pruning
403417
// final fields. So let's do it early here
404-
final PropertyName rename = ai.findRenameByField(_config, f, _propNameFromSimple(implName));
405-
if (rename != null) {
418+
final PropertyName rename = ai.findRenameByField(_config, f, implNameP);
419+
if ((rename != null) && !rename.equals(implNameP)) {
420+
if (_fieldRenameMappings == null) {
421+
_fieldRenameMappings = new HashMap<>();
422+
}
423+
_fieldRenameMappings.put(rename, implNameP);
406424
// todo
407425
}
408-
426+
409427
PropertyName pn;
410428

411429
if (_forSerialization) {
@@ -511,9 +529,12 @@ protected void _addCreatorParam(Map<String, POJOPropertyBuilder> props,
511529
pn = PropertyName.construct(impl);
512530
}
513531

532+
// 27-Dec-2019, tatu: [databind#2527] may need to rename according to field
533+
impl = _checkRenameByField(impl);
534+
514535
// shouldn't need to worry about @JsonIgnore, since creators only added
515536
// if so annotated
516-
537+
517538
/* 13-May-2015, tatu: We should try to start with implicit name, similar to how
518539
* fields and methods work; but unlike those, we don't necessarily have
519540
* implicit name to use (pre-Java8 at least). So:
@@ -531,11 +552,11 @@ protected void _addMethods(Map<String, POJOPropertyBuilder> props)
531552
{
532553
final AnnotationIntrospector ai = _annotationIntrospector;
533554
for (AnnotatedMethod m : _classDef.memberMethods()) {
534-
/* For methods, handling differs between getters and setters; and
535-
* we will also only consider entries that either follow the bean
536-
* naming convention or are explicitly marked: just being visible
537-
* is not enough (unlike with fields)
538-
*/
555+
// For methods, handling differs between getters and setters; and
556+
// we will also only consider entries that either follow the bean
557+
// naming convention or are explicitly marked: just being visible
558+
// is not enough (unlike with fields)
559+
539560
int argCount = m.getParameterCount();
540561
if (argCount == 0) { // getters (including 'any getter')
541562
_addGetterMethod(props, m, ai);
@@ -616,6 +637,8 @@ protected void _addGetterMethod(Map<String, POJOPropertyBuilder> props,
616637
}
617638
visible = true;
618639
}
640+
// 27-Dec-2019, tatu: [databind#2527] may need to rename according to field
641+
implName = _checkRenameByField(implName);
619642
boolean ignore = ai.hasIgnoreMarker(m);
620643
_property(props, implName).addGetter(m, pn, nameExplicit, visible, ignore);
621644
}
@@ -653,6 +676,8 @@ protected void _addSetterMethod(Map<String, POJOPropertyBuilder> props,
653676
}
654677
visible = true;
655678
}
679+
// 27-Dec-2019, tatu: [databind#2527] may need to rename according to field
680+
implName = _checkRenameByField(implName);
656681
boolean ignore = (ai == null) ? false : ai.hasIgnoreMarker(m);
657682
_property(props, implName).addSetter(m, pn, nameExplicit, visible, ignore);
658683
}
@@ -697,7 +722,20 @@ protected void _doAddInjectable(JacksonInject.Value injectable, AnnotatedMember
697722
private PropertyName _propNameFromSimple(String simpleName) {
698723
return PropertyName.construct(simpleName, null);
699724
}
700-
725+
726+
// @since 2.11
727+
private String _checkRenameByField(String implName) {
728+
if (_fieldRenameMappings != null) {
729+
PropertyName p = _fieldRenameMappings.get(_propNameFromSimple(implName));
730+
if (p != null) {
731+
implName = p.getSimpleName();
732+
return implName;
733+
734+
}
735+
}
736+
return implName;
737+
}
738+
701739
/*
702740
/**********************************************************
703741
/* Internal methods; removing ignored properties

src/test/java/com/fasterxml/jackson/failing/IsGetterRenaming2527Test.java

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.util.Collections;
44
import java.util.Map;
55

6+
import com.fasterxml.jackson.annotation.JsonProperty;
67
import com.fasterxml.jackson.databind.*;
78
import com.fasterxml.jackson.databind.cfg.MapperConfig;
89
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
@@ -24,18 +25,28 @@ public POJO2527(boolean b) {
2425
public void setEnabled(boolean b) { isEnabled = b; }
2526
}
2627

27-
static class POJO2527b {
28+
static class POJO2527PublicField {
2829
public boolean isEnabled;
2930

30-
protected POJO2527b() { }
31-
public POJO2527b(boolean b) {
31+
protected POJO2527PublicField() { }
32+
public POJO2527PublicField(boolean b) {
3233
isEnabled = b;
3334
}
3435

3536
public boolean getEnabled() { return isEnabled; }
3637
public void setEnabled(boolean b) { isEnabled = b; }
3738
}
38-
39+
40+
static class POJO2527Creator {
41+
private final boolean isEnabled;
42+
43+
public POJO2527Creator(@JsonProperty("enabled") boolean b) {
44+
isEnabled = b;
45+
}
46+
47+
public boolean getEnabled() { return isEnabled; }
48+
}
49+
3950
@SuppressWarnings("serial")
4051
static class MyIntrospector extends JacksonAnnotationIntrospector
4152
{
@@ -55,7 +66,9 @@ public PropertyName findRenameByField(MapperConfig<?> config,
5566
}
5667
}
5768

58-
private final ObjectMapper MAPPER = newJsonMapper();
69+
private final ObjectMapper MAPPER = jsonMapperBuilder()
70+
.annotationIntrospector(new MyIntrospector())
71+
.build();
5972

6073
public void testIsPropertiesStdKotlin() throws Exception
6174
{
@@ -70,16 +83,29 @@ public void testIsPropertiesStdKotlin() throws Exception
7083
assertEquals(input.isEnabled, output.isEnabled);
7184
}
7285

73-
public void testIsPropertiesAlt() throws Exception
86+
public void testIsPropertiesWithPublicField() throws Exception
87+
{
88+
POJO2527PublicField input = new POJO2527PublicField(true);
89+
final String json = MAPPER.writeValueAsString(input);
90+
91+
Map<?, ?> props = MAPPER.readValue(json, Map.class);
92+
assertEquals(Collections.singletonMap("isEnabled", Boolean.TRUE),
93+
props);
94+
95+
POJO2527PublicField output = MAPPER.readValue(json, POJO2527PublicField.class);
96+
assertEquals(input.isEnabled, output.isEnabled);
97+
}
98+
99+
public void testIsPropertiesViaCreator() throws Exception
74100
{
75-
POJO2527b input = new POJO2527b(true);
101+
POJO2527Creator input = new POJO2527Creator(true);
76102
final String json = MAPPER.writeValueAsString(input);
77103

78104
Map<?, ?> props = MAPPER.readValue(json, Map.class);
79105
assertEquals(Collections.singletonMap("isEnabled", Boolean.TRUE),
80106
props);
81107

82-
POJO2527b output = MAPPER.readValue(json, POJO2527b.class);
108+
POJO2527Creator output = MAPPER.readValue(json, POJO2527Creator.class);
83109
assertEquals(input.isEnabled, output.isEnabled);
84110
}
85111
}

0 commit comments

Comments
 (0)