SerializationFeaturesTest.java

package tools.jackson.databind.ser;

import java.io.*;
import java.util.*;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;

import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.*;
import tools.jackson.databind.annotation.JsonSerialize;
import tools.jackson.databind.exc.InvalidDefinitionException;
import tools.jackson.databind.testutil.DatabindTestUtil;

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

/**
 * Unit tests for checking handling of some of {@link MapperFeature}s
 * and {@link SerializationFeature}s for serialization.
 */
public class SerializationFeaturesTest
    extends DatabindTestUtil
{
    static class CloseableBean implements AutoCloseable
    {
        public int a = 3;

        protected boolean wasClosed = false;

        @Override
        public void close() throws IOException {
            wasClosed = true;
        }
    }

    private static class StringListBean {
        @SuppressWarnings("unused")
        public Collection<String> values;

        public StringListBean(Collection<String> v) { values = v; }
    }

    static class Empty { }

    @JsonSerialize
    static class EmptyWithAnno { }

    @JsonSerialize(using=NonZeroSerializer.class)
    static class NonZero {
        public int nr;

        public NonZero(int i) { nr = i; }
    }

    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    static class NonZeroWrapper {
        public NonZero value;

        public NonZeroWrapper(int i) {
            value = new NonZero(i);
        }
    }

    static class NonZeroSerializer extends ValueSerializer<NonZero>
    {
        @Override
        public void serialize(NonZero value, JsonGenerator g, SerializationContext ctxt)
        {
            g.writeNumber(value.nr);
        }

        @Override
        public boolean isEmpty(SerializationContext ctxt, NonZero value) {
            if (value == null) return true;
            return (value.nr == 0);
        }
    }

    // For [JACKSON-666] ("SerializationFeature of the Beast!")
    @JsonPropertyOrder(alphabetic=true)
    static class GettersWithoutSetters
    {
        public int d = 0;

        @JsonCreator
        public GettersWithoutSetters(@JsonProperty("a") int a) { }

        // included, since there is a constructor property
        public int getA() { return 3; }

        // not included, as there's nothing matching
        public int getB() { return 4; }

        // include as there is setter
        public int getC() { return 5; }
        public void setC(int v) { }

        // and included, as there is a field
        public int getD() { return 6; }
    }

    // [JACKSON-806]: override 'need-setter' with explicit annotation
    static class GettersWithoutSetters2
    {
        @JsonProperty
        public int getA() { return 123; }
    }

    // for [databind#736]
    public static class Data736 {
        private int readonly;
        private int readwrite;

        public Data736() {
            readonly = 1;
            readwrite = 2;
        }

        public int getReadwrite() {
            return readwrite;
        }
        public void setReadwrite(int readwrite) {
            this.readwrite = readwrite;
        }
        public int getReadonly() {
            return readonly;
        }
    }

    /*
    /**********************************************************************
    /* Test methods, SerializationFeature.CLOSE_CLOSEABLE
    /**********************************************************************
     */

    private final ObjectMapper MAPPER = newJsonMapper();

    @SuppressWarnings("resource")
    @Test
    public void testCloseCloseable() throws Exception
    {
        // default should be disabled:
        CloseableBean bean = new CloseableBean();
        MAPPER.writeValueAsString(bean);
        assertFalse(bean.wasClosed);

        // via writer as well
        bean = new CloseableBean();
        MAPPER.writer()
            .writeValueAsString(bean);
        assertFalse(bean.wasClosed);
        
        // but can enable it:
        ObjectMapper mapper2 = jsonMapperBuilder()
                .enable(SerializationFeature.CLOSE_CLOSEABLE)
                .build();
        bean = new CloseableBean();
        mapper2.writeValueAsString(bean);
        assertTrue(bean.wasClosed);

        // and same via writer
        bean = new CloseableBean();
        mapper2.writer()
            .writeValueAsString(bean);
        assertTrue(bean.wasClosed);

        // also: let's ensure that ObjectWriter won't interfere with it
        bean = new CloseableBean();
        MAPPER.writerFor(CloseableBean.class)
            .with(SerializationFeature.CLOSE_CLOSEABLE)
            .writeValueAsString(bean);
        assertTrue(bean.wasClosed);
    }

    /*
    /**********************************************************************
    /* SerializationFeature.WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS
    /**********************************************************************
     */
    
    @Test
    public void testCharArrays() throws Exception
    {
        char[] chars = new char[] { 'a','b','c' };
        ObjectMapper m = new ObjectMapper();
        // default: serialize as Strings
        assertEquals(q("abc"), m.writeValueAsString(chars));

        // new feature: serialize as JSON array:
        assertEquals("[\"a\",\"b\",\"c\"]",
                m.writer()
                .with(SerializationFeature.WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS)
                .writeValueAsString(chars));
    }

    /*
    /**********************************************************************
    /* Test methods, SerializationFeature.FLUSH_AFTER_WRITE_VALUE
    /**********************************************************************
     */

    @Test
    public void testFlushingAutomatic() throws Exception
    {
        ObjectMapper mapper = new ObjectMapper();
        assertTrue(mapper.serializationConfig().isEnabled(SerializationFeature.FLUSH_AFTER_WRITE_VALUE));
        // default is to flush after writeValue()
        StringWriter sw = new StringWriter();
        mapper.writeValue(sw, Integer.valueOf(13));
        assertEquals("13", sw.toString());

        // ditto with ObjectWriter
        sw = new StringWriter();
        ObjectWriter ow = mapper.writer();
        ow.writeValue(sw, Integer.valueOf(99));
        assertEquals("99", sw.toString());
    }

    @Test
    public void testFlushingNotAutomatic() throws Exception
    {
        // but should not occur if configured otherwise
        ObjectMapper mapper = jsonMapperBuilder()
                .configure(SerializationFeature.FLUSH_AFTER_WRITE_VALUE, false)
                .build();
        StringWriter sw = new StringWriter();
        JsonGenerator g = mapper.createGenerator(sw);

        mapper.writeValue(g, Integer.valueOf(13));
        // no flushing now:
        assertEquals("", sw.toString());
        // except when actually flushing
        g.flush();
        assertEquals("13", sw.toString());
        g.close();
        // Also, same should happen with ObjectWriter
        sw = new StringWriter();
        g = mapper.createGenerator(sw);
        ObjectWriter ow = mapper.writer();
        ow.writeValue(g, Integer.valueOf(99));
        assertEquals("", sw.toString());
        // except when actually flushing
        g.flush();
        assertEquals("99", sw.toString());
        g.close();
    }

    /*
    /**********************************************************************
    /* Test methods, SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED
    /**********************************************************************
     */
    
    @Test
    public void testSingleElementCollections() throws Exception
    {
        final ObjectWriter writer = objectWriter()
                .with(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED);

        // Lists:
        ArrayList<String> strs = new ArrayList<String>();
        strs.add("xyz");
        assertEquals(q("xyz"), writer.writeValueAsString(strs));
        ArrayList<Integer> ints = new ArrayList<Integer>();
        ints.add(13);
        assertEquals("13", writer.writeValueAsString(ints));

        // other Collections, like Sets:
        HashSet<Long> longs = new HashSet<Long>();
        longs.add(42L);
        assertEquals("42", writer.writeValueAsString(longs));
        // [databind#180]
        final String EXP_STRINGS = "{\"values\":\"foo\"}";
        assertEquals(EXP_STRINGS, writer.writeValueAsString(new StringListBean(Collections.singletonList("foo"))));

        final Set<String> SET = new HashSet<String>();
        SET.add("foo");
        assertEquals(EXP_STRINGS, writer.writeValueAsString(new StringListBean(SET)));

        // arrays:
        assertEquals("true", writer.writeValueAsString(new boolean[] { true }));
        assertEquals("[true,false]", writer.writeValueAsString(new boolean[] { true, false }));
        assertEquals("true", writer.writeValueAsString(new Boolean[] { Boolean.TRUE }));

        assertEquals("3", writer.writeValueAsString(new short[] { 3 }));
        assertEquals("[3,2]", writer.writeValueAsString(new short[] { 3, 2 }));

        assertEquals("3", writer.writeValueAsString(new int[] { 3 }));
        assertEquals("[3,2]", writer.writeValueAsString(new int[] { 3, 2 }));

        assertEquals("1", writer.writeValueAsString(new long[] { 1L }));
        assertEquals("[-1,4]", writer.writeValueAsString(new long[] { -1L, 4L }));

        assertEquals("0.5", writer.writeValueAsString(new double[] { 0.5 }));
        assertEquals("[0.5,2.5]", writer.writeValueAsString(new double[] { 0.5, 2.5 }));

        assertEquals("0.5", writer.writeValueAsString(new float[] { 0.5f }));
        assertEquals("[0.5,2.5]", writer.writeValueAsString(new float[] { 0.5f, 2.5f }));

        assertEquals(q("foo"), writer.writeValueAsString(new String[] { "foo" }));
    }

    /*
    /**********************************************************************
    /* Test methods, SerializationFeature.FAIL_ON_EMPTY_BEANS
    /**********************************************************************
     */
    
    @Test
    public void testEmptyWithAnnotations() throws Exception
    {
        // First: without annotations, should complain
        try {
            MAPPER.writer()
                .with(SerializationFeature.FAIL_ON_EMPTY_BEANS)
                    .writeValueAsString(new Empty());
            fail("Should fail");
        } catch (InvalidDefinitionException e) {
            verifyException(e, "No serializer found for class");
        }

        // But not if there is a recognized annotation
        assertEquals("{}", MAPPER.writeValueAsString(new EmptyWithAnno()));

        // Including class annotation through mix-ins
        ObjectMapper m2 = jsonMapperBuilder()
                .addMixIn(Empty.class, EmptyWithAnno.class)
                .build();
        assertEquals("{}", m2.writeValueAsString(new Empty()));
    }

    /**
     * Alternative it is possible to use a feature to allow
     * serializing empty classes, too
     */
    @Test
    public void testEmptyWithFeature() throws Exception
    {
        // should be disabled by default as of 3.x
        assertFalse(MAPPER.isEnabled(SerializationFeature.FAIL_ON_EMPTY_BEANS));
        assertEquals("{}",
                MAPPER.writer()
                    .writeValueAsString(new Empty()));
    }

    @Test
    public void testCustomNoEmpty() throws Exception
    {
        // first non-empty:
        assertEquals("{\"value\":123}", MAPPER.writeValueAsString(new NonZeroWrapper(123)));
        // then empty:
        assertEquals("{}", MAPPER.writeValueAsString(new NonZeroWrapper(0)));
    }

    /*
    /**********************************************************************
    /* Test methods, SerializationFeature.REQUIRE_SETTERS_FOR_GETTERS
    /**********************************************************************
     */

    @Test
    public void testGettersWithoutSetters() throws Exception
    {
        assertFalse(MAPPER.isEnabled(MapperFeature.REQUIRE_SETTERS_FOR_GETTERS));

        // by default, all 4 found:
        GettersWithoutSetters bean = new GettersWithoutSetters(123);
        assertEquals("{\"a\":3,\"b\":4,\"c\":5,\"d\":6}", MAPPER.writeValueAsString(bean));

        // but 3 if we require mutator:
        ObjectMapper m = jsonMapperBuilder()
                .enable(MapperFeature.REQUIRE_SETTERS_FOR_GETTERS)
                .build();
        assertEquals("{\"a\":3,\"c\":5,\"d\":6}", m.writeValueAsString(bean));
    }

    @Test
    public void testGettersWithoutSettersOverride() throws Exception
    {
        ObjectMapper m = jsonMapperBuilder()
                .enable(MapperFeature.REQUIRE_SETTERS_FOR_GETTERS)
                .build();
        assertEquals("{\"a\":123}", m.writeValueAsString(new GettersWithoutSetters2()));
    }

    // for [databind#736]
    @Test
    public void testNeedForSetters() throws Exception
    {
        ObjectMapper mapper = jsonMapperBuilder()
                .changeDefaultVisibility(vc -> vc
                        .withVisibility(PropertyAccessor.ALL, Visibility.NONE)
                        .withVisibility(PropertyAccessor.FIELD, Visibility.NONE)
                        .withVisibility(PropertyAccessor.GETTER, Visibility.PUBLIC_ONLY)
                        .withVisibility(PropertyAccessor.SETTER, Visibility.PUBLIC_ONLY))
                .enable(MapperFeature.REQUIRE_SETTERS_FOR_GETTERS)
                .build();
        Data736 dataB = new Data736();

        String json = mapper.writeValueAsString(dataB);
        assertEquals(a2q("{'readwrite':2}"), json);
    }
}