TypeDeserializerDefaultFallback2747Test.java

package tools.jackson.databind.jsontype.ext;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.annotation.JsonTypeInfo.As;

import tools.jackson.core.*;
import tools.jackson.databind.*;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.jsontype.TypeDeserializer;
import tools.jackson.databind.jsontype.TypeIdResolver;
import tools.jackson.databind.module.SimpleModule;
import tools.jackson.databind.testutil.DatabindTestUtil;

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

/**
 * Covers the default {@code deserializeTypedWithKnownTypeId} implementation on
 * {@link TypeDeserializer} ��� the wrapper-array fallback that is preserved for any
 * subclass that does not extend {@code TypeDeserializerBase}. [databind#2747]
 */
class TypeDeserializerDefaultFallback2747Test extends DatabindTestUtil
{
    // Minimal TypeDeserializer that extends the abstract base directly (NOT
    // TypeDeserializerBase), so invoking `deserializeTypedWithKnownTypeId`
    // exercises the default wrapper-array fallback on TypeDeserializer.
    static class WrapperArrayTypeDeser extends TypeDeserializer
    {
        @Override public TypeDeserializer forProperty(BeanProperty prop) { return this; }
        @Override public As getTypeInclusion() { return As.WRAPPER_ARRAY; }
        @Override public String getPropertyName() { return null; }
        @Override public TypeIdResolver getTypeIdResolver() { return null; }
        @Override public Class<?> getDefaultImpl() { return null; }

        // Decodes the synthetic `[typeId, value]` produced by the default
        // deserializeTypedWithKnownTypeId(...) implementation. Returns
        // "typeId:<number>" so the test can assert the round trip.
        @Override
        public Object deserializeTypedFromArray(JsonParser p, DeserializationContext ctxt)
            throws JacksonException
        {
            // p at START_ARRAY
            p.nextToken();
            String typeId = p.getString();
            p.nextToken();
            int value = p.getIntValue();
            p.nextToken(); // END_ARRAY
            return typeId + ":" + value;
        }

        @Override public Object deserializeTypedFromObject(JsonParser p, DeserializationContext ctxt) throws JacksonException { return deserializeTypedFromArray(p, ctxt); }
        @Override public Object deserializeTypedFromScalar(JsonParser p, DeserializationContext ctxt) throws JacksonException { return deserializeTypedFromArray(p, ctxt); }
        @Override public Object deserializeTypedFromAny(JsonParser p, DeserializationContext ctxt) throws JacksonException { return deserializeTypedFromArray(p, ctxt); }
    }

    // Placeholder value used as the registered deserialization target; its custom
    // deserializer is the shim that calls the TypeDeserializer default.
    static class Box {
        final String payload;
        Box(String payload) { this.payload = payload; }
    }

    static class BoxDeser extends ValueDeserializer<Box>
    {
        private static final WrapperArrayTypeDeser TD = new WrapperArrayTypeDeser();

        @Override
        public Box deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException {
            // Invokes the default TypeDeserializer.deserializeTypedWithKnownTypeId
            // (NOT the TypeDeserializerBase override, since our TD extends
            // TypeDeserializer directly).
            Object decoded = TD.deserializeTypedWithKnownTypeId(p, ctxt, "tag");
            return new Box((String) decoded);
        }
    }

    @Test
    void defaultFallbackRoutesThroughDeserializeTypedFromArray() throws Exception {
        JsonMapper mapper = JsonMapper.builder()
                .addModule(new SimpleModule().addDeserializer(Box.class, new BoxDeser()))
                .build();

        Box result = mapper.readValue("42", Box.class);
        // The default impl wraps as ["tag", 42] and hands it to deserializeTypedFromArray,
        // which in our test TypeDeserializer returns "tag:42".
        assertEquals("tag:42", result.payload);
    }
}