简体   繁体   English

Jackson 数据绑定枚举不区分大小写

[英]Jackson databind enum case insensitive

How can I deserialize JSON string that contains enum values that are case insensitive?如何反序列化包含不区分大小写的枚举值的 JSON 字符串? (using Jackson Databind) (使用杰克逊数据绑定)

The JSON string: JSON 字符串:

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

and my Java POJO:和我的 Java POJO:

public static class Endpoint {

    public enum DataType {
        JSON, HTML
    }

    public String url;
    public DataType type;

    public Endpoint() {

    }

}

in this case,deserializing the JSON with "type":"json" would fail where as "type":"JSON" would work.在这种情况下,使用"type":"json"反序列化 JSON 会失败,而"type":"JSON"会起作用。 But I want "json" to work as well for naming convention reasons.但出于命名约定的原因,我希望"json"能正常工作。

Serializing the POJO also results in upper case "type":"JSON"序列化 POJO 也会导致大写"type":"JSON"

I thought of using @JsonCreator and @JsonGetter:我想到了使用@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();
    }

And it worked.它奏效了。 But I was wondering whether there's a better solutuon because this looks like a hack to me.但我想知道是否有更好的解决方案,因为这对我来说看起来像是一个黑客。

I can also write a custom deserializer but I got many different POJOs that use enums and it would be hard to maintain.我也可以编写一个自定义的反序列化器,但我有许多使用枚举的不同 POJO,并且很难维护。

Can anyone suggest a better way to serialize and deserialize enums with proper naming convention?任何人都可以提出一种更好的方法来使用适当的命名约定序列化和反序列化枚举吗?

I don't want my enums in java to be lowercase!我不希望我在 java 中的枚举是小写的!

Here is some test code that I used:这是我使用的一些测试代码:

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

Jackson 2.9杰克逊 2.9

This is now very simple, using jackson-databind 2.9.0 and above这个现在很简单,使用jackson-databind 2.9.0及以上

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

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

Full example with tests带有测试的完整示例

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

I ran into this same issue in my project, we decided to build our enums with a string key and use @JsonValue and a static constructor for serialization and deserialization respectively.我在我的项目中遇到了同样的问题,我们决定使用字符串键构建我们的枚举,并分别使用@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;
    }
}

Since Jackson 2.6, you can simply do this:从 Jackson 2.6 开始,您可以简单地执行以下操作:

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

For a full example, see this gist .有关完整示例,请参阅此要点

In version 2.4.0 you can register a custom serializer for all the Enum types ( link to the github issue).在 2.4.0 版本中,您可以为所有 Enum 类型注册一个自定义序列化程序(链接到 github 问题)。 Also you can replace the standard Enum deserializer on your own that will be aware about the Enum type.您也可以自行替换标准的 Enum 解串器,以了解 Enum 类型。 Here is an example:下面是一个例子:

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

Output:输出:

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

I went for the solution of Sam B. but a simpler variant.我选择了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;
    }
}

如果您将 Spring Boot 2.1.x与 Jackson 2.9使用,您可以简单地使用此应用程序属性:

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

For those who tries to deserialize Enum ignoring case in GET parameters , enabling ACCEPT_CASE_INSENSITIVE_ENUMS will not do any good.对于那些试图反序列化 Enum 忽略GET 参数中的大小写的人,启用 ACCEPT_CASE_INSENSITIVE_ENUMS 不会有任何好处。 It won't help because this option only works for body deserialization .它无济于事,因为此选项仅适用于body deserialization Instead try this:而是试试这个:

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

and then进而

@Configuration
public class WebConfig implements WebMvcConfigurer {

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

The answer and code samples are from here答案和代码示例来自这里

To allow case insensitive deserialization of enums in jackson, simply add the below property to the application.properties file of your spring boot project.要在 jackson 中允许不区分大小写的枚举反序列化,只需将以下属性添加到 Spring Boot 项目的application.properties文件中。

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

If you have the yaml version of properties file, add below property to your application.yml file.如果您有 yaml 版本的属性文件,请将以下属性添加到您的application.yml文件中。

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

With apologies to @Konstantin Zyubin, his answer was close to what I needed - but I didn't understand it, so here's how I think it should go:向@Konstantin Zyubin 道歉,他的回答接近我所需要的——但我不明白,所以我认为它应该是这样的:

If you want to deserialize one enum type as case insensitive - ie you don't want to, or can't, modify the behavior of the entire application, you can create a custom deserializer just for one type - by sub-classing StdConverter and force Jackson to use it only on the relevant fields using the JsonDeserialize annotation.如果您想将一个枚举类型反序列化为不区分大小写 - 即您不想或不能修改整个应用程序的行为,您可以为一种类型创建一个自定义反序列化器 - 通过子类化StdConverter和强制杰克逊使用JsonDeserialize注释仅在相关字段上使用它。

Example:例子:

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

Problem is releated to com.fasterxml.jackson.databind.util.EnumResolver .问题与com.fasterxml.jackson.databind.util.EnumResolver 有关 it uses HashMap to hold enum values and HashMap doesn't support case insensitive keys.它使用 HashMap 来保存枚举值,而 HashMap 不支持不区分大小写的键。

in answers above, all chars should be uppercase or lowercase.在上面的答案中,所有字符都应该是大写或小写。 but I fixed all (in)sensitive problems for enums with that:但我修复了枚举的所有(in)敏感问题:

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

CustomDeserializers.java CustomDeserializers.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;
        }
    }
}

Usage:用法:

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

}

I used a modification of Iago Fernández and Paul solution .我使用了 Iago Fernández 和 Paul 解决方案的修改。

I had an enum in my requestobject which needed to be case insensitive我的 requestobject 中有一个枚举,它需要不区分大小写

@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 
 }
}

Here's how I sometimes handle enums when I want to deserialize in a case-insensitive manner (building on the code posted in the question):当我想以不区分大小写的方式(建立在问题中发布的代码上)进行反序列化时,我有时会处理枚举:

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

If the enum is non-trivial and thus in its own class I usually add a static parse method to handle lowercase Strings.如果枚举是非平凡的,因此在它自己的类中,我通常会添加一个静态解析方法来处理小写字符串。

Deserialize enum with jackson is simple.使用 jackson 反序列化枚举很简单。 When you want deserialize enum based in String need a constructor, a getter and a setter to your enum.Also class that use that enum must have a setter which receive DataType as param, not String:当您想要基于 String 反序列化枚举时,需要一个构造函数、一个 getter 和一个 setter 到您的 enum.Also 使用该枚举的类必须有一个接收 DataType 作为参数的 setter,而不是 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;
     }

}

When you have your json, you can deserialize to Endpoint class using ObjectMapper of Jackson:当您拥有 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();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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