Feature: Contextual Handlers (serializers, deserializers)

(see Jira entry JACKSON-385 for details)

Problem to Solve

The main goal of Jackson 1.7 was the extensibility. One of limitations for existing functionality for registering custom serializers and deserializers was that a single handler instance is used for all instances of a property, so it is not possible to have contextual behavior. Contextual behavior here means that serialization or deserialization can be done differently depending on which property (or root value) is being processed.

One practical example is that of modifying behavior of serialization with annotations added next to getter method or field that is used to access value to serialize. By default there isn't enough information to access such annotations; and even if this was possible, performance penalty could be significant.

Solution

Solution implemented here is as follows:

This allows customizing handling on per-property basis, while still maintaining good performance: annotation lookups are only done when constructing handler instance and not when they are used.

Example

Let's assume that we want to define an annotation that can be used to override java.util.DateFormat used for serializing java.util.Date values:

    @Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @JacksonAnnotation // important so that it will get included!
    public @interface CustomDateFormat {
        public String value();
    }

so that we could annotate a Bean like so:

    public class MyBean {
        @CustomDateFormat("YYYY-MM-DD")
        public java.util.Date getCreationDate();
    }

so serializer we want could be implemented something like:

   public class ContextualDateSerializer
        extends JsonSerializer<Date>
        implements ContextualSerializer<Date>
    {
        protected final DateFormat format;
        
        public AnnotatedContextualSerializer() { this(null); }
        public AnnotatedContextualSerializer(String formatStr) {
            format = (format == null) ? null new SimpleDateFormat(formatStr);
        }

        @Override
        public void serialize(Date date, JsonGenerator jgen, SerializerProvider provider) throws IOException
        {
            // null means "just use default"; provider has functionality to uyse
            if (format == null) {
                provider.defaultSerializeDateValue(date, jgen);
            } else {
                jgen.writeString(format.format(date));
            }
        }

        @Override
        public JsonSerializer<String> createContextual(SerializationConfig config, BeanProperty property)
                throws JsonMappingException
        {
            // First find annotation used for getter or field:
            CustomDateFormat ann = property.getAnnotation(CustomDateFormat.class);
            if (ann == null) { // but if missing, default one from class
                ann = property.getContextAnnotation(CustomDateFormat.class);
            }
            // If no customization found, just return base instance (this); no need to construct new serializer
            String format = (ann == null) ? null : ann.value();
            if (ann == null || ann.length() == 0) {
                return this;
            }
            return new ContextualDateSerializer(format);
        }

Given all these pieces, how would serializer be registered? With 1.7, preferred mechanism would be to implement a Module; for example:

    ObjectMapper mapper = new ObjectMapper();
    SimpleModule module = new SimpleModule("EnhancedDatesModule", new Version(0, 1, 0, "alpha"));
    module.addSerializer(java.util.Date.class, new ContextualDateSerializer());
    mapper.registerModule(module);


CategoryJackson

JacksonFeatureContextualHandlers (last edited 2010-12-20 18:46:46 by TatuSaloranta)

Copyright ©2009 FasterXML, LLC