DefaultTypingWithObjectId2780Test.java
package tools.jackson.databind.objectid;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import tools.jackson.databind.*;
import tools.jackson.databind.testutil.DatabindTestUtil;
import tools.jackson.databind.testutil.NoCheckSubTypeValidator;
import static org.junit.jupiter.api.Assertions.*;
/**
* Test for [databind#2780]: Deserialization with Default Typing (PTH) and
* {@code @JsonIdentityInfo} in untyped collections.
*<p>
* When a container object appears before its referenced object in an untyped
* collection, the referenced object is embedded inline in the container with
* full type info. Any later occurrence of that object in the collection is
* serialized as a bare object-id back-reference without type info. On
* deserialization Jackson cannot resolve the bare id back to the original
* object and instead produces an {@code Integer}.
*/
public class DefaultTypingWithObjectId2780Test extends DatabindTestUtil
{
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
static class User {
public int id;
public String login;
public User() {}
public User(int id, String login) {
this.id = id;
this.login = login;
}
}
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
static class UserContainer {
public int id;
public User user;
public UserContainer() {}
public UserContainer(int id, User user) {
this.id = id;
this.user = user;
}
}
private ObjectMapper defaultTypingMapper() {
return jsonMapperBuilder()
.activateDefaultTyping(NoCheckSubTypeValidator.instance, DefaultTyping.NON_FINAL)
.build();
}
// User appears first: serialized with full type/field info.
// Container appears second: its "user" field is a bare id back-reference.
// Deserialization resolves that reference correctly -- this case works fine.
@Test
public void testUserFirstThenContainer() throws Exception
{
ObjectMapper mapper = defaultTypingMapper();
User user = new User(42, "cool_man");
UserContainer container = new UserContainer(1, user);
List<Object> list = new ArrayList<>();
list.add(user);
list.add(container);
String json = mapper.writeValueAsString(list);
List<?> result = mapper.readValue(json, List.class);
assertEquals(2, result.size());
assertInstanceOf(User.class, result.get(0), "First element should be User");
assertInstanceOf(UserContainer.class, result.get(1), "Second element should be UserContainer");
User resultUser = (User) result.get(0);
UserContainer resultContainer = (UserContainer) result.get(1);
assertEquals(42, resultUser.id);
assertEquals("cool_man", resultUser.login);
assertSame(resultUser, resultContainer.user,
"Back-reference in container should point to the same User instance");
}
// Container appears first: User is embedded inline inside it.
// User then appears second in the list as a bare object-id (e.g. just 42) with no
// type wrapper, because the serializer already emitted the full object above.
// On deserialization the bare id lacks type info and is read as Integer instead
// of being resolved to the User -- this is the bug described in #2780.
@Test
public void testContainerFirstThenUser() throws Exception
{
ObjectMapper mapper = defaultTypingMapper();
User user = new User(42, "cool_man");
UserContainer container = new UserContainer(1, user);
List<Object> list = new ArrayList<>();
list.add(container);
list.add(user);
String json = mapper.writeValueAsString(list);
List<?> result = mapper.readValue(json, List.class);
assertEquals(2, result.size());
assertInstanceOf(UserContainer.class, result.get(0),
"First element should be UserContainer");
assertInstanceOf(User.class, result.get(1),
"Second element should be User (not Integer) -- bare id back-reference must be resolved with type info");
UserContainer resultContainer = (UserContainer) result.get(0);
User resultUser = (User) result.get(1);
assertEquals(42, resultUser.id);
assertEquals("cool_man", resultUser.login);
assertSame(resultUser, resultContainer.user,
"Container's user field and the list's second element should be the same instance");
}
}