How do you modify default enum (de)serialization for non-annotated enums but retain standard behavior (@JsonProperty/@JsonValue/…) in Jackson?



Currently jackson (uncustomized) serializes enums like this:

  1. If there is @JsonProperty or @JsonValue, they are used to derive serialized name
  2. Otherwise, standard serialization is carried out: index or toString() might be used (depending on the mapper settings), or .name() is called by default

For deserialization, it’s like this:

  1. If there is @JsonProperty, @JsonValue or @JsonCreator, they influence deserialization
  2. Otherwise, standard deserialization is used which mirrors serialization.

I’d like to keep items 1 in both cases (i.e. still support all 3 annotations, or more if I miss something), but modify behavior for the ‘default case’: for example, always serialize enums as .name().toLowercase() if no annotations are present.

I could use addSerializer(), addDeserializer() or (de)serializer modification mechanisms, but none of these allow easy and elegant solution. All I could invent is either copy/paste tons of code from jackson (which is ugly and fragile), or, using modification mechanism, introspect the enum class emulating jackson’s involved logic to identify cases when the ‘default’ logic would apply and then use my ‘to-lower-case’ strategy.

Is there a better way to modify the ‘default’ serialization strategy while still leaving all the ‘non-default’ cases?

Answer

You can insert a new AnnotationIntrospector whose findEnumValues method changes the enum name. The EnumRenamingModule from the therapi-json-rpc project uses this technique. (Disclaimer: this is my pet project.) Here’s the code, copied from the GitHub repo:

package com.github.therapi.jackson.enums;

import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;
import com.fasterxml.jackson.databind.module.SimpleModule;

/**
 * Customizes the way Jackson serializes enums.
 */
public abstract class EnumRenamingModule extends SimpleModule {
    private boolean overrideExistingNames;

    public EnumRenamingModule() {
        super("therapi-enum-renaming");
    }

    /**
     * Configures the module to clobber any enum names set by
     * a previous annotation introspector.
     */
    public EnumRenamingModule overrideExistingNames() {
        this.overrideExistingNames = true;
        return this;
    }

    @Override
    public void setupModule(Module.SetupContext context) {
        super.setupModule(context);
        context.insertAnnotationIntrospector(new EnumNamingAnnotationIntrospector());
    }

    private class EnumNamingAnnotationIntrospector extends NopAnnotationIntrospector {
        public String[] findEnumValues(Class<?> enumType, Enum<?>[] enumValues, String[] names) {
            for (int i = 0; i < enumValues.length; i++) {
                if (names[i] == null || overrideExistingNames) {
                    names[i] = EnumRenamingModule.this.getName(enumValues[i]);
                }
            }
            return names;
        }
    }

    /**
     * @param value the enum value to inspect
     * @return the JSON name for the enum value, or {@code null} to delegate to the next introspector
     */
    protected abstract String getName(Enum<?> value);
}

Create a subclass to do the transformation you want:

public class LowerCaseEnumModule extends EnumRenamingModule {
    @Override protected String getName(Enum<?> value) {
        return value.name().toLowerCase(Locale.ROOT);
    }
}

And register it with your ObjectMapper:

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new LowerCaseEnumModule());


Source: stackoverflow