JsonIncludeCollectionTest.java

package tools.jackson.databind.ser.filter;

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

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.annotation.JsonInclude;

import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.SerializationFeature;
import tools.jackson.databind.testutil.DatabindTestUtil;

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

public class JsonIncludeCollectionTest extends DatabindTestUtil
{
    // Basic NON_EMPTY on EnumSet field
    static class NonEmptyEnumSet {
        @JsonInclude(JsonInclude.Include.NON_EMPTY)
        public EnumSet<ABC> v;

        public NonEmptyEnumSet(ABC...values) {
            if (values.length == 0) {
                v = EnumSet.noneOf(ABC.class);
            } else {
                v = EnumSet.copyOf(Arrays.asList(values));
            }
        }
    }

    // [databind#5369] Content filtering on collections

    // String "foo" filter
    static class FooFilter {
        @Override
        public boolean equals(Object other) {
            if (other == null) {
                return false;
            }
            return "foo".equals(other);
        }
    }

    static class FooListBean {
        @JsonInclude(content = JsonInclude.Include.CUSTOM,
                contentFilter = FooFilter.class)
        public List<String> items = new ArrayList<>();

        FooListBean add(String value) {
            items.add(value);
            return this;
        }
    }

    static class FooSetBean {
        @JsonInclude(content = JsonInclude.Include.CUSTOM,
                contentFilter = FooFilter.class)
        public Set<String> items = new LinkedHashSet<>();

        FooSetBean add(String value) {
            items.add(value);
            return this;
        }
    }

    static class NonNullListBean {
        @JsonInclude(content = JsonInclude.Include.NON_NULL)
        public List<String> items = new ArrayList<>();

        NonNullListBean add(String value) {
            items.add(value);
            return this;
        }
    }

    static class NonEmptyListBean {
        @JsonInclude(content = JsonInclude.Include.NON_EMPTY)
        public List<String> items = new ArrayList<>();

        NonEmptyListBean add(String value) {
            items.add(value);
            return this;
        }
    }

    static class NonDefaultListBean {
        @JsonInclude(content = JsonInclude.Include.NON_DEFAULT)
        public List<String> items = new ArrayList<>();

        NonDefaultListBean add(String value) {
            items.add(value);
            return this;
        }
    }

    static class IntegerFilter {
        @Override
        public boolean equals(Object other) {
            return Integer.valueOf(42).equals(other);
        }
    }

    static class IntegerListPojo {
        @JsonInclude(content = JsonInclude.Include.CUSTOM,
                contentFilter = IntegerFilter.class)
        public List<Integer> values = new ArrayList<>();

        IntegerListPojo add(int v) {
            values.add(v);
            return this;
        }
    }

    static class ShortFilter {
        @Override
        public boolean equals(Object other) {
            return Short.valueOf((short) 7).equals(other);
        }
    }

    static class ShortListPojo {
        @JsonInclude(content = JsonInclude.Include.CUSTOM,
                contentFilter = ShortFilter.class)
        public List<Short> values = new ArrayList<>();

        ShortListPojo add(short v) {
            values.add(v);
            return this;
        }
    }

    static class ByteFilter {
        @Override
        public boolean equals(Object other) {
            return Byte.valueOf((byte) 9).equals(other);
        }
    }

    static class ByteListPojo {
        @JsonInclude(content = JsonInclude.Include.CUSTOM,
                contentFilter = ByteFilter.class)
        public List<Byte> values = new ArrayList<>();

        ByteListPojo add(byte v) {
            values.add(v);
            return this;
        }
    }

    static class LongFilter {
        @Override
        public boolean equals(Object other) {
            return Long.valueOf(100L).equals(other);
        }
    }

    static class LongListPojo {
        @JsonInclude(content = JsonInclude.Include.CUSTOM,
                contentFilter = LongFilter.class)
        public List<Long> values = new ArrayList<>();

        LongListPojo add(long v) {
            values.add(v);
            return this;
        }
    }

    static class DoubleFilter {
        @Override
        public boolean equals(Object other) {
            return Double.valueOf(1.25).equals(other);
        }
    }

    static class DoubleListPojo {
        @JsonInclude(content = JsonInclude.Include.CUSTOM,
                contentFilter = DoubleFilter.class)
        public List<Double> values = new ArrayList<>();

        DoubleListPojo add(double v) {
            values.add(v);
            return this;
        }
    }

    static class CountingFooFilter {
        static final AtomicInteger counter = new AtomicInteger();

        @Override
        public boolean equals(Object other) {
            counter.incrementAndGet();
            return "foo".equals(other);
        }
    }

    static class CountingFooListBean {
        @JsonInclude(content = JsonInclude.Include.CUSTOM,
                contentFilter = CountingFooFilter.class)
        public List<String> items = new ArrayList<>();

        CountingFooListBean add(String value) {
            items.add(value);
            return this;
        }
    }

    static class NumberFilter {
        @Override
        public boolean equals(Object other) {
            if (other == null) {
                return false;
            }
            return Integer.valueOf(42).equals(other);
        }
    }

    static class NumberListBean {
        @JsonInclude(content = JsonInclude.Include.CUSTOM,
                contentFilter = NumberFilter.class)
        public List<Integer> numbers = new ArrayList<>();

        public NumberListBean add(Integer value) {
            numbers.add(value);
            return this;
        }
    }

    static class SimpleList5369Bean {
        public List<String> values = new ArrayList<>();

        SimpleList5369Bean add(String v) {
            values.add(v);
            return this;
        }
    }

    enum Test5369Enum { A, FOO, B }

    static class FooEnum5369Filter {
        @Override
        public boolean equals(Object other) {
            return Test5369Enum.FOO.equals(other);
        }
    }

    static class EnumSet5369Bean {
        @JsonInclude(
                content = JsonInclude.Include.CUSTOM,
                contentFilter = FooEnum5369Filter.class
        )
        public EnumSet<Test5369Enum> values;

        EnumSet5369Bean(EnumSet<Test5369Enum> v) { values = v; }
    }

    static class Iterable5369 implements Iterable<Integer>
    {
        private final List<Integer> values;

        public Iterable5369(Integer... ints) { values = Arrays.asList(ints); }

        @Override
        public Iterator<Integer> iterator() { return values.iterator(); }
    }

    static class BeanWithIterableIncludeNonNull
    {
        @JsonInclude(content = JsonInclude.Include.NON_NULL)
        public Iterable<Integer> values = new Iterable5369(1);
    }

    static class IntegerOnly5369Filter {
        @Override
        public boolean equals(Object other) {
            return !Integer.valueOf(5369).equals(other);
        }
    }

    static class BeanWithIterableCustomInclude
    {
        @JsonInclude(
            content = JsonInclude.Include.CUSTOM,
            contentFilter = IntegerOnly5369Filter.class
        )
        public Iterable<Integer> values = new Iterable5369(1);
    }

    private final ObjectMapper MAPPER = newJsonMapper();

    // [databind#5369]
    private final ObjectMapper MAPPER_CONTAINERS = jsonMapperBuilder()
            .enable(SerializationFeature.APPLY_JSON_INCLUDE_FOR_CONTAINERS)
            .build();

    /*
    /**********************************************************************
    /* Test methods, @JsonInclude(NON_EMPTY) on collection field
    /**********************************************************************
     */

    @Test
    public void testEnumSet() throws Exception
    {
        assertEquals("{}", MAPPER.writeValueAsString(new NonEmptyEnumSet()));
        assertEquals("{\"v\":[\"B\"]}", MAPPER.writeValueAsString(new NonEmptyEnumSet(ABC.B)));
    }

    /*
    /**********************************************************************
    /* Test methods, content filtering on collections [databind#5369]
    /**********************************************************************
     */

    // [databind#5369]
    @Test
    public void testCustomFilterWithList() throws Exception {
        assertEquals(a2q("{'items':['1','2']}"),
                MAPPER_CONTAINERS.writeValueAsString(new FooListBean().add("1").add("foo").add("2")));
    }

    @Test
    public void testNonNullContentInclusion() throws Exception {
        assertEquals(a2q("{'items':['1','2']}"),
                MAPPER_CONTAINERS.writeValueAsString(new NonNullListBean().add("1").add(null).add("2")));
    }

    @Test
    public void testNonEmptyContentInclusion() throws Exception {
        assertEquals(a2q("{'items':['1','2']}"),
                MAPPER_CONTAINERS.writeValueAsString(new NonEmptyListBean().add("1").add("").add("2")));
    }

    @Test
    public void testNonDefaultContentInclusion() throws Exception {
        assertEquals(a2q("{'items':['1','2']}"),
                MAPPER_CONTAINERS.writeValueAsString(new NonDefaultListBean().add("1").add(null).add("2")));
    }

    @Test
    public void testCustomFilterWithSet() throws Exception {
        assertEquals(a2q("{'items':['1','2']}"),
                MAPPER_CONTAINERS.writeValueAsString(new FooSetBean().add("1").add("foo").add("2")));
    }

    @Test
    public void testCustomFilterWithIntegerList() throws Exception {
        assertEquals(a2q("{'values':[1,2]}"),
                MAPPER_CONTAINERS.writeValueAsString(new IntegerListPojo().add(1).add(42).add(2)));
    }

    @Test
    public void testCustomFilterWithShortList() throws Exception {
        assertEquals(a2q("{'values':[1,2]}"),
                MAPPER_CONTAINERS.writeValueAsString(
                        new ShortListPojo().add((short) 1).add((short) 7).add((short) 2)));
    }

    @Test
    public void testCustomFilterWithByteList() throws Exception {
        assertEquals(a2q("{'values':[1,2]}"),
                MAPPER_CONTAINERS.writeValueAsString(
                        new ByteListPojo().add((byte) 1).add((byte) 9).add((byte) 2)));
    }

    @Test
    public void testCustomFilterWithDoubleList() throws Exception {
        assertEquals(a2q("{'values':[0.5,2.5]}"),
                MAPPER_CONTAINERS.writeValueAsString(
                        new DoubleListPojo().add(0.5).add(1.25).add(2.5)));
    }

    @Test
    public void testCustomFilterWithLongList() throws Exception {
        assertEquals(a2q("{'values':[10,20]}"),
                MAPPER_CONTAINERS.writeValueAsString(
                        new LongListPojo().add(10L).add(100L).add(20L)));
    }

    @Test
    public void testCustomFilterWithNumbers() throws Exception {
        assertEquals(a2q("{'numbers':[1,3]}"),
                MAPPER_CONTAINERS.writeValueAsString(new NumberListBean().add(1).add(42).add(3)));
    }

    @Test
    public void testEmptyListWithCustomFilter() throws Exception {
        assertEquals(a2q("{'items':[]}"),
                MAPPER_CONTAINERS.writeValueAsString(new FooListBean()));
    }

    @Test
    public void testAllFilteredOut() throws Exception {
        assertEquals(a2q("{'items':[]}"),
                MAPPER_CONTAINERS.writeValueAsString(
                        new FooListBean().add("foo").add("foo").add("foo")));
    }

    @Test
    public void testMixedNullsAndFiltered() throws Exception {
        // Custom filter should not filter nulls (based on FooFilter.equals implementation)
        assertEquals(a2q("{'items':['1',null,'2',null]}"),
                MAPPER_CONTAINERS.writeValueAsString(
                        new FooListBean().add("1").add(null).add("foo").add("2").add(null)));
    }

    @Test
    public void testContentIncludeOverrideForCollection() throws Exception {
        ObjectMapper mapper = jsonMapperBuilder()
                .enable(SerializationFeature.APPLY_JSON_INCLUDE_FOR_CONTAINERS)
                .withConfigOverride(List.class,
                        o -> o.setInclude(JsonInclude.Value.empty().withContentFilter(FooFilter.class)))
                .build();

        assertEquals(a2q("{'values':['1','2']}"),
                mapper.writeValueAsString(new SimpleList5369Bean().add("1").add("foo").add("2")));
    }

    @Test
    public void testContentIncludeOverrideForList() throws Exception {
        ObjectMapper mapper = jsonMapperBuilder()
                .enable(SerializationFeature.APPLY_JSON_INCLUDE_FOR_CONTAINERS)
                .withConfigOverride(List.class,
                        o -> o.setInclude(JsonInclude.Value.empty().withContentFilter(FooFilter.class)))
                .build();

        assertEquals(a2q("{'values':['1','2']}"),
                mapper.writeValueAsString(new SimpleList5369Bean().add("1").add("foo").add("2")));
    }

    @Test
    public void testEnumSetWithContentFilter() throws Exception
    {
        assertEquals(a2q("{'values':['A','B']}"),
                MAPPER_CONTAINERS.writeValueAsString(
                        new EnumSet5369Bean(EnumSet.of(Test5369Enum.A, Test5369Enum.FOO, Test5369Enum.B))));
    }

    @Test
    public void testIterableWithContentFilteringForNulls() throws Exception
    {
        BeanWithIterableIncludeNonNull pojo = new BeanWithIterableIncludeNonNull();
        pojo.values = new Iterable5369(1, null, 2, null, 3);

        assertEquals("{\"values\":[1,2,3]}", MAPPER_CONTAINERS.writeValueAsString(pojo));
    }

    @Test
    public void testIterableWithContentFilteringMagicNumber() throws Exception
    {
        BeanWithIterableCustomInclude pojo = new BeanWithIterableCustomInclude();
        pojo.values = new Iterable5369(1, null, 2, 3, 5369);

        assertEquals("{\"values\":[5369]}", MAPPER_CONTAINERS.writeValueAsString(pojo));
    }
}