NullContentHandling5165Test.java

package tools.jackson.databind.deser.jdk;

import java.util.List;
import java.util.Map;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.annotation.Nulls;

import tools.jackson.core.JsonParser;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.deser.std.StdDeserializer;
import tools.jackson.databind.exc.InvalidNullException;
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.*;

// For [databind#5139] and [databind#5165]:
// Tests for null content handling across Collection, Map, Object array,
// String array, and String collection deserializers.
public class NullContentHandling5165Test
    extends DatabindTestUtil
{
    // Custom deserializer that converts empty strings to null
    static class EmptyStringToNullDeserializer extends StdDeserializer<String> {
        public EmptyStringToNullDeserializer() {
            super(String.class);
        }

        @Override
        public String deserialize(JsonParser p, DeserializationContext ctxt) {
            String value = p.getValueAsString();
            if (value != null && value.isEmpty()) {
                return null;
            }
            return value;
        }
    }

    // [databind#5139]
    static class CollectionDst {
        private List<Integer> list;

        public List<Integer> getList() {
            return list;
        }

        public void setList(List<Integer> list) {
            this.list = list;
        }
    }

    // [databind#5165]
    static class MapDst {
        private Map<String, Integer> map;

        public Map<String, Integer> getMap() {
            return map;
        }

        public void setMap(Map<String, Integer> map) {
            this.map = map;
        }
    }

    // [databind#5165]
    static class ObjectArrayDst {
        public Integer[] array;
    }

    // [databind#5165]
    static class StringArrayDst {
        public String[] array;
    }

    // [databind#5165]
    static class StringCollectionDst {
        public List<String> list;
    }

    private ObjectMapper createMapperWithCustomDeserializer() {
        SimpleModule module = new SimpleModule()
            .addDeserializer(String.class, new EmptyStringToNullDeserializer());

        return JsonMapper.builder()
                .addModule(module)
                .changeDefaultNullHandling(n -> JsonSetter.Value.forContentNulls(Nulls.FAIL))
                .build();
    }

    /*
    /**********************************************************************
    /* Test methods, Collection [databind#5139]
    /**********************************************************************
     */

    @Test
    public void testNullsFail_Collection() {
        ObjectMapper mapper = JsonMapper.builder()
                .changeDefaultNullHandling(n -> JsonSetter.Value.forContentNulls(Nulls.FAIL))
                .build();

        assertThrows(
                InvalidNullException.class,
                () -> mapper.readValue("{\"list\":[\"\"]}", new TypeReference<CollectionDst>(){})
        );
    }

    @Test
    public void testNullsSkip_Collection() throws Exception {
        ObjectMapper mapper = JsonMapper.builder()
                .changeDefaultNullHandling(n -> JsonSetter.Value.forContentNulls(Nulls.SKIP))
                .build();

        CollectionDst dst = mapper.readValue("{\"list\":[\"\"]}", new TypeReference<CollectionDst>() {});

        assertTrue(dst.getList().isEmpty());
    }

    /*
    /**********************************************************************
    /* Test methods, Map [databind#5165]
    /**********************************************************************
     */

    @Test
    public void testNullsFail_Map() {
        ObjectMapper mapper = JsonMapper.builder()
                .changeDefaultNullHandling(n -> JsonSetter.Value.forContentNulls(Nulls.FAIL))
                .build();
        assertThrows(
                InvalidNullException.class,
                () -> mapper.readValue("{\"map\":{\"key\":\"\"}}", new TypeReference<MapDst>(){})
        );
    }

    @Test
    public void testNullsSkip_Map() throws Exception {
        ObjectMapper mapper = JsonMapper.builder()
                .changeDefaultNullHandling(n -> JsonSetter.Value.forContentNulls(Nulls.SKIP))
                .build();
        MapDst dst = mapper.readValue("{\"map\":{\"key\":\"\"}}", new TypeReference<MapDst>() {});
        assertTrue(dst.getMap().isEmpty());
    }

    /*
    /**********************************************************************
    /* Test methods, Object array [databind#5165]
    /**********************************************************************
     */

    @Test
    public void testNullsFail_ObjectArray() {
        ObjectMapper mapper = JsonMapper.builder()
                .changeDefaultNullHandling(n -> JsonSetter.Value.forContentNulls(Nulls.FAIL))
                .build();

        // NOTE! Relies on default coercion of "" into `null` for `Integer`s...
        assertThrows(
                InvalidNullException.class,
                () -> mapper.readValue("{\"array\":[\"\"]}", ObjectArrayDst.class)
        );
    }

    @Test
    public void testNullsSkip_ObjectArray() throws Exception {
        ObjectMapper mapper = JsonMapper.builder()
                .changeDefaultNullHandling(n -> JsonSetter.Value.forContentNulls(Nulls.SKIP))
                .build();

        ObjectArrayDst dst = mapper.readValue("{\"array\":[\"\"]}", ObjectArrayDst.class);
        // NOTE! Relies on default coercion of "" into `null` for `Integer`s...
        assertEquals(0, dst.array.length, "Null values should be skipped");
    }

    /*
    /**********************************************************************
    /* Test methods, String array [databind#5165]
    /**********************************************************************
     */

    @Test
    public void testNullsVanilla_StringArray() {
        ObjectMapper mapper = sharedMapper();
        String[] arr = mapper.readValue("[ ]", String[].class);
        assertEquals(0, arr.length);

        arr = mapper.readValue("[null ]", String[].class);
        assertEquals(1, arr.length);
        assertNull(arr[0]);

        arr = mapper.readValue("[ \"abc\", null ]", String[].class);
        assertEquals(2, arr.length);
        assertEquals("abc", arr[0]);
        assertNull(arr[1]);
    }

    @Test
    public void testNullsFail_StringArray() {
        ObjectMapper mapper = createMapperWithCustomDeserializer();

        assertThrows(
                InvalidNullException.class,
                () -> mapper.readValue("{\"array\":[\"\"]}", StringArrayDst.class)
        );
    }

    @Test
    public void testNullsSkip_StringArray() throws Exception {
        SimpleModule module = new SimpleModule()
                .addDeserializer(String.class, new EmptyStringToNullDeserializer());

        ObjectMapper mapper = JsonMapper.builder()
                .addModule(module)
                .changeDefaultNullHandling(n -> JsonSetter.Value.forContentNulls(Nulls.SKIP))
                .build();

        StringArrayDst dst = mapper.readValue("{\"array\":[\"\"]}", StringArrayDst.class);
        assertEquals(0, dst.array.length, "Null values should be skipped");
    }

    /*
    /**********************************************************************
    /* Test methods, String collection [databind#5165]
    /**********************************************************************
     */

    @Test
    public void testNullsFail_StringCollection() {
        ObjectMapper mapper = createMapperWithCustomDeserializer();

        assertThrows(
                InvalidNullException.class,
                () -> mapper.readValue("{\"list\":[\"\"]}", StringCollectionDst.class)
        );
    }

    @Test
    public void testNullsSkip_StringCollection() throws Exception {
        SimpleModule module = new SimpleModule()
                .addDeserializer(String.class, new EmptyStringToNullDeserializer());

        ObjectMapper mapper = JsonMapper.builder()
                .addModule(module)
                .changeDefaultNullHandling(n -> JsonSetter.Value.forContentNulls(Nulls.SKIP))
                .build();

        StringCollectionDst dst = mapper.readValue("{\"list\":[\"\"]}", StringCollectionDst.class);

        assertTrue(dst.list.isEmpty(), "Null values should be skipped");
    }
}