Jackson'daki özel bir seri çözümleyiciden varsayılan seri kaldırıcıyı nasıl çağırırım


106

Jackson'daki özel seri kaldırıcımda bir sorun var. Seri durumdan çıkardığım nesneyi doldurmak için varsayılan serileştiriciye erişmek istiyorum. Popülasyondan sonra bazı özel şeyler yapacağım ama önce nesneyi varsayılan Jackson davranışıyla seri durumdan çıkarmak istiyorum.

Şu anda sahip olduğum kod bu.

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

}

İhtiyacım olan şey, özel mantığıma başlamadan önce POJO'mu önceden doldurabilmem için varsayılan seri kaldırıcıyı başlatmanın bir yoludur.

Özel serileştiricinin içinden seriyi kaldırmayı çağırırken Serileştirici sınıfını nasıl kurarsam yapayım, yöntem geçerli bağlamdan çağrılıyor gibi görünüyor. POJO'mdaki ek açıklama nedeniyle. Bu, bariz nedenlerle Yığın Taşması istisnasına neden olur.

A'yı başlatmayı denedim BeanDeserializerancak süreç son derece karmaşık ve bunu yapmanın doğru yolunu bulamadım. Ayrıca AnnotationIntrospector, içindeki ek açıklamayı görmezden gelmeme yardımcı olabileceğini düşünerek boşuna aşırı yüklemeyi denedim DeserializerContext. Sonunda JsonDeserializerBuilders, Spring'in uygulama içeriğini elde etmek için bazı sihirli şeyler yapmamı gerektirmesine rağmen, kullanarak biraz başarılı olabileceğimi gösteriyor . Beni daha temiz bir çözüme götürebilecek her şeyi takdir ederim, örneğin JsonDeserializerek açıklamayı okumadan seri durumdan çıkarma bağlamını nasıl oluşturabilirim .


2
Hayır. Bu yaklaşımlar yardımcı olmayacaktır: sorun, tamamen oluşturulmuş bir varsayılan seri kaldırıcıya ihtiyaç duymanızdır; ve bu, birinin oluşturulmasını ve ardından seri kaldırıcınızın ona erişmesini gerektirir. DeserializationContextyaratmanız veya değiştirmeniz gereken bir şey değildir; tarafından sağlanacaktır ObjectMapper. AnnotationIntrospector, aynı şekilde, erişim sağlamada yardımcı olmayacaktır.
StaxMan

Sonunda bunu nasıl başardın?
khituras

İyi soru. Emin değilim ama aşağıdaki cevabın bana yardımcı olduğundan eminim. Şu anda yazdığımız kod bende yok eğer bir çözüm bulursanız lütfen buraya başkaları için gönderin.
Pablo Jomer

Yanıtlar:


93

StaxMan'ın daha önce önerdiği gibi, bunu bir yazıp BeanDeserializerModifierkayıt yaparak yapabilirsiniz SimpleModule. Aşağıdaki örnek çalışmalıdır:

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

Teşekkürler! Bunu zaten başka bir şekilde çözdüm ama daha fazla zamanım olduğunda çözümünüze bakacağım.
Pablo Jomer

5
Aynısını yapmanın bir yolu var mı ama a ile JsonSerializer? Birkaç serileştiricim var ama ortak kodları var, bu yüzden onu doğrulamak istiyorum. Doğrudan serileştiriciyi aramaya çalışıyorum ama sonuç JSON sonucunda açılmıyor (her serileştirici çağrısı yeni bir nesne oluşturuyor)
herau

1
@herau BeanSerializerModifier, ResolvableSerializerve ContextualSerializerserileştirme için kullanılacak eşleştirme arayüzleri bulunmaktadır.
StaxMan

Bu, EE sürüm kapları için geçerli mi (Wildfly 10)? JsonMappingException'ı alıyorum: (java.lang.NullPointerException) (referans zinciri aracılığıyla: java.util.ArrayList [0])
user1927033

Soru kullanır readTree()ama cevap kullanmaz . Bu yaklaşımın Derek Cochran tarafından yayınlanan yaklaşıma göre avantajı nedir ? Bunu çalıştırmanın bir yolu var mı readTree()?
Gili

15

Ben de bir cevap buldum ans çok daha okunabilir kabul cevap aşıyor.

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

Gerçekten bundan daha kolay olamaz.


Merhaba Gili! Bunun için teşekkürler, insanların bu cevabı bulmalarını ve onaylamak için zamanları olmasını umuyorum. Artık orada bunu yapacak durumda değilim çünkü şu anda cevabı kabul edemiyorum. İnsanların bunun olası bir çözüm olduğunu söylediğini görürsem, elbette onları ona doğru yönlendireceğim. Ayrıca bu, tüm sürümler için mümkün olmayabilir. Yine de paylaştığın için teşekkürler.
Pablo Jomer

Jackson 2.9.9 ile derlenmez. JsonParser.readTree () mevcut değil.
ccleve

@ccleve Basit bir yazım hatası gibi görünüyor. Sabit.
Gili

Bunun Jackson 2.10 ile çalıştığını doğrulayabilir, teşekkürler!
Stuart Leyland-Cole

3
Bunun nasıl çalıştığını anlamıyorum, bu a ile sonuçlanıyor StackOverflowError, çünkü Jackson aynı serileştiriciyi tekrar kullanacak User...
john16384

12

DeserializationContextBir sahiptir readValue()kullanabileceğiniz yöntemi. Bu, hem varsayılan seri kaldırıcı hem de sahip olduğunuz tüm özel seri kaldırıcılar için çalışmalıdır.

Sadece çağrı emin olun traverse()üzerinde JsonNodeGetirmek okumak istediğiniz seviyeye JsonParsergeçmek 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 () mevcut değil, bu ObjectMapper'ın bir yöntemidir
Pedro Borges

bu çözüm iyi çalışıyor, ancak bir değer sınıfının serisini kaldırırsanız nextToken () 'i çağırmanız gerekebilir, örneğin Date.class
revau.lt

9

Bunu yapmanın birkaç yolu vardır, ancak bunu doğru yapmak biraz daha fazla çalışmayı gerektirir. Temel olarak alt sınıflandırma kullanamazsınız, çünkü varsayılan seri durum kaldırıcıların ihtiyaç duyduğu bilgiler sınıf tanımlarından oluşturulmuştur.

Öyleyse, büyük olasılıkla kullanabileceğiniz şey, bir oluşturmak BeanDeserializerModifier, bunu kaydetmekModule arayüz kaydedin (kullanın SimpleModule). Tanımlamanız / geçersiz kılmanız modifyDeserializerve kendi mantığınızı eklemek istediğiniz özel durum için (tür eşleştiğinde) kendi seri kaldırıcınızı oluşturun, size verilen varsayılan seri kaldırıcıyı geçin. Ve sonra deserialize()yöntemde çağrıyı delege edebilir, sonuç Object'i alabilirsiniz.

Alternatif olarak, nesneyi gerçekten oluşturmanız ve doldurmanız gerekiyorsa, bunu yapabilir ve deserialize()üçüncü argümanı alan aşırı yüklenmiş sürümünü çağırabilirsiniz ; serisini kaldırılacak nesne.

Çalışabilecek (ancak% 100 emin olmayan) başka bir yol da Converterobject ( @JsonDeserialize(converter=MyConverter.class)) belirtmektir . Bu, yeni bir Jackson 2.2 özelliğidir. Sizin durumunuzda, Dönüştürücü aslında türü dönüştürmez, ancak nesneyi değiştirmeyi basitleştirir: ancak bunun tam olarak istediğiniz şeyi yapmanıza izin verip vermeyeceğini bilmiyorum, çünkü varsayılan seri çözücü önce ve yalnızca o zaman sizin Converter.


Cevabım hala geçerli: Jackson'ın delege etmek için varsayılan seri kaldırıcıyı oluşturmasına izin vermelisiniz; ve onu "geçersiz kılmanın" bir yolunu bulmalı. BeanDeserializerModifierbuna izin veren geri arama işleyicisidir.
StaxMan

7

Ekstra Kullanıcı sınıfı bildirmeniz mümkünse, bunu yalnızca ek açıklamaları kullanarak uygulayabilirsiniz.

// 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
Evet. Benim için işe yarayan tek yaklaşım. Seri durumdan çıkarıcıya yapılan özyinelemeli bir çağrı nedeniyle StackOverflowErrors alıyordum.
ccleve

3

Ne doğrultuda Tomáš Záluský önerdi kullanarak durumlarda, BeanDeserializerModifierkullandığınız kendinize deserializer varsayılan inşa edebilir istenmeyen BeanDeserializerFactorygerekli bazı ekstra kurulum olmasına rağmen,. Bağlamda, bu çözüm şöyle görünecektir:

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

Bu, Jackson 2.9.9 ile bir rüya gibi çalışır. Verilen diğer örnek gibi bir StackOverflowError'dan muzdarip değildir.
meta1203

2

İşte ObjectMapper kullanan bir 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;
}

Ve lütfen: Herhangi bir String değeri veya başka bir şey kullanmaya gerçekten gerek yoktur. Gerekli tüm bilgiler JsonParser tarafından verilmektedir, bu yüzden onu kullanın.


1

Özel seri çözümleyicinin kendisinden ziyade BeanSerializerModifierbazı davranış değişikliklerini merkezi olarak bildirmeye zorladığı ObjectMapperve aslında varlık sınıfına açıklama eklemeye paralel bir çözüm olduğu için kullanmakta sorunum yoktu JsonSerialize. Benzer şekilde hissediyorsanız, cevabımı burada takdir edebilirsiniz : https://stackoverflow.com/a/43213463/653539


1

Benim için daha basit bir çözüm, başka bir fasulye eklemek ObjectMapperve nesneyi seri durumdan çıkarmak için kullanmaktı ( https://stackoverflow.com/users/1032167/varren yorumu sayesinde ) - benim durumumda, kimliğine göre seri durumdan çıkarmakla ilgileniyordum (bir int) veya tüm nesne 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;
     }
}

özel serileştiriciden geçmesi gereken her varlık için ObjectMapperbenim durumumda Spring Boot Uygulamasının global çekirdeğinde yapılandırmamız gerekir (örneğin 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

Özel seri kaldırıcınızı sıfırdan oluşturmaya çalışırsanız başarısız olabilirsiniz.

Bunun yerine, (tamamen yapılandırılmış) varsayılan seri kaldırıcı örneğini bir özel aracılığıyla BeanDeserializerModifierelde etmeniz ve ardından bu örneği özel seri kaldırma sınıfınıza iletmeniz gerekir:

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

Not: Bu modül kaydı, @JsonDeserializeaçıklamanın yerini alır , yani Usersınıf veya Useralanlara artık bu açıklama ile açıklama eklenmemelidir.

DelegatingDeserializerAçık bir uygulama sağlamadığınız sürece, özel seri durumdan çıkarıcı , tüm yöntemlerin delege edilmesi için a'ya dayanmalıdır :

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

0

Kullanmak BeanDeserializerModifieriyi çalışıyor, ancak kullanmanız gerekiyorsa, JsonDeserializebunun AnnotationIntrospector gibi yapmanın bir yolu var :

ObjectMapper originalMapper = new ObjectMapper();
ObjectMapper copy = originalMapper.copy();//to keep original configuration
copy.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {

            @Override
            public Object findDeserializer(Annotated a) {
                Object deserializer = super.findDeserializer(a);
                if (deserializer == null) {
                    return null;
                }
                if (deserializer.equals(MyDeserializer.class)) {
                    return null;
                }
                return deserializer;
            }
});

Şimdi kopyalanan eşleyici artık özel seri kaldırıcınızı (MyDeserializer.class) yok sayacak ve varsayılan uygulamayı kullanacaktır. deserializeKopyalanan eşleyiciyi statik hale getirerek özyinelemeyi önlemek için özel seriyi kaldırma yönteminin içinde kullanabilirsiniz veya Yay kullanıyorsanız onu bağlayabilirsiniz.

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.