Skip to content

Commit 11808cc

Browse files
committed
Fixed #1990
1 parent 69f6f52 commit 11808cc

File tree

6 files changed

+77
-34
lines changed

6 files changed

+77
-34
lines changed

release-notes/CREDITS-2.x

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,10 @@ roeltje25@github
768768
infinite loop
769769
(2.9.5)
770770
771+
Freddy Boucher (freddyboucher@github)
772+
* Reported #1990: MixIn `@JsonProperty` for `Object.hashCode()` is ignored
773+
(2.9.6)
774+
771775
Ondrej Zizka (OndraZizk@github)
772776
* Reported #1999: "Duplicate property" issue should mention which class it complains about
773777
(2.9.6)

release-notes/VERSION-2.x

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ Project: jackson-databind
1111
#1964: Failed to specialize `Map` type during serialization where key type
1212
incompatibility overidden via "raw" types
1313
(reported by ptirador@github)
14+
#1990: MixIn `@JsonProperty` for `Object.hashCode()` is ignored
15+
(reported by Freddy B)
1416
#1998: Removing "type" attribute with Mixin not taken in account if
1517
using ObjectMapper.copy()
1618
(reported by SBKila@github)

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

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -49,37 +49,40 @@ AnnotatedMethodMap collect(TypeFactory typeFactory, TypeResolutionContext tc,
4949
type.getRawClass(), methods, mixin);
5050
}
5151
// Special case: mix-ins for Object.class? (to apply to ALL classes)
52-
/*
52+
boolean checkJavaLangObject = false;
5353
if (_mixInResolver != null) {
5454
Class<?> mixin = _mixInResolver.findMixInClassFor(Object.class);
5555
if (mixin != null) {
56-
_addMethodMixIns(tc, mainType.getRawClass(), memberMethods, mixin, mixins);
56+
_addMethodMixIns(tc, mainType.getRawClass(), methods, mixin); //, mixins);
57+
checkJavaLangObject = true;
5758
}
5859
}
5960

6061
// Any unmatched mix-ins? Most likely error cases (not matching any method);
6162
// but there is one possible real use case: exposing Object#hashCode
6263
// (alas, Object#getClass can NOT be exposed)
63-
if (_intr != null) {
64-
if (!mixins.isEmpty()) {
65-
Iterator<AnnotatedMethod> it = mixins.iterator();
66-
while (it.hasNext()) {
67-
AnnotatedMethod mixIn = it.next();
68-
try {
69-
Method m = Object.class.getDeclaredMethod(mixIn.getName(), mixIn.getRawParameterTypes());
70-
if (m != null) {
71-
// Since it's from java.lang.Object, no generics, no need for real type context:
72-
AnnotatedMethod am = _constructMethod(tc, m);
73-
_addMixOvers(mixIn.getAnnotated(), am, false);
74-
memberMethods.add(am);
75-
}
76-
} catch (Exception e) { }
64+
// Since we only know of that ONE case, optimize for it
65+
if (checkJavaLangObject && (_intr != null) && !methods.isEmpty()) {
66+
// Could use lookup but probably as fast or faster to traverse
67+
for (Map.Entry<MemberKey,MethodBuilder> entry : methods.entrySet()) {
68+
MemberKey k = entry.getKey();
69+
if (!"hashCode".equals(k.getName()) || (0 != k.argCount())) {
70+
continue;
7771
}
72+
try {
73+
// And with that, we can generate it appropriately
74+
Method m = Object.class.getDeclaredMethod(k.getName());
75+
if (m != null) {
76+
MethodBuilder b = entry.getValue();
77+
b.annotations = collectDefaultAnnotations(b.annotations,
78+
m.getDeclaredAnnotations());
79+
b.method = m;
80+
}
81+
} catch (Exception e) { }
7882
}
7983
}
80-
*/
8184

82-
// And then let's
85+
// And then let's create the lookup map
8386
if (methods.isEmpty()) {
8487
return new AnnotatedMethodMap();
8588
}

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ protected final AnnotationCollector collectFromBundle(AnnotationCollector c, Ann
6868
// // // Defaulting ("mix under")
6969

7070
// Variant that only adds annotations that are missing
71-
protected final AnnotationCollector collectDefaultAnnotations(AnnotationCollector c, Annotation[] anns) {
71+
protected final AnnotationCollector collectDefaultAnnotations(AnnotationCollector c,
72+
Annotation[] anns) {
7273
for (int i = 0, end = anns.length; i < end; ++i) {
7374
Annotation ann = anns[i];
7475
if (!c.isPresent(ann)) {
@@ -81,7 +82,8 @@ protected final AnnotationCollector collectDefaultAnnotations(AnnotationCollecto
8182
return c;
8283
}
8384

84-
protected final AnnotationCollector collectDefaultFromBundle(AnnotationCollector c, Annotation bundle) {
85+
protected final AnnotationCollector collectDefaultFromBundle(AnnotationCollector c,
86+
Annotation bundle) {
8587
Annotation[] anns = ClassUtil.findClassAnnotations(bundle.annotationType());
8688
for (int i = 0, end = anns.length; i < end; ++i) {
8789
Annotation ann = anns[i];

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,21 @@ public MemberKey(String name, Class<?>[] argTypes)
3131
_argTypes = (argTypes == null) ? NO_CLASSES : argTypes;
3232
}
3333

34+
public String getName() {
35+
return _name;
36+
}
37+
38+
public int argCount() {
39+
return _argTypes.length;
40+
}
41+
3442
@Override
3543
public String toString() {
3644
return _name + "(" + _argTypes.length+"-args)";
3745
}
3846

3947
@Override
40-
public int hashCode()
41-
{
48+
public int hashCode() {
4249
return _name.hashCode() + _argTypes.length;
4350
}
4451

src/test/java/com/fasterxml/jackson/databind/mixins/TestMixinDeserForClass.java

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,6 @@
1010
public class TestMixinDeserForClass
1111
extends BaseMapTest
1212
{
13-
/*
14-
/**********************************************************
15-
/* Helper bean classes
16-
/**********************************************************
17-
*/
18-
1913
static class BaseClass
2014
{
2115
/* property that is always found; but has lower priority than
@@ -35,6 +29,21 @@ static class LeafClass
3529
@JsonAutoDetect(setterVisibility=Visibility.NONE, fieldVisibility=Visibility.NONE)
3630
interface MixIn { }
3731

32+
// [databind#1990]
33+
public interface HashCodeMixIn {
34+
@Override
35+
@JsonProperty
36+
public int hashCode();
37+
}
38+
39+
public class Bean1990WithoutHashCode {
40+
}
41+
42+
public class Bean1990WithHashCode {
43+
@Override
44+
public int hashCode() { return 13; }
45+
}
46+
3847
/*
3948
/**********************************************************
4049
/* Unit tests
@@ -48,18 +57,16 @@ public void testClassMixInsTopLevel() throws IOException
4857
LeafClass result = m.readValue("{\"a\":\"value\"}", LeafClass.class);
4958
assertEquals("XXXvalue", result.a);
5059

51-
/* Then with leaf-level mix-in; without (method) auto-detect, should
52-
* use field
53-
*/
60+
// Then with leaf-level mix-in; without (method) auto-detect,
61+
// should use field
5462
m = new ObjectMapper();
5563
m.addMixIn(LeafClass.class, MixIn.class);
5664
result = m.readValue("{\"a\":\"value\"}", LeafClass.class);
5765
assertEquals("value", result.a);
5866
}
5967

60-
/* and then a test for mid-level mixin; should have no effect
61-
* when deserializing leaf (but will if deserializing base class)
62-
*/
68+
// and then a test for mid-level mixin; should have no effect
69+
// when deserializing leaf (but will if deserializing base class)
6370
public void testClassMixInsMidLevel() throws IOException
6471
{
6572
ObjectMapper m = new ObjectMapper();
@@ -95,4 +102,22 @@ public void testClassMixInsForObjectClass() throws IOException
95102
assertEquals("XXX", result.a);
96103
}
97104
}
105+
106+
// [databind#1990]: can apply mix-in to `Object#hashCode()` to force serialization
107+
public void testHashCodeViaObject() throws Exception
108+
{
109+
ObjectMapper mapper = new ObjectMapper()
110+
.addMixIn(Object.class, HashCodeMixIn.class);
111+
112+
// First, with something that overrides hashCode()
113+
assertEquals( "{\"hashCode\":13}",
114+
mapper.writeValueAsString(new Bean1990WithHashCode()));
115+
116+
// and then special case of accessing Object#hashCode()
117+
String prefix = "{\"hashCode\":";
118+
String json = mapper.writeValueAsString(new Bean1990WithoutHashCode());
119+
if (!json.startsWith(prefix)) {
120+
fail("Should start with ["+prefix+"], does not: ["+json+"]");
121+
}
122+
}
98123
}

0 commit comments

Comments
 (0)