DelegatingCreatorsTest.java

package tools.jackson.databind.deser.creators;

import java.math.BigDecimal;
import java.util.*;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.annotation.*;

import tools.jackson.core.*;
import tools.jackson.databind.*;
import tools.jackson.databind.testutil.DatabindTestUtil.Point;
import tools.jackson.databind.util.TokenBuffer;

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

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

public class DelegatingCreatorsTest
{
    static class BooleanBean
    {
        protected Boolean value;

        public BooleanBean(Boolean v) { value = v; }

        @JsonCreator
        protected static BooleanBean create(Boolean value) {
            return new BooleanBean(value);
        }
    }

    static class IntegerBean
    {
        protected Integer value;

        public IntegerBean(Integer v) { value = v; }

        @JsonCreator
        protected static IntegerBean create(Integer value) {
            return new IntegerBean(value);
        }
    }

    static class LongBean
    {
        protected Long value;

        public LongBean(Long v) { value = v; }

        @JsonCreator
        protected static LongBean create(Long value) {
            return new LongBean(value);
        }
    }

    static class CtorBean711
    {
        protected String name;
        protected int age;

        @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
        public CtorBean711(@JacksonInject String n, int a)
        {
            name = n;
            age = a;
        }
    }

    static class FactoryBean711
    {
        protected String name1;
        protected String name2;
        protected int age;

        private FactoryBean711(int a, String n1, String n2) {
            age = a;
            name1 = n1;
            name2 = n2;
        }

        @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
        public static FactoryBean711 create(@JacksonInject String n1, int a, @JacksonInject String n2) {
            return new FactoryBean711(a, n1, n2);
        }
    }

    static class Value592
    {
        protected Object stuff;

        protected Value592(Object ob, boolean bogus) {
            stuff = ob;
        }

        @JsonCreator
        public static Value592 from(TokenBuffer buffer) {
            return new Value592(buffer, false);
        }
    }

    static class MapBean
    {
        protected Map<String,Long> map;

        @JsonCreator
        public MapBean(Map<String, Long> map) {
            this.map = map;
        }
    }

    // [databind#4688]
    static final class NoFieldSingletonWithDelegatingCreator {
        static final NoFieldSingletonWithDelegatingCreator INSTANCE = new NoFieldSingletonWithDelegatingCreator();

        private NoFieldSingletonWithDelegatingCreator() {}

        @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
        static NoFieldSingletonWithDelegatingCreator of() {
            return INSTANCE;
        }
    }

    // [databind#4688]
    static final class NoFieldSingletonWithPropertiesCreator {
        static final NoFieldSingletonWithPropertiesCreator INSTANCE = new NoFieldSingletonWithPropertiesCreator();

        private NoFieldSingletonWithPropertiesCreator() {}

        @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
        static NoFieldSingletonWithPropertiesCreator of() {
            return INSTANCE;
        }
    }

    // [databind#4688]
    static final class NoFieldSingletonWithDefaultCreator {
        static final NoFieldSingletonWithDefaultCreator INSTANCE = new NoFieldSingletonWithDefaultCreator();

        private NoFieldSingletonWithDefaultCreator() {}

        @JsonCreator
        static NoFieldSingletonWithDefaultCreator of() {
            return INSTANCE;
        }
    }

    // [databind#1003]
    public interface Hero1003 { }

    // [databind#1003]
    static class HeroBattle1003 {

        private final Hero1003 hero;

        HeroBattle1003(Hero1003 hero) {
            if (hero == null) throw new Error();
            this.hero = hero;
        }

        @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "heroType")
        public Hero1003 getHero() {
            return hero;
        }

        @JsonCreator
        static HeroBattle1003 fromJson(Delegate1003 json) {
            return new HeroBattle1003(json.hero);
        }
    }

    // [databind#1003]
    static class Delegate1003 {
        @JsonProperty
        @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "heroType")
        public Hero1003 hero;
    }

    // [databind#1003]
    static class Superman1003 implements Hero1003 {
        String name = "superman";

        public String getName() {
            return name;
        }
    }

    // For [databind#580]
    @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
    static abstract class Issue580Base {
    }

    static class Issue580Impl extends Issue580Base {
        public int id = 3;

        public Issue580Impl() { }
        public Issue580Impl(int id) { this.id = id; }
    }

    static class Issue580Bean {
        public Issue580Base value;

        @JsonCreator
        public Issue580Bean(Issue580Base v) {
            value = v;
        }

        @JsonValue
        public Issue580Base value() {
            return value;
        }
    }

    // [TestConstructFromMap]

    static class ConstructorFromMap
    {
        int _x;
        String _y;

        @JsonCreator
        ConstructorFromMap(Map<?,?> arg)
        {
            _x = ((Number) arg.get("x")).intValue();
            _y = (String) arg.get("y");
        }
    }

    static class FactoryFromPoint
    {
        int _x, _y;

        private FactoryFromPoint(Point p) {
            _x = p.x;
            _y = p.y;
        }

        @JsonCreator
        static FactoryFromPoint createIt(Point p)
        {
            return new FactoryFromPoint(p);
        }
    }

    // Also: let's test BigDecimal-from-JSON-String factory
    static class FactoryFromDecimalString
    {
        int _value;

        private FactoryFromDecimalString(BigDecimal d) {
	    _value = d.intValue();
        }

        @JsonCreator
        static FactoryFromDecimalString whateverNameWontMatter(BigDecimal d)
        {
            return new FactoryFromDecimalString(d);
        }
    }

    // [databind#2353]: allow delegating and properties-based
    static class SuperToken2353 {
        public long time;
        public String username;

        @JsonCreator(mode=JsonCreator.Mode.DELEGATING) // invoked when a string is passed
        public static SuperToken2353 from(String username) {
            SuperToken2353 token = new SuperToken2353();
            token.username = username;
            token.time = System.currentTimeMillis();
            return token;
        }

        @JsonCreator(mode=JsonCreator.Mode.PROPERTIES) // invoked when an object is passed, pre-validating property existence
        public static SuperToken2353 create(
                @JsonProperty("name") String username,
                @JsonProperty("time") long time)
        {
            SuperToken2353 token = new SuperToken2353();
            token.username = username;
            token.time = time;

            return token;
        }
    }

    /*
    /**********************************************************
    /* Test methods
    /**********************************************************
     */

    private final ObjectMapper MAPPER = newJsonMapper();

    @Test
    public void testBooleanDelegate() throws Exception
    {
        // should obviously work with booleans...
        BooleanBean bb = MAPPER.readValue("true", BooleanBean.class);
        assertEquals(Boolean.TRUE, bb.value);

        // but also with value conversion from String
        bb = MAPPER.readValue(q("true"), BooleanBean.class);
        assertEquals(Boolean.TRUE, bb.value);
    }

    @Test
    public void testIntegerDelegate() throws Exception
    {
        IntegerBean bb = MAPPER.readValue("-13", IntegerBean.class);
        assertEquals(Integer.valueOf(-13), bb.value);

        // but also with value conversion from String (unless blocked)
        bb = MAPPER.readValue(q("127"), IntegerBean.class);
        assertEquals(Integer.valueOf(127), bb.value);
    }

    @Test
    public void testLongDelegate() throws Exception
    {
        LongBean bb = MAPPER.readValue("11", LongBean.class);
        assertEquals(Long.valueOf(11L), bb.value);

        // but also with value conversion from String (unless blocked)
        bb = MAPPER.readValue(q("-99"), LongBean.class);
        assertEquals(Long.valueOf(-99L), bb.value);
    }

    // should also work with delegate model (single non-annotated arg)
    @Test
    public void testWithCtorAndDelegate() throws Exception
    {
        ObjectMapper mapper = jsonMapperBuilder()
                .injectableValues(new InjectableValues.Std()
                        .addValue(String.class, "Pooka"))
                .build();
        CtorBean711 bean = mapper.readValue("38", CtorBean711.class);
        assertEquals(38, bean.age);
        assertEquals("Pooka", bean.name);
    }

    @Test
    public void testWithFactoryAndDelegate() throws Exception
    {
        ObjectMapper mapper = jsonMapperBuilder()
                .injectableValues(new InjectableValues.Std()
                        .addValue(String.class, "Fygar"))
                .build();
        FactoryBean711 bean = mapper.readValue("38", FactoryBean711.class);
        assertEquals(38, bean.age);
        assertEquals("Fygar", bean.name1);
        assertEquals("Fygar", bean.name2);
    }

    // [databind#592]
    @Test
    public void testDelegateWithTokenBuffer() throws Exception
    {
        Value592 value = MAPPER.readValue("{\"a\":1,\"b\":2}", Value592.class);
        assertNotNull(value);
        Object ob = value.stuff;
        assertEquals(TokenBuffer.class, ob.getClass());
        JsonParser p = ((TokenBuffer) ob).asParser(ObjectReadContext.empty());
        assertToken(JsonToken.START_OBJECT, p.nextToken());
        assertToken(JsonToken.PROPERTY_NAME, p.nextToken());
        assertEquals("a", p.currentName());
        assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
        assertEquals(1, p.getIntValue());
        assertToken(JsonToken.PROPERTY_NAME, p.nextToken());
        assertEquals("b", p.currentName());
        assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
        assertEquals(2, p.getIntValue());
        assertToken(JsonToken.END_OBJECT, p.nextToken());
        p.close();
    }

    @SuppressWarnings("unchecked")
    @Test
    public void testIssue465() throws Exception
    {
        final String JSON = "{\"A\":12}";

        // first, test with regular Map, non empty
        Map<String,Long> map = MAPPER.readValue(JSON, Map.class);
        assertEquals(1, map.size());
        assertEquals(Integer.valueOf(12), map.get("A"));

        MapBean bean = MAPPER.readValue(JSON, MapBean.class);
        assertEquals(1, bean.map.size());
        assertEquals(Long.valueOf(12L), bean.map.get("A"));

        // and then empty ones
        final String EMPTY_JSON = "{}";

        map = MAPPER.readValue(EMPTY_JSON, Map.class);
        assertEquals(0, map.size());

        bean = MAPPER.readValue(EMPTY_JSON, MapBean.class);
        assertEquals(0, bean.map.size());
    }

    // [databind#2353]: allow delegating and properties-based
    @Test
    public void testMultipleCreators2353() throws Exception
    {
        // first, test delegating
        SuperToken2353 result = MAPPER.readValue(q("Bob"), SuperToken2353.class);
        assertEquals("Bob", result.username);

        // and then properties-based
        result = MAPPER.readValue(a2q("{'name':'Billy', 'time':123}"), SuperToken2353.class);
        assertEquals("Billy", result.username);
        assertEquals(123L, result.time);
    }

    // [databind#4688]
    @Test
    public void testNoFieldSingletonWithDelegatingCreator() throws Exception
    {
        NoFieldSingletonWithDelegatingCreator deserialized = MAPPER.readValue("{}",
                NoFieldSingletonWithDelegatingCreator.class);
        assertSame(NoFieldSingletonWithDelegatingCreator.INSTANCE, deserialized);
    }

    // [databind#4688]
    @Test
    public void testNoFieldSingletonWithPropertiesCreator() throws Exception
    {
        NoFieldSingletonWithPropertiesCreator deserialized = MAPPER.readValue("{}",
                NoFieldSingletonWithPropertiesCreator.class);
        assertSame(NoFieldSingletonWithPropertiesCreator.INSTANCE, deserialized);
    }

    // [databind#4688]
    @Test
    public void testNoFieldSingletonWithDefaultCreator() throws Exception
    {
        NoFieldSingletonWithDefaultCreator deserialized = MAPPER.readValue("{}",
                NoFieldSingletonWithDefaultCreator.class);
        assertSame(NoFieldSingletonWithDefaultCreator.INSTANCE, deserialized);
    }

    // [databind#1003]
    @Test
    public void testExtrnalPropertyDelegatingCreator() throws Exception
    {
        final String json = MAPPER.writeValueAsString(new HeroBattle1003(new Superman1003()));
        final HeroBattle1003 battle = MAPPER.readValue(json, HeroBattle1003.class);

        assertInstanceOf(Superman1003.class, battle.getHero());
    }

    // [databind#580]
    @Test
    public void testAbstractDelegateWithCreator() throws Exception
    {
        Issue580Bean input = new Issue580Bean(new Issue580Impl(13));
        String json = MAPPER.writeValueAsString(input);
        Issue580Bean result = MAPPER.readValue(json, Issue580Bean.class);
        assertNotNull(result);
        assertNotNull(result.value);
        assertEquals(13, ((Issue580Impl) result.value).id);
    }

    // [TestConstructFromMap]

    @Test
    public void testViaConstructor() throws Exception
    {
        ConstructorFromMap result = MAPPER.readValue
            ("{ \"x\":1, \"y\" : \"abc\" }", ConstructorFromMap.class);
        assertEquals(1, result._x);
        assertEquals("abc", result._y);
    }

    @Test
    public void testViaFactory() throws Exception
    {
        FactoryFromPoint result = MAPPER.readValue("{ \"x\" : 3, \"y\" : 4 }", FactoryFromPoint.class);
        assertEquals(3, result._x);
        assertEquals(4, result._y);
    }

    @Test
    public void testViaFactoryUsingString() throws Exception
    {
        FactoryFromDecimalString result = MAPPER.readValue("\"12.57\"", FactoryFromDecimalString.class);
        assertNotNull(result);
        assertEquals(12, result._value);
    }
}