Джексон 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));
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-databind2.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