ObjectId825Test.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.*;

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

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

// [databind#825] + [databind#825B]: ObjectId + default typing, simple and complex hierarchies
@SuppressWarnings("serial")
public class ObjectId825Test extends DatabindTestUtil
{
    // // // [databind#825]: Simple hierarchy with PropertyGenerator + default typing

    @JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="oidString")
    public static class AbstractEntity {
        public String oidString;
    }

    public static class TestA extends AbstractEntity {
        public TestAbst testAbst;
        public TestD d;
    }

    static class TestAbst extends AbstractEntity { }

    static class TestC extends TestAbst {
        public TestD d;
    }

    static class TestD extends AbstractEntity { }

    // // // [databind#825B]: Complex BPEL-like hierarchy with ObjectId + JsonTypeInfo + default typing

    @JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="oidString")
    @JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="@class")
    static abstract class TypedEntity implements java.io.Serializable {
        public String oidString;

        protected TypedEntity() { }

        public String getOidString() { return oidString; }
        public void setOidString(String oidString) { this.oidString = oidString; }
    }

    static abstract class AbstractSym extends TypedEntity { }

    static abstract class AbstractData extends AbstractSym { }

    static abstract class AbstractAct extends TypedEntity {
        protected java.util.ArrayList<Tr> outTr;

        public java.util.ArrayList<Tr> getOutTr() { return this.outTr; }
        public void setOutTr(java.util.ArrayList<Tr> outTr) { this.outTr = outTr; }
    }

    static abstract class AbstractCond extends AbstractAct { }

    static abstract class AbstractDec extends AbstractAct {
        protected java.util.ArrayList<Dec> dec;

        public java.util.ArrayList<Dec> getDec() { return this.dec; }
        public void setDec(java.util.ArrayList<Dec> dec) { this.dec = dec; }
    }

    static class Ch extends TypedEntity {
        protected java.util.ArrayList<? extends AbstractAct> act;

        public java.util.ArrayList<? extends AbstractAct> getAct() { return this.act; }
        public void setAct(java.util.ArrayList<? extends AbstractAct> act) { this.act = act; }
    }

    static class CTC extends TypedEntity {
        protected java.util.ArrayList<CTV> var;

        public CTC() { }

        public java.util.ArrayList<CTV> getVar() {
            if (var == null) {
                var = new ArrayList<CTV>();
            }
            return new ArrayList<CTV>(var);
        }

        public void setVar(java.util.ArrayList<CTV> var) { this.var = var; }
    }

    static class CTD extends AbstractDec { }

    static class CTV extends TypedEntity {
        protected Ch ch;
        protected java.util.ArrayList<? extends AbstractData> locV;

        public Ch getCh() { return this.ch; }
        public void setCh(Ch ch) { this.ch = ch; }

        public java.util.ArrayList<? extends AbstractData> getLocV() { return this.locV; }
        public void setLocV(java.util.ArrayList<? extends AbstractData> locV) { this.locV = locV; }
    }

    static class Dec extends AbstractCond { }

    static class Ti extends AbstractAct {
        protected AbstractData timer;

        public AbstractData getTimer() { return this.timer; }
        public void setTimer(AbstractData timer) { this.timer = timer; }
    }

    static class Tr extends TypedEntity {
        protected AbstractAct target;

        public AbstractAct getTarget() { return this.target; }
        public void setTarget(AbstractAct target) { this.target = target; }
    }

    static class V extends AbstractData {
        private static final long serialVersionUID = 1L;
    }

    // // // [databind#2780]: Default Typing + @JsonIdentityInfo in untyped collections

    @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
    static class User2780 {
        public int id;
        public String login;

        public User2780() {}
        public User2780(int id, String login) {
            this.id = id;
            this.login = login;
        }
    }

    @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
    static class UserContainer2780 {
        public int id;
        public User2780 user;

        public UserContainer2780() {}
        public UserContainer2780(int id, User2780 user) {
            this.id = id;
            this.user = user;
        }
    }

    /*
    /**********************************************************
    /* Unit tests, simple hierarchy [databind#825]
    /**********************************************************
     */

    private final ObjectMapper DEF_TYPING_MAPPER = jsonMapperBuilder()
            .activateDefaultTyping(NoCheckSubTypeValidator.instance,
                    DefaultTyping.NON_FINAL)
            .build();

    @Test
    public void testDeserialize() throws Exception {
        TestA a = new TestA();
        a.oidString = "oidA";

        TestC c = new TestC();
        c.oidString = "oidC";

        a.testAbst = c;

        TestD d = new TestD();
        d.oidString = "oidD";

        c.d = d;
        a.d = d;

        String json = DEF_TYPING_MAPPER.writeValueAsString(a);
        TestA testADeserialized = DEF_TYPING_MAPPER.readValue(json, TestA.class);

        assertNotNull(testADeserialized);
        assertNotNull(testADeserialized.d);
        assertEquals("oidD", testADeserialized.d.oidString);
    }

    /*
    /**********************************************************************
    /* Unit tests, complex BPEL-like hierarchy [databind#825]
    /**********************************************************************
     */

    // [databind#825]
    @Test
    public void testFull825() throws Exception
    {
        final ObjectMapper mapper = jsonMapperBuilder()
                .activateDefaultTyping(NoCheckSubTypeValidator.instance,
                        DefaultTyping.OBJECT_AND_NON_CONCRETE)
                .build();

        String INPUT = """
                {
                    "@class": "_PKG_CTC",
                     "var": [{
                      "ch": {
                        "@class": "_PKG_Ch",
                         "act": [{
                            "@class": "_PKG_CTD",
                            "oidString": "oid1",
                            "dec": [{
                              "@class": "_PKG_Dec",
                                "oidString": "oid2",
                                "outTr": [{
                                  "@class": "_PKG_Tr",
                                  "target": {
                                    "@class": "_PKG_Ti",
                                    "oidString": "oid3",
                                    "timer": "problemoid",
                                    "outTr": [{
                                      "@class": "_PKG_Tr",
                                      "target": {
                                        "@class": "_PKG_Ti",
                                        "oidString": "oid4",
                                        "timer": {
                                          "@class": "_PKG_V",
                                          "oidString": "problemoid"
                                        }
                                      }
                                    }]
                                  }
                                }]
                              }]
                         }],
                         "oidString": "oid5"
                      },
                       "@class": "_PKG_CTV",
                       "oidString": "oid6",
                       "locV": ["problemoid"]
                    }],
                     "oidString": "oid7"
                }
                """;

        // Replace package placeholder with actual inner-class package
        final String newPkg = getClass().getName() + "\\$";
        INPUT = INPUT.replaceAll("_PKG_", newPkg);

        CTC result = mapper.readValue(INPUT, CTC.class);
        assertNotNull(result);
    }

    /*
    /**********************************************************************
    /* Unit tests, default typing + untyped collections [databind#2780]
    /**********************************************************************
     */

    // User appears first: back-reference resolves correctly
    @Test
    public void testUserFirstThenContainer2780() throws Exception
    {
        ObjectMapper mapper = jsonMapperBuilder()
            .activateDefaultTyping(NoCheckSubTypeValidator.instance, DefaultTyping.NON_FINAL)
            .build();

        User2780 user = new User2780(42, "cool_man");
        UserContainer2780 container = new UserContainer2780(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(User2780.class, result.get(0), "First element should be User2780");
        assertInstanceOf(UserContainer2780.class, result.get(1), "Second element should be UserContainer2780");

        User2780 resultUser = (User2780) result.get(0);
        UserContainer2780 resultContainer = (UserContainer2780) 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 User2780 instance");
    }

    // [databind#2780]: Container appears first, bare id back-reference must resolve with type info
    @Test
    public void testContainerFirstThenUser2780() throws Exception
    {
        ObjectMapper mapper = jsonMapperBuilder()
            .activateDefaultTyping(NoCheckSubTypeValidator.instance, DefaultTyping.NON_FINAL)
            .build();

        User2780 user = new User2780(42, "cool_man");
        UserContainer2780 container = new UserContainer2780(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(UserContainer2780.class, result.get(0),
            "First element should be UserContainer2780");
        assertInstanceOf(User2780.class, result.get(1),
            "Second element should be User2780 (not Integer) -- bare id back-reference must be resolved with type info");

        UserContainer2780 resultContainer = (UserContainer2780) result.get(0);
        User2780 resultUser = (User2780) 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");
    }
}