ExternalTypeIdWithAliases3209Test.java

package tools.jackson.databind.jsontype.ext;

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.databind.DeserializationFeature;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.exc.UnrecognizedPropertyException;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.testutil.DatabindTestUtil;

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.assertThrows;

// [databind#3209]: @JsonAlias on EXTERNAL_PROPERTY-typed value
class ExternalTypeIdWithAliases3209Test extends DatabindTestUtil {
    static class Nar {
        public String dar;
        public String zar;
    }

    static class Foo {
        public String bar;
        public String zar;
    }

    static class Container {
        public String id;

        @JsonTypeInfo(use = Id.NAME, include = As.EXTERNAL_PROPERTY, property = "id")
        @JsonSubTypes({
                @Type(name = "FOO", value = Foo.class),
                @Type(name = "NAR", value = Nar.class)
        })
        @JsonAlias({ "fooValue", "narValue" })
        public Object target;
    }

    static class CreatorContainer {
        public final String id;
        public final Object target;

        @JsonCreator
        public CreatorContainer(
                @JsonProperty("id") String id,
                @JsonTypeInfo(use = Id.NAME, include = As.EXTERNAL_PROPERTY, property = "id")
                @JsonSubTypes({
                        @Type(name = "FOO", value = Foo.class),
                        @Type(name = "NAR", value = Nar.class)
                })
                @JsonAlias({ "fooValue", "narValue" })
                @JsonProperty("target") Object target) {
            this.id = id;
            this.target = target;
        }
    }

    private final ObjectMapper MAPPER = newJsonMapper();

    @Test
    void testAliasResolvesNarValue() throws Exception {
        Container c = MAPPER.readValue(
                "{ \"id\": \"NAR\", \"narValue\": { \"dar\": \"d\" }}",
                Container.class);
        assertNotNull(c.target);
        assertInstanceOf(Nar.class, c.target);
    }

    @Test
    void testAliasResolvesFooValue() throws Exception {
        Container c = MAPPER.readValue(
                "{ \"id\": \"FOO\", \"fooValue\": { \"bar\": \"b\" }}",
                Container.class);
        assertNotNull(c.target);
        assertInstanceOf(Foo.class, c.target);
    }

    @Test
    void testCanonicalNameStillWorks() throws Exception {
        Container c = MAPPER.readValue(
                "{ \"id\": \"NAR\", \"target\": { \"dar\": \"d\" }}",
                Container.class);
        assertNotNull(c.target);
        assertInstanceOf(Nar.class, c.target);
    }

    // [databind#3209]: creator-based path uses same ExternalTypeHandler,
    // so alias should also resolve when value is a @JsonCreator arg.
    @Test
    void testAliasWithCreator() throws Exception {
        CreatorContainer c = MAPPER.readValue(
                "{ \"id\": \"NAR\", \"narValue\": { \"dar\": \"d\" }}",
                CreatorContainer.class);
        assertEquals("NAR", c.id);
        assertInstanceOf(Nar.class, c.target);
    }

    @Test
    void testUnknownAliasStillFails() throws Exception {
        ObjectMapper strict = JsonMapper.builder()
                .enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
                .build();
        assertThrows(UnrecognizedPropertyException.class, () ->
                strict.readValue(
                        "{ \"id\": \"NAR\", \"bogusValue\": { \"dar\": \"d\" }}",
                        Container.class));
    }

}