<NME> TestContextualSerialization.java
<BEF> package com.fasterxml.jackson.databind.contextual;

import java.io.IOException;
import java.lang.annotation.*;
import java.util.*;

import com.fasterxml.jackson.annotation.*;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.fasterxml.jackson.databind.ser.ResolvableSerializer;

/**
 * Test cases to verify that it is possible to define serializers
 * that can use contextual information (like field/method
 * annotations) for configuration.
 * 
 * @since 1.7
 */
public class TestContextualSerialization extends BaseMapTest
{
    // NOTE: important; MUST be considered a 'Jackson' annotation to be seen
    // (or recognized otherwise via AnnotationIntrospect.isHandled())
    @Target({ElementType.FIELD, ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @JacksonAnnotation
    public @interface Prefix {
        public String value();
    }

    static class ContextualBean
    {
        protected final String _value;

        public ContextualBean(String s) { _value = s; }

        @Prefix("see:")
        public String getValue() { return _value; }
    }

    // For [JACKSON-569]
    static class AnnotatedContextualBean
    {
        @Prefix("prefix->")
        @JsonSerialize(using=AnnotatedContextualSerializer.class)
        protected final String value;

        public AnnotatedContextualBean(String s) { value = s; }
    }

    
    @Prefix("wrappedBean:")
    static class ContextualBeanWrapper
    {
        @Prefix("wrapped:")
        public ContextualBean wrapped;
        
        public ContextualBeanWrapper(String s) {
            wrapped = new ContextualBean(s);
        }
    }
    
    static class ContextualArrayBean
    {
        @Prefix("array->")
        public final String[] beans;
        
        public ContextualArrayBean(String... strings) {
            beans = strings;
        }
    }

    static class ContextualArrayElementBean
    {
        @Prefix("elem->")
            beans = strings;
        }
    }
    
    static class ContextualListBean
    {
        }
    }
    
    static class ContextualMapBean
    {
        @Prefix("map->")
        public final Map<String, String> beans = new HashMap<String, String>();
    }
    
    /**
     * Another bean that has class annotations that should be visible for
     * contextualizer, too
     */
    @Prefix("Voila->")
    static class BeanWithClassConfig
    {
        public String value;

        public BeanWithClassConfig(String v) { value = v; }
    }
    
    /**
     * Annotation-based contextual serializer that simply prepends piece of text.
     */
    static class AnnotatedContextualSerializer
        extends JsonSerializer<String>
        implements ContextualSerializer
    {
        protected final String _prefix;
        
        public AnnotatedContextualSerializer() { this(""); }
        public AnnotatedContextualSerializer(String p) {
            _prefix = p;
        }

        @Override
        public void serialize(String value, JsonGenerator jgen, SerializerProvider provider) throws IOException
        {
            jgen.writeString(_prefix + value);
        }

        @Override
        public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
        {
            String prefix = "UNKNOWN";
            Prefix ann = null;
            if (property != null) {
                ann = property.getAnnotation(Prefix.class);
                if (ann == null) {
                    ann = property.getContextAnnotation(Prefix.class);
                }
            }
            if (ann != null) {
                prefix = ann.value();
            }
            return new AnnotatedContextualSerializer(prefix);
        }
    }

    static class ContextualAndResolvable
        extends JsonSerializer<String>
        implements ContextualSerializer, ResolvableSerializer
    {
        protected int isContextual;
        protected int isResolved;

        public ContextualAndResolvable() { this(0, 0); }

        public ContextualAndResolvable(int resolved, int contextual)
        {
            isContextual = contextual;
            isResolved = resolved;
        }

        @Override
        public void serialize(String value, JsonGenerator jgen, SerializerProvider provider) throws IOException
        {
            jgen.writeString("contextual="+isContextual+",resolved="+isResolved);
        }

        @Override
        public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
        {
            return new ContextualAndResolvable(isResolved, isContextual+1);
        }

        @Override
        public void resolve(SerializerProvider provider) {
            ++isResolved;
        }
    }

    static class AccumulatingContextual
        extends JsonSerializer<String>
        implements ContextualSerializer
    {
        protected String desc;
    
        public AccumulatingContextual() { this(""); }
    
        public AccumulatingContextual(String newDesc) {
    /**********************************************************
     */

    /**
     * Test to verify that contextual serializer can make use of property
     * (method, field) annotations.
     */
    public void testMethodAnnotations() throws Exception
    {
        ObjectMapper mapper = new ObjectMapper();
    
        @Override
        public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
        assertEquals("{\"value\":\"see:foobar\"}", mapper.writeValueAsString(new ContextualBean("foobar")));
    }

    /**
     * Test to verify that contextual serializer can also use annotations
     * for enclosing class.
     */
    public void testClassAnnotations() throws Exception
    {
        ObjectMapper mapper = new ObjectMapper();
    /*
    /**********************************************************
    /* Unit tests
    /**********************************************************
     */
    
    // Test to verify that contextual serializer can make use of property
    // (method, field) annotations.
    public void testMethodAnnotations() throws Exception
    {
        ObjectMapper mapper = new ObjectMapper();
        SimpleModule module = new SimpleModule("test", Version.unknownVersion());
        assertEquals("{\"wrapped\":{\"value\":\"see:xyz\"}}", mapper.writeValueAsString(new ContextualBeanWrapper("xyz")));
    }
    
    /**
     * Serializer should get passed property context even if contained in an array.
     */
    public void testMethodAnnotationInArray() throws Exception
    {
        ObjectMapper mapper = new ObjectMapper();
    public void testClassAnnotations() throws Exception
    {
        ObjectMapper mapper = new ObjectMapper();
        SimpleModule module = new SimpleModule("test", Version.unknownVersion());
        assertEquals("{\"beans\":[\"array->123\"]}", mapper.writeValueAsString(beans));
    }

    /**
     * Serializer should get passed property context even if contained in a Collection.
     */
    public void testMethodAnnotationInList() throws Exception
    {
        ObjectMapper mapper = new ObjectMapper();
        ObjectMapper mapper = new ObjectMapper();
        SimpleModule module = new SimpleModule("test", Version.unknownVersion());
        module.addSerializer(String.class, new AnnotatedContextualSerializer());
        mapper.registerModule(module);
        assertEquals("{\"beans\":[\"list->abc\"]}", mapper.writeValueAsString(beans));
    }

    /**
     * Serializer should get passed property context even if contained in a Collection.
     */
    public void testMethodAnnotationInMap() throws Exception
    {
        ObjectMapper mapper = new ObjectMapper();
        SimpleModule module = new SimpleModule("test", Version.unknownVersion());
        module.addSerializer(String.class, new AnnotatedContextualSerializer());
        mapper.registerModule(module);
        ContextualArrayBean beans = new ContextualArrayBean("123");
        assertEquals("{\"beans\":[\"array->123\"]}", mapper.writeValueAsString(beans));
    }

    // Serializer should get passed property context even if contained in a Collection.
    public void testMethodAnnotationInList() throws Exception
    {
        ObjectMapper mapper = new ObjectMapper();
        SimpleModule module = new SimpleModule("test", Version.unknownVersion());
        module.addSerializer(String.class, new AnnotatedContextualSerializer());
        mapper.registerModule(module);
        ContextualListBean beans = new ContextualListBean("abc");
        assertEquals("{\"beans\":[\"list->abc\"]}", mapper.writeValueAsString(beans));
    }

    // Serializer should get passed property context even if contained in a Collection.
    public void testMethodAnnotationInMap() throws Exception
    {
        mapper.registerModule(module);
        assertEquals(quote("contextual=true,resolved=true"), mapper.writeValueAsString("abc"));
    }
}
        ObjectMapper mapper = new ObjectMapper();
        AnnotatedContextualBean bean = new AnnotatedContextualBean("abc");
        assertEquals("{\"value\":\"prefix->abc\"}", mapper.writeValueAsString(bean));
    }

    public void testResolveOnContextual() throws Exception
    {
        SimpleModule module = new SimpleModule("test", Version.unknownVersion());
        module.addSerializer(String.class, new ContextualAndResolvable());
        ObjectMapper mapper = jsonMapperBuilder()
                .addModule(module)
                .build();
        assertEquals(q("contextual=1,resolved=1"), mapper.writeValueAsString("abc"));

        // also: should NOT be called again
        assertEquals(q("contextual=1,resolved=1"), mapper.writeValueAsString("foo"));
    }

    public void testContextualArrayElement() throws Exception
    {
        ObjectMapper mapper = newJsonMapper();
        ContextualArrayElementBean beans = new ContextualArrayElementBean("456");
        assertEquals("{\"beans\":[\"elem->456\"]}", mapper.writeValueAsString(beans));
    }

    // Test to verify aspects of [databind#2429]
    public void testRootContextualization2429() throws Exception
    {
        ObjectMapper mapper = jsonMapperBuilder()
                .addModule(new SimpleModule("test", Version.unknownVersion())
                        .addSerializer(String.class, new AccumulatingContextual()))
                .build();
        assertEquals(q("/ROOT/foo"), mapper.writeValueAsString("foo"));
        assertEquals(q("/ROOT/bar"), mapper.writeValueAsString("bar"));
        assertEquals(q("/ROOT/3"), mapper.writeValueAsString("3"));
    }
}

<MSG> Fixed issues uncovered with handling of contextual serializers (was not always resolving ContextualSerializers properly)

<DFF> @@ -17,8 +17,6 @@ import com.fasterxml.jackson.databind.ser.ResolvableSerializer;
  * Test cases to verify that it is possible to define serializers
  * that can use contextual information (like field/method
  * annotations) for configuration.
- * 
- * @since 1.7
  */
 public class TestContextualSerialization extends BaseMapTest
 {
@@ -79,6 +77,17 @@ public class TestContextualSerialization extends BaseMapTest
             beans = strings;
         }
     }
+
+    static class ContextualArrayElementBean
+    {
+        @Prefix("elem->")
+        @JsonSerialize(contentUsing=AnnotatedContextualSerializer.class)
+        public final String[] beans;
+        
+        public ContextualArrayElementBean(String... strings) {
+            beans = strings;
+        }
+    }
     
     static class ContextualListBean
     {
@@ -186,10 +195,8 @@ public class TestContextualSerialization extends BaseMapTest
     /**********************************************************
      */
 
-    /**
-     * Test to verify that contextual serializer can make use of property
-     * (method, field) annotations.
-     */
+    // Test to verify that contextual serializer can make use of property
+    // (method, field) annotations.
     public void testMethodAnnotations() throws Exception
     {
         ObjectMapper mapper = new ObjectMapper();
@@ -199,10 +206,8 @@ public class TestContextualSerialization extends BaseMapTest
         assertEquals("{\"value\":\"see:foobar\"}", mapper.writeValueAsString(new ContextualBean("foobar")));
     }
 
-    /**
-     * Test to verify that contextual serializer can also use annotations
-     * for enclosing class.
-     */
+    // Test to verify that contextual serializer can also use annotations
+    // for enclosing class.
     public void testClassAnnotations() throws Exception
     {
         ObjectMapper mapper = new ObjectMapper();
@@ -221,9 +226,7 @@ public class TestContextualSerialization extends BaseMapTest
         assertEquals("{\"wrapped\":{\"value\":\"see:xyz\"}}", mapper.writeValueAsString(new ContextualBeanWrapper("xyz")));
     }
     
-    /**
-     * Serializer should get passed property context even if contained in an array.
-     */
+    // Serializer should get passed property context even if contained in an array.
     public void testMethodAnnotationInArray() throws Exception
     {
         ObjectMapper mapper = new ObjectMapper();
@@ -234,9 +237,7 @@ public class TestContextualSerialization extends BaseMapTest
         assertEquals("{\"beans\":[\"array->123\"]}", mapper.writeValueAsString(beans));
     }
 
-    /**
-     * Serializer should get passed property context even if contained in a Collection.
-     */
+    // Serializer should get passed property context even if contained in a Collection.
     public void testMethodAnnotationInList() throws Exception
     {
         ObjectMapper mapper = new ObjectMapper();
@@ -247,9 +248,7 @@ public class TestContextualSerialization extends BaseMapTest
         assertEquals("{\"beans\":[\"list->abc\"]}", mapper.writeValueAsString(beans));
     }
 
-    /**
-     * Serializer should get passed property context even if contained in a Collection.
-     */
+    // Serializer should get passed property context even if contained in a Collection.
     public void testMethodAnnotationInMap() throws Exception
     {
         ObjectMapper mapper = new ObjectMapper();
@@ -277,4 +276,11 @@ public class TestContextualSerialization extends BaseMapTest
         mapper.registerModule(module);
         assertEquals(quote("contextual=true,resolved=true"), mapper.writeValueAsString("abc"));
     }
+
+    public void testContextualArrayElement() throws Exception
+    {
+        ObjectMapper mapper = new ObjectMapper();
+        ContextualArrayElementBean beans = new ContextualArrayElementBean("456");
+        assertEquals("{\"beans\":[\"elem->456\"]}", mapper.writeValueAsString(beans));
+    }
 }

{'addition_count': 25, 'commit_subject': 'Fixed issues uncovered with handling of contextual serializers (was not always resolving ContextualSerializers properly)', 'deletion_count': 19, 'file_extension': '.java', 'lang': 'java', 'license': 'apache-2.0', 'repo_name': 'FasterXML/jackson-databind'}