MixinForFactoryMethod3220Test.java
package com.fasterxml.jackson.databind.mixins;
import java.util.Objects;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.testutil.DatabindTestUtil;
import static org.junit.jupiter.api.Assertions.*;
// 23-Aug-2021, tatu: UGGGGH. This is a recurring problem; users assuming
// that a generic type parameter T (or whatever) of a Class will be related
// to locally declared type parameter with same name (T), assume Creator
// factory method will magically work. Despite Java Language quite clearly
// considering these separate bindings regardless of name overlap.
//
// But for Jackson 2.x there is common enough usage of this anti-patterns so
// we cannot block it.
//
// NOTE! Problem is actually not the mixin handling but type resolution;
// see f.ex [databind#2821], [databind#2895]
public class MixinForFactoryMethod3220Test
extends DatabindTestUtil
{
// [databind#3220]
static class Timestamped<T> {
private final T value;
private final int timestamp;
Timestamped(T value, int timestamp) {
this.value = value;
this.timestamp = timestamp;
}
public static <T> Timestamped<T> stamp(T value, int timestamp) {
return new Timestamped<>(value, timestamp);
}
public T getValue() {
return value;
}
public int getTimestamp() {
return timestamp;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Timestamped<?> that = (Timestamped<?>) o;
return timestamp == that.timestamp && Objects.equals(value, that.value);
}
@Override
public int hashCode() {
return Objects.hash(value, timestamp);
}
}
abstract static class TimestampedMixin<T> {
@JsonCreator
public static <T> void stamp(
@JsonProperty("value") T value,
@JsonProperty("timestamp") int timestamp) {
}
@JsonGetter("value")
abstract T getValue();
@JsonGetter("timestamp")
abstract int getTimestamp();
}
static class Profile {
private final String firstName;
private final String lastName;
@JsonCreator
public Profile(@JsonProperty("firstName") String firstName,
@JsonProperty("lastName") String lastName
) {
this.firstName = firstName;
this.lastName = lastName;
}
@JsonGetter("firstName")
public String getFirstName() {
return firstName;
}
@JsonGetter("lastName")
public String getLastName() {
return lastName;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Profile profile = (Profile) o;
return Objects.equals(firstName, profile.firstName) && Objects.equals(lastName, profile.lastName);
}
@Override
public int hashCode() {
return Objects.hash(firstName, lastName);
}
}
static class User {
private final Timestamped<Profile> profile;
@JsonCreator
User(@JsonProperty("profile") Timestamped<Profile> profile) {
this.profile = profile;
}
@JsonGetter("profile")
public Timestamped<Profile> getProfile() {
return profile;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(profile, user.profile);
}
@Override
public int hashCode() {
return Objects.hash(profile);
}
}
// [databind#3220]
@Test
public void testMixin3220() throws Exception
{
ObjectMapper mapper = JsonMapper.builder()
.addMixIn(Timestamped.class, TimestampedMixin.class)
.build();
Profile profile = new Profile("Jackson", "Databind");
User user = new User(new Timestamped<>(profile, 1));
User deserializedUser = mapper.readValue(
mapper.writerFor(User.class).writeValueAsString(user),
User.class
);
Timestamped<Profile> td = deserializedUser.getProfile();
Profile deserializedProfile = td.getValue();
assertEquals(profile, deserializedProfile);
assertEquals(user, deserializedUser);
}
}