Skip to content

Commit

Permalink
Work on #2527
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed Dec 27, 2019
1 parent 8846b47 commit e1de974
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,42 @@ public AnnotatedMethod resolveSetterConflict(MapperConfig<?> config,
return null;
}

/**
* Method called on fields that are eligible candidates for properties
* (that is, non-static member fields), but not necessarily selected (may
* or may not be visible), to let fields affect name linking.
* Call will be made after finding implicit name (which by default is just
* name of the field, but may be overridden by introspector), but before
* discovering other accessors.
* If non-null name returned, it is to be used to find other accessors (getters,
* setters, creator parameters) and replace their implicit names with that
* of field's implicit name (assuming they differ).
*<p>
* Specific example (and initial use case is for support Kotlin's "is getter"
* matching (see
* <a href="https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html">Kotling Interop</a>
* for details), in which field like '{@code isOpen}' would have implicit name of
* "isOpen", match getter {@code getOpen()} and setter {@code setOpen(boolean)},
* but use logical external name of "isOpen" (and not implicit name of getter/setter, "open"!).
* To achieve this, field implicit name needs to remain "isOpen" but this method needs
* to return name {@code PropertyName.construct("open")}: doing so will "pull in" getter
* and/or setter, and rename them as "isOpen".
*
* @param config Effective mapper configuration in use
* @param f Field to check
* @param implName Implicit name of the field; usually name of field itself but not always,
* used as the target name for accessors to rename.
*
* @return Name used to find other accessors to rename, if any; {@code null} to indicate
* no renaming
*
* @since 2.11
*/
public PropertyName findRenameByField(MapperConfig<?> config,
AnnotatedField f, PropertyName implName) {
return null;
}

/**
* @deprecated Since 2.9 Use {@link #findInjectableValue} instead
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,16 @@ public AnnotatedMethod resolveSetterConflict(MapperConfig<?> config,
return res;
}

@Override // since 2.11
public PropertyName findRenameByField(MapperConfig<?> config,
AnnotatedField f, PropertyName implName) {
PropertyName n = _secondary.findRenameByField(config, f, implName);
if (n == null) {
n = _primary.findRenameByField(config, f, implName);
}
return n;
}

// // // Serialization: type refinements

@Override // since 2.7
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,13 @@ public AnnotatedMethod resolveSetterConflict(MapperConfig<?> config,
return null;
}

@Override // since 2.11
public PropertyName findRenameByField(MapperConfig<?> config,
AnnotatedField f, PropertyName implName) {
// Nothing to report, only used by modules. But define just as documentation
return null;
}

/*
/**********************************************************
/* Annotations for Polymorphic Type handling
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,9 @@ protected void collectAll()
}
_addInjectables(props);

// 27-Dec-2019, tatu: [databind#2527] initial re-linking by Field needs to
// be applied before other processing

// Remove ignored properties, first; this MUST precede annotation merging
// since logic relies on knowing exactly which accessor has which annotation
_removeUnwantedProperties(props);
Expand Down Expand Up @@ -372,9 +375,8 @@ protected void _addFields(Map<String, POJOPropertyBuilder> props)
*/
final boolean pruneFinalFields = !_forSerialization && !_config.isEnabled(MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS);
final boolean transientAsIgnoral = _config.isEnabled(MapperFeature.PROPAGATE_TRANSIENT_MARKER);

for (AnnotatedField f : _classDef.fields()) {
String implName = ai.findImplicitPropertyName(f);
// @JsonValue?
if (Boolean.TRUE.equals(ai.hasAsValue(f))) {
if (_jsonValueAccessors == null) {
Expand All @@ -391,17 +393,26 @@ protected void _addFields(Map<String, POJOPropertyBuilder> props)
_anySetterField.add(f);
continue;
}
String implName = ai.findImplicitPropertyName(f);
if (implName == null) {
implName = f.getName();
}

// [databind#2527: Field-based renaming can be applied early (here),
// or at a later point, but probably must be done before pruning
// final fields. So let's do it early here
final PropertyName rename = ai.findRenameByField(_config, f, _propNameFromSimple(implName));
if (rename != null) {
// todo
}

PropertyName pn;

if (_forSerialization) {
/* 18-Aug-2011, tatu: As per existing unit tests, we should only
* use serialization annotation (@JsonSerialize) when serializing
* fields, and similarly for deserialize-only annotations... so
* no fallbacks in this particular case.
*/
// 18-Aug-2011, tatu: As per existing unit tests, we should only
// use serialization annotation (@JsonSerialize) when serializing
// fields, and similarly for deserialize-only annotations... so
// no fallbacks in this particular case.
pn = ai.findNameForSerialization(f);
} else {
pn = ai.findNameForDeserialization(f);
Expand Down Expand Up @@ -434,7 +445,7 @@ protected void _addFields(Map<String, POJOPropertyBuilder> props)
}
/* [databind#190]: this is the place to prune final fields, if they are not
* to be used as mutators. Must verify they are not explicitly included.
* Also: if 'ignored' is set, need to included until a later point, to
* Also: if 'ignored' is set, need to include until a later point, to
* avoid losing ignoral information.
*/
if (pruneFinalFields && (pn == null) && !ignored
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,9 +260,11 @@ protected static String legacyManglePropertyName(final String basename, final in
}

/**
* Note: public only since 2.11
*
* @since 2.5
*/
protected static String stdManglePropertyName(final String basename, final int offset)
public static String stdManglePropertyName(final String basename, final int offset)
{
final int end = basename.length();
if (end == offset) { // empty name, nope
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
import java.util.Map;

import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
import com.fasterxml.jackson.databind.util.BeanUtil;

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

static class POJO2527b {
public boolean isEnabled;

protected POJO2527b() { }
public POJO2527b(boolean b) {
isEnabled = b;
}

public boolean getEnabled() { return isEnabled; }
public void setEnabled(boolean b) { isEnabled = b; }
}

@SuppressWarnings("serial")
static class MyIntrospector extends JacksonAnnotationIntrospector
{
@Override
public PropertyName findRenameByField(MapperConfig<?> config,
AnnotatedField f, PropertyName implName)
{
final String origSimple = implName.getSimpleName();
if (origSimple.startsWith("is")) {
String mangledName = BeanUtil.stdManglePropertyName(origSimple, 2);
// Needs to be valid ("is" -> null), and different from original
if ((mangledName != null) && !mangledName.equals(origSimple)) {
return PropertyName.construct(mangledName);
}
}
return null;
}
}

private final ObjectMapper MAPPER = newJsonMapper();

public void testIsProperties() throws Exception
public void testIsPropertiesStdKotlin() throws Exception
{
POJO2527 input = new POJO2527(true);
final String json = MAPPER.writeValueAsString(input);
Expand All @@ -34,4 +69,17 @@ public void testIsProperties() throws Exception
POJO2527 output = MAPPER.readValue(json, POJO2527.class);
assertEquals(input.isEnabled, output.isEnabled);
}

public void testIsPropertiesAlt() throws Exception
{
POJO2527b input = new POJO2527b(true);
final String json = MAPPER.writeValueAsString(input);

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

POJO2527b output = MAPPER.readValue(json, POJO2527b.class);
assertEquals(input.isEnabled, output.isEnabled);
}
}

0 comments on commit e1de974

Please sign in to comment.