Skip to content

Commit e1de974

Browse files
committed
Work on #2527
1 parent 8846b47 commit e1de974

File tree

6 files changed

+124
-10
lines changed

6 files changed

+124
-10
lines changed

src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java

+36
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,42 @@ public AnnotatedMethod resolveSetterConflict(MapperConfig<?> config,
614614
return null;
615615
}
616616

617+
/**
618+
* Method called on fields that are eligible candidates for properties
619+
* (that is, non-static member fields), but not necessarily selected (may
620+
* or may not be visible), to let fields affect name linking.
621+
* Call will be made after finding implicit name (which by default is just
622+
* name of the field, but may be overridden by introspector), but before
623+
* discovering other accessors.
624+
* If non-null name returned, it is to be used to find other accessors (getters,
625+
* setters, creator parameters) and replace their implicit names with that
626+
* of field's implicit name (assuming they differ).
627+
*<p>
628+
* Specific example (and initial use case is for support Kotlin's "is getter"
629+
* matching (see
630+
* <a href="https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html">Kotling Interop</a>
631+
* for details), in which field like '{@code isOpen}' would have implicit name of
632+
* "isOpen", match getter {@code getOpen()} and setter {@code setOpen(boolean)},
633+
* but use logical external name of "isOpen" (and not implicit name of getter/setter, "open"!).
634+
* To achieve this, field implicit name needs to remain "isOpen" but this method needs
635+
* to return name {@code PropertyName.construct("open")}: doing so will "pull in" getter
636+
* and/or setter, and rename them as "isOpen".
637+
*
638+
* @param config Effective mapper configuration in use
639+
* @param f Field to check
640+
* @param implName Implicit name of the field; usually name of field itself but not always,
641+
* used as the target name for accessors to rename.
642+
*
643+
* @return Name used to find other accessors to rename, if any; {@code null} to indicate
644+
* no renaming
645+
*
646+
* @since 2.11
647+
*/
648+
public PropertyName findRenameByField(MapperConfig<?> config,
649+
AnnotatedField f, PropertyName implName) {
650+
return null;
651+
}
652+
617653
/**
618654
* @deprecated Since 2.9 Use {@link #findInjectableValue} instead
619655
*/

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

+10
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,16 @@ public AnnotatedMethod resolveSetterConflict(MapperConfig<?> config,
520520
return res;
521521
}
522522

523+
@Override // since 2.11
524+
public PropertyName findRenameByField(MapperConfig<?> config,
525+
AnnotatedField f, PropertyName implName) {
526+
PropertyName n = _secondary.findRenameByField(config, f, implName);
527+
if (n == null) {
528+
n = _primary.findRenameByField(config, f, implName);
529+
}
530+
return n;
531+
}
532+
523533
// // // Serialization: type refinements
524534

525535
@Override // since 2.7

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

+7
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,13 @@ public AnnotatedMethod resolveSetterConflict(MapperConfig<?> config,
533533
return null;
534534
}
535535

536+
@Override // since 2.11
537+
public PropertyName findRenameByField(MapperConfig<?> config,
538+
AnnotatedField f, PropertyName implName) {
539+
// Nothing to report, only used by modules. But define just as documentation
540+
return null;
541+
}
542+
536543
/*
537544
/**********************************************************
538545
/* Annotations for Polymorphic Type handling

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

+19-8
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,9 @@ protected void collectAll()
314314
}
315315
_addInjectables(props);
316316

317+
// 27-Dec-2019, tatu: [databind#2527] initial re-linking by Field needs to
318+
// be applied before other processing
319+
317320
// Remove ignored properties, first; this MUST precede annotation merging
318321
// since logic relies on knowing exactly which accessor has which annotation
319322
_removeUnwantedProperties(props);
@@ -372,9 +375,8 @@ protected void _addFields(Map<String, POJOPropertyBuilder> props)
372375
*/
373376
final boolean pruneFinalFields = !_forSerialization && !_config.isEnabled(MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS);
374377
final boolean transientAsIgnoral = _config.isEnabled(MapperFeature.PROPAGATE_TRANSIENT_MARKER);
375-
378+
376379
for (AnnotatedField f : _classDef.fields()) {
377-
String implName = ai.findImplicitPropertyName(f);
378380
// @JsonValue?
379381
if (Boolean.TRUE.equals(ai.hasAsValue(f))) {
380382
if (_jsonValueAccessors == null) {
@@ -391,17 +393,26 @@ protected void _addFields(Map<String, POJOPropertyBuilder> props)
391393
_anySetterField.add(f);
392394
continue;
393395
}
396+
String implName = ai.findImplicitPropertyName(f);
394397
if (implName == null) {
395398
implName = f.getName();
396399
}
400+
401+
// [databind#2527: Field-based renaming can be applied early (here),
402+
// or at a later point, but probably must be done before pruning
403+
// final fields. So let's do it early here
404+
final PropertyName rename = ai.findRenameByField(_config, f, _propNameFromSimple(implName));
405+
if (rename != null) {
406+
// todo
407+
}
408+
397409
PropertyName pn;
398410

399411
if (_forSerialization) {
400-
/* 18-Aug-2011, tatu: As per existing unit tests, we should only
401-
* use serialization annotation (@JsonSerialize) when serializing
402-
* fields, and similarly for deserialize-only annotations... so
403-
* no fallbacks in this particular case.
404-
*/
412+
// 18-Aug-2011, tatu: As per existing unit tests, we should only
413+
// use serialization annotation (@JsonSerialize) when serializing
414+
// fields, and similarly for deserialize-only annotations... so
415+
// no fallbacks in this particular case.
405416
pn = ai.findNameForSerialization(f);
406417
} else {
407418
pn = ai.findNameForDeserialization(f);
@@ -434,7 +445,7 @@ protected void _addFields(Map<String, POJOPropertyBuilder> props)
434445
}
435446
/* [databind#190]: this is the place to prune final fields, if they are not
436447
* to be used as mutators. Must verify they are not explicitly included.
437-
* Also: if 'ignored' is set, need to included until a later point, to
448+
* Also: if 'ignored' is set, need to include until a later point, to
438449
* avoid losing ignoral information.
439450
*/
440451
if (pruneFinalFields && (pn == null) && !ignored

src/main/java/com/fasterxml/jackson/databind/util/BeanUtil.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -260,9 +260,11 @@ protected static String legacyManglePropertyName(final String basename, final in
260260
}
261261

262262
/**
263+
* Note: public only since 2.11
264+
*
263265
* @since 2.5
264266
*/
265-
protected static String stdManglePropertyName(final String basename, final int offset)
267+
public static String stdManglePropertyName(final String basename, final int offset)
266268
{
267269
final int end = basename.length();
268270
if (end == offset) { // empty name, nope

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

+49-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
import java.util.Map;
55

66
import com.fasterxml.jackson.databind.*;
7+
import com.fasterxml.jackson.databind.cfg.MapperConfig;
8+
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
9+
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
10+
import com.fasterxml.jackson.databind.util.BeanUtil;
711

812
// [databind#2527] Support Kotlin-style "is" properties
913
public class IsGetterRenaming2527Test extends BaseMapTest
@@ -20,9 +24,40 @@ public POJO2527(boolean b) {
2024
public void setEnabled(boolean b) { isEnabled = b; }
2125
}
2226

27+
static class POJO2527b {
28+
public boolean isEnabled;
29+
30+
protected POJO2527b() { }
31+
public POJO2527b(boolean b) {
32+
isEnabled = b;
33+
}
34+
35+
public boolean getEnabled() { return isEnabled; }
36+
public void setEnabled(boolean b) { isEnabled = b; }
37+
}
38+
39+
@SuppressWarnings("serial")
40+
static class MyIntrospector extends JacksonAnnotationIntrospector
41+
{
42+
@Override
43+
public PropertyName findRenameByField(MapperConfig<?> config,
44+
AnnotatedField f, PropertyName implName)
45+
{
46+
final String origSimple = implName.getSimpleName();
47+
if (origSimple.startsWith("is")) {
48+
String mangledName = BeanUtil.stdManglePropertyName(origSimple, 2);
49+
// Needs to be valid ("is" -> null), and different from original
50+
if ((mangledName != null) && !mangledName.equals(origSimple)) {
51+
return PropertyName.construct(mangledName);
52+
}
53+
}
54+
return null;
55+
}
56+
}
57+
2358
private final ObjectMapper MAPPER = newJsonMapper();
2459

25-
public void testIsProperties() throws Exception
60+
public void testIsPropertiesStdKotlin() throws Exception
2661
{
2762
POJO2527 input = new POJO2527(true);
2863
final String json = MAPPER.writeValueAsString(input);
@@ -34,4 +69,17 @@ public void testIsProperties() throws Exception
3469
POJO2527 output = MAPPER.readValue(json, POJO2527.class);
3570
assertEquals(input.isEnabled, output.isEnabled);
3671
}
72+
73+
public void testIsPropertiesAlt() throws Exception
74+
{
75+
POJO2527b input = new POJO2527b(true);
76+
final String json = MAPPER.writeValueAsString(input);
77+
78+
Map<?, ?> props = MAPPER.readValue(json, Map.class);
79+
assertEquals(Collections.singletonMap("isEnabled", Boolean.TRUE),
80+
props);
81+
82+
POJO2527b output = MAPPER.readValue(json, POJO2527b.class);
83+
assertEquals(input.isEnabled, output.isEnabled);
84+
}
3785
}

0 commit comments

Comments
 (0)