SealedTypesWithJsonTypeInfoSimpleClassName4061Test.java

package tools.jackson.databind.jsontype;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonMerge;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import tools.jackson.databind.DeserializationConfig;
import tools.jackson.databind.JavaType;
import tools.jackson.databind.MapperFeature;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.exc.InvalidTypeIdException;
import tools.jackson.databind.jsontype.impl.SimpleNameIdResolver;
import tools.jackson.databind.testutil.DatabindTestUtil;

/**
 * Test for <a href="https://github.com/FasterXML/jackson-databind/issues/4061"> [databind#4061] Add
 * JsonTypeInfo.Id.SIMPLE_NAME
 */
public class SealedTypesWithJsonTypeInfoSimpleClassName4061Test extends DatabindTestUtil {
  @JsonTypeInfo(use = JsonTypeInfo.Id.SIMPLE_NAME)
  static sealed class InnerSuper4061 permits InnerSub4061A, InnerSub4061B {
  }

  static final class InnerSub4061A extends InnerSuper4061 {
  }

  static final class InnerSub4061B extends InnerSuper4061 {
  }

  @JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)
  static sealed class MinimalInnerSuper4061 permits MinimalInnerSub4061A, MinimalInnerSub4061B {
  }

  static final class MinimalInnerSub4061A extends MinimalInnerSuper4061 {
  }

  static final class MinimalInnerSub4061B extends MinimalInnerSuper4061 {
  }

  @JsonTypeInfo(use = JsonTypeInfo.Id.SIMPLE_NAME)
  static sealed class MixedSuper4061
      permits MixedSub4061AForSealedClasses, MixedSub4061BForSealedClasses {
  }

  @JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)
  static sealed class MixedMinimalSuper4061
      permits MixedMinimalSub4061AForSealedClasses, MixedMinimalSub4061BForSealedClasses {
  }

  static class Root {
    @JsonMerge
    public MergeChild child;
  }

  @JsonTypeInfo(use = JsonTypeInfo.Id.SIMPLE_NAME)
  static abstract sealed class MergeChild permits MergeChildA, MergeChildB {
  }

  static final class MergeChildA extends MergeChild {
    public String name;
  }

  static final 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)
  static sealed class DuplicateSuperClass permits DuplicateSubClassForSealedClasses,
      tools.jackson.databind.jsontype.DuplicateSubClassForSealedClasses {
  }

  static final class DuplicateSubClassForSealedClasses 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':'.SealedTypesWithJsonTypeInfoSimpleClassName4061Test$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':'MixedSub4061AForSealedClasses'}");

    // ser
    assertEquals(jsonStr, MAPPER.writeValueAsString(new MixedSub4061AForSealedClasses()));

    // deser
    MixedSuper4061 bean = MAPPER.readValue(jsonStr, MixedSuper4061.class);
    assertInstanceOf(MixedSuper4061.class, bean);
    assertInstanceOf(MixedSub4061AForSealedClasses.class, bean);
  }

  // Mixed MinimalClass : parent as inner, subtype as basic
  @Test
  public void testMixedMinimalClass() throws Exception {
    String jsonStr = a2q("{'@c':'.MixedMinimalSub4061AForSealedClasses'}");

    // ser
    assertEquals(jsonStr, MAPPER.writeValueAsString(new MixedMinimalSub4061AForSealedClasses()));

    // deser
    MixedMinimalSuper4061 bean = MAPPER.readValue(jsonStr, MixedMinimalSuper4061.class);
    assertInstanceOf(MixedMinimalSuper4061.class, bean);
    assertInstanceOf(MixedMinimalSub4061AForSealedClasses.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.deserializationConfig();
    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':'DuplicateSubClassForSealedClasses'}");

    // deser
    DuplicateSuperClass bean = MAPPER.readValue(jsonStr, DuplicateSuperClass.class);
    assertInstanceOf(tools.jackson.databind.jsontype.DuplicateSubClassForSealedClasses.class, bean);
  }
}


@JsonTypeInfo(use = JsonTypeInfo.Id.SIMPLE_NAME)
sealed class BasicSuper4061ForSealedTypes
    permits BasicSub4061AForSealedTypes, BasicSub4061BForSealedTypes {
}


final class BasicSub4061AForSealedTypes extends BasicSuper4061ForSealedTypes {
}


final class BasicSub4061BForSealedTypes extends BasicSuper4061ForSealedTypes {
}


final class MixedSub4061AForSealedClasses
    extends SealedTypesWithJsonTypeInfoSimpleClassName4061Test.MixedSuper4061 {
}


final class MixedSub4061BForSealedClasses
    extends SealedTypesWithJsonTypeInfoSimpleClassName4061Test.MixedSuper4061 {
}


final class MixedMinimalSub4061AForSealedClasses
    extends SealedTypesWithJsonTypeInfoSimpleClassName4061Test.MixedMinimalSuper4061 {
}


final class MixedMinimalSub4061BForSealedClasses
    extends SealedTypesWithJsonTypeInfoSimpleClassName4061Test.MixedMinimalSuper4061 {
}


final class DuplicateSubClassForSealedClasses
    extends SealedTypesWithJsonTypeInfoSimpleClassName4061Test.DuplicateSuperClass {
}