简体   繁体   中英

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 (ie 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?

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM