package com.fasterxml.jackson.databind.struct; import java.util.*; import com.fasterxml.jackson.annotation.*; import com.fasterxml.jackson.annotation.JsonTypeInfo.Id; import com.fasterxml.jackson.databind.*; public class TestParentChildReferences extends BaseMapTest { /* /********************************************************** /* Test classes /********************************************************** */ /** * First, a simple 'tree': just parent/child linkage */ static class SimpleTreeNode { public String name; // Reference back to parent; reference, ignored during ser, // re-constructed during deser @JsonBackReference public SimpleTreeNode parent; // Reference that is serialized normally during ser, back // reference within pointed-to instance assigned to point to // referring bean ("this") @JsonManagedReference public SimpleTreeNode child; public SimpleTreeNode() { this(null); } public SimpleTreeNode(String n) { name = n; } } static class SimpleTreeNode2 { public String name; protected SimpleTreeNode2 parent; protected SimpleTreeNode2 child; public SimpleTreeNode2() { this(null); } public SimpleTreeNode2(String n) { name = n; } @JsonBackReference public SimpleTreeNode2 getParent() { return parent; } public void setParent(SimpleTreeNode2 p) { parent = p; } @JsonManagedReference public SimpleTreeNode2 getChild() { return child; } public void setChild(SimpleTreeNode2 c) { child = c; } } /** * Then nodes with two separate linkages; parent/child * and prev/next-sibling */ static class FullTreeNode { public String name; // parent-child links @JsonBackReference("parent") public FullTreeNode parent; @JsonManagedReference("parent") public FullTreeNode firstChild; // sibling-links @JsonManagedReference("sibling") public FullTreeNode next; @JsonBackReference("sibling") protected FullTreeNode prev; public FullTreeNode() { this(null); } public FullTreeNode(String name) { this.name = name; } } /** * Class for testing managed references via arrays */ static class NodeArray { @JsonManagedReference("arr") public ArrayNode[] nodes; } static class ArrayNode { public String name; @JsonBackReference("arr") public NodeArray parent; public ArrayNode() { this(null); } public ArrayNode(String n) { name = n; } } /** * Class for testing managed references via Collections */ static class NodeList { @JsonManagedReference public List<NodeForList> nodes; } static class NodeForList { public String name; @JsonBackReference public NodeList parent; public NodeForList() { this(null); } public NodeForList(String n) { name = n; } } static class NodeMap { @JsonManagedReference public Map<String,NodeForMap> nodes; } static class NodeForMap { public String name; @JsonBackReference public NodeMap parent; public NodeForMap() { this(null); } public NodeForMap(String n) { name = n; } } public static class Parent { @JsonManagedReference protected final List<Child> children = new ArrayList<Child>(); public List<Child> getChildren() { return children; } public void addChild(Child child) { children.add(child); child.setParent(this); } } public static class Child { protected Parent parent; protected final String value; // So that the bean is not empty of properties public Child(@JsonProperty("value") String value) { this.value = value; } public String getValue() { return value; } @JsonBackReference public Parent getParent() { return parent; } public void setParent(Parent parent) { this.parent = parent; } } @JsonTypeInfo(use=Id.NAME) @JsonSubTypes({@JsonSubTypes.Type(ConcreteNode.class)}) static abstract class AbstractNode { public String id; @JsonManagedReference public AbstractNode next; @JsonBackReference public AbstractNode prev; } @JsonTypeName("concrete") static class ConcreteNode extends AbstractNode { public ConcreteNode() { } public ConcreteNode(String id) { this.id = id; } } // [JACKSON-708] static class Model708 { } static class Advertisement708 extends Model708 { public String title; @JsonManagedReference public List<Photo708> photos; } static class Photo708 extends Model708 { public int id; @JsonBackReference public Advertisement708 advertisement; } /* /********************************************************** /* Unit tests /********************************************************** */ private final ObjectMapper MAPPER = objectMapper(); public void testSimpleRefs() throws Exception { SimpleTreeNode root = new SimpleTreeNode("root"); SimpleTreeNode child = new SimpleTreeNode("kid"); root.child = child; child.parent = root; String json = MAPPER.writeValueAsString(root); SimpleTreeNode resultNode = MAPPER.readValue(json, SimpleTreeNode.class); assertEquals("root", resultNode.name); SimpleTreeNode resultChild = resultNode.child; assertNotNull(resultChild); assertEquals("kid", resultChild.name); assertSame(resultChild.parent, resultNode); } // [JACKSON-693] public void testSimpleRefsWithGetter() throws Exception { SimpleTreeNode2 root = new SimpleTreeNode2("root"); SimpleTreeNode2 child = new SimpleTreeNode2("kid"); root.child = child; child.parent = root; String json = MAPPER.writeValueAsString(root); SimpleTreeNode2 resultNode = MAPPER.readValue(json, SimpleTreeNode2.class); assertEquals("root", resultNode.name); SimpleTreeNode2 resultChild = resultNode.child; assertNotNull(resultChild); assertEquals("kid", resultChild.name); assertSame(resultChild.parent, resultNode); } public void testFullRefs() throws Exception { FullTreeNode root = new FullTreeNode("root"); FullTreeNode child1 = new FullTreeNode("kid1"); FullTreeNode child2 = new FullTreeNode("kid2"); root.firstChild = child1; child1.parent = root; child1.next = child2; child2.prev = child1; String json = MAPPER.writeValueAsString(root); FullTreeNode resultNode = MAPPER.readValue(json, FullTreeNode.class); assertEquals("root", resultNode.name); FullTreeNode resultChild = resultNode.firstChild; assertNotNull(resultChild); assertEquals("kid1", resultChild.name); assertSame(resultChild.parent, resultNode); // and then sibling linkage assertNull(resultChild.prev); FullTreeNode resultChild2 = resultChild.next; assertNotNull(resultChild2); assertEquals("kid2", resultChild2.name); assertSame(resultChild, resultChild2.prev); assertNull(resultChild2.next); } public void testArrayOfRefs() throws Exception { NodeArray root = new NodeArray(); ArrayNode node1 = new ArrayNode("a"); ArrayNode node2 = new ArrayNode("b"); root.nodes = new ArrayNode[] { node1, node2 }; String json = MAPPER.writeValueAsString(root); NodeArray result = MAPPER.readValue(json, NodeArray.class); ArrayNode[] kids = result.nodes; assertNotNull(kids); assertEquals(2, kids.length); assertEquals("a", kids[0].name); assertEquals("b", kids[1].name); assertSame(result, kids[0].parent); assertSame(result, kids[1].parent); } public void testListOfRefs() throws Exception { NodeList root = new NodeList(); NodeForList node1 = new NodeForList("a"); NodeForList node2 = new NodeForList("b"); root.nodes = Arrays.asList(node1, node2); String json = MAPPER.writeValueAsString(root); NodeList result = MAPPER.readValue(json, NodeList.class); List<NodeForList> kids = result.nodes; assertNotNull(kids); assertEquals(2, kids.size()); assertEquals("a", kids.get(0).name); assertEquals("b", kids.get(1).name); assertSame(result, kids.get(0).parent); assertSame(result, kids.get(1).parent); } public void testMapOfRefs() throws Exception { NodeMap root = new NodeMap(); NodeForMap node1 = new NodeForMap("a"); NodeForMap node2 = new NodeForMap("b"); Map<String,NodeForMap> nodes = new HashMap<String, NodeForMap>(); nodes.put("a1", node1); nodes.put("b2", node2); root.nodes = nodes; String json = MAPPER.writeValueAsString(root); NodeMap result = MAPPER.readValue(json, NodeMap.class); Map<String,NodeForMap> kids = result.nodes; assertNotNull(kids); assertEquals(2, kids.size()); assertNotNull(kids.get("a1")); assertNotNull(kids.get("b2")); assertEquals("a", kids.get("a1").name); assertEquals("b", kids.get("b2").name); assertSame(result, kids.get("a1").parent); assertSame(result, kids.get("b2").parent); } // for [JACKSON-368] public void testAbstract368() throws Exception { AbstractNode parent = new ConcreteNode("p"); AbstractNode child = new ConcreteNode("c"); parent.next = child; child.prev = parent; // serialization ought to be ok String json = MAPPER.writeValueAsString(parent); AbstractNode root = MAPPER.readValue(json, AbstractNode.class); assertEquals(ConcreteNode.class, root.getClass()); assertEquals("p", root.id); assertNull(root.prev); AbstractNode leaf = root.next; assertNotNull(leaf); assertEquals("c", leaf.id); assertSame(root, leaf.prev); } public void testIssue693() throws Exception { Parent parent = new Parent(); parent.addChild(new Child("foo")); parent.addChild(new Child("bar")); byte[] bytes = MAPPER.writeValueAsBytes(parent); Parent value = MAPPER.readValue(bytes, Parent.class); for (Child child : value.children) { assertEquals(value, child.getParent()); } } public void testIssue708() throws Exception { Advertisement708 ad = MAPPER.readValue("{\"title\":\"Hroch\",\"photos\":[{\"id\":3}]}", Advertisement708.class); assertNotNull(ad); } }