Bagaimana cara menghentikan derialisasi string JSON yang berisi nilai enum yang tidak peka huruf besar / kecil? (menggunakan Jackson Databind)
String JSON:
[{"url": "foo", "type": "json"}]
dan POJO Java saya:
public static class Endpoint {
public enum DataType {
JSON, HTML
}
public String url;
public DataType type;
public Endpoint() {
}
}
dalam kasus ini, deserialisasi JSON dengan "type":"json"
akan gagal di tempat yang "type":"JSON"
akan berhasil. Tapi saya ingin "json"
bekerja juga karena alasan konvensi penamaan.
Serialisasi POJO juga menghasilkan huruf besar "type":"JSON"
Saya berpikir untuk menggunakan @JsonCreator
dan @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();
}
Dan itu berhasil. Tapi saya bertanya-tanya apakah ada solusi yang lebih baik karena ini terlihat seperti hack bagi saya.
Saya juga dapat menulis deserializer khusus tetapi saya mendapatkan banyak POJO berbeda yang menggunakan enum dan akan sulit untuk dipelihara.
Adakah yang bisa menyarankan cara yang lebih baik untuk membuat serial dan deserialisasi enum dengan konvensi penamaan yang tepat?
Saya tidak ingin enum saya di java menjadi huruf kecil!
Berikut beberapa kode tes yang saya gunakan:
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));
Jawaban:
Di versi 2.4.0 Anda dapat mendaftarkan serializer khusus untuk semua jenis Enum ( tautan ke masalah github). Anda juga dapat mengganti deserializer Enum standar Anda sendiri yang akan mengetahui tentang jenis Enum. Berikut ini contohnya:
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); } }
Keluaran:
["json","html"] [JSON, HTML]
sumber
Jackson 2.9
Ini sekarang sangat sederhana, menggunakan
jackson-databind
2.9.0 ke atasObjectMapper objectMapper = new ObjectMapper(); objectMapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS); // objectMapper now deserializes enums in a case-insensitive manner
Contoh lengkap dengan tes
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(); } } }
sumber
jackson-databind
2.9.2 secara khusus berfungsi seperti yang diharapkan.spring.jackson.mapper.accept-case-insensitive-enums=true
Saya mengalami masalah yang sama ini dalam proyek saya, kami memutuskan untuk membangun enum kami dengan kunci string dan menggunakan
@JsonValue
dan konstruktor statis untuk serialisasi dan deserialisasi masing-masing.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; } }
sumber
DataType.valueOf(key.toUpperCase())
- jika tidak, Anda tidak benar-benar mengubah apa pun. Pengkodean pertahanan untuk menghindari NPE:return (null == key ? null : DataType.valueOf(key.toUpperCase()))
key
lapangan itu tidak perlu. MasukgetKey
, Anda bisa sajareturn name().toLowerCase()
Sejak Jackson 2.6, Anda cukup melakukan ini:
public enum DataType { @JsonProperty("json") JSON, @JsonProperty("html") HTML }
Untuk contoh lengkap, lihat inti ini .
sumber
Saya memilih solusi Sam B. tetapi varian yang lebih sederhana.
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; } }
sumber
DataType.valueOf(key.toUpperCase())
adalah instantiation langsung di mana Anda memiliki loop. Ini bisa menjadi masalah untuk banyak enum. Tentu saja,valueOf
dapat memunculkan IllegalArgumentException, yang dihindari kode Anda, jadi itu adalah keuntungan yang baik jika Anda lebih memilih pemeriksaan null daripada pemeriksaan pengecualian.Jika Anda menggunakan Spring Boot
2.1.x
with Jackson,2.9
Anda cukup menggunakan properti aplikasi ini:spring.jackson.mapper.accept-case-insensitive-enums=true
sumber
Bagi mereka yang mencoba untuk menghilangkan kasus Enum mengabaikan kasus di parameter GET , mengaktifkan ACCEPT_CASE_INSENSITIVE_ENUMS tidak akan ada gunanya. Ini tidak akan membantu karena opsi ini hanya berfungsi untuk deserialisasi tubuh . Sebagai gantinya coba ini:
public class StringToEnumConverter implements Converter<String, Modes> { @Override public Modes convert(String from) { return Modes.valueOf(from.toUpperCase()); } }
lalu
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new StringToEnumConverter()); } }
Jawaban dan contoh kode berasal dari sini
sumber
Dengan permintaan maaf kepada @Konstantin Zyubin, jawabannya mendekati apa yang saya butuhkan - tetapi saya tidak memahaminya, jadi begini menurut saya jawabannya:
Jika Anda ingin deserialize satu jenis enum sebagai case insensitive - yaitu Anda tidak ingin, atau tidak bisa, mengubah perilaku seluruh aplikasi, Anda dapat membuat deserializer kustom hanya untuk satu jenis - dengan sub-classing
StdConverter
dan memaksa Jackson menggunakannya hanya pada bidang yang relevan dengan menggunakanJsonDeserialize
penjelasan.Contoh:
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; }
sumber
Untuk mengizinkan deserialisasi enum yang tidak peka huruf besar / kecil di jackson, cukup tambahkan properti di bawah ini ke
application.properties
file proyek boot musim semi Anda.spring.jackson.mapper.accept-case-insensitive-enums=true
Jika Anda memiliki versi yaml dari file properti, tambahkan properti di bawah ini ke
application.yml
file Anda .spring: jackson: mapper: accept-case-insensitive-enums: true
sumber
Masalah dilepaskan ke com.fasterxml.jackson.databind.util.EnumResolver . ia menggunakan HashMap untuk menyimpan nilai enum dan HashMap tidak mendukung kunci case insensitive.
dalam jawaban di atas, semua karakter harus dalam huruf besar atau kecil. tapi saya memperbaiki semua (dalam) masalah sensitif untuk enum dengan itu:
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; } } }
Pemakaian:
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); } }
sumber
Saya menggunakan modifikasi solusi Iago Fernández dan Paul.
Saya memiliki enum di objek permintaan saya yang harus case insensitive
@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 } }
sumber
Inilah cara saya terkadang menangani enum ketika saya ingin melakukan deserialisasi dengan cara yang tidak peka huruf besar / kecil (membangun kode yang diposting dalam pertanyaan):
@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)); }
Jika enumnya tidak sepele dan dengan demikian di kelasnya sendiri, saya biasanya menambahkan metode parse statis untuk menangani String huruf kecil.
sumber
Deserialisasi enum dengan jackson itu sederhana. Ketika Anda ingin deserialisasi enum yang berbasis di String membutuhkan konstruktor, pengambil dan penyetel ke enum Anda. Juga kelas yang menggunakan enum itu harus memiliki penyetel yang menerima DataType sebagai param, bukan 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; } }
Saat Anda memiliki json, Anda dapat melakukan deserialisasi ke kelas Endpoint menggunakan ObjectMapper of Jackson:
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(); }
sumber