StructuralTypeDeserTest.java

package tools.jackson.databind.deser;

import java.util.List;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.annotation.*;

import tools.jackson.databind.*;
import tools.jackson.databind.exc.InvalidDefinitionException;

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

import static tools.jackson.databind.testutil.DatabindTestUtil.*;

/**
 * Tests for deserialization of structural/complex object patterns:
 * inner classes, object-or-array delegation, and cyclic references.
 */
public class StructuralTypeDeserTest
{
    /*
    /**********************************************************************
    /* Helper classes for inner class tests
    /**********************************************************************
     */

    static class Dog
    {
      public String name;
      public Brain brain;

      public Dog() { }
      protected Dog(String n, boolean thinking) {
          name = n;
          brain = new Brain();
          brain.isThinking = thinking;
      }

      // note: non-static
      public class Brain {
          @JsonProperty("brainiac")
          public boolean isThinking;

          public String parentName() { return name; }
      }
    }

    /*
    /**********************************************************************
    /* Helper classes for object-or-array delegation tests
    /**********************************************************************
     */

    public static class SomeObject {
        public String someField;
    }

    public static class ArrayOrObject {
        final List<SomeObject> objects;
        final SomeObject object;

        @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
        public ArrayOrObject(List<SomeObject> objects) {
            this.objects = objects;
            this.object = null;
        }

        @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
        public ArrayOrObject(SomeObject object) {
            this.objects = null;
            this.object = object;
        }
    }

    /*
    /**********************************************************************
    /* Helper classes for cyclic type tests
    /**********************************************************************
     */

    static class CyclicBean
    {
        CyclicBean _next;
        String _name;

        public CyclicBean() { }

        public void setNext(CyclicBean b) { _next = b; }
        public void setName(String n) { _name = n; }

    }

    static class LinkA {
        public LinkB next;
    }

    static class LinkB {
        protected LinkA a;

        public void setA(LinkA a) { this.a = a; }
        public LinkA getA() { return a; }
    }

    static class GenericLink<T> {
        public GenericLink<T> next;
    }

    static class StringLink extends GenericLink<String> {
    }

    @JsonPropertyOrder({ "id", "parent" })
    static class Selfie405 {
        public int id;

        @JsonIgnoreProperties({ "parent" })
        public Selfie405 parent;

        public Selfie405(int id) { this.id = id; }
    }

    private final ObjectMapper MAPPER = newJsonMapper();

    /*
    /**********************************************************
    /* Test methods, object-or-array delegation
    /**********************************************************
     */

    @Test
    public void testObjectCase() throws Exception {
        ArrayOrObject arrayOrObject = MAPPER.readValue("{}", ArrayOrObject.class);
        assertNull(arrayOrObject.objects, "expected objects field to be null");
        assertNotNull(arrayOrObject.object, "expected object field not to be null");
    }

    @Test
    public void testEmptyArrayCase() throws Exception {
        ArrayOrObject arrayOrObject = MAPPER.readValue("[]", ArrayOrObject.class);
        assertNotNull(arrayOrObject.objects, "expected objects field not to be null");
        assertTrue(arrayOrObject.objects.isEmpty(), "expected objects field to be an empty list");
        assertNull(arrayOrObject.object, "expected object field to be null");
    }

    @Test
    public void testNotEmptyArrayCase() throws Exception {
        ArrayOrObject arrayOrObject = MAPPER.readValue("[{}, {}]", ArrayOrObject.class);
        assertNotNull(arrayOrObject.objects, "expected objects field not to be null");
        assertEquals(2, arrayOrObject.objects.size(), "expected objects field to have size 2");
        assertNull(arrayOrObject.object, "expected object field to be null");
    }

    /*
    /**********************************************************
    /* Test methods, cyclic types
    /**********************************************************
     */

    @Test
    public void testLinked() throws Exception
    {
        CyclicBean first = MAPPER.readValue
            ("{\"name\":\"first\", \"next\": { "
             +" \"name\":\"last\", \"next\" : null }}",
             CyclicBean.class);

        assertNotNull(first);
        assertEquals("first", first._name);
        CyclicBean last = first._next;
        assertNotNull(last);
        assertEquals("last", last._name);
        assertNull(last._next);
    }

    @Test
    public void testLinkedGeneric() throws Exception
    {
        StringLink link = MAPPER.readValue("{\"next\":null}", StringLink.class);
        assertNotNull(link);
        assertNull(link.next);
    }

    @Test
    public void testCycleWith2Classes() throws Exception
    {
        LinkA a = MAPPER.readValue("{\"next\":{\"a\":null}}", LinkA.class);
        assertNotNull(a.next);
        LinkB b = a.next;
        assertNull(b.a);
    }

    // [Issue#405]: Should be possible to ignore cyclic ref
    @Test
    public void testIgnoredCycle() throws Exception
    {
        Selfie405 self1 = new Selfie405(1);
        self1.parent = self1;

        // First: exception with default settings:
        assertTrue(MAPPER.isEnabled(SerializationFeature.FAIL_ON_SELF_REFERENCES));
        try {
            MAPPER.writeValueAsString(self1);
            fail("Should fail with direct self-ref");
        } catch (InvalidDefinitionException e) {
            verifyException(e, "Direct self-reference");
        }

        ObjectWriter w = MAPPER.writer()
                .without(SerializationFeature.FAIL_ON_SELF_REFERENCES);
        String json = w.writeValueAsString(self1);
        assertNotNull(json);
        assertEquals(a2q("{'id':1,'parent':{'id':1}}"), json);
    }

    /*
    /**********************************************************************
    /* Test methods, inner classes
    /**********************************************************************
     */

    @Test
    public void testSimpleNonStaticInner() throws Exception
    {
        // Let's actually verify by first serializing, then deserializing back
        Dog input = new Dog("Smurf", true);
        String json = MAPPER.writeValueAsString(input);
        Dog output = MAPPER.readValue(json, Dog.class);
        assertEquals("Smurf", output.name);
        assertNotNull(output.brain);
        assertTrue(output.brain.isThinking);
        // and verify correct binding...
        assertEquals("Smurf", output.brain.parentName());
        output.name = "Foo";
        assertEquals("Foo", output.brain.parentName());

        // also, null handling
        input.brain = null;

        output = MAPPER.readValue(MAPPER.writeValueAsString(input), Dog.class);
        assertNull(output.brain);
    }
}