Roman Puchkovskiy
Roman Puchkovskiy

Reputation: 11865

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?

Upvotes: 1

Views: 1736

Answers (2)

dnault
dnault

Reputation: 8909

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());

Upvotes: 1

IQbrod
IQbrod

Reputation: 2285

Annotation @JsonCreator is used to deserialize (it's a static factory).
Annotation @JsonValue is used to serialize.
The given example does work for me :

class MyException extends RuntimeException {}
enum MyEnum {
    TEST;

    private final String value;

    private MyEnum() {
        this.value = this.name().toLowerCase();
    }

    @JsonValue
    public final String value() {
        return this.value;
    }

    @JsonCreator
    public static MyEnum forValue(String value) {
        return Arrays.stream(MyEnum.values()).filter(myEnum -> myEnum.value.equals(value)).findFirst().orElseThrow(MyException::new);
    }
}

Upvotes: 0

Related Questions