जैक्सन में एक कस्टम deserializer से मैं डिफॉल्ट डिसेरिएलाइज़र को कैसे कॉल करूं


105

मुझे जैक्सन में अपने कस्टम डिसेरिएलाइज़र में समस्या है। मैं डिफॉल्ट धारावाहिक को एक्सेस करना चाहता हूं ताकि मैं जिस ऑब्जेक्ट को डिस्क्राइबलाइज कर रहा हूं, उसे पॉप्युलेट कर सके। आबादी के बाद मैं कुछ कस्टम चीजें करूंगा लेकिन पहले मैं डिफ़ॉल्ट जैक्सन व्यवहार के साथ वस्तु को अलग करना चाहता हूं।

यह वह कोड है जो मेरे पास इस समय है।

public class UserEventDeserializer extends StdDeserializer<User> {

  private static final long serialVersionUID = 7923585097068641765L;

  public UserEventDeserializer() {
    super(User.class);
  }

  @Override
  @Transactional
  public User deserialize(JsonParser jp, DeserializationContext ctxt)
      throws IOException, JsonProcessingException {

    ObjectCodec oc = jp.getCodec();
    JsonNode node = oc.readTree(jp);
    User deserializedUser = null;
    deserializedUser = super.deserialize(jp, ctxt, new User()); 
    // The previous line generates an exception java.lang.UnsupportedOperationException
    // Because there is no implementation of the deserializer.
    // I want a way to access the default spring deserializer for my User class.
    // How can I do that?

    //Special logic

    return deserializedUser;
  }

}

मुझे डिफ़ॉल्ट डिसेरिएलाइज़र को इनिशियलाइज़ करने का एक तरीका है, ताकि मैं अपना विशेष तर्क शुरू करने से पहले अपने POJO को पहले से आबाद कर सकूँ।

कस्टम deserializer के भीतर से deserialize कॉल करते समय ऐसा लगता है कि विधि को वर्तमान संदर्भ से बुलाया जाता है, इससे कोई फर्क नहीं पड़ता कि मैं धारावाहिक वर्ग का निर्माण कैसे करता हूं। मेरे POJO में एनोटेशन के कारण। यह स्पष्ट कारणों के लिए एक स्टैक ओवरफ्लो अपवाद का कारण बनता है।

मैंने शुरू में कोशिश की है BeanDeserializerलेकिन प्रक्रिया बेहद जटिल है और मैं इसे करने का सही तरीका खोजने में कामयाब नहीं हुआ हूं। मैंने भी AnnotationIntrospectorबिना किसी लाभ के ओवरलोडिंग की कोशिश की , यह सोचकर कि इससे मुझे एनोटेशन को अनदेखा करने में मदद मिल सकती है DeserializerContext। अंत में यह सीम मैं कुछ सफलता का उपयोग कर लिया है, JsonDeserializerBuildersहालांकि यह मुझे वसंत से आवेदन के संदर्भ में पकड़ पाने के लिए कुछ जादू सामान करने के लिए आवश्यक हो सकता है। मैं किसी भी चीज की सराहना करता हूं जो मुझे उदाहरण के लिए एक क्लीनर समाधान तक ले जा सकता है मैं JsonDeserializerएनोटेशन को पढ़े बिना एक डीरिएराइजेशन संदर्भ कैसे बना सकता हूं ।


2
नहीं। वे दृष्टिकोण मदद नहीं करेंगे: समस्या यह है कि आपको एक पूर्ण रूप से निर्मित डिफाल्टाइज़र की आवश्यकता होगी; और इसके लिए यह आवश्यक है कि कोई व्यक्ति निर्मित हो, और फिर आपके डीसेरलाइज़र को उस तक पहुंच प्राप्त हो। DeserializationContextकुछ ऐसा नहीं है जिसे आपको या तो बनाना चाहिए या बदलना चाहिए; यह द्वारा प्रदान किया जाएगा ObjectMapperAnnotationIntrospectorइसी तरह, पहुँच प्राप्त करने में मदद नहीं करेगा।
StaxMan

आप इसे अंत में कैसे कर रहे थे?
खितुरस

अच्छा प्रश्न। मुझे यकीन नहीं है, लेकिन मुझे यकीन है कि नीचे दिए गए जवाब से मुझे मदद मिली। मैं वर्तमान में उस कोड के कब्जे में नहीं हूं जो हमने लिखा था यदि आप एक समाधान ढूंढते हैं तो कृपया इसे दूसरों के लिए यहां पोस्ट करें।
पाब्लो जोमेर

जवाबों:


93

जैसा कि स्टैक्समैन ने पहले ही सुझाव दिया था कि आप इसे लिखकर BeanDeserializerModifierऔर इसके माध्यम से पंजीकरण कर सकते हैं SimpleModule। निम्नलिखित उदाहरण काम करना चाहिए:

public class UserEventDeserializer extends StdDeserializer<User> implements ResolvableDeserializer
{
  private static final long serialVersionUID = 7923585097068641765L;

  private final JsonDeserializer<?> defaultDeserializer;

  public UserEventDeserializer(JsonDeserializer<?> defaultDeserializer)
  {
    super(User.class);
    this.defaultDeserializer = defaultDeserializer;
  }

  @Override public User deserialize(JsonParser jp, DeserializationContext ctxt)
      throws IOException, JsonProcessingException
  {
    User deserializedUser = (User) defaultDeserializer.deserialize(jp, ctxt);

    // Special logic

    return deserializedUser;
  }

  // for some reason you have to implement ResolvableDeserializer when modifying BeanDeserializer
  // otherwise deserializing throws JsonMappingException??
  @Override public void resolve(DeserializationContext ctxt) throws JsonMappingException
  {
    ((ResolvableDeserializer) defaultDeserializer).resolve(ctxt);
  }


  public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException
  {
    SimpleModule module = new SimpleModule();
    module.setDeserializerModifier(new BeanDeserializerModifier()
    {
      @Override public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer)
      {
        if (beanDesc.getBeanClass() == User.class)
          return new UserEventDeserializer(deserializer);
        return deserializer;
      }
    });


    ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(module);
    User user = mapper.readValue(new File("test.json"), User.class);
  }
}

धन्यवाद! मैंने पहले से ही इसे दूसरे तरीके से हल कर लिया है, लेकिन अधिक समय होने पर मैं आपके समाधान पर ध्यान दूंगा।
पाब्लो जोमेर

5
वहाँ एक ही करने के लिए एक तरीका है, लेकिन साथ है JsonSerializer? मेरे पास कई धारावाहिक हैं, लेकिन उनके पास आम पर कोड है इसलिए मैं इसे उत्पन्न करना चाहता हूं। मैं सीधे धारावाहिक को कॉल करने की कोशिश करता हूं, लेकिन परिणाम JSON परिणाम में
उजागर

1
@ हेरू BeanSerializerModifier, ResolvableSerializerऔर ContextualSerializerक्रमांकन के लिए उपयोग करने के लिए मिलान इंटरफेस हैं।
StaxMan

क्या यह ईई संस्करण कंटेनरों (वाइल्डफ्लाइ 10) के लिए लागू है? (Java.lang.NullPointerException था): मैं JsonMappingException मिल (संदर्भ श्रृंखला के माध्यम से: java.util.ArrayList [0])
user1927033

सवाल का उपयोग करता है, readTree()लेकिन जवाब नहीं है। डेरेक कोचरन द्वारा पोस्ट किए गए इस दृष्टिकोण का क्या फायदा है ? क्या इस काम को करने का कोई तरीका है readTree()?
जिली

14

मुझे एन्स पर एक उत्तर मिला जो स्वीकृत उत्तर की तुलना में बहुत अधिक पठनीय है।

    public User deserialize(JsonParser jp, DeserializationContext ctxt)
        throws IOException, JsonProcessingException {
            User user = jp.readValueAs(User.class);
             // some code
             return user;
          }

यह वास्तव में इससे आसान नहीं है।


हाय गिली! इसके लिए धन्यवाद मैं उम्मीद कर रहा हूं कि लोगों को यह उत्तर मिल जाएगा और इसे मान्य करने का समय मिल जाएगा। मैं अब ऐसा करने की स्थिति में नहीं हूं क्योंकि मैं इस समय उत्तर को स्वीकार नहीं कर सकता। अगर मुझे लगता है कि लोग कहते हैं कि यह एक संभव समाधान है, तो मैं निश्चित रूप से उन्हें इसके प्रति मार्गदर्शन करूंगा। यह भी हो सकता है कि यह सभी संस्करणों के लिए संभव न हो। फिर भी साझा करने के लिए धन्यवाद।
पाब्लो जोमेर

जैक्सन के साथ संकलन नहीं करता है 2.9.9। JsonParser.readTree () मौजूद नहीं है।
चाचा

@ccleve एक साधारण टाइपो की तरह दिखता है। फिक्स्ड।
जिली

पुष्टि कर सकते हैं कि यह जैक्सन 2.10 के साथ काम करता है, धन्यवाद!
स्टुअर्ट लीलैंड-कोल

2
मुझे नहीं पता कि यह कैसे काम करता है, यह एक परिणाम है StackOverflowError, क्योंकि जैक्सन फिर से के लिए एक ही धारावाहिक का उपयोग करेगा User...
john16384

12

DeserializationContextएक है readValue()विधि आप उपयोग कर सकते हैं। यह आपके डिफ़ॉल्ट डिफाइरलाइज़र और आपके पास किसी भी कस्टम डेज़रलाइज़र दोनों के लिए काम करना चाहिए।

बस traverse()उस JsonNodeस्तर पर कॉल करना सुनिश्चित करें जिसे आप JsonParserपास करने के लिए पुनः प्राप्त करने के लिए पढ़ना चाहते हैं readValue()

public class FooDeserializer extends StdDeserializer<FooBean> {

    private static final long serialVersionUID = 1L;

    public FooDeserializer() {
        this(null);
    }

    public FooDeserializer(Class<FooBean> t) {
        super(t);
    }

    @Override
    public FooBean deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        JsonNode node = jp.getCodec().readTree(jp);
        FooBean foo = new FooBean();
        foo.setBar(ctxt.readValue(node.get("bar").traverse(), BarBean.class));
        return foo;
    }

}

DeserialisationContext.readValue () मौजूद नहीं है, यह ऑब्जेक्ट मेपर का एक तरीका है
पेड्रो बोर्ग्स

यह समाधान अच्छी तरह से काम कर रहा है, हालांकि आपको अगली क्लास को () कॉल करने की आवश्यकता हो सकती है, यदि आप एक मूल्य वर्ग उदासीन करते हैं जैसे Date.class
Revau.lt

9

ऐसा करने के कुछ तरीके हैं, लेकिन इसे सही करने के लिए थोड़ा और काम करना पड़ता है। मूल रूप से आप उप-क्लासिंग का उपयोग नहीं कर सकते हैं, क्योंकि जानकारी डिफ़ॉल्ट डिसरिज़लाइज़र की आवश्यकता वर्ग परिभाषाओं से बनाई गई है।

तो क्या आप सबसे अधिक संभावना उपयोग कर सकते हैं एक निर्माण करने के लिए है BeanDeserializerModifier, कि Moduleइंटरफेस (उपयोग SimpleModule) के माध्यम से रजिस्टर । आपको परिभाषित करने / ओवरराइड करने की आवश्यकता है modifyDeserializer, और उस विशिष्ट मामले के लिए जहां आप अपना तर्क जोड़ना चाहते हैं (जहां मैच टाइप करते हैं), अपने खुद के डिसेरिएलाइज़र का निर्माण करें, आपके द्वारा दिए गए डिफॉल्टर्स को पास करें। और फिर deserialize()विधि में आप केवल कॉल प्रतिनिधि कर सकते हैं, परिणाम वस्तु ले सकते हैं।

वैकल्पिक रूप से, यदि आपको वास्तव में ऑब्जेक्ट को बनाना और आबाद करना है, तो आप ऐसा कर सकते हैं और deserialize()उस के अतिभारित संस्करण को कॉल कर सकते हैं जो तीसरा तर्क लेता है; में deserialize करने के लिए वस्तु।

एक और तरीका जो काम कर सकता है (लेकिन 100% निश्चित नहीं) Converterऑब्जेक्ट ( @JsonDeserialize(converter=MyConverter.class)) निर्दिष्ट करना होगा । यह एक नया जैक्सन 2.2 फीचर है। आपके मामले में, कनवर्टर वास्तव में प्रकार में परिवर्तित नहीं होगा, लेकिन ऑब्जेक्ट को संशोधित करना आसान है: लेकिन मुझे नहीं पता कि अगर आप चाहते हैं कि आप वास्तव में क्या करना चाहते हैं, क्योंकि डिफ़ॉल्ट डिसेरिएलाइज़र को पहले कहा जाएगा, और उसके बाद ही आपका Converter


मेरा जवाब अभी भी खड़ा है: आपको जैक्सन को प्रतिनिधि बनाने के लिए डिफ़ॉल्ट deserializer का निर्माण करने की आवश्यकता है; और इसे "ओवरराइड" करने का एक तरीका खोजना होगा। BeanDeserializerModifierकॉलबैक हैंडलर है जो इसकी अनुमति देता है।
StaxMan

7

यदि आपके लिए अतिरिक्त उपयोगकर्ता वर्ग घोषित करना संभव है तो आप इसे केवल एनोटेशन का उपयोग करके लागू कर सकते हैं

// your class
@JsonDeserialize(using = UserEventDeserializer.class)
public class User {
...
}

// extra user class
// reset deserializer attribute to default
@JsonDeserialize
public class UserPOJO extends User {
}

public class UserEventDeserializer extends StdDeserializer<User> {

  ...
  @Override
  public User deserialize(JsonParser jp, DeserializationContext ctxt)
      throws IOException, JsonProcessingException {
    // specify UserPOJO.class to invoke default deserializer
    User deserializedUser = jp.ReadValueAs(UserPOJO.class);
    return deserializedUser;

    // or if you need to walk the JSON tree

    ObjectMapper mapper = (ObjectMapper) jp.getCodec();
    JsonNode node = oc.readTree(jp);
    // specify UserPOJO.class to invoke default deserializer
    User deserializedUser = mapper.treeToValue(node, UserPOJO.class);

    return deserializedUser;
  }

}

1
हाँ। एकमात्र दृष्टिकोण जो मेरे लिए काम करता था। मैं deserializer के लिए एक पुनरावर्ती कॉल के कारण StackOverflowErrors हो रहा था।
22

3

Tomáš Záluský ने जो सुझाव दिए हैं , उन पंक्तियों के साथ , ऐसे मामलों में जहां उपयोग BeanDeserializerModifierकरना अवांछनीय है आप स्वयं का उपयोग करके एक डिफ़ॉल्ट डेज़रलाइज़र का निर्माण कर सकते हैं BeanDeserializerFactory, हालांकि कुछ अतिरिक्त सेटअप आवश्यक है। संदर्भ में, यह समाधान ऐसा दिखेगा:

public User deserialize(JsonParser jp, DeserializationContext ctxt)
  throws IOException, JsonProcessingException {

    ObjectCodec oc = jp.getCodec();
    JsonNode node = oc.readTree(jp);
    User deserializedUser = null;

    DeserializationConfig config = ctxt.getConfig();
    JavaType type = TypeFactory.defaultInstance().constructType(User.class);
    JsonDeserializer<Object> defaultDeserializer = BeanDeserializerFactory.instance.buildBeanDeserializer(ctxt, type, config.introspect(type));

    if (defaultDeserializer instanceof ResolvableDeserializer) {
        ((ResolvableDeserializer) defaultDeserializer).resolve(ctxt);
    }

    JsonParser treeParser = oc.treeAsTokens(node);
    config.initialize(treeParser);

    if (treeParser.getCurrentToken() == null) {
        treeParser.nextToken();
    }

    deserializedUser = (User) defaultDeserializer.deserialize(treeParser, context);

    return deserializedUser;
}

यह जैक्सन के साथ सपने की तरह काम करता है 2.9.9। यह दिए गए अन्य उदाहरण की तरह एक StackOverflowError से पीड़ित नहीं है।
meta1203

2

यहाँ ObjectMapper का उपयोग कर एक oneliner है

public MyObject deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
    OMyObject object = new ObjectMapper().readValue(p, MyObject.class);
    // do whatever you want 
    return object;
}

और कृपया: वास्तव में किसी भी स्ट्रिंग मूल्य या कुछ और का उपयोग करने की कोई आवश्यकता नहीं है। सभी आवश्यक जानकारी JsonParser द्वारा दी गई है, इसलिए इसका उपयोग करें।


1

मैं कस्टम डिसेरिज़ाइज़र के बजाय BeanSerializerModifierकेंद्रीय में कुछ व्यवहार परिवर्तन की घोषणा करने के लिए मजबूर करने के बाद से इसका उपयोग करने के साथ ठीक नहीं था ObjectMapperऔर वास्तव में यह इकाई वर्ग के साथ एनोटेट करने के लिए समानांतर समाधान है JsonSerialize। यदि आप इसे इसी तरह महसूस करते हैं, तो आप यहाँ मेरे उत्तर की सराहना कर सकते हैं: https://stackoverflow.com/a/43213463/653539


1

मेरे लिए एक सरल उपाय सिर्फ एक और बीन जोड़ना ObjectMapperऔर उपयोग करना था जो कि वस्तु को निष्क्रिय करने के लिए ( https://stackoverflow.com/users/1032167/varren टिप्पणी के लिए धन्यवाद ) - मेरे मामले में मैं अपनी आईडी के लिए या तो deserialize करना चाहता था (a int) या संपूर्ण वस्तु https://stackoverflow.com/a/46618193/986160

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import org.springframework.context.annotation.Bean;

import java.io.IOException;

public class IdWrapperDeserializer<T> extends StdDeserializer<T> {

    private Class<T> clazz;

    public IdWrapperDeserializer(Class<T> clazz) {
        super(clazz);
        this.clazz = clazz;
    }

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true);
        mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
        mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
        return mapper;
    }

    @Override
    public T deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
        String json = jp.readValueAsTree().toString();
          // do your custom deserialization here using json
          // and decide when to use default deserialization using local objectMapper:
          T obj = objectMapper().readValue(json, clazz);

          return obj;
     }
}

प्रत्येक इकाई के लिए जिसे कस्टम डिसेरिएलाइज़र के माध्यम से जाने की आवश्यकता है, हमें इसे ObjectMapperमेरे मामले में स्प्रिंग बूट ऐप के वैश्विक बीन में कॉन्फ़िगर करने की आवश्यकता है (उदाहरण के लिए Category):

@Bean
public ObjectMapper objectMapper() {
    ObjectMapper mapper = new ObjectMapper();
                mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true);
            mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
            mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
            mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
    SimpleModule testModule = new SimpleModule("MyModule")
            .addDeserializer(Category.class, new IdWrapperDeserializer(Category.class))

    mapper.registerModule(testModule);

    return mapper;
}

0

यदि आप खरोंच से अपने कस्टम deserializer बनाने की कोशिश करते हैं तो आप असफल होने के लिए बाध्य हैं।

इसके बजाय, आपको एक कस्टम के माध्यम से (पूरी तरह से कॉन्फ़िगर किए गए) डिफॉल्ट डेसलाइज़र उदाहरण को पकड़ना होगा BeanDeserializerModifier, और फिर इस कस्टम को अपने कस्टम डीसेरलाइज़र क्लास में पास करना होगा:

public ObjectMapper getMapperWithCustomDeserializer() {
    ObjectMapper objectMapper = new ObjectMapper();

    SimpleModule module = new SimpleModule();
    module.setDeserializerModifier(new BeanDeserializerModifier() {
        @Override
        public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
                    BeanDescription beanDesc, JsonDeserializer<?> defaultDeserializer) 
            if (beanDesc.getBeanClass() == User.class) {
                return new UserEventDeserializer(defaultDeserializer);
            } else {
                return defaultDeserializer;
            }
        }
    });
    objectMapper.registerModule(module);

    return objectMapper;
}

नोट: यह मॉड्यूल पंजीकरण @JsonDeserializeएनोटेशन की जगह लेता है , अर्थात इस एनोटेशन के साथ Userवर्ग या Userफ़ील्ड को अब एनोटेट नहीं किया जाना चाहिए।

कस्टम डिसेरिएलाइज़र तब पर आधारित होना चाहिए DelegatingDeserializerताकि सभी विधियाँ प्रत्यायोजित हों, जब तक कि आप एक स्पष्ट कार्यान्वयन प्रदान न करें:

public class UserEventDeserializer extends DelegatingDeserializer {

    public UserEventDeserializer(JsonDeserializer<?> delegate) {
        super(delegate);
    }

    @Override
    protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegate) {
        return new UserEventDeserializer(newDelegate);
    }

    @Override
    public User deserialize(JsonParser p, DeserializationContext ctxt)
            throws IOException {
        User result = (User) super.deserialize(p, ctxt);

        // add special logic here

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