NoTypeInfoTest.java

package tools.jackson.databind.jsontype;

import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.annotation.JsonTypeInfo;

import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonGenerator;
import tools.jackson.core.JsonParser;

import tools.jackson.databind.*;
import tools.jackson.databind.annotation.JsonDeserialize;
import tools.jackson.databind.annotation.JsonSerialize;
import tools.jackson.databind.testutil.DatabindTestUtil;
import tools.jackson.databind.testutil.NoCheckSubTypeValidator;

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

/**
 * Tests for [databind#1654]: {@code @JsonTypeInfo(use = Id.NONE)} override
 * across Collection, Map and Reference (Optional, AtomicReference) types.
 */
class NoTypeInfoTest extends DatabindTestUtil
{
    @JsonTypeInfo(use=JsonTypeInfo.Id.NONE)
    @JsonDeserialize(as=NoType.class)
    static interface NoTypeInterface {
    }

    final static class NoType implements NoTypeInterface {
        public int a = 3;
    }

    // [databind#1391]
    static class ListWrapper {
        @JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
        public Collection<String> stuff = Collections.emptyList();
    }

    // Shared value type and custom ser/deser

    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
    static class Value1654 {
        public int x;

        protected Value1654() { }

        public Value1654(int x) {
            this.x = x;
        }
    }

    static class Value1654Deserializer extends ValueDeserializer<Value1654> {
        @Override
        public Value1654 deserialize(JsonParser p, DeserializationContext ctxt) {
            JsonNode n = ctxt.readTree(p);
            if (!n.has("v")) {
                ctxt.reportInputMismatch(Value1654.class, "Bad JSON input (no 'v'): " + n);
            }
            return new Value1654(n.path("v").intValue());
        }
    }

    static class Value1654Serializer extends ValueSerializer<Value1654> {
        @Override
        public void serialize(Value1654 value, JsonGenerator gen, SerializationContext ctxt)
                throws JacksonException {
            gen.writeStartObject(value);
            gen.writeNumberProperty("v", value.x);
            gen.writeEndObject();
        }
    }

    static class SingleValue1654UsingCustomSerDeserUntyped {
        @JsonDeserialize(using = Value1654Deserializer.class)
        @JsonSerialize(using = Value1654Serializer.class)
        @JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
        public Value1654 value;

        protected SingleValue1654UsingCustomSerDeserUntyped() { }

        public SingleValue1654UsingCustomSerDeserUntyped(Value1654 v) {
            value = v;
        }
    }

    // Collection-specific types

    static class Value1654TypedContainer {
        public List<Value1654> values;

        protected Value1654TypedContainer() { }

        public Value1654TypedContainer(Value1654... v) {
            values = Arrays.asList(v);
        }
    }

    static class Value1654UntypedContainer {
        @JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
        public List<Value1654> values;

        protected Value1654UntypedContainer() { }

        public Value1654UntypedContainer(Value1654... v) {
            values = Arrays.asList(v);
        }
    }

    static class Value1654UsingCustomSerDeserUntypedContainer {
        @JsonDeserialize(contentUsing = Value1654Deserializer.class)
        @JsonSerialize(contentUsing = Value1654Serializer.class)
        @JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
        public List<Value1654> values;

        protected Value1654UsingCustomSerDeserUntypedContainer() { }

        public Value1654UsingCustomSerDeserUntypedContainer(Value1654... v) {
            values = Arrays.asList(v);
        }
    }

    // Map-specific types

    static class Value1654TypedMapContainer {
        public Map<String, Value1654> values;

        protected Value1654TypedMapContainer() { }

        public Value1654TypedMapContainer(Map<String, Value1654> v) {
            values = v;
        }
    }

    static class Value1654UntypedMapContainer {
        @JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
        public Map<String, Value1654> values;

        protected Value1654UntypedMapContainer() { }

        public Value1654UntypedMapContainer(Map<String, Value1654> v) {
            values = v;
        }
    }

    static class Value1654UsingCustomSerDeserUntypedMapContainer {
        @JsonDeserialize(contentUsing = Value1654Deserializer.class)
        @JsonSerialize(contentUsing = Value1654Serializer.class)
        @JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
        public Map<String, Value1654> values;

        protected Value1654UsingCustomSerDeserUntypedMapContainer() { }

        public Value1654UsingCustomSerDeserUntypedMapContainer(Map<String, Value1654> v) {
            values = v;
        }
    }

    // Reference-specific types (Optional, AtomicReference)

    static class Value1654TypedOptionalContainer {
        public Optional<Value1654> value;

        protected Value1654TypedOptionalContainer() { }

        public Value1654TypedOptionalContainer(Value1654 v) {
            value = Optional.ofNullable(v);
        }
    }

    static class Value1654UntypedOptionalContainer {
        @JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
        public Optional<Value1654> value;

        protected Value1654UntypedOptionalContainer() { }

        public Value1654UntypedOptionalContainer(Value1654 v) {
            value = Optional.ofNullable(v);
        }
    }

    static class Value1654UsingCustomSerDeserUntypedOptionalContainer {
        @JsonDeserialize(contentUsing = Value1654Deserializer.class)
        @JsonSerialize(contentUsing = Value1654Serializer.class)
        @JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
        public Optional<Value1654> value;

        protected Value1654UsingCustomSerDeserUntypedOptionalContainer() { }

        public Value1654UsingCustomSerDeserUntypedOptionalContainer(Value1654 v) {
            value = Optional.ofNullable(v);
        }
    }

    static class Value1654TypedAtomicRefContainer {
        public AtomicReference<Value1654> value;

        protected Value1654TypedAtomicRefContainer() { }

        public Value1654TypedAtomicRefContainer(Value1654 v) {
            value = new AtomicReference<>(v);
        }
    }

    static class Value1654UsingCustomSerDeserUntypedAtomicRefContainer {
        @JsonDeserialize(contentUsing = Value1654Deserializer.class)
        @JsonSerialize(contentUsing = Value1654Serializer.class)
        @JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
        public AtomicReference<Value1654> value;

        protected Value1654UsingCustomSerDeserUntypedAtomicRefContainer() { }

        public Value1654UsingCustomSerDeserUntypedAtomicRefContainer(Value1654 v) {
            value = new AtomicReference<>(v);
        }
    }

    /*
    /**********************************************************************
    /* Test methods, Collection
    /**********************************************************************
     */

    private final ObjectMapper MAPPER = newJsonMapper();

    // no override, default polymorphic type id
    @Test
    void withoutNoTypeElementOverrideSerAndDeserCollection() throws Exception {
        String json = MAPPER.writeValueAsString(new Value1654TypedContainer(
                new Value1654(1),
                new Value1654(2)
        ));
        String typeId = Value1654.class.getName();
        typeId = "'@type':'" + typeId.substring(typeId.lastIndexOf('.') + 1) + "'";
        assertEquals(a2q("{'values':[{"+typeId+",'x':1},{"+typeId+",'x':2}]}"), json);

        Value1654TypedContainer result = MAPPER.readValue(json, Value1654TypedContainer.class);
        assertEquals(2, result.values.size());
        assertEquals(2, result.values.get(1).x);
    }

    // override, no polymorphic type id, serialization
    @Test
    void withNoTypeInfoDefaultSerCollection() throws Exception {
        Value1654UntypedContainer cont = new Value1654UntypedContainer(
                new Value1654(3),
                new Value1654(7)
        );
        assertEquals(a2q("{'values':[{'x':3},{'x':7}]}"),
                MAPPER.writeValueAsString(cont));
    }

    // override, no polymorphic type id, deserialization
    @Test
    void withNoTypeInfoDefaultDeserCollection() throws Exception {
        final String noTypeJson = a2q(
                "{'values':[{'x':3},{'x':7}]}"
        );
        Value1654UntypedContainer unResult = MAPPER.readValue(noTypeJson,
                Value1654UntypedContainer.class);
        assertEquals(2, unResult.values.size());
        assertEquals(7, unResult.values.get(1).x);
    }

    // override, no polymorphic type id, custom serialization
    @Test
    void withNoTypeInfoOverrideSerCollection() throws Exception {
        Value1654UsingCustomSerDeserUntypedContainer cont = new Value1654UsingCustomSerDeserUntypedContainer(
                new Value1654(1),
                new Value1654(2)
        );
        assertEquals(a2q("{'values':[{'v':1},{'v':2}]}"),
                MAPPER.writeValueAsString(cont));
    }

    // override, no polymorphic type id, custom deserialization
    @Test
    void withNoTypeInfoOverrideDeserCollection() throws Exception {
        final String noTypeJson = a2q(
                "{'values':[{'v':3},{'v':7}]}"
        );
        Value1654UsingCustomSerDeserUntypedContainer unResult = MAPPER.readValue(noTypeJson,
                Value1654UsingCustomSerDeserUntypedContainer.class);
        assertEquals(2, unResult.values.size());
        assertEquals(3, unResult.values.get(0).x);
        assertEquals(7, unResult.values.get(1).x);
    }

    // // And then validation for individual value, not in Container

    // override, no polymorphic type id, custom serialization
    @Test
    void singleWithNoTypeInfoOverrideSer() throws Exception {
        SingleValue1654UsingCustomSerDeserUntyped wrapper = new SingleValue1654UsingCustomSerDeserUntyped(
                new Value1654(42));
        assertEquals(a2q("{'value':{'v':42}}"),
                MAPPER.writeValueAsString(wrapper));
    }

    // override, no polymorphic type id, custom deserialization
    @Test
    void singleWithNoTypeInfoOverrideDeser() throws Exception {
        String noTypeJson = a2q("{'value':{'v':42}}");
        SingleValue1654UsingCustomSerDeserUntyped result = MAPPER.readValue(noTypeJson,
                SingleValue1654UsingCustomSerDeserUntyped.class);
        assertEquals(42,result.value.x);
    }

    /*
    /**********************************************************************
    /* Test methods, Map
    /**********************************************************************
     */

    // no override, default polymorphic type id for Map values
    @Test
    void withoutNoTypeElementOverrideSerAndDeserMap() throws Exception {
        Map<String, Value1654> map = new LinkedHashMap<>();
        map.put("first", new Value1654(1));
        map.put("second", new Value1654(2));

        String json = MAPPER.writeValueAsString(new Value1654TypedMapContainer(map));
        String typeId = Value1654.class.getName();
        typeId = "'@type':'" + typeId.substring(typeId.lastIndexOf('.') + 1) + "'";
        assertEquals(a2q("{'values':{'first':{"+typeId+",'x':1},'second':{"+typeId+",'x':2}}}"), json);

        Value1654TypedMapContainer result = MAPPER.readValue(json, Value1654TypedMapContainer.class);
        assertEquals(2, result.values.size());
        assertEquals(2, result.values.get("second").x);
    }

    // override, no polymorphic type id for Map values, serialization
    @Test
    void withNoTypeInfoDefaultSerMap() throws Exception {
        Map<String, Value1654> map = new LinkedHashMap<>();
        map.put("first", new Value1654(3));
        map.put("second", new Value1654(7));

        Value1654UntypedMapContainer cont = new Value1654UntypedMapContainer(map);
        assertEquals(a2q("{'values':{'first':{'x':3},'second':{'x':7}}}"),
                MAPPER.writeValueAsString(cont));
    }

    // override, no polymorphic type id for Map values, deserialization
    @Test
    void withNoTypeInfoDefaultDeserMap() throws Exception {
        final String noTypeJson = a2q(
                "{'values':{'first':{'x':3},'second':{'x':7}}}"
        );
        Value1654UntypedMapContainer unResult = MAPPER.readValue(noTypeJson,
                Value1654UntypedMapContainer.class);
        assertEquals(2, unResult.values.size());
        assertEquals(7, unResult.values.get("second").x);
    }

    // override, no polymorphic type id for Map values, custom serialization
    @Test
    void withNoTypeInfoOverrideSerMap() throws Exception {
        Map<String, Value1654> map = new LinkedHashMap<>();
        map.put("first", new Value1654(1));
        map.put("second", new Value1654(2));

        Value1654UsingCustomSerDeserUntypedMapContainer cont =
                new Value1654UsingCustomSerDeserUntypedMapContainer(map);
        assertEquals(a2q("{'values':{'first':{'v':1},'second':{'v':2}}}"),
                MAPPER.writeValueAsString(cont));
    }

    // override, no polymorphic type id for Map values, custom deserialization
    @Test
    void withNoTypeInfoOverrideDeserMap() throws Exception {
        final String noTypeJson = a2q(
                "{'values':{'first':{'v':3},'second':{'v':7}}}"
        );
        Value1654UsingCustomSerDeserUntypedMapContainer unResult = MAPPER.readValue(noTypeJson,
                Value1654UsingCustomSerDeserUntypedMapContainer.class);
        assertEquals(2, unResult.values.size());
        assertEquals(3, unResult.values.get("first").x);
        assertEquals(7, unResult.values.get("second").x);
    }

    // override, no polymorphic type id, custom serialization (single Map value)
    @Test
    void singleWithNoTypeInfoOverrideSerMap() throws Exception {
        SingleValue1654UsingCustomSerDeserUntyped wrapper = new SingleValue1654UsingCustomSerDeserUntyped(
                new Value1654(42));
        assertEquals(a2q("{'value':{'v':42}}"),
                MAPPER.writeValueAsString(wrapper));
    }

    // override, no polymorphic type id, custom deserialization (single Map value)
    @Test
    void singleWithNoTypeInfoOverrideDeserMap() throws Exception {
        String noTypeJson = a2q("{'value':{'v':42}}");
        SingleValue1654UsingCustomSerDeserUntyped result = MAPPER.readValue(noTypeJson,
                SingleValue1654UsingCustomSerDeserUntyped.class);
        assertEquals(42,result.value.x);
    }

    /*
    /**********************************************************************
    /* Test methods, Reference types (Optional, AtomicReference)
    /**********************************************************************
     */

    // no override, default polymorphic type id for Optional
    @Test
    void withoutNoTypeElementOverrideSerAndDeserOptional() throws Exception {
        Value1654TypedOptionalContainer cont = new Value1654TypedOptionalContainer(new Value1654(42));
        String json = MAPPER.writeValueAsString(cont);
        String typeId = Value1654.class.getName();
        typeId = "'@type':'" + typeId.substring(typeId.lastIndexOf('.') + 1) + "'";
        assertEquals(a2q("{'value':{"+typeId+",'x':42}}"), json);

        Value1654TypedOptionalContainer result = MAPPER.readValue(json, Value1654TypedOptionalContainer.class);
        assertTrue(result.value.isPresent());
        assertEquals(42, result.value.get().x);
    }

    // override, no polymorphic type id for Optional, serialization
    @Test
    void withNoTypeInfoDefaultSerOptional() throws Exception {
        Value1654UntypedOptionalContainer cont = new Value1654UntypedOptionalContainer(new Value1654(42));
        assertEquals(a2q("{'value':{'x':42}}"), MAPPER.writeValueAsString(cont));
    }

    // override, no polymorphic type id for Optional, deserialization
    @Test
    void withNoTypeInfoDefaultDeserOptional() throws Exception {
        String noTypeJson = a2q("{'value':{'x':42}}");
        Value1654UntypedOptionalContainer result = MAPPER.readValue(noTypeJson,
                Value1654UntypedOptionalContainer.class);
        assertTrue(result.value.isPresent());
        assertEquals(42, result.value.get().x);
    }

    // override, no polymorphic type id for Optional, custom serialization
    @Test
    void withNoTypeInfoOverrideSerOptional() throws Exception {
        Value1654UsingCustomSerDeserUntypedOptionalContainer cont =
                new Value1654UsingCustomSerDeserUntypedOptionalContainer(new Value1654(42));
        assertEquals(a2q("{'value':{'v':42}}"), MAPPER.writeValueAsString(cont));
    }

    // override, no polymorphic type id for Optional, custom deserialization
    @Test
    void withNoTypeInfoOverrideDeserOptional() throws Exception {
        String noTypeJson = a2q("{'value':{'v':42}}");
        Value1654UsingCustomSerDeserUntypedOptionalContainer result = MAPPER.readValue(noTypeJson,
                Value1654UsingCustomSerDeserUntypedOptionalContainer.class);
        assertTrue(result.value.isPresent());
        assertEquals(42, result.value.get().x);
    }

    // no override, default polymorphic type id for AtomicReference
    @Test
    void withoutNoTypeElementOverrideSerAndDeserAtomicRef() throws Exception {
        Value1654TypedAtomicRefContainer cont = new Value1654TypedAtomicRefContainer(new Value1654(42));
        String json = MAPPER.writeValueAsString(cont);
        String typeId = Value1654.class.getName();
        typeId = "'@type':'" + typeId.substring(typeId.lastIndexOf('.') + 1) + "'";
        assertEquals(a2q("{'value':{"+typeId+",'x':42}}"), json);

        Value1654TypedAtomicRefContainer result = MAPPER.readValue(json, Value1654TypedAtomicRefContainer.class);
        assertEquals(42, result.value.get().x);
    }

    // override, no polymorphic type id for AtomicReference, custom serialization
    @Test
    void withNoTypeInfoOverrideSerAtomicRef() throws Exception {
        Value1654UsingCustomSerDeserUntypedAtomicRefContainer cont =
                new Value1654UsingCustomSerDeserUntypedAtomicRefContainer(new Value1654(42));
        assertEquals(a2q("{'value':{'v':42}}"), MAPPER.writeValueAsString(cont));
    }

    // override, no polymorphic type id for AtomicReference, custom deserialization
    @Test
    void withNoTypeInfoOverrideDeserAtomicRef() throws Exception {
        String noTypeJson = a2q("{'value':{'v':42}}");
        Value1654UsingCustomSerDeserUntypedAtomicRefContainer result = MAPPER.readValue(noTypeJson,
                Value1654UsingCustomSerDeserUntypedAtomicRefContainer.class);
        assertEquals(42, result.value.get().x);
    }

    /*
    /**********************************************************************
    /* Test methods, Id.NONE with default typing
    /**********************************************************************
     */

    @Test
    public void testWithIdNone() throws Exception
    {
        ObjectMapper mapper = jsonMapperBuilder()
                .activateDefaultTyping(NoCheckSubTypeValidator.instance)
                .build();
        // serialize without type info
        String json = mapper.writeValueAsString(new NoType());
        assertEquals("{\"a\":3}", json);

        // and deserialize successfully
        NoTypeInterface bean = mapper.readValue("{\"a\":6}", NoTypeInterface.class);
        assertNotNull(bean);
        NoType impl = (NoType) bean;
        assertEquals(6, impl.a);
    }

    // [databind#1391]: should allow disabling of default typing
    // via explicit {@link JsonTypeInfo}
    @Test
    public void testCollectionWithOverride() throws Exception
    {
        ObjectMapper mapper = jsonMapperBuilder()
            .activateDefaultTypingAsProperty(NoCheckSubTypeValidator.instance,
                    DefaultTyping.OBJECT_AND_NON_CONCRETE,
                    "$type")
            .build();
        String json = mapper.writeValueAsString(new ListWrapper());
        assertEquals(a2q("{'stuff':[]}"), json);

        // And verify deserialization works too
        ListWrapper result = mapper.readValue(json, ListWrapper.class);
        assertEquals(0, result.stuff.size());
    }
}