Como personalizar o mapeador Jackson JSON usado implicitamente pelo Spring Boot?

101

Estou usando Spring Boot (1.2.1), de maneira semelhante ao tutorial Construindo um serviço da Web RESTful :

@RestController
public class EventController {

    @RequestMapping("/events/all")
    EventList events() {
        return proxyService.getAllEvents();
    }

}

Acima, Spring MVC implicitamente usa Jackson para serializar meu EventListobjeto em JSON.

Mas quero fazer algumas personalizações simples para o formato JSON, como:

setSerializationInclusion(JsonInclude.Include.NON_NULL)

A questão é: qual é a maneira mais simples de personalizar o mapeador JSON implícito?

Tentei a abordagem nesta postagem do blog , criando um CustomObjectMapper e assim por diante, mas a etapa 3, "Registrar classes no contexto Spring", falhou:

org.springframework.beans.factory.BeanCreationException: 
  Error creating bean with name 'jacksonFix': Injection of autowired dependencies failed; 
  nested exception is org.springframework.beans.factory.BeanCreationException: 
  Could not autowire method: public void com.acme.project.JacksonFix.setAnnotationMethodHandlerAdapter(org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter); 
  nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: 
  No qualifying bean of type [org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter]   
  found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}

Parece que essas instruções são para versões mais antigas do Spring MVC, mas estou procurando uma maneira simples de fazer isso funcionar com o Spring Boot mais recente.

Jonik
fonte
Você inseriu esta anotação ?: @SuppressWarnings ({"SpringJavaAutowiringInspection"})
sven.kwiotek
Observe que, se você também estiver usando Spring Web, precisará instruí-lo manualmente para usar este ObjectMapper, caso contrário, ele criará sua própria instância, que não será configurada. Consulte stackoverflow.com/questions/7854030/…
Constantino Cronemberger

Respostas:

116

Se estiver usando Spring Boot 1.3, você pode configurar a inclusão de serialização via application.properties:

spring.jackson.serialization-inclusion=non_null

Após as mudanças no Jackson 2.7, Spring Boot 1.4 usa uma propriedade chamada em spring.jackson.default-property-inclusionvez disso:

spring.jackson.default-property-inclusion=non_null

Consulte a seção " Customizar o Jackson ObjectMapper " na documentação do Spring Boot.

Se você estiver usando uma versão anterior do Spring Boot, a maneira mais fácil de configurar a inclusão de serialização no Spring Boot é declarar seu próprio Jackson2ObjectMapperBuilderbean configurado apropriadamente . Por exemplo:

@Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
    Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
    builder.serializationInclusion(JsonInclude.Include.NON_NULL);
    return builder;
}
Andy Wilkinson
fonte
1
Tudo bem, isso parece funcionar. Onde você colocaria tal método? Talvez em uma classe de aplicativo principal (com @ComponentScan, @EnableAutoConfigurationetc)?
Jonik
2
Sim. Este método pode ser usado em qualquer @Configurationclasse em seu aplicativo. A Applicationaula principal é um bom lugar para isso.
Andy Wilkinson
2
Observação importante: a classe Jackson2ObjectMapperBuilder faz parte do componente spring-web e foi adicionada na versão 4.1.1.
gaoagong
2
@Deprecated setSerializationInclusion
Dimitri Kopriwa
6
Obsoleto: ObjectMapper.setSerializationInclusion foi descontinuado no Jackson 2.7 ... use stackoverflow.com/a/44137972/6785908 spring.jackson.default-property-inclusão = non_null em vez
so-random-dude
24

Estou respondendo um pouco tarde a esta pergunta, mas alguém, no futuro, pode achar isso útil. A abordagem abaixo, além de muitas outras abordagens, funciona melhor e, pessoalmente, acho que seria mais adequada para um aplicativo da web.

@Configuration
@EnableWebMvc
public class WebConfiguration extends WebMvcConfigurerAdapter {

 ... other configurations

@Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
        builder.serializationInclusion(JsonInclude.Include.NON_NULL);
        builder.propertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
        builder.serializationInclusion(Include.NON_EMPTY);
        builder.indentOutput(true).dateFormat(new SimpleDateFormat("yyyy-MM-dd"));
        converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
        converters.add(new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()));
    }
}
Vijay Veeraraghavan
fonte
1
Em versão recente deve implementarWebMvcConfigurer
João Pedro Schmitt
sim, pessoal, tente esta solução, eu tentei fornecer Bean para ObjectMapper, então Bean para Jackson2ObjectMapperBuilder compra que não funcionou e ainda não tenho ideia do porquê. Apêndice coverter funcionou!
Somal Somalski
23

Muitas coisas podem ser configuradas nas propriedades do aplicativo. Infelizmente, esse recurso está apenas na versão 1.3, mas você pode adicionar uma classe de configuração

@Autowired(required = true)
public void configureJackson(ObjectMapper jackson2ObjectMapper) {
    jackson2ObjectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
}

[ATUALIZAÇÃO: Você deve trabalhar no ObjectMapper porque o build()-method é chamado antes que a configuração seja executada.]

nbank
fonte
Essa foi a solução que salvou meu dia. Exceto que eu adicionei este método ao próprio controlador REST, em vez de à classe de configuração.
Nestor Milyaev
20

A documentação indica várias maneiras de fazer isso.

Se você deseja substituir o padrão ObjectMappercompletamente, defina um @Beandesse tipo e marque-o como @Primary.

Definir um @Beande tipo Jackson2ObjectMapperBuilderpermitirá que você personalize o padrão ObjectMappere XmlMapper(usado em MappingJackson2HttpMessageConvertere MappingJackson2XmlHttpMessageConverterrespectivamente).

Predrag Maric
fonte
2
Como fazer o mesmo sem substituir o padrão ObjectMapper? Quero dizer, manter o padrão também um costume.
soufrk de
12

Você pode adicionar um método a seguir dentro de sua classe de bootstrap que é anotado com @SpringBootApplication

    @Bean
    @Primary
    public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
    ObjectMapper objectMapper = builder.createXmlMapper(false).build();
    objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    objectMapper.configure(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS, false);

    objectMapper.registerModule(new JodaModule());

    return objectMapper;
}
sendon1982
fonte
Este funcionou para mim usando o Boot 2.1.3. As propriedades spring.jackson não tiveram efeito.
Richtopia
9

spring.jackson.serialization-inclusion=non_null costumava trabalhar para nós

Mas quando atualizamos a versão do Spring Boot para 1.4.2.RELEASE ou superior, ele parou de funcionar.

Agora, outra propriedade spring.jackson.default-property-inclusion=non_nullestá fazendo a mágica.

na verdade, serialization-inclusionestá obsoleto. Isso é o que meu intellij joga em mim.

Obsoleto: ObjectMapper.setSerializationInclusion foi descontinuado no Jackson 2.7

Então, começar a usar spring.jackson.default-property-inclusion=non_nullem vez

cara-tão-aleatório
fonte
4

Eu tropecei em outra solução, que é muito boa.

Basicamente, execute apenas a etapa 2 do blog postado mencionado e defina um ObjectMapper customizado como Spring @Component. (As coisas começaram a funcionar quando acabei de remover todas as coisas do AnnotationMethodHandlerAdapter da etapa 3.)

@Component
@Primary
public class CustomObjectMapper extends ObjectMapper {
    public CustomObjectMapper() {
        setSerializationInclusion(JsonInclude.Include.NON_NULL); 
        configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 
    }
}

Funciona desde que o componente esteja em um pacote verificado pelo Spring. (Usar @Primarynão é obrigatório no meu caso, mas por que não tornar as coisas explícitas.)

Para mim, existem dois benefícios em comparação com a outra abordagem:

  • Isso é mais simples; Eu posso simplesmente estender uma aula de Jackson e não preciso saber sobre coisas altamente específicas da primavera, como Jackson2ObjectMapperBuilder.
  • Quero usar as mesmas configurações de Jackson para desserializar JSON em outra parte do meu aplicativo e, dessa forma, é muito simples: em new CustomObjectMapper()vez de new ObjectMapper().
Jonik
fonte
A desvantagem dessa abordagem é que sua configuração personalizada não será aplicada a nenhuma ObjectMapperinstância criada ou configurada pelo Spring Boot.
Andy Wilkinson
Hmm, a configuração personalizada é usada para a serialização implícita em @RestControllerclasses que atualmente é suficiente para mim. (Então você quer dizer que essas instâncias são criadas pelo Spring MVC, não Spring Boot?) Mas se eu encontrar outros casos onde ObjectMappers precisa ser instanciado, vou manter a abordagem Jackson2ObjectMapperBuilder em mente!
Jonik
3

Quando tentei tornar o ObjectMapper primário no spring boot 2.0.6, obtive erros, então modifiquei aquele que o spring boot criou para mim

Consulte também https://stackoverflow.com/a/48519868/255139

@Lazy
@Autowired
ObjectMapper mapper;

@PostConstruct
public ObjectMapper configureMapper() {
    mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);

    mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    mapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);

    mapper.configure(MapperFeature.ALLOW_COERCION_OF_SCALARS, true);
    mapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);

    SimpleModule module = new SimpleModule();
    module.addDeserializer(LocalDate.class, new LocalDateDeserializer());
    module.addSerializer(LocalDate.class, new LocalDateSerializer());
    mapper.registerModule(module);

    return mapper;
}
Kalpesh Soni
fonte
1

Encontrei a solução descrita acima com:

spring.jackson.serialization-inclusão = non_null

Para funcionar apenas a partir da versão 1.4.0.RELEASE do Spring Boot. Em todos os outros casos, a configuração é ignorada.

Eu verifiquei isso experimentando uma modificação do exemplo de bota de mola "spring-boot-sample-jersey"

Tim Dugan
fonte
0

Eu sei a pergunta sobre a bota Spring, mas acredito que muitas pessoas procuram como fazer isso sem a bota Spring, como eu procurando quase o dia todo.

Acima do Spring 4, não há necessidade de configurar MappingJacksonHttpMessageConverterse você apenas pretende configurar ObjectMapper.

Você só precisa fazer:

public class MyObjectMapper extends ObjectMapper {

    private static final long serialVersionUID = 4219938065516862637L;

    public MyObjectMapper() {
        super();
        enable(SerializationFeature.INDENT_OUTPUT);
    }       
}

E em sua configuração Spring, crie este bean:

@Bean 
public MyObjectMapper myObjectMapper() {        
    return new MyObjectMapper();
}
GMsoF
fonte