问题
Currently jackson (uncustomized) serializes enums like this:
- If there is
@JsonProperty
or@JsonValue
, they are used to derive serialized name - 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:
- If there is
@JsonProperty
,@JsonValue
or@JsonCreator
, they influence deserialization - 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