ParentChildReferencesTest.java

package tools.jackson.databind.struct;

import java.beans.ConstructorProperties;
import java.io.IOException;
import java.util.*;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;

import tools.jackson.databind.*;
import tools.jackson.databind.annotation.JsonDeserialize;
import tools.jackson.databind.annotation.JsonPOJOBuilder;
import tools.jackson.databind.exc.InvalidDefinitionException;
import tools.jackson.databind.testutil.DatabindTestUtil;

import static org.junit.jupiter.api.Assertions.*;

// Unit tests for parent-child references (@JsonManagedReference / @JsonBackReference)
public class ParentChildReferencesTest
    extends DatabindTestUtil
{
    /*
    /**********************************************************
    /* Test classes, simple tree (TestParentChildReferences)
    /**********************************************************
     */

    /**
     * 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; }
    }

    // [databind#1878]
    static class Child1878 {
        @JsonBackReference
        public Parent1878 b;
    }

    static class Parent1878 {
        @JsonManagedReference
        public Child1878 a;
    }

    // Forward reference with @JsonIdentityInfo
    @JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include= JsonTypeInfo.As.PROPERTY)
    static class ForwardReferenceContainerClass
    {
        public ForwardReferenceClass frc;
        public YetAnotherClass2 yac;
        public String id;
    }

    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
    @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
    @JsonSubTypes({
            @JsonSubTypes.Type(value = ForwardReferenceClassOne.class, name = "One"),
            @JsonSubTypes.Type(value = ForwardReferenceClassTwo.class, name = "Two")})
    static abstract class ForwardReferenceClass
    {
        public String id;
        public void setId(String id) {
            this.id = id;
        }
    }

    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
    static class YetAnotherClass2
    {
        public YetAnotherClass2() {}
        public ForwardReferenceClass frc;
        public String id;
    }

    static class ForwardReferenceClassOne extends ForwardReferenceClass { }

    static class ForwardReferenceClassTwo extends ForwardReferenceClass { }

    // [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;
    }

    /*
    /**********************************************************
    /* Test classes, misc back-reference edge cases (BackReferenceMiscTest)
    /**********************************************************
     */

    // [JACKSON-890]

    @JsonPropertyOrder(alphabetic=true)
    interface Entity
    {
        @JsonIgnore String getEntityType();
        Long getId();
        void setId(Long id);
        @JsonIgnore void setPersistable();
    }

    @JsonDeserialize(as = NestedPropertySheetImpl.class)
    interface NestedPropertySheet
        extends Property<PropertySheet>
    {
        @Override PropertySheet getValue();
        void setValue(PropertySheet propertySheet);
    }

    @JsonDeserialize(as = AbstractProperty.class)
    @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS,
        include  = JsonTypeInfo.As.PROPERTY,
        property = "@class")
    interface Property<T> extends Entity
    {
        String getName();
        PropertySheet getParentSheet();
        T getValue();
        void setName(String name);
        void setParentSheet(PropertySheet parentSheet);
    }

    @JsonDeserialize(as = PropertySheetImpl.class)
    @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS,
        include  = JsonTypeInfo.As.PROPERTY,
        property = "@class")
    @SuppressWarnings("rawtypes")
    interface PropertySheet extends Entity
    {
        void addProperty(Property property);
        Map<String, Property> getProperties();
        void setProperties(Map<String, Property> properties);
    }

    @JsonDeserialize(as = StringPropertyImpl.class)
    interface StringProperty
        extends Property<String>
    {
        @Override String getValue();
        void setValue(String value);
    }

    static class AbstractEntity implements Entity
    {
        private long id;

        @Override public String getEntityType() {
            return "";
        }

        @Override public Long getId() {
            return id;
        }

        @Override public void setId(Long id)
        {
            this.id = id;
        }

        @Override public void setPersistable() { }
    }

    abstract static class AbstractProperty<T>
        extends AbstractEntity
        implements Property<T>
    {
        private String        m_name;
        private PropertySheet m_parentSheet;

        protected AbstractProperty() { }

        protected AbstractProperty(String name) {
            m_name = name;
        }

        @Override public String getName() {
            return m_name;
        }

        @JsonBackReference("propertySheet-properties")
        @Override public PropertySheet getParentSheet() {
            return m_parentSheet;
        }

        @Override public void setName(String name) {
            m_name = name;
        }

        @Override public void setParentSheet(PropertySheet parentSheet) {
            m_parentSheet = parentSheet;
        }
    }

    @JsonPropertyOrder(alphabetic=true)
    static class NestedPropertySheetImpl
        extends AbstractProperty<PropertySheet>
        implements NestedPropertySheet
    {
        private PropertySheet m_propertySheet;

        protected NestedPropertySheetImpl(String name,
                PropertySheet propertySheet)
        {
            super(name);
            m_propertySheet = propertySheet;
        }

        NestedPropertySheetImpl() { }

        @Override public PropertySheet getValue() {
            return m_propertySheet;
        }

        @Override public void setValue(PropertySheet propertySheet) {
            m_propertySheet = propertySheet;
        }
    }

    @SuppressWarnings("rawtypes")
    static class PropertySheetImpl
        extends AbstractEntity
        implements PropertySheet
    {
        private Map<String, Property> m_properties;

        @Override public void addProperty(Property property)
        {
            if (m_properties == null) {
                m_properties = new TreeMap<String, Property>();
            }
            property.setParentSheet(this);
            m_properties.put(property.getName(), property);
        }

        @JsonDeserialize(as = TreeMap.class,
            keyAs     = String.class,
            contentAs = Property.class)
        @JsonManagedReference("propertySheet-properties")
        @Override public Map<String, Property> getProperties() {
            return m_properties;
        }

        @Override public void setProperties(Map<String, Property> properties) {
            m_properties = properties;
        }
    }

    static class StringPropertyImpl
        extends AbstractProperty<String>
        implements StringProperty
    {
        private String m_value;

        protected StringPropertyImpl(String name, String value) {
            super(name);
            m_value = value;
        }

        StringPropertyImpl() { }

        @Override public String getValue() {
            return m_value;
        }

        @Override public void setValue(String value) {
            m_value = value;
        }
    }

    static class YetAnotherClass extends StringPropertyImpl { }

    // [databind#3304]
    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
            include = JsonTypeInfo.As.PROPERTY,
            property = "type")
    @JsonSubTypes({
        @JsonSubTypes.Type(value = SimpleChild3304.class, name = "SIMPLE"),
        @JsonSubTypes.Type(value = RefChild3304.class, name = "REF")
    })
    interface IChild3304 { }

    static class SimpleChild3304 implements IChild3304 {
        public String value;

        public SimpleChild3304() { }
        public SimpleChild3304(String v) { value = v; }
    }

    static class RefChild3304 implements IChild3304 {
        public String data;

        @JsonBackReference
        public Container3304 parent;

        public RefChild3304() { }
        public RefChild3304(String d) { data = d; }
    }

    static class Container3304 {
        @JsonManagedReference
        public IChild3304[] children;
    }

    static class ContainerWithList3304 {
        @JsonManagedReference
        public java.util.List<IChild3304> children;
    }

    // [databind#1516]
    static class ParentWithCreator
    {
        String id, name;

        @JsonManagedReference
        ChildObject1 child;

        @ConstructorProperties({"id", "name", "child"})
        public ParentWithCreator(String id, String name, ChildObject1 child) {
            this.id = id;
            this.name = name;
            this.child = child;
        }
    }

    static class ChildObject1
    {
        public String id, name;

        @JsonBackReference
        public ParentWithCreator parent;

        @ConstructorProperties({"id", "name", "parent"})
        public ChildObject1(String id, String name, ParentWithCreator parent) {
            this.id = id;
            this.name = name;
            this.parent = parent;
        }
    }

    static class ParentWithoutCreator {
        public String id, name;

        @JsonManagedReference
        public ChildObject2 child;
    }

    static class ChildObject2 {
        public String id, name;

        @JsonBackReference
        public ParentWithoutCreator parent;

        @ConstructorProperties({"id", "name", "parent"})
        public ChildObject2(String id, String name,
                            ParentWithoutCreator parent) {
            this.id = id;
            this.name = name;
            this.parent = parent;
        }
    }

    // [Kotlin#129]
    static class Car {
        private final long id;

        @JsonManagedReference
        private final List<Color> colors;

        @JsonCreator
        public Car(@JsonProperty("id") long id,
                   @JsonProperty("colors") List<Color> colors) {
            this.id = id;
            this.colors = colors != null ? colors : new ArrayList<>();
        }

        public long getId() { return id; }
        public List<Color> getColors() { return colors; }
    }

    static class Color {
        private final long id;
        private final String code;

        @JsonBackReference
        private Car car;

        @JsonCreator
        public Color(@JsonProperty("id") long id,
                     @JsonProperty("code") String code) {
            this.id = id;
            this.code = code;
        }

        public long getId() { return id; }
        public String getCode() { return code; }
        public Car getCar() { return car; }
        public void setCar(Car car) { this.car = car; }
    }

    // [databind#2686]
    public static class Container2686 {
        Content2686 forward;

        String containerValue;

        @JsonManagedReference
        public Content2686 getForward() {
            return forward;
        }

        @JsonManagedReference
        public void setForward(Content2686 forward) {
            this.forward = forward;
        }

        public String getContainerValue() {
            return containerValue;
        }

        public void setContainerValue(String containerValue) {
            this.containerValue = containerValue;
        }
    }

    @JsonDeserialize(builder = Content2686.Builder.class)
    public static class Content2686 {
        private Container2686 back;

        private String contentValue;

        public Content2686(Container2686 back, String contentValue) {
            this.back = back;
            this.contentValue = contentValue;
        }

        public String getContentValue() {
            return contentValue;
        }

        @JsonBackReference
        public Container2686 getBack() {
            return back;
        }

        @JsonPOJOBuilder(withPrefix = "")
        public static class Builder {
            private Container2686 back;
            private String contentValue;

            @JsonBackReference
            Builder back(Container2686 b) {
                this.back = b;
                return this;
            }

            Builder contentValue(String cv) {
                this.contentValue = cv;
                return this;
            }

            Content2686 build() {
                return new Content2686(back, contentValue);
            }
        }
    }

    /*
    /**********************************************************
    /* Unit tests (TestParentChildReferences)
    /**********************************************************
     */

    private final ObjectMapper MAPPER = newJsonMapper();

    @Test
    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]
    @Test
    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);
    }

    @Test
    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);
    }

    @Test
    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);
    }

    @Test
    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);
    }

    @Test
    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]
    @Test
    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);
    }

    @Test
    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());
        }
    }

    @Test
    public void testIssue708() throws Exception
    {
        Advertisement708 ad = MAPPER.readValue("{\"title\":\"Hroch\",\"photos\":[{\"id\":3}]}", Advertisement708.class);
        assertNotNull(ad);
    }

    // [databind#1878]
    @Test
    public void testChildDeserialization() throws Exception {
        Child1878 child = MAPPER.readValue("{\"b\": {}}", Child1878.class);
        assertNotNull(child.b);
    }

    // Forward reference with @JsonIdentityInfo
    @Test
    public void testForwardRef() throws IOException {
        ObjectMapper mapper = jsonMapperBuilder()
                .changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(JsonInclude.Include.NON_NULL))
                .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
                .enable(SerializationFeature.INDENT_OUTPUT)
                .build();
        final String outerTypeName = getClass().getSimpleName();
        mapper.readValue("{" +
                "  \"@type\" : \"" + outerTypeName + "$ForwardReferenceContainerClass\"," +
                "  \"frc\" : \"willBeForwardReferenced\"," +
                "  \"yac\" : {" +
                "    \"@type\" : \"" + outerTypeName + "$YetAnotherClass2\"," +
                "    \"frc\" : {" +
                "      \"@type\" : \"One\"," +
                "      \"id\" : \"willBeForwardReferenced\"" +
                "    }," +
                "    \"id\" : \"anId\"" +
                "  }," +
                "  \"id\" : \"ForwardReferenceContainerClass1\"" +
                "}", ForwardReferenceContainerClass.class);
    }

    /*
    /**********************************************************
    /* Unit tests, misc back-reference edge cases (BackReferenceMiscTest)
    /**********************************************************
     */

    // NOTE: order is arbitrary, test is fragile... has to work for now
    private final String CLASS_NAME = getClass().getName();
    private final String JSON =
        "{\"@class\":\""+CLASS_NAME+"$PropertySheetImpl\",\"id\":0,\"properties\":{\"p1name\":{\"@class\":"
            +"\"" +CLASS_NAME+ "$StringPropertyImpl\",\"id\":0,\"name\":\"p1name\",\"value\":\"p1value\"},"
            +"\"p2name\":{\"@class\":\""+CLASS_NAME+"$StringPropertyImpl\",\"id\":0,"
            +"\"name\":\"p2name\",\"value\":\"p2value\"}}}";

    @Test
    public void testDeserialize() throws IOException
    {
        PropertySheet input = MAPPER.readValue(JSON, PropertySheet.class);
        assertEquals(JSON, MAPPER.writeValueAsString(input));
    }

    @Test
    public void testSerialize() throws IOException
    {
        PropertySheet sheet = new PropertySheetImpl();

        sheet.addProperty(new StringPropertyImpl("p1name", "p1value"));
        sheet.addProperty(new StringPropertyImpl("p2name", "p2value"));
        String actual = MAPPER.writeValueAsString(sheet);
        assertEquals(JSON, actual);
    }

    // [databind#3304]: Verify error message mentions the abstract type issue
    @Test
    public void testBackRefOnSubtypeOnly_array() {
        InvalidDefinitionException ex = assertThrows(
                InvalidDefinitionException.class,
                () -> MAPPER.readValue(
                        "{\"children\":[{\"type\":\"SIMPLE\",\"value\":\"x\"}]}",
                        Container3304.class));
        String msg = ex.getMessage();
        assertNotNull(msg);
        assertTrue(msg.contains("abstract"),
                "Error message should mention abstract type issue, got: " + msg);
        assertTrue(msg.contains("@JsonBackReference"),
                "Error message should mention @JsonBackReference, got: " + msg);
    }

    // [databind#3304]
    @Test
    public void testBackRefOnSubtypeOnly_list() {
        InvalidDefinitionException ex = assertThrows(
                InvalidDefinitionException.class,
                () -> MAPPER.readValue(
                        "{\"children\":[{\"type\":\"SIMPLE\",\"value\":\"x\"}]}",
                        ContainerWithList3304.class));
        String msg = ex.getMessage();
        assertNotNull(msg);
        assertTrue(msg.contains("abstract"),
                "Error message should mention abstract type issue, got: " + msg);
        assertTrue(msg.contains("@JsonBackReference"),
                "Error message should mention @JsonBackReference, got: " + msg);
    }

    // [databind#1516]
    @Test
    public void testWithParentCreator() throws Exception {
        String json = a2q(
                "{ 'id': 'abc',\n" +
                "  'name': 'Bob',\n" +
                "  'child': { 'id': 'def', 'name':'Bert' }\n" +
                "}");
        ParentWithCreator result = MAPPER.readValue(json, ParentWithCreator.class);
        assertNotNull(result);
        assertNotNull(result.child);
        assertSame(result, result.child.parent);
    }

    // [databind#1516]
    @Test
    public void testWithParentNoCreator() throws Exception {
        String json = a2q(
                "{ 'id': 'abc',\n" +
                "  'name': 'Bob',\n" +
                "  'child': { 'id': 'def', 'name':'Bert' }\n" +
                "}");
        ParentWithoutCreator result = MAPPER.readValue(json, ParentWithoutCreator.class);
        assertNotNull(result);
        assertNotNull(result.child);
        assertSame(result, result.child.parent);
    }

    // [Kotlin#129]
    @Test
    public void testManagedReferenceOnCreator() throws Exception
    {
        Car car = new Car(100, new ArrayList<>());
        Color color = new Color(100, "#FFFFF");
        color.setCar(car);
        car.getColors().add(color);

        String json = MAPPER.writeValueAsString(car);
        Car result = MAPPER.readValue(json, Car.class);

        assertNotNull(result);
        assertEquals(100, result.getId());
        assertNotNull(result.getColors());
        assertEquals(1, result.getColors().size());

        Color resultColor = result.getColors().get(0);
        assertEquals(100, resultColor.getId());
        assertEquals("#FFFFF", resultColor.getCode());

        assertNotNull(resultColor.getCar());
        assertSame(result, resultColor.getCar());
    }

    // [databind#2686]
    @Test
    public void testBuildWithBackRefs2686() throws Exception {
        Container2686 container = new Container2686();
        container.containerValue = "containerValue";
        Content2686 content = new Content2686(container, "contentValue");
        container.forward = content;

        String json = MAPPER.writeValueAsString(container);
        Container2686 result = MAPPER.readValue(json, Container2686.class);

        assertNotNull(result);
        assertNotNull(result.getForward());
        assertEquals("contentValue", result.getForward().getContentValue());
    }
}