JsonTypeInfoSimpleClassName4061Test.java

package com.fasterxml.jackson.databind.jsontype;

import java.util.ArrayList;
import java.util.List;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.annotation.*;

import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
import com.fasterxml.jackson.databind.jsontype.impl.SimpleNameIdResolver;
import com.fasterxml.jackson.databind.testutil.DatabindTestUtil;

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

/**
 * Test for <a href="https://github.com/FasterXML/jackson-databind/issues/4061">
 * [databind#4061] Add JsonTypeInfo.Id.SIMPLE_NAME
 * 
 * @since 2.16
 */
public class JsonTypeInfoSimpleClassName4061Test extends DatabindTestUtil
{

    @JsonTypeInfo(
            use = JsonTypeInfo.Id.SIMPLE_NAME)
    @JsonSubTypes({
            @JsonSubTypes.Type(value = InnerSub4061A.class),
            @JsonSubTypes.Type(value = InnerSub4061B.class)
    })
    static class InnerSuper4061 { }

    static class InnerSub4061A extends InnerSuper4061 { }

    static class InnerSub4061B extends InnerSuper4061 { }

    @JsonTypeInfo(
            use = JsonTypeInfo.Id.MINIMAL_CLASS)
    @JsonSubTypes({
            @JsonSubTypes.Type(value = MinimalInnerSub4061A.class),
            @JsonSubTypes.Type(value = MinimalInnerSub4061B.class)
    })
    static class MinimalInnerSuper4061 { }

    static class MinimalInnerSub4061A extends MinimalInnerSuper4061 { }

    static class MinimalInnerSub4061B extends MinimalInnerSuper4061 { }

    @JsonTypeInfo(
            use = JsonTypeInfo.Id.SIMPLE_NAME)
    @JsonSubTypes({
            @JsonSubTypes.Type(value = MixedSub4061A.class),
            @JsonSubTypes.Type(value = MixedSub4061B.class)
    })
    static class MixedSuper4061 { }

    @JsonTypeInfo(
            use = JsonTypeInfo.Id.MINIMAL_CLASS)
    @JsonSubTypes({
            @JsonSubTypes.Type(value = MixedMinimalSub4061A.class),
            @JsonSubTypes.Type(value = MixedMinimalSub4061B.class)
    })
    static class MixedMinimalSuper4061 { }

    static class Root {
        @JsonMerge
        public MergeChild child;
    }

    @JsonTypeInfo(use = JsonTypeInfo.Id.SIMPLE_NAME)
    @JsonSubTypes({
            @JsonSubTypes.Type(value = MergeChildA.class, name = "MergeChildA"),
            @JsonSubTypes.Type(value = MergeChildB.class, name = "MergeChildB")
    })
    static abstract class MergeChild {
    }

    static class MergeChildA extends MergeChild {
        public String name;
    }

    static class MergeChildB extends MergeChild {
        public String code;
    }

    static class PolyWrapperForAlias {
        @JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
                include = JsonTypeInfo.As.WRAPPER_ARRAY)
        @JsonSubTypes({
                @JsonSubTypes.Type(value = AliasBean.class,name = "ab")})
        public Object value;

        protected PolyWrapperForAlias() { }
        
        public PolyWrapperForAlias(Object v) { value = v; }
    }

    static class AliasBean {
        @JsonAlias({ "nm", "Name" })
        public String name;
        int _xyz;
        int _a;

        @JsonCreator
        public AliasBean(@JsonProperty("a") @JsonAlias("A") int a) {
            _a = a;
        }

        @JsonAlias({ "Xyz" })
        public void setXyz(int x) {
            _xyz = x;
        }
    }

    @JsonTypeInfo(
            use = JsonTypeInfo.Id.SIMPLE_NAME)
    @JsonSubTypes({
            @JsonSubTypes.Type(value = DuplicateSubClass.class),
            @JsonSubTypes.Type(value = com.fasterxml.jackson.databind.jsontype.DuplicateSubClass.class)
    })
    static class DuplicateSuperClass { }
    
    static class DuplicateSubClass extends DuplicateSuperClass { }

    /*
    /**********************************************************
    /* Unit tests
    /**********************************************************
     */
    
    private final ObjectMapper MAPPER = newJsonMapper();

    // inner class that has contains dollar sign
    @Test
    public void testInnerClass() throws Exception
    {
        String jsonStr = a2q("{'@type':'InnerSub4061A'}");
        
        // ser
        assertEquals(jsonStr, MAPPER.writeValueAsString(new InnerSub4061A()));
        
        // deser <- breaks!
        InnerSuper4061 bean = MAPPER.readValue(jsonStr, InnerSuper4061.class);
        assertInstanceOf(InnerSuper4061.class, bean);
    }

    // inner class that has contains dollar sign
    @Test
    public void testMinimalInnerClass() throws Exception
    {
        String jsonStr = a2q("{'@c':'.JsonTypeInfoSimpleClassName4061Test$MinimalInnerSub4061A'}");
        
        // ser
        assertEquals(jsonStr, MAPPER.writeValueAsString(new MinimalInnerSub4061A()));
        
        // deser <- breaks!
        MinimalInnerSuper4061 bean = MAPPER.readValue(jsonStr, MinimalInnerSuper4061.class);
        assertInstanceOf(MinimalInnerSuper4061.class, bean);
        assertNotNull(bean);
    }

    // Basic : non-inner class, without dollar sign
    @Test
    public void testBasicClass() throws Exception
    {
        String jsonStr = a2q("{'@type':'BasicSub4061A'}");
        
        // ser
        assertEquals(jsonStr, MAPPER.writeValueAsString(new BasicSub4061A()));
        
        // deser
        BasicSuper4061 bean = MAPPER.readValue(jsonStr, BasicSuper4061.class);
        assertInstanceOf(BasicSuper4061.class, bean);
        assertInstanceOf(BasicSub4061A.class, bean);

    }
    
    // Mixed SimpleClassName : parent as inner, subtype as basic
    @Test
    public void testMixedClass() throws Exception
    {
        String jsonStr = a2q("{'@type':'MixedSub4061A'}");
        
        // ser
        assertEquals(jsonStr, MAPPER.writeValueAsString(new MixedSub4061A()));
        
        // deser
        MixedSuper4061 bean = MAPPER.readValue(jsonStr, MixedSuper4061.class);
        assertInstanceOf(MixedSuper4061.class, bean);
        assertInstanceOf(MixedSub4061A.class, bean);
    }
    
    // Mixed MinimalClass : parent as inner, subtype as basic
    @Test
    public void testMixedMinimalClass() throws Exception
    {
        String jsonStr = a2q("{'@c':'.MixedMinimalSub4061A'}");
        
        // ser
        assertEquals(jsonStr, MAPPER.writeValueAsString(new MixedMinimalSub4061A()));
        
        // deser
        MixedMinimalSuper4061 bean = MAPPER.readValue(jsonStr, MixedMinimalSuper4061.class);
        assertInstanceOf(MixedMinimalSuper4061.class, bean);
        assertInstanceOf(MixedMinimalSub4061A.class, bean);
    }

    @Test
    public void testPolymorphicNewObject() throws Exception
    {
        String jsonStr = "{\"child\": { \"@type\": \"MergeChildA\", \"name\": \"I'm child A\" }}";
        
        Root root = MAPPER.readValue(jsonStr, Root.class);
        
        assertTrue(root.child instanceof MergeChildA);
        assertEquals("I'm child A", ((MergeChildA) root.child).name);
    }

    // case insenstive type name
    @Test
    public void testPolymorphicNewObjectCaseInsensitive() throws Exception
    {
        String jsonStr = "{\"child\": { \"@type\": \"mergechilda\", \"name\": \"I'm child A\" }}";
        ObjectMapper mapper = jsonMapperBuilder()
                .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_VALUES)
                .build();

        Root root = mapper.readValue(jsonStr, Root.class);
        
        assertTrue(root.child instanceof MergeChildA);
        assertEquals("I'm child A", ((MergeChildA) root.child).name);
    }

    @Test
    public void testPolymorphicNewObjectUnknownTypeId() throws Exception
    {
        try {
            MAPPER.readValue("{\"child\": { \"@type\": \"UnknownChildA\", \"name\": \"I'm child A\" }}", Root.class);    
        } catch (InvalidTypeIdException e) {
            verifyException(e, "Could not resolve type id 'UnknownChildA' as a subtype of");
        }
    }

    @Test
    public void testAliasWithPolymorphic() throws Exception
    {
        String jsonStr = a2q("{'value': ['ab', {'nm' : 'Bob', 'A' : 17} ] }");
        
        PolyWrapperForAlias value = MAPPER.readValue(jsonStr, PolyWrapperForAlias.class);
        
        assertNotNull(value.value);
        AliasBean bean = (AliasBean) value.value;
        assertEquals("Bob", bean.name);
        assertEquals(17, bean._a);
    }

    @Test
    public void testGetMechanism()
    {
        final DeserializationConfig config = MAPPER.getDeserializationConfig();
        JavaType javaType = config.constructType(InnerSub4061B.class);
        List<NamedType> namedTypes = new ArrayList<>();
        namedTypes.add(new NamedType(InnerSub4061A.class));
        namedTypes.add(new NamedType(InnerSub4061B.class));
        
        SimpleNameIdResolver idResolver = SimpleNameIdResolver.construct(config, javaType, namedTypes, false, true);
        
        assertEquals(JsonTypeInfo.Id.SIMPLE_NAME, idResolver.getMechanism());
    }

    @Test
    public void testDuplicateNameLastOneWins() throws Exception
    {
        String jsonStr = a2q("{'@type':'DuplicateSubClass'}");
        
        // deser
        DuplicateSuperClass bean = MAPPER.readValue(jsonStr, DuplicateSuperClass.class);
        assertInstanceOf(com.fasterxml.jackson.databind.jsontype.DuplicateSubClass.class, bean);
    }
}

@JsonTypeInfo(
        use = JsonTypeInfo.Id.SIMPLE_NAME)
@JsonSubTypes({
        @JsonSubTypes.Type(value = BasicSub4061A.class),
        @JsonSubTypes.Type(value = BasicSub4061B.class)
})
class BasicSuper4061 { }

class BasicSub4061A extends BasicSuper4061 { }

class BasicSub4061B extends BasicSuper4061 { }

class MixedSub4061A extends JsonTypeInfoSimpleClassName4061Test.MixedSuper4061 { }

class MixedSub4061B extends JsonTypeInfoSimpleClassName4061Test.MixedSuper4061 { }

class MixedMinimalSub4061A extends JsonTypeInfoSimpleClassName4061Test.MixedMinimalSuper4061 { }

class MixedMinimalSub4061B extends JsonTypeInfoSimpleClassName4061Test.MixedMinimalSuper4061 { }

class DuplicateSubClass extends JsonTypeInfoSimpleClassName4061Test.DuplicateSuperClass { }