UnwrappedWithDelegatingDeser5728Test.java

package tools.jackson.databind.struct;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.annotation.JsonUnwrapped;

import tools.jackson.databind.*;
import tools.jackson.databind.deser.ValueDeserializerModifier;
import tools.jackson.databind.deser.bean.BeanDeserializerBase;
import tools.jackson.databind.deser.std.DelegatingDeserializer;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.module.SimpleModule;
import tools.jackson.databind.testutil.DatabindTestUtil;

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

// [databind#5728]: @JsonUnwrapped fails when deserializer is wrapped
//   with DelegatingDeserializer
public class UnwrappedWithDelegatingDeser5728Test extends DatabindTestUtil
{
    static class Inner5728 {
        public String p1;
        public String p2;
    }

    static class Outer5728 {
        @JsonUnwrapped
        public Inner5728 inner;
    }

    // Minimal DelegatingDeserializer that just delegates everything
    static class WrappingDeserializer extends DelegatingDeserializer {
        WrappingDeserializer(ValueDeserializer<?> d) {
            super(d);
        }

        @Override
        protected ValueDeserializer<?> newDelegatingInstance(ValueDeserializer<?> newDelegatee) {
            return new WrappingDeserializer(newDelegatee);
        }
    }

    @SuppressWarnings("serial")
    static class WrappingModifier extends ValueDeserializerModifier {
        final int levels;

        public WrappingModifier(int levels) {
            this.levels = levels;
        }

        @Override
        public ValueDeserializer<?> modifyDeserializer(DeserializationConfig config,
                BeanDescription.Supplier beanDescRef, ValueDeserializer<?> deserializer) {
            if (deserializer instanceof BeanDeserializerBase) {
                WrappingDeserializer wrapped = new WrappingDeserializer(deserializer);
                for (int i = 1; i < levels; i++) {
                    wrapped = new WrappingDeserializer(wrapped);
                }
                return new WrappingDeserializer(wrapped);
            }
            return deserializer;
        }
    }

    private final ObjectMapper UNWRAP_MAPPER = JsonMapper.builder()
            .addModule(new SimpleModule()
                    .setDeserializerModifier(new WrappingModifier(1))).build();

    // Verify that @JsonUnwrapped works when the inner type's deserializer
    // is wrapped with a DelegatingDeserializer
    @Test
    public void testUnwrappedWithDelegatingDeserializer() throws Exception {
        String json = a2q("{'p1':'value1','p2':'value2'}");
        Outer5728 result = UNWRAP_MAPPER.readValue(json, Outer5728.class);
        assertNotNull(result.inner, "Unwrapped inner object should not be null");
        assertEquals("value1", result.inner.p1);
        assertEquals("value2", result.inner.p2);
    }

    // Verify that @JsonUnwrapped works when the inner type's deserializer
    // is wrapped multiple times with a DelegatingDeserializer
    @Test
    public void testUnwrappedWithMultipleLevelsOfDelegatingDeserializer() throws Exception {
        String json = a2q("{'p1':'value1','p2':'value2'}");
        Outer5728 result = JsonMapper.builder()
                .addModule(new SimpleModule().setDeserializerModifier(new WrappingModifier(10))).build()
                .readValue(json, Outer5728.class);
        assertNotNull(result.inner, "Unwrapped inner object should not be null");
        assertEquals("value1", result.inner.p1);
        assertEquals("value2", result.inner.p2);
    }

    // Same test but with FAIL_ON_UNKNOWN_PROPERTIES enabled ��� this is
    // where the issue manifests as an UnrecognizedPropertyException
    @Test
    public void testUnwrappedWithDelegatingDeserializerAndFailOnUnknown() throws Exception {
        ObjectReader r = UNWRAP_MAPPER.readerFor(Outer5728.class)
                .with(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        String json = a2q("{'p1':'value1','p2':'value2'}");
        Outer5728 result = r.readValue(json);
        assertNotNull(result.inner, "Unwrapped inner object should not be null");
        assertEquals("value1", result.inner.p1);
        assertEquals("value2", result.inner.p2);
    }
}