RecordExternalPropertyGeneric3786Test.java

package tools.jackson.databind.records;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.annotation.JsonTypeInfo;

import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.testutil.DatabindTestUtil;
import tools.jackson.databind.testutil.NoCheckSubTypeValidator;

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

// [databind#3786]: Deserialization of generic container (Record type) using
// EXTERNAL_PROPERTY fails for boxed built-ins because type information is missing
public class RecordExternalPropertyGeneric3786Test extends DatabindTestUtil
{
    record Container3786<T>(
        int id,
        @JsonTypeInfo(
            use = JsonTypeInfo.Id.CLASS,
            include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
            property = "type"
        )
        T value
    ) { }

    record MyObject3786(
        String foo,
        String bar
    ) { }

    // Need to allow Object as base type for CLASS-based type id
    private final ObjectMapper MAPPER = jsonMapperBuilder()
            .polymorphicTypeValidator(NoCheckSubTypeValidator.instance)
            .build();

    // This case works: custom object has type info written
    @Test
    public void testCustomObjectRoundTrip() {
        Container3786<MyObject3786> myContainer = new Container3786<>(1, new MyObject3786("foo", "bar"));
        String json = MAPPER.writeValueAsString(myContainer);

        // Should include type property for custom object
        Container3786<?> result = MAPPER.readValue(json, new TypeReference<Container3786<?>>() { });
        assertNotNull(result);
        assertEquals(1, result.id());
        assertNotNull(result.value());
    }

    // This case fails: String value has no type info written,
    // but deserialization requires it
    @Test
    public void testStringValueRoundTrip() {
        Container3786<String> strContainer = new Container3786<>(1, "Hello");
        String json = MAPPER.writeValueAsString(strContainer);

        // JSON will be {"id":1,"value":"Hello"} -- no "type" property
        Container3786<?> result = MAPPER.readValue(json, new TypeReference<Container3786<?>>() { });
        assertNotNull(result);
        assertEquals(1, result.id());
        assertEquals("Hello", result.value());
    }

    // Also test with Integer value
    @Test
    public void testIntegerValueRoundTrip() {
        Container3786<Integer> intContainer = new Container3786<>(1, 42);
        String json = MAPPER.writeValueAsString(intContainer);

        Container3786<?> result = MAPPER.readValue(json, new TypeReference<Container3786<?>>() { });
        assertNotNull(result);
        assertEquals(1, result.id());
        assertEquals(42, result.value());
    }

    // Also test with Boolean value
    @Test
    public void testBooleanValueRoundTrip() {
        Container3786<Boolean> boolContainer = new Container3786<>(1, true);
        String json = MAPPER.writeValueAsString(boolContainer);

        Container3786<?> result = MAPPER.readValue(json, new TypeReference<Container3786<?>>() { });
        assertNotNull(result);
        assertEquals(1, result.id());
        assertEquals(true, result.value());
    }
}