Jackson 数据绑定枚举大小写不敏感

如何对包含不区分大小写的枚举值的 JSON 字符串进行反序列化? (使用 Jackson Databind)

JSON 字符串:

[{"url": "foo", "type": "json"}]

还有我的 Java POJO:

public static class Endpoint {


public enum DataType {
JSON, HTML
}


public String url;
public DataType type;


public Endpoint() {


}


}

在这种情况下,使用 "type":"json"反序列化 JSON 将会失败,因为 "type":"JSON"可以工作。 但出于变数命名原则的原因,我希望 "json"也能运行。

序列化 POJO 还会导致大写的 "type":"JSON"

我想过用 @JsonCreator和@JsonGetter:

    @JsonCreator
private Endpoint(@JsonProperty("name") String url, @JsonProperty("type") String type) {
this.url = url;
this.type = DataType.valueOf(type.toUpperCase());
}


//....
@JsonGetter
private String getType() {
return type.name().toLowerCase();
}

而且成功了,但我想知道是否有更好的解决方案因为在我看来这像是黑客干的。

我也可以编写一个自定义反序列化器,但是我得到了许多不同的使用枚举的 POJO,它很难维护。

有没有人能提供一种更好的方法来使用适当的变数命名原则序列化和反序列化枚举?

我不希望我在爪哇的枚举是小写!

下面是我使用的一些测试代码:

    String data = "[{\"url\":\"foo\", \"type\":\"json\"}]";
Endpoint[] arr = new ObjectMapper().readValue(data, Endpoint[].class);
System.out.println("POJO[]->" + Arrays.toString(arr));
System.out.println("JSON ->" + new ObjectMapper().writeValueAsString(arr));
101067 次浏览

我在项目中遇到了同样的问题,我们决定使用字符串键构建枚举,并分别使用 @JsonValue和静态构造函数进行序列化和反序列化。

public enum DataType {
JSON("json"),
HTML("html");


private String key;


DataType(String key) {
this.key = key;
}


@JsonCreator
public static DataType fromString(String key) {
return key == null
? null
: DataType.valueOf(key.toUpperCase());
}


@JsonValue
public String getKey() {
return key;
}
}

在版本2.4.0中,您可以为所有 Enum 类型注册一个自定义序列化器(针对 github 问题的 链接)。此外,您还可以自己替换标准的 Enum 反序列化器,该反序列化器将能够识别 Enum 类型。这里有一个例子:

public class JacksonEnum {


public static enum DataType {
JSON, HTML
}


public static void main(String[] args) throws IOException {
List<DataType> types = Arrays.asList(JSON, HTML);
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
@Override
public JsonDeserializer<Enum> modifyEnumDeserializer(DeserializationConfig config,
final JavaType type,
BeanDescription beanDesc,
final JsonDeserializer<?> deserializer) {
return new JsonDeserializer<Enum>() {
@Override
public Enum deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
Class<? extends Enum> rawClass = (Class<Enum<?>>) type.getRawClass();
return Enum.valueOf(rawClass, jp.getValueAsString().toUpperCase());
}
};
}
});
module.addSerializer(Enum.class, new StdSerializer<Enum>(Enum.class) {
@Override
public void serialize(Enum value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeString(value.name().toLowerCase());
}
});
mapper.registerModule(module);
String json = mapper.writeValueAsString(types);
System.out.println(json);
List<DataType> types2 = mapper.readValue(json, new TypeReference<List<DataType>>() {});
System.out.println(types2);
}
}

产出:

["json","html"]
[JSON, HTML]

问题与 Com.fasterxml.jackson.databind.util. EnumResolver有关。它使用 HashMap 来保存枚举值,而且 HashMap 不支持不区分大小写的键。

在上面的答案中,所有的字符都应该是大写或者小写。但是我修复了枚举的所有敏感问题:

Https://gist.github.com/bhdrk/02307ba8066d26fa1537

Customerializers.java

import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.deser.std.EnumDeserializer;
import com.fasterxml.jackson.databind.module.SimpleDeserializers;
import com.fasterxml.jackson.databind.util.EnumResolver;


import java.util.HashMap;
import java.util.Map;




public class CustomDeserializers extends SimpleDeserializers {


@Override
@SuppressWarnings("unchecked")
public JsonDeserializer<?> findEnumDeserializer(Class<?> type, DeserializationConfig config, BeanDescription beanDesc) throws JsonMappingException {
return createDeserializer((Class<Enum>) type);
}


private <T extends Enum<T>> JsonDeserializer<?> createDeserializer(Class<T> enumCls) {
T[] enumValues = enumCls.getEnumConstants();
HashMap<String, T> map = createEnumValuesMap(enumValues);
return new EnumDeserializer(new EnumCaseInsensitiveResolver<T>(enumCls, enumValues, map));
}


private <T extends Enum<T>> HashMap<String, T> createEnumValuesMap(T[] enumValues) {
HashMap<String, T> map = new HashMap<String, T>();
// from last to first, so that in case of duplicate values, first wins
for (int i = enumValues.length; --i >= 0; ) {
T e = enumValues[i];
map.put(e.toString(), e);
}
return map;
}


public static class EnumCaseInsensitiveResolver<T extends Enum<T>> extends EnumResolver<T> {
protected EnumCaseInsensitiveResolver(Class<T> enumClass, T[] enums, HashMap<String, T> map) {
super(enumClass, enums, map);
}


@Override
public T findEnum(String key) {
for (Map.Entry<String, T> entry : _enumsById.entrySet()) {
if (entry.getKey().equalsIgnoreCase(key)) { // magic line <--
return entry.getValue();
}
}
return null;
}
}
}

用法:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;




public class JSON {


public static void main(String[] args) {
SimpleModule enumModule = new SimpleModule();
enumModule.setDeserializers(new CustomDeserializers());


ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(enumModule);
}


}

当我想以不区分大小写的方式反序列化(基于问题中发布的代码)时,有时我是这样处理枚举的:

@JsonIgnore
public void setDataType(DataType dataType)
{
type = dataType;
}


@JsonProperty
public void setDataType(String dataType)
{
// Clean up/validate String however you want. I like
// org.apache.commons.lang3.StringUtils.trimToEmpty
String d = StringUtils.trimToEmpty(dataType).toUpperCase();
setDataType(DataType.valueOf(d));
}

如果枚举是非平凡的,因此在它自己的类中,我通常添加一个静态解析方法来处理小写字符串。

用 Jackson 反序列化枚举很简单。当需要基于 String 的反序列化枚举时,需要一个构造函数、一个 getter 和一个 setter 来实现枚举。另外,使用枚举的类必须有一个 setter,它将 DataType 作为参数而不是 String 接收:

public class Endpoint {


public enum DataType {
JSON("json"), HTML("html");


private String type;


@JsonValue
public String getDataType(){
return type;
}


@JsonSetter
public void setDataType(String t){
type = t.toLowerCase();
}
}


public String url;
public DataType type;


public Endpoint() {


}


public void setType(DataType dataType){
type = dataType;
}


}

有了 json 之后,就可以使用 Jackson 的 ObjectMapper 反序列化到 Endpoint 类:

ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
try {
Endpoint endpoint = mapper.readValue("{\"url\":\"foo\",\"type\":\"json\"}", Endpoint.class);
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}

我选择了 Sam B.的解决方案,但是一个更简单的变体。

public enum Type {
PIZZA, APPLE, PEAR, SOUP;


@JsonCreator
public static Type fromString(String key) {
for(Type type : Type.values()) {
if(type.name().equalsIgnoreCase(key)) {
return type;
}
}
return null;
}
}

从 Jackson 2.6开始,你可以简单地这样做:

    public enum DataType {
@JsonProperty("json")
JSON,
@JsonProperty("html")
HTML
}

有关完整示例,请参见 这个要点

杰克逊2.9

现在使用 jackson-databind2.9.0及以上版本非常简单

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);


// objectMapper now deserializes enums in a case-insensitive manner

测试的完整例子

import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;


public class Main {


private enum TestEnum { ONE }
private static class TestObject { public TestEnum testEnum; }


public static void main (String[] args) {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);


try {
TestObject uppercase =
objectMapper.readValue("{ \"testEnum\": \"ONE\" }", TestObject.class);
TestObject lowercase =
objectMapper.readValue("{ \"testEnum\": \"one\" }", TestObject.class);
TestObject mixedcase =
objectMapper.readValue("{ \"testEnum\": \"oNe\" }", TestObject.class);


if (uppercase.testEnum != TestEnum.ONE) throw new Exception("cannot deserialize uppercase value");
if (lowercase.testEnum != TestEnum.ONE) throw new Exception("cannot deserialize lowercase value");
if (mixedcase.testEnum != TestEnum.ONE) throw new Exception("cannot deserialize mixedcase value");


System.out.println("Success: all deserializations worked");
} catch (Exception e) {
e.printStackTrace();
}
}
}

我用了伊阿古 · 费尔南德斯和保罗的改良溶液。

我的请求对象中有一个枚举,它需要不区分大小写

@POST
public Response doSomePostAction(RequestObject object){
//resource implementation
}






class RequestObject{
//other params
MyEnumType myType;


@JsonSetter
public void setMyType(String type){
myType = MyEnumType.valueOf(type.toUpperCase());
}
@JsonGetter
public String getType(){
return myType.toString();//this can change
}
}

如果您使用的是 SpringBoot2.1.x和 Jackson 2.9,您可以简单地使用这个应用程序属性:

spring.jackson.mapper.accept-case-insensitive-enums=true

对于那些试图在 GET 参数中反序列化 Enum 忽略事例的用户,启用 ACCEPT _ CASE _ INSENSITIVE _ ENUMS 将不会有任何好处。这没有帮助,因为这个选项只适用于 身体反序列化。试试这个:

public class StringToEnumConverter implements Converter<String, Modes> {
@Override
public Modes convert(String from) {
return Modes.valueOf(from.toUpperCase());
}
}

然后

@Configuration
public class WebConfig implements WebMvcConfigurer {


@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToEnumConverter());
}
}

答案和代码示例来自 给你

对于@Konstantin Zyubin 的道歉,他的回答接近我所需要的——但我不明白,所以我认为应该这样做:

如果你想将一个枚举类型反序列化为不区分大小写的——也就是说,你不想或者不能修改整个应用程序的行为,你可以为一个类型创建一个自定义反序列化器——通过子类化 StdConverter,强制 Jackson 只在相关字段使用它,使用 JsonDeserialize注释。

例如:

public class ColorHolder {


public enum Color {
RED, GREEN, BLUE
}


public static final class ColorParser extends StdConverter<String, Color> {
@Override
public Color convert(String value) {
return Arrays.stream(Color.values())
.filter(e -> e.getName().equalsIgnoreCase(value.trim()))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Invalid value '" + value + "'"));
}
}


@JsonDeserialize(converter = ColorParser.class)
Color color;
}

要允许在 jackson 中对枚举进行不区分大小写的反序列化,只需将以下属性添加到 Spring 启动项目的 application.properties文件中。

spring.jackson.mapper.accept-case-insensitive-enums=true

如果您有属性文件的 yaml 版本,请将以下属性添加到 application.yml文件中。

spring:
jackson:
mapper:
accept-case-insensitive-enums: true