ObjectIdWithCreatorTest.java
package tools.jackson.databind.objectid;
import java.util.*;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.*;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.*;
import tools.jackson.databind.annotation.JsonDeserialize;
import tools.jackson.databind.annotation.JsonSerialize;
import tools.jackson.databind.testutil.DatabindTestUtil;
import static org.junit.jupiter.api.Assertions.*;
/**
* Unit tests for Object Id handling in conjunction with {@code @JsonCreator}.
*/
public class ObjectIdWithCreatorTest extends DatabindTestUtil
{
// // // [databind#687]: PropertyGenerator + @JsonCreator
@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="label")
static class ReferredWithCreator {
public String label;
@JsonCreator
ReferredWithCreator(@JsonProperty("label") String label) {
this.label = label;
}
}
@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="label")
static class ReferringToObjWithCreator {
public String label = "test1";
public List<ReferredWithCreator> refs = new ArrayList<>();
public void addRef(ReferredWithCreator r) { refs.add(r); }
}
@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="label")
static class EnclosingForRefsWithCreator {
public String label = "enclosing1";
public ReferredWithCreator baseRef;
public ReferringToObjWithCreator nextRef;
}
// // // [databind#687]: PropertyGenerator without @JsonCreator
@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="label")
static class ReferredWithNoCreator {
public String label = "label2";
}
@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="label")
static class ReferringToObjWithNoCreator {
public String label = "test2";
public List<ReferredWithNoCreator> refs = new ArrayList<>();
public void addRef(ReferredWithNoCreator r) { refs.add(r); }
}
@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="label")
static class EnclosingForRefWithNoCreator {
public String label = "enclosing2";
public ReferredWithNoCreator baseRef;
public ReferringToObjWithNoCreator nextRef;
}
// // // [databind#1261]: IntSequenceGenerator + @JsonCreator with complex nesting
static class Answer {
public SortedMap<String, Parent> parents;
public Answer() { parents = new TreeMap<>(); }
}
@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id")
static class Parent {
public Map<String, Child> children;
@JsonIdentityReference(alwaysAsId=true)
public Child favoriteChild;
public String name;
protected Parent() { }
protected Parent(String name, boolean ignored) {
children = new TreeMap<>();
this.name = name;
}
}
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id")
static class Child {
public String name;
@JsonIdentityReference(alwaysAsId=true)
public Parent parent;
@JsonIdentityReference(alwaysAsId=true)
public List<Parent> parentAsList;
public String someNullProperty;
protected Child() { }
@JsonCreator
public Child(@JsonProperty("name") String name,
@JsonProperty("someNullProperty") String someNullProperty) {
this.name = name;
this.someNullProperty = someNullProperty;
}
public Child(String n) { name = n; }
}
// // // [databind#2944]: PropertyGenerator + @JsonCreator, verify setter not called
@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
static class JsonBean2944 {
String _id, _value;
String _setterId;
@JsonCreator
public JsonBean2944(@JsonProperty("id") String id, @JsonProperty("value") String value) {
_id = id;
_value = value;
}
public void setId(String v) { _setterId = v; }
}
// // // [databind#3185]: PropertyGenerator + @JsonCreator must not overwrite
// // // final field after construction
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
static class Pojo3185 {
private final String fieldForId;
@JsonCreator
public Pojo3185(@JsonProperty("id") String fieldForId) {
this.fieldForId = fieldForId + "-from-constructor";
}
@JsonGetter("id")
public String getFieldForId() {
return fieldForId;
}
}
// // // [databind#3030]: Forward reference with @JsonCreator
static class ContainerABC3030 {
public List<RefTarget3030> bs;
public List<RefSource3030> cs;
}
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
static class RefTarget3030 {
public String id;
}
static class RefSource3030 {
private RefTarget3030 b;
@JsonCreator
public RefSource3030(@JsonProperty("b") RefTarget3030 b) {
this.b = b;
}
@JsonGetter("b")
public RefTarget3030 getB() {
return b;
}
}
// // // [databind#1706]: @JsonCreator(mode=DELEGATING) with @JsonIdentityInfo
@JsonSerialize(as = ImmutableItem1706.class)
@JsonDeserialize(as = ImmutableItem1706.class)
@JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class)
interface Item1706 {
Integer getId();
String getName();
}
static class ImmutableItem1706 implements Item1706 {
private final Integer id;
private final String name;
ImmutableItem1706(Integer id, String name) {
this.id = id;
this.name = name;
}
@Override
public Integer getId() { return id; }
@Override
public String getName() { return name; }
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
static ImmutableItem1706 fromJson(MutableItem1706 json) {
return new ImmutableItem1706(json.id, json.name);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ImmutableItem1706 that = (ImmutableItem1706) o;
return id.equals(that.id) && name.equals(that.name);
}
@Override
public int hashCode() {
return 31 * id.hashCode() + name.hashCode();
}
}
@JsonDeserialize
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE)
static class MutableItem1706 implements Item1706 {
private String name;
private Integer id;
public void setId(Integer id) { this.id = id; }
public void setName(String name) { this.name = name; }
@Override
public String getName() { throw new UnsupportedOperationException(); }
@Override
public Integer getId() { throw new UnsupportedOperationException(); }
}
// // // [databind#639]: ObjectId with @JacksonInject
@JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class)
public static final class InjectParent639 {
@JsonProperty
public InjectChild639 child;
@JsonCreator
public InjectParent639(@JacksonInject("context") String context) {
}
}
@JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class)
public static final class InjectChild639 {
@JsonProperty
private final InjectParent639 parent;
@JsonCreator
public InjectChild639(@JsonProperty("parent") InjectParent639 parent) {
this.parent = parent;
}
}
/*
/**********************************************************
/* Unit tests, PropertyGenerator + @JsonCreator [databind#687]
/**********************************************************
*/
private final ObjectMapper MAPPER = newJsonMapper();
@Test
public void testSerializeDeserializeWithCreator() throws Exception {
ReferredWithCreator base = new ReferredWithCreator("label1");
ReferringToObjWithCreator r = new ReferringToObjWithCreator();
r.addRef(base);
EnclosingForRefsWithCreator e = new EnclosingForRefsWithCreator();
e.baseRef = base;
e.nextRef = r;
String json = MAPPER.writeValueAsString(e);
EnclosingForRefsWithCreator result = MAPPER.readValue(json, EnclosingForRefsWithCreator.class);
assertNotNull(result);
assertEquals(result.label, e.label);
// also, compare by re-serializing:
assertEquals(json, MAPPER.writeValueAsString(result));
}
@Test
public void testSerializeDeserializeNoCreator() throws Exception {
ReferredWithNoCreator base = new ReferredWithNoCreator();
ReferringToObjWithNoCreator r = new ReferringToObjWithNoCreator();
r.addRef(base);
EnclosingForRefWithNoCreator e = new EnclosingForRefWithNoCreator();
e.baseRef = base;
e.nextRef = r;
String json = MAPPER.writeValueAsString(e);
EnclosingForRefWithNoCreator result = MAPPER.readValue(json, EnclosingForRefWithNoCreator.class);
assertNotNull(result);
assertEquals(result.label, e.label);
// also, compare by re-serializing:
assertEquals(json, MAPPER.writeValueAsString(result));
}
/*
/**********************************************************
/* Unit tests, IntSequenceGenerator + @JsonCreator [databind#1261]
/**********************************************************
*/
@Test
public void testObjectIds1261() throws Exception {
ObjectMapper mapper = jsonMapperBuilder()
.enable(SerializationFeature.INDENT_OUTPUT)
.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY)
.build();
Answer initialAnswer = _createInitialAnswer();
String initialAnswerString = mapper.writeValueAsString(initialAnswer);
JsonNode tree = mapper.readTree(initialAnswerString);
Answer deserializedAnswer = mapper.readValue(initialAnswerString, Answer.class);
String reserializedAnswerString = mapper.writeValueAsString(deserializedAnswer);
JsonNode newTree = mapper.readTree(reserializedAnswerString);
if (!tree.equals(newTree)) {
fail("Original and recovered Json are different. Recovered = \n"
+ reserializedAnswerString + "\n");
}
}
private Answer _createInitialAnswer() {
Answer answer = new Answer();
String child1Name = "child1";
String child2Name = "child2";
String parent1Name = "parent1";
String parent2Name = "parent2";
Parent parent1 = new Parent(parent1Name, false);
answer.parents.put(parent1Name, parent1);
Child child1 = new Child(child1Name);
child1.parent = parent1;
child1.parentAsList = Collections.singletonList(parent1);
Child child2 = new Child(child2Name);
Parent parent2 = new Parent(parent2Name, false);
child2.parent = parent2;
child2.parentAsList = Collections.singletonList(parent2);
parent1.children.put(child1Name, child1);
parent1.children.put(child2Name, child2);
answer.parents.put(parent2Name, parent2);
return answer;
}
/*
/**********************************************************
/* Unit tests, PropertyGenerator + @JsonCreator [databind#2944]
/**********************************************************
*/
// [databind#2944]
@Test
public void testObjectIdWithCreator() throws Exception {
JsonBean2944 result = MAPPER.readValue(a2q("{'id': 'myId','value': 'myValue'}"),
JsonBean2944.class);
assertNotNull(result);
assertEquals("myId", result._id,
"Incorrect creator-passed-id (setter id: ["+result._setterId+"])");
}
/*
/**********************************************************
/* Unit tests, PropertyGenerator + @JsonCreator [databind#3185]
/**********************************************************
*/
// [databind#3185]: value computed in creator must be preserved; id field
// must NOT be written to after construction (breaks final fields + GraalVM
// native image). Fixed as a side-effect of [databind#5238].
@Test
public void testCreatorValuePreservedWithIdentityInfo3185() throws Exception
{
Pojo3185 result = MAPPER.readValue(
a2q("{'id': 'valueFromJson'}"), Pojo3185.class);
assertEquals("valueFromJson-from-constructor", result.getFieldForId());
}
/*
/**********************************************************
/* Unit tests, Forward reference with @JsonCreator [databind#3030]
/**********************************************************
*/
// No forward reference: Bs comes before Cs in JSON
@Test
public void testNoForwardReferenceWithCreator3030() throws Exception
{
String json = "{\"bs\":[{\"id\":\"b1\"},{\"id\":\"b2\"}],\"cs\":[{\"b\":\"b1\"},{\"b\":\"b2\"}]}";
ContainerABC3030 result = MAPPER.readValue(json, ContainerABC3030.class);
assertNotNull(result);
assertEquals(2, result.bs.size());
assertEquals(2, result.cs.size());
assertEquals("b1", result.bs.get(0).id);
assertEquals("b2", result.bs.get(1).id);
assertSame(result.bs.get(0), result.cs.get(0).getB());
assertSame(result.bs.get(1), result.cs.get(1).getB());
}
// [databind#3030] Forward reference WITH @JsonCreator: cs comes before bs
@Test
public void testForwardReferenceWithCreator3030() throws Exception
{
String json = "{\"cs\":[{\"b\":\"b1\"},{\"b\":\"b2\"}],\"bs\":[{\"id\":\"b1\"},{\"id\":\"b2\"}]}";
ContainerABC3030 result = MAPPER.readValue(json, ContainerABC3030.class);
assertNotNull(result);
assertEquals(2, result.bs.size());
assertEquals(2, result.cs.size());
assertSame(result.bs.get(0), result.cs.get(0).getB());
assertSame(result.bs.get(1), result.cs.get(1).getB());
}
/*
/**********************************************************
/* Unit tests, @JsonCreator(mode=DELEGATING) [databind#1706]
/**********************************************************
*/
// [databind#1706]
@Test
public void testObjectIdWithDelegatingCreator() throws Exception
{
ImmutableItem1706 item = new ImmutableItem1706(1, "test");
String json = MAPPER.writeValueAsString(Arrays.asList(item, item));
List<Item1706> result = MAPPER.readValue(json, new TypeReference<List<Item1706>>() {});
assertEquals(2, result.size());
assertInstanceOf(ImmutableItem1706.class, result.get(0),
"First item should be ImmutableItem1706");
assertInstanceOf(ImmutableItem1706.class, result.get(1),
"Second item (back-reference) should be ImmutableItem1706, not intermediate MutableItem1706");
assertEquals(result.get(0), result.get(1));
}
/*
/**********************************************************
/* Unit tests, ObjectId with @JacksonInject [databind#639]
/**********************************************************
*/
// [databind#639]
@Test
public void testObjectIdWithInjectable639() throws Exception
{
ObjectMapper mapper = jsonMapperBuilder()
.injectableValues(new InjectableValues.Std().
addValue("context", "Stuff"))
.build();
InjectParent639 parent = new InjectParent639("foo");
InjectChild639 child = new InjectChild639(parent);
parent.child = child;
String json = mapper.writeValueAsString(parent);
parent = mapper.readValue(json, InjectParent639.class);
assertNotNull(parent);
assertNotNull(parent.child);
}
}