Джексон databind перечисление без учета регистра



как я могу десериализовать строку 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() {

}

}


в этом случае десериализация JSON с помощью "type":"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();
}


и это сработало. Но мне было интересно, есть ли лучше solutuon, потому что это похоже на хак для меня.



Я также могу написать пользовательский десериализатор, но у меня есть много разных POJOs, которые используют перечисления, и их было бы трудно поддерживать.



может ли кто-нибудь предложить лучший способ сериализации и десериализации перечислений с правильным соглашением об именах?



я не хочу, чтобы мои перечисления в java были строчные буквы!



вот некоторые тестовый код, который я использовал:



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

8 ответов:

в версии 2.4.0 вы можете зарегистрировать пользовательский сериализатор для всех типов перечислений (ссылке к вопросу github). Также вы можете заменить стандартный десериализатор перечисления самостоятельно, который будет знать о типе перечисления. Вот пример:

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]

я столкнулся с этой же проблемой в своем проекте, мы решили построить наши перечисления со строковым ключом и использовать @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.9

Это теперь очень просто, от jackson-databind 2.9.0 и выше

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

полный пример кода

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

начиная с Jackson 2.6, вы можете просто сделать это:

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

полный пример см. В разделе в этом суть.

Я пошел за решением Б но более простой вариант.

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

проблема освобождается до com.быстрее!Джексон.databind.утиль.EnumResolver. он использует HashMap для хранения значений перечислений, а HashMap не поддерживает ключи без учета регистра.

в ответы выше, все символы должны быть прописными или строчными. но я исправил все (в)чувствительные проблемы для перечисления что:

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

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

использование:

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

если перечисление нетривиально и, следовательно, в своем собственном классе я обычно добавляю метод статического разбора для обработки строк в нижнем регистре.

десериализовать перечисление с Джексоном-это просто. Когда вы хотите десериализовать перечисление на основе строки, вам нужен конструктор, геттер и сеттер для вашего перечисления.Также класс, который использует это перечисление, должен иметь сеттер, который получает тип данных как param, а не 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, вы можете десериализовать класс Endpoint с помощью ObjectMapper Джексона:

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

Comments

    Ничего не найдено.