Como faço para chamar o desserializador padrão de um desserializador personalizado em Jackson


105

Tenho um problema no meu desserializador personalizado em Jackson. Desejo acessar o serializador padrão para preencher o objeto para o qual estou desserializando. Depois da população, farei algumas coisas personalizadas, mas primeiro quero desserializar o objeto com o comportamento padrão de Jackson.

Este é o código que tenho no momento.

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

}

O que eu preciso é de uma maneira de inicializar o desserializador padrão para que eu possa pré-preencher meu POJO antes de iniciar minha lógica especial.

Ao chamar desserializar de dentro do desserializador personalizado Parece que o método é chamado a partir do contexto atual, não importa como eu construo a classe do serializador. Por causa da anotação no meu POJO. Isso causa uma exceção Stack Overflow por razões óbvias.

Tentei inicializar um, BeanDeserializermas o processo é extremamente complexo e não consegui encontrar a maneira certa de fazer isso. Também tentei sobrecarregar o AnnotationIntrospectorsem sucesso, pensando que isso poderia me ajudar a ignorar a anotação no DeserializerContext. Por fim, parece que tive algum sucesso com o uso, JsonDeserializerBuildersembora isso exigisse que eu fizesse algumas coisas mágicas para obter o contexto do aplicativo do Spring. Eu apreciaria qualquer coisa que pudesse me levar a uma solução mais limpa, por exemplo, como posso construir um contexto de desserialização sem ler a JsonDeserializeranotação.


2
Não. Essas abordagens não ajudarão: o problema é que você precisará de um desserializador padrão totalmente construído; e isso requer que um seja criado e, em seguida, seu desserializador tenha acesso a ele. DeserializationContextnão é algo que você deve criar ou alterar; será fornecido por ObjectMapper. AnnotationIntrospector, da mesma forma, não ajudará na obtenção de acesso.
— StaxMan de

Como você acabou fazendo isso no final?
— khituras

Boa pergunta. Não tenho certeza, mas tenho certeza de que a resposta abaixo me ajudou. No momento, não estou de posse do código que escrevemos. Se você encontrar uma solução, poste-o aqui para outras pessoas.
— Pablo Jomer

Respostas:


93

Como StaxMan já sugeriu, você pode fazer isso escrevendo um BeanDeserializerModifier e registrando-o via SimpleModule. O seguinte exemplo deve funcionar:

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

Obrigado! Já resolvi isso de outra forma, mas irei buscar sua solução quando tiver mais tempo.
— Pablo Jomer

5
Existe uma maneira de fazer o mesmo, mas com um JsonSerializer? Eu tenho vários serializadores, mas eles têm código em comum, então quero generalizá-lo. Tento chamar diretamente o serializador, mas o resultado não é desembrulhado no resultado JSON (cada chamada do serializador cria um novo objeto)
— herau

1
@herau BeanSerializerModifier, ResolvableSerializere ContextualSerializersão as interfaces de correspondência a ser usado para serialização.
— StaxMan

Isso é aplicável para contêineres da edição EE (Wildfly 10)? Recebo JsonMappingException: (era java.lang.NullPointerException) (por meio da cadeia de referência: java.util.ArrayList [0])
— user1927033 de

A pergunta usa, readTree()mas a resposta não. Qual é a vantagem dessa abordagem em relação à postada por Derek Cochran ? Existe uma maneira de fazer isso funcionar readTree()?
— Gili

14

Encontrei uma resposta em ans que é muito mais legível do que a resposta aceita.

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

Realmente não pode ser mais fácil do que isso.


Oi Gili! Obrigado por isso, espero que as pessoas encontrem essa resposta e tenham tempo para validá-la. Não estou mais em posição de fazê-lo lá, pois não posso aceitar a resposta no momento. Se eu vir que as pessoas dizem que essa é uma solução possível, é claro que as guiarei nessa direção. Também pode ser que isso não seja possível para todas as versões. Ainda obrigado por compartilhar.
— Pablo Jomer

Não compila com Jackson 2.9.9. JsonParser.readTree () não existe.
— ccleve

@ccleve Parece um erro de digitação simples. Fixo.
— Gili

Posso confirmar que isso funciona com Jackson 2.10, obrigado!
— Stuart Leyland-Cole

2
Não entendi como isso funciona, o que resulta em um StackOverflowError, já que Jackson usará o mesmo serializador novamente para User...
— john16384

12

O DeserializationContexttem um readValue()método que você pode usar. Isso deve funcionar para o desserializador padrão e qualquer desserializador personalizado que você tiver.

Apenas certifique-se de chamar traverse()no JsonNodenível que deseja ler para recuperar o JsonParserpara passar 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 () não existe, que é um método de ObjectMapper
— Pedro Borges

esta solução está funcionando bem, no entanto, você pode precisar chamar nextToken () se você desserializar uma classe de valor, por exemplo, Date.class
— revau.lt

9

Existem algumas maneiras de fazer isso, mas fazer certo envolve um pouco mais de trabalho. Basicamente, você não pode usar a subclasse, uma vez que as informações de que os desserializadores padrão precisam é criado a partir de definições de classe.

Portanto, o que você provavelmente pode usar é construir um BeanDeserializerModifier, registrá-lo via Moduleinterface (use SimpleModule). Você precisa definir / substituir modifyDeserializere, para o caso específico em que deseja adicionar sua própria lógica (em que o tipo corresponde), construir seu próprio desserializador, passar o desserializador padrão que você recebeu. E então, no deserialize()método, você pode simplesmente delegar a chamada, pegar o Object resultante.

Alternativamente, se você realmente deve criar e preencher o objeto, você pode fazer isso e chamar a versão sobrecarregada deserialize()que leva o terceiro argumento; objeto para desserializar.

Outra maneira que pode funcionar (mas não 100% certa) seria especificar Converterobject ( @JsonDeserialize(converter=MyConverter.class)). Este é um novo recurso do Jackson 2.2. No seu caso, o Converter não converteria realmente o tipo, mas simplificaria a modificação do objeto: mas não sei se isso permitiria que você fizesse exatamente o que deseja, já que o desserializador padrão seria chamado primeiro, e só depois o seu Converter.


Minha resposta ainda permanece: você precisa deixar Jackson construir o desserializador padrão para o qual delegar; e tem que encontrar uma maneira de "substituí-lo". BeanDeserializerModifieré o manipulador de retorno de chamada que permite isso.
— StaxMan de

7

Se for possível para você declarar uma classe de usuário extra, você pode implementá-la apenas usando anotações

// 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
Sim. A única abordagem que funcionou para mim. Eu estava recebendo StackOverflowErrors por causa de uma chamada recursiva para o desserializador.
— ccleve

3

Seguindo as linhas do que Tomáš Záluský sugeriu , nos casos em que o uso BeanDeserializerModifieré indesejável, você pode construir um desserializador padrão usando BeanDeserializerFactory, embora haja alguma configuração extra necessária. No contexto, essa solução seria assim:

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

Isso funciona como um sonho com Jackson 2.9.9. Ele não sofre de StackOverflowError como o outro exemplo dado.
— meta1203

2

Aqui está um oneliner usando ObjectMapper

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

E por favor: realmente não há necessidade de usar nenhum valor String ou qualquer outra coisa. Todas as informações necessárias são fornecidas pelo JsonParser, então use-o.


1

Eu não estava ok com o uso, BeanSerializerModifierpois força a declarar algumas mudanças comportamentais no central em ObjectMappervez do próprio desserializador personalizado e, de fato, é uma solução paralela para anotar classes de entidade com JsonSerialize. Se você acha que é da mesma forma, pode gostar de minha resposta aqui: https://stackoverflow.com/a/43213463/653539


1

Uma solução mais simples para mim era apenas adicionar outro feijão de ObjectMapper e usá-lo para desserializar o objeto (graças ao https://stackoverflow.com/users/1032167/varren comentário) - no meu caso, estava interessado em desserializar para seu id (um int) ou o objeto inteiro 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;
     }
}

para cada entidade que precisa passar pelo desserializador personalizado, precisamos configurá-la no ObjectMapperbean global do Spring Boot App no ​​meu caso (por exemplo, para 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

Você está fadado ao fracasso se tentar criar seu desserializador personalizado do zero.

Em vez disso, você precisa obter a instância do desserializador padrão (totalmente configurada) por meio de um personalizado BeanDeserializerModifiere, em seguida, passar essa instância para sua classe do desserializador personalizado:

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

Nota: O registro deste módulo substitui a @JsonDeserializeanotação, ou seja, a Userclasse ou os Usercampos não devem mais ser anotados com esta anotação.

O desserializador personalizado deve ser baseado em um DelegatingDeserializerpara que todos os métodos sejam delegados, a menos que você forneça uma implementação explícita:

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;
    }
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.