Jackson databind enum büyük / küçük harfe duyarsız


107

Büyük / küçük harfe duyarlı olmayan enum değerleri içeren JSON dizesini seri durumdan nasıl kaldırabilirim? (Jackson Databind kullanarak)

JSON dizesi:

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

ve Java POJO'm:

public static class Endpoint {

    public enum DataType {
        JSON, HTML
    }

    public String url;
    public DataType type;

    public Endpoint() {

    }

}

bu durumda, JSON ile seri durumunun kaldırılması çalıştığı "type":"json"yerde başarısız olur "type":"JSON". Ama ben "json"de konvansiyonel nedenleri adlandırmak için çalışmak istiyorum .

POJO'nun seri hale getirilmesi de büyük harfle sonuçlanır "type":"JSON"

@JsonCreatorVe @JsonGetter kullanmayı düşündüm :

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

Ve işe yaradı. Ama daha iyi bir çözüm olup olmadığını merak ediyordum çünkü bu bana bir hileye benziyor.

Ayrıca özel bir seri dışı bırakıcı da yazabilirim, ancak numaralandırmayı kullanan birçok farklı POJO'm var ve bakımı zor olacak.

Herhangi biri numaralandırmaları uygun adlandırma kurallarıyla seri hale getirmenin ve seri durumdan çıkarmanın daha iyi bir yolunu önerebilir mi?

Java'daki numaralarımın küçük harf olmasını istemiyorum!

İşte kullandığım bazı test kodları:

    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'ın hangi versiyonundasın? JIRA'ya bir göz atın jira.codehaus.org/browse/JACKSON-861
Alexey Gavrilov

Jackson 2.2.3 kullanıyorum
tom91136

Tamam 2.4.0-
RC3'e

Yanıtlar:


38

2.4.0 sürümünde, tüm Enum türleri için özel bir serileştirici kaydedebilirsiniz ( github sorununa bağlantı ). Ayrıca, Enum türü hakkında bilgi sahibi olacak standart Enum serisini kaldırıcıyı kendi başınıza değiştirebilirsiniz. İşte bir örnek:

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

Çıktı:

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

1
Teşekkürler, şimdi
POJO'mdaki

Projelerimde şahsen bunu savunuyorum. Örneğime bakarsanız, çok fazla standart kod gerektirir. Serileştirme / serileştirme için ayrı bir öznitelik kullanmanın bir yararı, Java açısından önemli değerlerin (enum adları) adlarını istemci açısından önemli değerlere (oldukça baskı) ayırmasıdır. Örneğin, HTML Veri Tipini HTML_DATA_TYPE olarak değiştirmek istenirse, bunu harici API'yi etkilemeden, eğer belirtilmiş bir anahtar varsa yapabilirsiniz.
Sam Berry

1
Bu iyi bir başlangıçtır, ancak numaranız JsonProperty veya JsonCreator kullanıyorsa başarısız olur. Dropwizard vardır FuzzyEnumModule daha sağlam bir uygulamasıdır.
Pixel Elephant

146

Jackson 2.9

jackson-databind2.9.0 ve üzeri sürümleri kullanarak bu artık çok basit

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

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

Testlerle tam örnek

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

3
Bu altın!
Vikas Prasad

8
2.9.2 kullanıyorum ve çalışmıyor. Nedeni: com.fasterxml.jackson.databind.exc.InvalidFormatException: Tür değeri seri durumdan çıkarılamıyor .... "erkek" Dizesinden Cinsiyet`: bildirilen Enum örnek adlarından biri olmayan değer: [FAMALE, ERKEK]
Jordan Silva

@JordanSilva kesinlikle v2.9.2 ile çalışıyor. Doğrulama testleri içeren tam bir kod örneği ekledim. Sizin durumunuzda ne olabileceğini bilmiyorum, ancak örnek kodu jackson-databind2.9.2 ile çalıştırmak özellikle beklendiği gibi çalışıyor.
davnicwil

8
Spring Boot'u kullanarak özelliği ekleyebilirsinizspring.jackson.mapper.accept-case-insensitive-enums=true
Arne Burmeister

1
@JordanSilva belki benim yaptığım gibi get parametrelerinde numaralandırmayı kaldırmaya çalışıyorsunuz? =) Sorunumu çözdüm ve burada cevapladım. Umarım yardımcı olabilir
Konstantin Zyubin

86

Projemde de aynı sorunla karşılaştım, numaralandırmalarımızı bir dizge anahtarı ile oluşturmaya karar verdik @JsonValueve sırasıyla serileştirme ve serileştirme için statik bir kurucu kullanmaya karar verdik .

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

1
Bu olmalı DataType.valueOf(key.toUpperCase())- aksi takdirde, gerçekten hiçbir şeyi değiştirmediniz. return (null == key ? null : DataType.valueOf(key.toUpperCase()))
NPE'den

2
İyi yakalama @sarumont. Düzenlemeyi yaptım. Ayrıca, JAX-RS ile güzelce oynamak için yöntem "fromString" olarak yeniden adlandırıldı .
Sam Berry

1
Bu yaklaşımı beğendim, ancak daha az ayrıntılı bir varyantı seçtim, aşağıya bakın.
linqu

2
görünüşe göre keyalan gereksiz. İçinde getKey, sadecereturn name().toLowerCase()
yair

1
Numaralamayı json'un sahip olacağından farklı bir şekilde adlandırmak istediğiniz durumda anahtar alanı seviyorum. Benim durumumda, eski bir sistem, gönderdiği değer için gerçekten kısaltılmış ve hatırlanması zor bir ad gönderir ve bu alanı java numaram için daha iyi bir isme çevirmek için kullanabilirim.
grinch

53

Jackson 2.6'dan beri, bunu kolayca yapabilirsiniz:

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

Tam bir örnek için bu öze bakın .


28
Bunu yapmanın sorunu tersine çevireceğini unutmayın. Şimdi Jackson yalnızca küçük harf kabul edecek ve tüm büyük harfli veya karışık harfli değerleri reddedecektir.
Pixel Elephant

30

Sam B.'nin çözümüne gittim ama daha basit bir varyant.

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

Bunun daha basit olduğunu sanmıyorum. DataType.valueOf(key.toUpperCase())bir döngünüzün olduğu doğrudan bir somutlaştırmadır. Bu, çok sayıda sayım için bir sorun olabilir. Elbette, valueOfkodunuzun önlediği bir IllegalArgumentException oluşturabilir, bu nedenle, istisna denetimine null denetimi tercih ediyorsanız bu iyi bir avantajdır.
Patrick M

25

Spring Boot'u 2.1.xJackson ile 2.9kullanıyorsanız, bu uygulama özelliğini kullanabilirsiniz:

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


4

GET parametrelerindeki durumu yok sayarak Enum serisini kaldırmaya çalışanlar için , ACCEPT_CASE_INSENSITIVE_ENUMS'u etkinleştirmek işe yaramayacaktır. Yardımcı olmaz çünkü bu seçenek yalnızca gövde serisini kaldırma için çalışır . Bunun yerine şunu deneyin:

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

ve sonra

@Configuration
public class WebConfig implements WebMvcConfigurer {

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

Cevap ve kod örnekleri buradan


1

@Konstantin Zyubin'den özür dileyerek, cevabı ihtiyacım olana yakındı - ama anlamadım, işte böyle olması gerektiğini düşünüyorum:

Bir numaralandırma türünü büyük / küçük harfe duyarlı olmayacak şekilde kaldırmak istiyorsanız - yani, tüm uygulamanın davranışını değiştirmek istemiyor veya yapamıyorsanız, alt sınıflandırma StdConverterve zorlama yoluyla yalnızca bir tür için özel bir seri çözümleyici oluşturabilirsiniz. Jackson'ın JsonDeserializenotu kullanarak yalnızca ilgili alanlarda kullanması için .

Misal:

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

1

Jackson'daki numaralandırmaların büyük / küçük harf duyarlılığının kaldırılmasına izin vermek için, aşağıdaki özelliği application.propertiesbahar önyükleme projenizin dosyasına eklemeniz yeterlidir .

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

Özellikler dosyasının yaml sürümüne sahipseniz, aşağıdaki özelliği dosyanıza ekleyin application.yml.

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

Teşekkürler, bunun Spring Boot'ta çalıştığını onaylayabilirsiniz.
Joonas Vali

0

Sorun com.fasterxml.jackson.databind.util.EnumResolver'a bildirildi . enum değerlerini tutmak için HashMap kullanır ve HashMap büyük / küçük harfe duyarsız anahtarları desteklemez.

Yukarıdaki yanıtlarda tüm karakterler büyük veya küçük harf olmalıdır. ancak bununla numaralandırmalar için tüm (in) hassas sorunları düzelttim:

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

Kullanım:

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

}

0

Iago Fernández ve Paul çözümünün bir modifikasyonunu kullandım.

İstek nesnemde büyük / küçük harfe duyarlı olmaması gereken bir sıralama vardı

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

-1

Büyük / küçük harfe duyarlı olmayan bir şekilde seri durumdan çıkarmak istediğimde (soruda yayınlanan kodu temel alarak) bazen numaralandırmaları şu şekilde ele alıyorum:

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

Enum önemsiz değilse ve dolayısıyla kendi sınıfındaysa, küçük harfli Dizeleri işlemek için genellikle statik bir ayrıştırma yöntemi eklerim.


-1

Jackson ile numaralandırmayı kaldırmak basittir. String tabanlı numaralandırmayı kaldırmak istediğinizde bir yapıcıya, bir alıcıya ve numaralandırmanız için bir ayarlayıcıya ihtiyaç duyduğunuzda, bu numaralandırmayı kullanan sınıfın, DataType'ı String değil, param olarak alan bir ayarlayıcıya sahip olması gerekir:

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'unuzu aldığınızda, ObjectMapper of Jackson'ı kullanarak Endpoint sınıfına seri durumdan kaldırabilirsiniz:

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();
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.