TestWithGenerics.java

package tools.jackson.databind.jsontype;

import java.io.Serializable;
import java.util.*;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;

import tools.jackson.core.*;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.*;
import tools.jackson.databind.annotation.JsonSerialize;
import tools.jackson.databind.exc.InvalidTypeIdException;
import tools.jackson.databind.testutil.DatabindTestUtil;
import tools.jackson.databind.testutil.NoCheckSubTypeValidator;

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

public class TestWithGenerics extends DatabindTestUtil
{
    @JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "object-type")
    @JsonSubTypes( { @Type(value = Dog.class, name = "doggy") })
    static abstract class Animal {
        public String name;
    }

    static class Dog extends Animal {
        public int boneCount;

        public Dog(String name, int b) {
            super();
            this.name = name;
            boneCount = b;
        }
    }

    static class ContainerWithGetter<T extends Animal> {
        private T animal;

        public ContainerWithGetter(T a) { animal = a; }

        public T getAnimal() { return animal; }
    }

    static class ContainerWithField<T extends Animal> {
        public T animal;

        public ContainerWithField(T a) { animal = a; }
    }

    static class WrappedContainerWithField {
        public ContainerWithField<?> animalContainer;
    }

	// Beans for [JACKSON-387], [JACKSON-430]

    @JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="@classAttr1")
    static class MyClass {
        public List<MyParam<?>> params = new ArrayList<MyParam<?>>();
    }

    @JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="@classAttr2")
    static class MyParam<T>{
        public T value;

        public MyParam() { }
        public MyParam(T v) { value = v; }
    }

    static class SomeObject {
        public String someValue = UUID.randomUUID().toString();
    }

    static class CustomValueSerializer extends ValueSerializer<Object>
    {
        private final ValueSerializer<Object> beanSerializer;

        public CustomValueSerializer( ValueSerializer<Object> beanSerializer ) { this.beanSerializer = beanSerializer; }

        @Override
        public void serialize( Object value, JsonGenerator g, SerializationContext provider )
        {
            beanSerializer.serialize(value, g, provider);
        }

        @Override
        public Class<?> handledType() { return beanSerializer.handledType(); }

        @Override
        public void serializeWithType( Object value, JsonGenerator g, SerializationContext provider, TypeSerializer typeSer )
        {
            beanSerializer.serializeWithType(value, g, provider, typeSer);
        }

        @Override
        public void resolve(SerializationContext provider)
        {
            beanSerializer.resolve(provider);
        }
    }

    // [databind#543]
    static class ContainerWithTwoAnimals<U extends Animal,V extends Animal> extends ContainerWithField<U> {
         public V otherAnimal;

         public ContainerWithTwoAnimals(U a1, V a2) {
              super(a1);
              otherAnimal = a2;
         }
    }

    // [databind#1128]
    @SuppressWarnings("rawtypes")
    static abstract class HObj<M extends HObj> {
        public long id;

        // important: do not serialize as subtype, but only as type that
        // is statically recognizable here.
        @JsonSerialize(typing=JsonSerialize.Typing.STATIC)
        public M parent;
    }

    static class DevBase extends HObj<DevBase> {
        public String tag;

        // for some reason, setter is needed to expose this...
        public void setTag(String t) { tag = t; }
    }

    static class Dev extends DevBase {
        public long p1;

        public void setP1(long l) { p1 = l; }
        public long getP1() { return p1; }
    }

    static class DevM extends Dev {
        private long m1;

        public long getM1() { return m1; }
    }

    static abstract class ContainerBase<T> {
        public T entity;
    }

    static class DevMContainer extends ContainerBase<DevM>{ }

    // [databind#2331]
    static class SuperNode<T> { }
    static class SuperTestClass { }

    @SuppressWarnings("serial")
    static class Node<T extends SuperTestClass & Cloneable> extends SuperNode<Node<T>> implements Serializable {

        public List<Node<T>> children;

        public Node() {
            children = new ArrayList<Node<T>>();
        }

        /**
         * The Wildcard here seems to be the Issue.
         * If we remove this full getter, everything is working as expected.
         */
        public List<? extends SuperNode<Node<T>>> getChildren() {
            return children;
        }
    }

    // [databind#1735]
    static class Wrapper1735 {
        @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "type")
        public Payload1735 w;
    }

    static class Payload1735 {
        public void setValue(String str) { }
    }

    static class Nefarious1735 {
        public Nefarious1735() {
            throw new Error("Never call this constructor");
        }

        public void setValue(String str) {
            throw new Error("Never call this setter");
        }
    }

    // for [JACKSON-356]
    public static class JSONResponse<T> {

        private T result;

        public T getResult() {
            return result;
        }

        public void setResult(T result) {
            this.result = result;
        }
    }

    @JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="@class")
    public static class Parent356 {
        public String parentContent = "PARENT";
    }

    public static class Child356_1 extends Parent356 {
        public String childContent1 = "CHILD1";
    }

    public static class Child356_2 extends Parent356 {
        public String childContent2 = "CHILD2";
    }

    /*
    /**********************************************************
    /* Unit tests
    /**********************************************************
     */

    private final ObjectMapper MAPPER = newJsonMapper();

    @Test
    public void testWrapperWithGetter() throws Exception
    {
        Dog dog = new Dog("Fluffy", 3);
        String json = MAPPER.writeValueAsString(new ContainerWithGetter<Animal>(dog));
        if (json.indexOf("\"object-type\":\"doggy\"") < 0) {
            fail("polymorphic type not kept, result == "+json+"; should contain 'object-type':'...'");
        }
    }

    @Test
    public void testWrapperWithField() throws Exception
    {
        Dog dog = new Dog("Fluffy", 3);
        String json = MAPPER.writeValueAsString(new ContainerWithField<Animal>(dog));
        if (json.indexOf("\"object-type\":\"doggy\"") < 0) {
            fail("polymorphic type not kept, result == "+json+"; should contain 'object-type':'...'");
        }
    }

    @Test
    public void testWrapperWithExplicitType() throws Exception
    {
        Dog dog = new Dog("Fluffy", 3);
        ContainerWithGetter<Animal> c2 = new ContainerWithGetter<Animal>(dog);
        String json = MAPPER.writerFor(MAPPER.getTypeFactory().constructParametricType(ContainerWithGetter.class,
                Animal.class)).writeValueAsString(c2);
        if (json.indexOf("\"object-type\":\"doggy\"") < 0) {
            fail("polymorphic type not kept, result == "+json+"; should contain 'object-type':'...'");
        }
    }

    @Test
    public void testJackson387() throws Exception
    {
        ObjectMapper om = jsonMapperBuilder()
                .enable( SerializationFeature.INDENT_OUTPUT)
                .activateDefaultTyping(NoCheckSubTypeValidator.instance,
                        DefaultTyping.JAVA_LANG_OBJECT, JsonTypeInfo.As.PROPERTY)
                .changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(JsonInclude.Include.NON_NULL))
                .build();

        MyClass mc = new MyClass();

        MyParam<Integer> moc1 = new MyParam<Integer>(1);
        MyParam<String> moc2 = new MyParam<String>("valueX");

        SomeObject so = new SomeObject();
        so.someValue = "xxxxxx";
        MyParam<SomeObject> moc3 = new MyParam<SomeObject>(so);

        List<SomeObject> colist = new ArrayList<SomeObject>();
        colist.add( new SomeObject() );
        colist.add( new SomeObject() );
        colist.add( new SomeObject() );
        MyParam<List<SomeObject>> moc4 = new MyParam<List<SomeObject>>(colist);

        mc.params.add( moc1 );
        mc.params.add( moc2 );
        mc.params.add( moc3 );
        mc.params.add( moc4 );

        String json = om.writeValueAsString( mc );

        MyClass mc2 = om.readValue(json, MyClass.class );
        assertNotNull(mc2);
        assertNotNull(mc2.params);
        assertEquals(4, mc2.params.size());
    }

    // [databind#543]
    @Test
    public void testValueWithMoreGenericParameters() throws Exception
    {
        WrappedContainerWithField wrappedContainerWithField = new WrappedContainerWithField();
        wrappedContainerWithField.animalContainer = new ContainerWithTwoAnimals<Dog,Dog>(new Dog("d1",1), new Dog("d2",2));
        String json = MAPPER.writeValueAsString(wrappedContainerWithField);
        assertNotNull(json);
    }

    // [databind#1128]
    @Test
    public void testIssue1128() throws Exception
    {
        ObjectMapper mapper = jsonMapperBuilder()
                .changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(JsonInclude.Include.NON_EMPTY))
                .build();
        final DevMContainer devMContainer1 = new DevMContainer();
        final DevM entity = new DevM();
        final Dev parent = new Dev();
        parent.id = 2L;
        entity.parent = parent;
        devMContainer1.entity = entity;

        String json = mapper.writeValueAsString(devMContainer1);

        final DevMContainer devMContainer = mapper.readValue(json, DevMContainer.class);
        long id = devMContainer.entity.parent.id;
        assertEquals(2, id);
    }

    // [databind#2331]
    @SuppressWarnings({ "rawtypes", "unchecked" })
    @Test
    public void testGeneric2331() throws Exception {
        Node root = new Node();
        root.children.add(new Node());

        String json = newJsonMapper().writeValueAsString(root);
        assertNotNull(json);
    }

    // [databind#1735]
    @Test
    public void testSimpleTypeCheck1735() throws Exception
    {
        final String NEF_CLASS = Nefarious1735.class.getName();
        InvalidTypeIdException e = assertThrows(InvalidTypeIdException.class,
                () -> MAPPER.readValue(a2q(
                        "{'w':{'type':'"+NEF_CLASS+"'}}"),
                        Wrapper1735.class));
        verifyException(e, "could not resolve type id");
        verifyException(e, "not a subtype");
    }

    // [databind#1735]
    @Test
    public void testNestedTypeCheck1735() throws Exception
    {
        InvalidTypeIdException e = assertThrows(InvalidTypeIdException.class,
                () -> MAPPER.readValue(a2q(
                        "{'w':{'type':'java.util.HashMap<java.lang.String,java.lang.String>'}}"),
                        Wrapper1735.class));
        verifyException(e, "could not resolve type id");
        verifyException(e, "not a subtype");
    }

    // [JACKSON-356]
    @Test
    public void testSubTypesFor356() throws Exception
    {
        JSONResponse<List<Parent356>> input = new JSONResponse<List<Parent356>>();

        List<Parent356> embedded = new ArrayList<Parent356>();
        embedded.add(new Child356_1());
        embedded.add(new Child356_2());
        input.setResult(embedded);

        ObjectMapper mapper = jsonMapperBuilder()
                .configure(MapperFeature.USE_STATIC_TYPING, true)
                .build();

        JavaType rootType = defaultTypeFactory().constructType(new TypeReference<JSONResponse<List<Parent356>>>() { });
        byte[] json = mapper.writerFor(rootType).writeValueAsBytes(input);

        JSONResponse<List<Parent356>> out = mapper.readValue(json, 0, json.length, rootType);

        List<Parent356> deserializedContent = out.getResult();

        assertEquals(2, deserializedContent.size());
        assertInstanceOf(Parent356.class, deserializedContent.get(0));
        assertInstanceOf(Child356_1.class, deserializedContent.get(0));
        assertFalse(deserializedContent.get(0) instanceof Child356_2);
        assertInstanceOf(Child356_2.class, deserializedContent.get(1));
        assertFalse(deserializedContent.get(1) instanceof Child356_1);

        assertEquals("PARENT", ((Child356_1) deserializedContent.get(0)).parentContent);
        assertEquals("PARENT", ((Child356_2) deserializedContent.get(1)).parentContent);
        assertEquals("CHILD1", ((Child356_1) deserializedContent.get(0)).childContent1);
        assertEquals("CHILD2", ((Child356_2) deserializedContent.get(1)).childContent2);
    }
}