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

走远了吗. 提交于 2021-01-05 07:32:17

问题


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?


回答1:


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



回答2:


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


来源:https://stackoverflow.com/questions/64593254/how-do-you-modify-default-enum-deserialization-for-non-annotated-enums-but-ret

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!