ValueSerializerModifierTest.java

package tools.jackson.databind.ser;

import java.util.*;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.annotation.*;

import tools.jackson.core.*;
import tools.jackson.databind.*;
import tools.jackson.databind.exc.InvalidDefinitionException;
import tools.jackson.databind.introspect.AnnotatedField;
import tools.jackson.databind.introspect.POJOPropertyBuilder;
import tools.jackson.databind.module.SimpleModule;
import tools.jackson.databind.ser.std.StdSerializer;
import tools.jackson.databind.testutil.DatabindTestUtil;
import tools.jackson.databind.type.ArrayType;
import tools.jackson.databind.type.CollectionType;
import tools.jackson.databind.type.MapType;

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

/**
 * Unit tests for verifying that it is possible to configure
 * construction of {@link BeanSerializer} instances.
 */
@SuppressWarnings("serial")
public class ValueSerializerModifierTest extends DatabindTestUtil
{
    static class SerializerModifierModule extends SimpleModule
    {
        protected ValueSerializerModifier modifier;

        public SerializerModifierModule(ValueSerializerModifier modifier)
        {
            super("test", Version.unknownVersion());
            this.modifier = modifier;
        }

        @Override
        public void setupModule(SetupContext context)
        {
            super.setupModule(context);
            if (modifier != null) {
                context.addSerializerModifier(modifier);
            }
        }
    }

    @JsonPropertyOrder({"b", "a"})
    static class Bean {
        public String b = "b";
        public String a = "a";
    }

    static class RemovingModifier extends ValueSerializerModifier
    {
        private final String _removedProperty;

        public RemovingModifier(String remove) { _removedProperty = remove; }

        @Override
        public List<BeanPropertyWriter> changeProperties(SerializationConfig config,
                BeanDescription.Supplier beanDesc,
                List<BeanPropertyWriter> beanProperties)
        {
            Iterator<BeanPropertyWriter> it = beanProperties.iterator();
            while (it.hasNext()) {
                BeanPropertyWriter bpw = it.next();
                if (bpw.getName().equals(_removedProperty)) {
                    it.remove();
                }
            }
            return beanProperties;
        }
    }

    static class ReorderingModifier extends ValueSerializerModifier
    {
        @Override
        public List<BeanPropertyWriter> orderProperties(SerializationConfig config,
                BeanDescription.Supplier beanDesc, List<BeanPropertyWriter> beanProperties)
        {
            TreeMap<String,BeanPropertyWriter> props = new TreeMap<String,BeanPropertyWriter>();
            for (BeanPropertyWriter bpw : beanProperties) {
                props.put(bpw.getName(), bpw);
            }
            return new ArrayList<BeanPropertyWriter>(props.values());
        }
    }

    static class ReplacingModifier extends ValueSerializerModifier
    {
        private final ValueSerializer<?> _serializer;

        public ReplacingModifier(ValueSerializer<?> s) { _serializer = s; }

        @Override
        public ValueSerializer<?> modifySerializer(SerializationConfig config,
                BeanDescription.Supplier beanDesc, ValueSerializer<?> serializer) {
            return _serializer;
        }
    }

    static class BuilderModifier extends ValueSerializerModifier
    {
        private final ValueSerializer<?> _serializer;

        public BuilderModifier(ValueSerializer<?> ser) {
            _serializer = ser;
        }

        @Override
        public BeanSerializerBuilder updateBuilder(SerializationConfig config,
                BeanDescription.Supplier beanDesc, BeanSerializerBuilder builder) {
            return new BogusSerializerBuilder(builder, _serializer);
        }
    }

    static class BogusSerializerBuilder extends BeanSerializerBuilder
    {
        private final ValueSerializer<?> _serializer;

        public BogusSerializerBuilder(BeanSerializerBuilder src,
                ValueSerializer<?> ser) {
            super(src);
            _serializer = ser;
        }

        @Override
        public ValueSerializer<?> build() {
            return _serializer;
        }
    }

    static class BogusBeanSerializer extends StdSerializer<Object>
    {
        private final int _value;

        public BogusBeanSerializer(int v) {
            super(Object.class);
            _value = v;
        }

        @Override
        public void serialize(Object value, JsonGenerator g,
                SerializationContext provider) {
            g.writeNumber(_value);
        }
    }

    static class EmptyBean {
        @JsonIgnore
        public String name = "foo";
    }

    static class EmptyBeanModifier extends ValueSerializerModifier
    {
        @Override
        public List<BeanPropertyWriter> changeProperties(SerializationConfig config,
                BeanDescription.Supplier beanDesc, List<BeanPropertyWriter> beanProperties)
        {
            JavaType strType = config.constructType(String.class);
            // we need a valid BeanPropertyDefinition; this will do (just need name to match)
            POJOPropertyBuilder prop = new POJOPropertyBuilder(config, null, true, new PropertyName("bogus"));
            try {
                AnnotatedField f = new AnnotatedField(null, EmptyBean.class.getDeclaredField("name"), null);
                beanProperties.add(new BeanPropertyWriter(prop, f, null,
                        strType,
                        null, null, strType,
                        false, null,
                        null));
            } catch (NoSuchFieldException e) {
                throw new IllegalStateException(e.getMessage());
            }
            return beanProperties;
        }
    }

    // [Issue#539]: use post-modifier
    static class EmptyBeanModifier539 extends ValueSerializerModifier
    {
        @Override
        public List<BeanPropertyWriter> changeProperties(SerializationConfig config,
                BeanDescription.Supplier beanDesc, List<BeanPropertyWriter> beanProperties)
        {
            return beanProperties;
        }

        @Override
        public ValueSerializer<?> modifySerializer(SerializationConfig config,
                BeanDescription.Supplier beanDesc, ValueSerializer<?> serializer) {
            return new BogusBeanSerializer(42);
        }
    }
    // [databind#120], arrays, collections, maps

    static class ArraySerializerModifier extends ValueSerializerModifier {
        @Override
        public ValueSerializer<?> modifyArraySerializer(SerializationConfig config,
                ArrayType valueType, BeanDescription.Supplier beanDesc, ValueSerializer<?> serializer) {
            return new StdSerializer<Object>(Object.class) {
                @Override public void serialize(Object value, JsonGenerator g, SerializationContext provider) {
                    g.writeNumber(123);
                }
            };
        }
    }

    static class CollectionSerializerModifier extends ValueSerializerModifier {
        @Override
        public ValueSerializer<?> modifyCollectionSerializer(SerializationConfig config,
                CollectionType valueType, BeanDescription.Supplier beanDesc, ValueSerializer<?> serializer) {
            return new StdSerializer<Object>(Object.class) {
                @Override public void serialize(Object value, JsonGenerator g, SerializationContext provider) {
                    g.writeNumber(123);
                }
            };
        }
    }

    static class MapSerializerModifier extends ValueSerializerModifier {
        @Override
        public ValueSerializer<?> modifyMapSerializer(SerializationConfig config,
                MapType valueType, BeanDescription.Supplier beanDesc, ValueSerializer<?> serializer) {
            return new StdSerializer<Object>(Object.class) {
                @Override public void serialize(Object value, JsonGenerator g, SerializationContext provider) {
                    g.writeNumber(123);
                }
            };
        }
    }

    static class EnumSerializerModifier extends ValueSerializerModifier {
        @Override
        public ValueSerializer<?> modifyEnumSerializer(SerializationConfig config,
                JavaType valueType, BeanDescription.Supplier beanDesc, ValueSerializer<?> serializer) {
            return new StdSerializer<Object>(Object.class) {
                @Override public void serialize(Object value, JsonGenerator g, SerializationContext provider) {
                    g.writeNumber(123);
                }
            };
        }
    }

    static class KeySerializerModifier extends ValueSerializerModifier {
        @Override
        public ValueSerializer<?> modifyKeySerializer(SerializationConfig config,
                JavaType valueType, BeanDescription.Supplier beanDesc, ValueSerializer<?> serializer) {
            return new StdSerializer<Object>(Object.class) {
                @Override public void serialize(Object value, JsonGenerator g, SerializationContext provider) {
                    g.writeName("foo");
                }
            };
        }
    }

    // [databind#1612]
    @JsonPropertyOrder({ "a", "b", "c" })
    static class Bean1612 {
        public Integer a;
        public Integer b;
        public Double c;

        public Bean1612(Integer a, Integer b, Double c) {
            this.a = a;
            this.b = b;
            this.c = c;
        }
    }

    static class Modifier1612 extends ValueSerializerModifier {
        private static final long serialVersionUID = 1L;

        @Override
        public BeanSerializerBuilder updateBuilder(SerializationConfig config,
                BeanDescription.Supplier beanDescRef,
                BeanSerializerBuilder builder) {
            List<BeanPropertyWriter> filtered = new ArrayList<BeanPropertyWriter>(2);
            List<BeanPropertyWriter> properties = builder.getProperties();
            //Make the filtered properties list bigger
            builder.setFilteredProperties(new BeanPropertyWriter[] {properties.get(0), properties.get(1), properties.get(2)});

            //The props will be shorter
            filtered.add(properties.get(1));
            filtered.add(properties.get(2));
            builder.setProperties(filtered);
            return builder;
        }
    }

    /*
    /**********************************************************************
    /* Test methods
    /**********************************************************************
     */

    @Test
    public void testPropertyRemoval() throws Exception
    {
        ObjectMapper mapper = jsonMapperBuilder()
                .addModule(new SerializerModifierModule(new RemovingModifier("a")))
                .build();
        Bean bean = new Bean();
        assertEquals("{\"b\":\"b\"}", mapper.writeValueAsString(bean));
    }

    @Test
    public void testPropertyReorder() throws Exception
    {
        ObjectMapper mapper = jsonMapperBuilder()
                .addModule(new SerializerModifierModule(new ReorderingModifier()))
                .build();
        Bean bean = new Bean();
        assertEquals("{\"a\":\"a\",\"b\":\"b\"}", mapper.writeValueAsString(bean));
    }

    @Test
    public void testBuilderReplacement() throws Exception
    {
        ObjectMapper mapper = jsonMapperBuilder()
                .addModule(new SerializerModifierModule(new BuilderModifier(new BogusBeanSerializer(17))))
                .build();
        Bean bean = new Bean();
        assertEquals("17", mapper.writeValueAsString(bean));
    }

    @Test
    public void testSerializerReplacement() throws Exception
    {
        ObjectMapper mapper = jsonMapperBuilder()
                .addModule(new SerializerModifierModule(new ReplacingModifier(new BogusBeanSerializer(123))))
                .build();
        Bean bean = new Bean();
        assertEquals("123", mapper.writeValueAsString(bean));
    }

    @Test
    public void testEmptyBean() throws Exception
    {
        ObjectMapper mapper = jsonMapperBuilder()
                .addModule(new SimpleModule("test", Version.unknownVersion()) {
            @Override
            public void setupModule(SetupContext context)
            {
                super.setupModule(context);
                context.addSerializerModifier(new EmptyBeanModifier());
            }
                })
                .build();
        String json = mapper.writeValueAsString(new EmptyBean());
        assertEquals("{\"bogus\":\"foo\"}", json);
    }

    @Test
    public void testEmptyBean539() throws Exception
    {
        ObjectMapper mapper = jsonMapperBuilder()
                .addModule(new SimpleModule("test", Version.unknownVersion()) {
            @Override
            public void setupModule(SetupContext context)
            {
                super.setupModule(context);
                context.addSerializerModifier(new EmptyBeanModifier539());
            }
                })
                .build();
        String json = mapper.writeValueAsString(new EmptyBean());
        assertEquals("42", json);
    }

    // [databind#121]

    @Test
    public void testModifyArraySerializer() throws Exception
    {
        ObjectMapper mapper = jsonMapperBuilder()
                .addModule(new SimpleModule("test")
                        .setSerializerModifier(new ArraySerializerModifier()))
                .build();
        assertEquals("123", mapper.writeValueAsString(new Integer[] { 1, 2 }));
    }

    @Test
    public void testModifyCollectionSerializer() throws Exception
    {
        ObjectMapper mapper = jsonMapperBuilder()
                .addModule(new SimpleModule("test")
                        .setSerializerModifier(new CollectionSerializerModifier()))
                .build();
        assertEquals("123", mapper.writeValueAsString(new ArrayList<Integer>()));
    }

    @Test
    public void testModifyMapSerializer() throws Exception
    {
        ObjectMapper mapper = jsonMapperBuilder()
                .addModule(new SimpleModule("test")
                        .setSerializerModifier(new MapSerializerModifier()))
                .build();
        assertEquals("123", mapper.writeValueAsString(new HashMap<String,String>()));
    }

    @Test
    public void testModifyEnumSerializer() throws Exception
    {
        ObjectMapper mapper = jsonMapperBuilder()
                .addModule(new SimpleModule("test")
                        .setSerializerModifier(new EnumSerializerModifier()))
                .build();
        assertEquals("123", mapper.writeValueAsString(ABC.C));
    }

    @Test
    public void testModifyKeySerializer() throws Exception
    {
        ObjectMapper mapper = jsonMapperBuilder()
                .addModule(new SimpleModule("test")
                        .setSerializerModifier(new KeySerializerModifier()))
                .build();
        Map<String,Integer> map = new HashMap<String,Integer>();
        map.put("x", 3);
        assertEquals("{\"foo\":3}", mapper.writeValueAsString(map));
    }

    // [databind#1612]
    @Test
    public void modifierIssue1612Test() throws Exception
    {
        SimpleModule mod = new SimpleModule();
        mod.setSerializerModifier(new Modifier1612());
        ObjectMapper mapper = jsonMapperBuilder()
                .addModule(mod)
                .build();
        try {
            mapper.writeValueAsString(new Bean1612(0, 1, 2d));
            fail("Should not pass");
        } catch (InvalidDefinitionException e) {
            verifyException(e, "Failed to construct BeanSerializer");
            verifyException(e, Bean1612.class.getName());
        }
    }

}