Comment personnaliser le mappeur Jackson JSON utilisé implicitement par Spring Boot?

101

J'utilise Spring Boot (1.2.1), de la même manière que dans leur tutoriel sur la création d'un service Web RESTful :

@RestController
public class EventController {

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

}

Donc ci-dessus, Spring MVC utilise implicitement Jackson pour sérialiser mon EventListobjet en JSON.

Mais je veux faire quelques personnalisations simples au format JSON, telles que:

setSerializationInclusion(JsonInclude.Include.NON_NULL)

La question est la suivante: quel est le moyen le plus simple de personnaliser le mappeur JSON implicite?

J'ai essayé l'approche dans ce billet de blog , en créant un CustomObjectMapper et ainsi de suite, mais l'étape 3, «Enregistrer les classes dans le contexte Spring», échoue:

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: {}

Il semble que ces instructions concernent les anciennes versions de Spring MVC, alors que je recherche un moyen simple de le faire fonctionner avec le dernier Spring Boot.

Jonik
la source
Avez-vous inséré cette annotation?: @SuppressWarnings ({"SpringJavaAutowiringInspection"})
sven.kwiotek
Notez que si vous utilisez également Spring Web, vous devrez lui dire manuellement d'utiliser cet ObjectMapper, sinon il créera sa propre instance qui ne sera pas configurée. Voir stackoverflow.com/questions/7854030/…
Constantino Cronemberger

Réponses:

116

Si vous utilisez Spring Boot 1.3, vous pouvez configurer l'inclusion de sérialisation via application.properties:

spring.jackson.serialization-inclusion=non_null

Suite aux modifications apportées à Jackson 2.7, Spring Boot 1.4 utilise une propriété nommée à la spring.jackson.default-property-inclusionplace:

spring.jackson.default-property-inclusion=non_null

Voir la section « Personnaliser le Jackson ObjectMapper » dans la documentation de Spring Boot.

Si vous utilisez une version antérieure de Spring Boot, le moyen le plus simple de configurer l'inclusion de la sérialisation dans Spring Boot est de déclarer votre propre Jackson2ObjectMapperBuilderbean correctement configuré . Par exemple:

@Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
    Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
    builder.serializationInclusion(JsonInclude.Include.NON_NULL);
    return builder;
}
Andy Wilkinson
la source
1
Très bien, cela semble fonctionner. Où mettriez-vous une telle méthode? Peut-être dans une classe Application principale (avec @ComponentScan, @EnableAutoConfigurationetc.)?
Jonik le
2
Oui. Cette méthode peut être utilisée dans n'importe quelle @Configurationclasse de votre application. La Applicationclasse principale est un bon endroit pour cela.
Andy Wilkinson
2
Remarque importante: la classe Jackson2ObjectMapperBuilder fait partie du composant spring-web et a été ajoutée dans la version 4.1.1.
gaoagong
2
@Deprecated setSerializationInclusion
Dimitri Kopriwa
6
Obsolète: ObjectMapper.setSerializationInclusion était obsolète dans Jackson 2.7 ... utilisez stackoverflow.com/a/44137972/6785908 spring.jackson.default-property-inclusion = non_null à la place
so-random-dude
24

Je réponds un peu tard à cette question, mais quelqu'un, à l'avenir, pourrait trouver cela utile. L'approche ci-dessous, en plus de nombreuses autres approches, fonctionne mieux et je pense personnellement qu'elle conviendrait mieux à une application 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
la source
1
Dans la version récente, vous devez implémenterWebMvcConfigurer
João Pedro Schmitt le
oui, les gens, essayez cette solution, j'ai essayé de fournir Bean pour ObjectMapper, puis Bean pour Jackson2ObjectMapperBuilder acheter qui n'a pas fonctionné et je ne sais toujours pas pourquoi. L'ajout de coverter a fonctionné!
Somal Somalski
23

Beaucoup de choses peuvent être configurées dans les propriétés de l'application. Malheureusement, cette fonctionnalité uniquement dans la version 1.3, mais vous pouvez ajouter une classe de configuration

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

[MISE À JOUR: Vous devez travailler sur l'ObjectMapper car la build()méthode est appelée avant l'exécution de la configuration.]

nbank
la source
C'était la solution qui m'a sauvé la journée. Sauf que j'ai ajouté cette méthode au contrôleur REST lui-même, plutôt qu'à la classe de configuration.
Nestor Milyaev
20

La documentation indique plusieurs façons de procéder.

Si vous souhaitez remplacer ObjectMappercomplètement la valeur par défaut , définissez un @Beande ce type et marquez-le comme @Primary.

La définition d'un @Beande type Jackson2ObjectMapperBuildervous permettra de personnaliser à la fois par défaut ObjectMapperet XmlMapper(utilisé dans MappingJackson2HttpMessageConverteret MappingJackson2XmlHttpMessageConverterrespectivement).

Predrag Maric
la source
2
Comment faire de même sans remplacer la valeur par défaut ObjectMapper? Je veux dire garder la valeur par défaut ainsi qu'une coutume.
soufrk
12

Vous pouvez ajouter une méthode suivante dans votre classe de bootstrap qui est annotée avec @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
la source
Celui-ci a fonctionné pour moi en utilisant Boot 2.1.3. Les propriétés spring.jackson n'avaient aucun effet.
Richtopia
9

spring.jackson.serialization-inclusion=non_null utilisé pour travailler pour nous

Mais lorsque nous avons mis à niveau la version Spring Boot vers 1.4.2.RELEASE ou une version supérieure, cela a cessé de fonctionner.

Maintenant, une autre propriété spring.jackson.default-property-inclusion=non_nullfait la magie.

en fait, serialization-inclusionest obsolète. C'est ce que mon intellij me lance.

Obsolète: ObjectMapper.setSerializationInclusion était obsolète dans Jackson 2.7

Alors, commencez à utiliser à la spring.jackson.default-property-inclusion=non_nullplace

mec tellement aléatoire
la source
4

Je suis tombé sur une autre solution, ce qui est plutôt sympa.

Fondamentalement, ne faites que l' étape 2 du blog publié mentionné et définissez un ObjectMapper personnalisé en tant que Spring @Component. (Les choses ont commencé à fonctionner lorsque je viens de supprimer tous les éléments AnnotationMethodHandlerAdapter de l'étape 3.)

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

Fonctionne tant que le composant est dans un package analysé par Spring. (L'utilisation @Primaryn'est pas obligatoire dans mon cas, mais pourquoi ne pas rendre les choses explicites.)

Pour moi, il y a deux avantages par rapport à l'autre approche:

  • C'est plus simple; Je peux simplement prolonger une classe de Jackson et je n'ai pas besoin de connaître des choses très spécifiques au printemps comme Jackson2ObjectMapperBuilder.
  • Je souhaite utiliser les mêmes configurations Jackson pour désérialiser JSON dans une autre partie de mon application, et de cette façon, c'est très simple: new CustomObjectMapper()au lieu de new ObjectMapper().
Jonik
la source
L'inconvénient de cette approche est que votre configuration personnalisée ne sera appliquée à aucune ObjectMapperinstance créée ou configurée par Spring Boot.
Andy Wilkinson
Hmm, la configuration personnalisée est utilisée pour la sérialisation implicite dans les @RestControllerclasses qui me suffit actuellement. (Vous voulez dire que ces instances sont créées par Spring MVC, pas par Spring Boot?) Mais si je rencontre d'autres cas où les ObjectMappers doivent être instanciés, je garderai à l'esprit l'approche Jackson2ObjectMapperBuilder!
Jonik
3

Quand j'ai essayé de rendre ObjectMapper primaire dans Spring Boot 2.0.6, j'ai eu des erreurs J'ai donc modifié celui que Spring Boot a créé pour moi

Voir également 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
la source
1

J'ai trouvé la solution décrite ci-dessus avec:

spring.jackson.serialization-inclusion = non_null

Pour ne fonctionner qu'à partir de la version 1.4.0.RELEASE de spring boot. Dans tous les autres cas, la configuration est ignorée.

J'ai vérifié cela en expérimentant une modification de l'échantillon de botte à ressort "spring-boot-sample-jersey"

Tim Dugan
la source
0

Je connais la question qui pose la botte Spring, mais je crois que beaucoup de gens cherchent comment faire cela dans une botte non Spring, comme moi qui recherchent presque toute la journée.

Au-dessus de Spring 4, il n'est pas nécessaire de configurer MappingJacksonHttpMessageConvertersi vous avez uniquement l'intention de configurer ObjectMapper.

Il vous suffit de faire:

public class MyObjectMapper extends ObjectMapper {

    private static final long serialVersionUID = 4219938065516862637L;

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

Et dans votre configuration Spring, créez ce bean:

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