जैक्सन डेटाबाइंड एनम केस असंवेदनशील


97

मैं JSON स्ट्रिंग को कैसे निष्क्रिय कर सकता हूं जिसमें Enum मान शामिल हैं जो केस असंवेदनशील हैं? (जैक्सन डाटबिंड का उपयोग करके)

JSON स्ट्रिंग:

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

और मेरे जावा POJO:

public static class Endpoint {

    public enum DataType {
        JSON, HTML
    }

    public String url;
    public DataType type;

    public Endpoint() {

    }

}

इस मामले में, JSON के साथ deserializing "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();
    }

और इसने काम किया। लेकिन मैं सोच रहा था कि क्या कोई बेहतर विलेय है क्योंकि यह मेरे लिए एक हैक की तरह दिखता है।

मैं एक कस्टम डिसेरिज़ाइज़र भी लिख सकता हूं लेकिन मुझे कई अलग-अलग पीओजेओ मिले जो एनम का उपयोग करते हैं और इसे बनाए रखना मुश्किल होगा।

क्या कोई उचित नामकरण सम्मेलन के साथ शत्रुओं को अनुक्रमित और निर्जन करने का एक बेहतर तरीका सुझा सकता है?

मैं नहीं चाहता कि जावा में मेरी दुश्मनी कम हो!

यहाँ कुछ परीक्षण कोड का उपयोग किया गया है:

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

आप जैक्सन के किस संस्करण पर हैं? इस JIRA jira.codehaus.org/browse/JACKSON-861
एलेक्सी

मैं जैक्सन का उपयोग कर रहा हूँ 2.2.3
tom91136

ठीक है मैंने अभी-अभी 2.4.0-RC3
tom91136

जवाबों:


38

2.4.0 संस्करण में, आप सभी Enum प्रकारों के लिए एक कस्टम धारावाहिक निर्माता ( जीथब इश्यू के लिए लिंक ) को पंजीकृत कर सकते हैं । इसके अलावा आप अपने दम पर मानक Enum deserializer को बदल सकते हैं जो Enum प्रकार के बारे में जागरूक होगा। यहाँ एक उदाहरण है:

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]

1
धन्यवाद, अब मैं अपने POJO :)
tom91136

मैं व्यक्तिगत रूप से अपनी परियोजनाओं में इसके लिए वकालत कर रहा हूं। यदि आप मेरे उदाहरण को देखते हैं, तो इसके लिए बहुत सारे बॉयलरप्लेट कोड की आवश्यकता होती है। De / serialization के लिए एक अलग विशेषता का उपयोग करने का एक लाभ यह है कि यह क्लाइंट-महत्वपूर्ण मान (सुंदर प्रिंट) के लिए जावा-महत्वपूर्ण मान (एनम नाम) के नामों को डिकॉय करता है। उदाहरण के लिए, यदि यह HTML DataType को HTML_DATA_TYPE में बदलना चाहते हैं, तो आप बाहरी API को प्रभावित किए बिना ऐसा कर सकते हैं, यदि कोई कुंजी निर्दिष्ट हो।
सैम बेरी

1
यह एक अच्छी शुरुआत है लेकिन यह विफल हो जाएगा यदि आपकी ईमली JsonProperty या JsonCreator का उपयोग कर रही है। Dropwizard में FuzzyEnumModule है जो अधिक मजबूत कार्यान्वयन है।
Pixel Elephant

131

जैक्सन 2.9

jackson-databind2.9.0 और इसके बाद के संस्करण का उपयोग करते हुए यह अब बहुत सरल है

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

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

परीक्षणों के साथ पूर्ण उदाहरण

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
यह एक सोना है!
विकास प्रसाद

8
मैं 2.9.2 का उपयोग कर रहा हूँ और यह काम नहीं करता है। इसके कारण: com.fasterxml.jackson.databind.exc.InvalidFormatException: टाइप का deserialize मान नहीं हो सकता है .... स्ट्रिंग "पुरुष" से लिंग: घोषित नहीं उदाहरण उदाहरणों में से एक मूल्य: [FAMALE, MALE]
जॉर्डन सिल्वा

@ जोर्डनसिल्वा यह निश्चित रूप से v2.9.2 के साथ काम करता है। मैंने सत्यापन के लिए परीक्षणों के साथ एक पूर्ण कोड उदाहरण जोड़ा है। मुझे नहीं पता कि आपके मामले में क्या हुआ होगा, लेकिन jackson-databind2.9.2 के साथ उदाहरण कोड चलाना विशेष रूप से अपेक्षित है।
davnicwil

4
स्प्रिंग बूट का उपयोग करके, आप बस संपत्ति जोड़ सकते हैंspring.jackson.mapper.accept-case-insensitive-enums=true
Arne Burmeister

1
@ जोर्डनसिल्वा शायद आप मापदंडों को प्राप्त करने के लिए दुश्मनी करने की कोशिश कर रहे हैं जैसा कि मैंने किया? =) मैंने अपनी समस्या हल की है और यहां जवाब दिया है। आशा है कि यह मदद कर सकता है
Konstantin Zyubin

83

मैं अपनी परियोजना में इसी मुद्दे पर भाग गया, हमने @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;
    }
}

1
यह होना चाहिए DataType.valueOf(key.toUpperCase())- अन्यथा, आपने वास्तव में कुछ भी नहीं बदला है। एनपीई से बचने के लिए रक्षात्मक रूप से कोडिंग:return (null == key ? null : DataType.valueOf(key.toUpperCase()))
sarumont

2
अच्छा पकड़ @sarumont। मैंने संपादन किया है। इसके अलावा, JAX-RS के साथ अच्छी तरह से खेलने के लिए "fromString" का बदला हुआ तरीका ।
सैम बेरी

1
मुझे यह दृष्टिकोण पसंद आया, लेकिन कम वर्बोस वैरिएंट के लिए गया, नीचे देखें।
linqu

2
स्पष्ट रूप से keyक्षेत्र अनावश्यक है। में getKey, आप बसreturn name().toLowerCase()
जुआर

1
मैं मामले में प्रमुख क्षेत्र को पसंद करता हूं जहां आप एनम का नाम कुछ अलग करना चाहते हैं जो कि जौन के पास होगा। मेरे मामले में एक विरासत प्रणाली वास्तव में संक्षिप्त और कठिन है कि वह जो मूल्य भेजता है उसका नाम याद रखना, और मैं इस क्षेत्र का उपयोग अपने जावा एनम के लिए बेहतर नाम में अनुवाद करने के लिए कर सकता हूं।
ग्रिंच

45

जैक्सन 2.6 के बाद से, आप बस यह कर सकते हैं:

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

एक पूर्ण उदाहरण के लिए, यह देखें ।


25
ध्यान दें कि ऐसा करने से समस्या उलट जाएगी। अब जैक्सन केवल लोअरकेस को स्वीकार करेगा, और किसी भी अपरकेस या मिश्रित-केस मान को अस्वीकार कर देगा।
Pixel Elephant

30

मैं सैम बी के समाधान के लिए गया था लेकिन एक सरल संस्करण।

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

मुझे नहीं लगता कि यह सरल है। DataType.valueOf(key.toUpperCase())एक सीधा तात्कालिकता है जहाँ आपके पास एक लूप है। यह एक बहुत ही असंख्य के लिए एक मुद्दा हो सकता है। बेशक, valueOfएक IllegalArgumentException को फेंक सकता है, जिसे आपका कोड बचता है, इसलिए यह अच्छा लाभ है यदि आप अपवाद जाँच के लिए शून्य जाँच पसंद करते हैं।
पैट्रिक एम

20

यदि आप 2.1.xजैक्सन के साथ स्प्रिंग बूट का उपयोग कर रहे हैं तो आप 2.9बस इस एप्लिकेशन प्रॉपर्टी का उपयोग कर सकते हैं:

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


3

उन लोगों के लिए जो जीईटी मापदंडों में मामले की अनदेखी करने वाले एनम को अयोग्य बनाने की कोशिश करते हैं, सक्षम करने से ACCEPT_CASE_INSENSITIVE_ENUMS अच्छा नहीं होगा। यह मदद नहीं करेगा क्योंकि यह विकल्प केवल शरीर के डीराइजेशन के लिए काम करता है । इसके बजाय यह प्रयास करें:

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

और फिर

@Configuration
public class WebConfig implements WebMvcConfigurer {

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

जवाब और कोड के नमूने यहां से हैं


1

@Konstantin Zyubin से माफी मांगने के साथ, उनका जवाब था कि मुझे क्या चाहिए - लेकिन मुझे यह समझ में नहीं आया, इसलिए यहां बताया गया है कि मुझे क्या करना चाहिए:

यदि आप मामले में असंवेदनशील के रूप में एक enum प्रकार deserialize करना चाहते हैं - यानी आप नहीं चाहते हैं, या नहीं कर सकते, पूरे आवेदन के व्यवहार को संशोधित, आप सिर्फ एक प्रकार के लिए एक कस्टम deserializer बना सकते हैं - उप-क्लासिंग StdConverterऔर बल द्वारा जैक्सन का उपयोग करने के लिए प्रासंगिक क्षेत्रों पर ही इसका उपयोग करने के लिएJsonDeserialize एनोटेशन ।

उदाहरण:

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

0

समस्या com.fasterxml.jackson.databind.util.EnumResolver से संबंधित है । यह Enum मान रखने के लिए 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);
    }

}

0

मैंने इगो फर्नांडीज और पॉल समाधान का एक संशोधन किया।

मेरे पास मेरे अनुरोध में एक एनम थी जो असंवेदनशील होने की जरूरत थी

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

0

जैकसन में मामलों के असंवेदनशील मामले में असंवेदनशील अनुमति देने के लिए, बस application.propertiesअपनी स्प्रिंग बूट परियोजना की फ़ाइल में नीचे की संपत्ति जोड़ें ।

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

यदि आपके पास गुण फ़ाइल का yaml संस्करण है, तो अपनी application.ymlफ़ाइल में संपत्ति के नीचे जोड़ें ।

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

-1

यहां बताया गया है कि जब मैं केस-असंवेदनशील तरीके से (प्रश्न में पोस्ट किए गए कोड पर बिल्डिंग का निर्माण करना चाहता हूं):

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

यदि एनम गैर-तुच्छ है और इस प्रकार अपने स्वयं के वर्ग में मैं आमतौर पर लोअरकेस स्ट्रिंग्स को संभालने के लिए एक स्थिर पार्स विधि जोड़ देता हूं।


-1

जैकसन के साथ डीमेरिटाइज़ एनम सरल है। जब आप स्ट्रिंग में स्थित deserialize enum चाहते हैं, तो आपको अपने enum.Also वर्ग का उपयोग करने वाले एक कंस्ट्रक्टर, एक गेटर और एक सेटर की आवश्यकता होती है, जिसमें उस एनम का उपयोग एक सेटर होना चाहिए जो DataType को परम के रूप में प्राप्त करता है, न कि स्ट्रिंग:

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

}

जब आपके पास अपना जसन होता है, तो आप ऑब्जेक्ट जैक्सन के ऑब्जेक्टमैपर का उपयोग करके एंडपॉइंट क्लास को डिसेर्बलाइज कर सकते हैं:

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();
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.