¿Cómo personalizar el mapeador Jackson JSON utilizado implícitamente por Spring Boot?

101

Estoy usando Spring Boot (1.2.1), de manera similar a su tutorial de Construcción de un servicio web RESTful :

@RestController
public class EventController {

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

}

Así que arriba, Spring MVC usa implícitamente a Jackson para serializar mi EventListobjeto en JSON.

Pero quiero hacer algunas personalizaciones simples al formato JSON, como:

setSerializationInclusion(JsonInclude.Include.NON_NULL)

La pregunta es, ¿cuál es la forma más sencilla de personalizar el asignador JSON implícito?

Probé el enfoque en esta publicación de blog , creando un CustomObjectMapper y así sucesivamente, pero el paso 3, "Registrar clases en el contexto de Spring", falla:

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 esas instrucciones son para versiones anteriores de Spring MVC, mientras que estoy buscando una forma sencilla de hacer que esto funcione con la última versión de Spring Boot.

Jonik
fuente
¿Tiene esta anotación insertada ?: @SuppressWarnings ({"SpringJavaAutowiringInspection"})
sven.kwiotek
Tenga en cuenta que si también está utilizando Spring Web, deberá indicarle manualmente que utilice este ObjectMapper; de lo contrario, creará su propia instancia que no se configurará. Ver stackoverflow.com/questions/7854030/…
Constantino Cronemberger

Respuestas:

116

Si está utilizando Spring Boot 1.3, puede configurar la inclusión de serialización a través de application.properties:

spring.jackson.serialization-inclusion=non_null

Tras los cambios en Jackson 2.7, Spring Boot 1.4 usa una propiedad denominada spring.jackson.default-property-inclusionen su lugar:

spring.jackson.default-property-inclusion=non_null

Consulte la sección " Personalizar Jackson ObjectMapper " en la documentación de Spring Boot.

Si está utilizando una versión anterior de Spring Boot, la forma más fácil de configurar la inclusión de serialización en Spring Boot es declarar su propio Jackson2ObjectMapperBuilderbean configurado apropiadamente . Por ejemplo:

@Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
    Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
    builder.serializationInclusion(JsonInclude.Include.NON_NULL);
    return builder;
}
Andy Wilkinson
fuente
1
Muy bien, esto parece funcionar. ¿Dónde pondrías tal método? ¿Quizás en una clase de aplicación principal (con @ComponentScan, @EnableAutoConfigurationetc.)?
Jonik
2
Si. Este método puede ir en cualquier @Configurationclase de su aplicación. La Applicationclase principal es un buen lugar para ello.
Andy Wilkinson
2
Nota importante: la clase Jackson2ObjectMapperBuilder es parte del componente spring-web y se agregó en la versión 4.1.1.
gaoagong
2
@Deprecated setSerializationInclusion
Dimitri Kopriwa
6
En desuso: ObjectMapper.setSerializationInclusion fue obsoleto en Jackson 2.7 ... use stackoverflow.com/a/44137972/6785908 spring.jackson.default-property-included = non_null en su lugar
so-random-dude
24

Estoy respondiendo un poco tarde a esta pregunta, pero alguien, en el futuro, podría encontrar esto útil. El siguiente enfoque, además de muchos otros enfoques, funciona mejor y, personalmente, creo que se adaptaría mejor a una aplicación 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
fuente
1
En la versión reciente debe implementarWebMvcConfigurer
João Pedro Schmitt
sí, gente, prueben esta solución, he intentado proporcionar Bean para ObjectMapper, luego Bean para Jackson2ObjectMapperBuilder compre eso no funcionó y todavía no tengo idea de por qué. ¡La adición del coverter funcionó!
Somal Somalski
23

Se pueden configurar muchas cosas en las propiedades de la aplicación. Desafortunadamente, esta función solo en la versión 1.3, pero puede agregar una clase de configuración

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

[ACTUALIZACIÓN: Debe trabajar en ObjectMapper porque build()se llama al método-antes de que se ejecute la configuración].

nbank
fuente
Esa fue la solución que me salvó el día. Excepto que agregué este método al controlador REST en sí, en lugar de a la clase de configuración.
Nestor Milyaev
20

La documentación indica varias formas de hacer esto.

Si desea reemplazar el predeterminado ObjectMappercompletamente, defina un @Beande ese tipo y márquelo como @Primary.

Definir un @Beantipo de Jackson2ObjectMapperBuilderle permitirá personalizar tanto el valor predeterminado ObjectMappercomo XmlMapper(usado en MappingJackson2HttpMessageConvertery MappingJackson2XmlHttpMessageConverterrespectivamente).

Predrag Maric
fuente
2
¿Cómo hacer lo mismo sin reemplazar el predeterminado ObjectMapper? Me refiero a mantener el valor predeterminado también como personalizado.
soufrk
12

Puede agregar un método siguiente dentro de su clase de arranque que está anotado con @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
fuente
Este funcionó para mí usando Boot 2.1.3. Las propiedades de spring.jackson no tuvieron ningún efecto.
Richtopia
9

spring.jackson.serialization-inclusion=non_null solía trabajar para nosotros

Pero cuando actualizamos la versión de Spring Boot a 1.4.2.RELEASE o superior, dejó de funcionar.

Ahora, otra propiedad spring.jackson.default-property-inclusion=non_nullestá haciendo la magia.

de hecho, serialization-inclusionestá en desuso. Esto es lo que me arroja mi intellij.

En desuso: ObjectMapper.setSerializationInclusion quedó en desuso en Jackson 2.7

Entonces, comience a usar en su spring.jackson.default-property-inclusion=non_nulllugar

tio tan aleatorio
fuente
4

Me encontré con otra solución, que es bastante agradable.

Básicamente, solo realice el paso 2 del blog publicado mencionado y defina un ObjectMapper personalizado como Spring @Component. (Las cosas comenzaron a funcionar cuando acabo de eliminar todo el material AnnotationMethodHandlerAdapter del paso 3.)

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

Funciona siempre que el componente esté en un paquete escaneado por Spring. (El uso @Primaryno es obligatorio en mi caso, pero ¿por qué no hacer las cosas explícitas?)

Para mí, hay dos beneficios en comparación con el otro enfoque:

  • Esto es más sencillo; Puedo extender una clase de Jackson y no necesito saber sobre cosas muy específicas de Spring como Jackson2ObjectMapperBuilder.
  • Quiero usar las mismas configuraciones de Jackson para deserializar JSON en otra parte de mi aplicación, y de esta manera es muy simple: en new CustomObjectMapper()lugar de new ObjectMapper().
Jonik
fuente
La desventaja de este enfoque es que su configuración personalizada no se aplicará a ninguna ObjectMapperinstancia creada o configurada por Spring Boot.
Andy Wilkinson
Hmm, la configuración personalizada se usa para la serialización implícita en @RestControllerclases que actualmente me es suficiente. (¿Quiere decir que esas instancias son creadas por Spring MVC, no Spring Boot?) Pero si me encuentro con otros casos en los que se deben crear instancias de ObjectMappers, ¡tendré en cuenta el enfoque Jackson2ObjectMapperBuilder!
Jonik
3

Cuando intenté hacer ObjectMapper principal en Spring Boot 2.0.6, obtuve errores, así que modifiqué el que Spring Boot creó para mí.

También vea 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
fuente
1

Encontré la solución descrita anteriormente con:

spring.jackson.serialization-inclusión = non_null

Solo funciona a partir de la versión 1.4.0.RELEASE de spring boot. En todos los demás casos, se ignora la configuración.

Verifiqué esto experimentando con una modificación de la muestra de arranque de primavera "spring-boot-sample-jersey"

Tim Dugan
fuente
0

Conozco la pregunta sobre el arranque de primavera, pero creo que mucha gente busca cómo hacer esto en un arranque que no sea de primavera, como yo buscando casi todo el día.

Por encima de Spring 4, no es necesario configurar MappingJacksonHttpMessageConvertersi solo tiene la intención de configurar ObjectMapper.

Solo necesitas hacer:

public class MyObjectMapper extends ObjectMapper {

    private static final long serialVersionUID = 4219938065516862637L;

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

Y en su configuración de Spring, cree este bean:

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