Wie kann ich den von Spring Boot implizit verwendeten Jackson JSON-Mapper anpassen?

101

Ich verwende Spring Boot (1.2.1) auf ähnliche Weise wie im Tutorial zum Erstellen eines RESTful-Webdiensts :

@RestController
public class EventController {

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

}

Daher verwendet Spring MVC Jackson implizit zum Serialisieren meines EventListObjekts in JSON.

Ich möchte jedoch einige einfache Anpassungen am JSON-Format vornehmen, z.

setSerializationInclusion(JsonInclude.Include.NON_NULL)

Die Frage ist, wie der implizite JSON-Mapper am einfachsten angepasst werden kann.

Ich habe den Ansatz in diesem Blog-Beitrag ausprobiert, indem ich einen CustomObjectMapper usw. erstellt habe, aber Schritt 3, "Registrieren von Klassen im Spring-Kontext", schlägt fehl:

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

Es sieht so aus, als ob diese Anweisungen für ältere Versionen von Spring MVC gelten, während ich nach einer einfachen Möglichkeit suche, dies mit dem neuesten Spring Boot zum Laufen zu bringen.

Jonik
quelle
Haben Sie diese Anmerkung eingefügt?: @SuppressWarnings ({"SpringJavaAutowiringInspection"})
sven.kwiotek
Wenn Sie Spring Web ebenfalls verwenden, müssen Sie es manuell anweisen, diesen ObjectMapper zu verwenden. Andernfalls wird eine eigene Instanz erstellt, die nicht konfiguriert wird. Siehe stackoverflow.com/questions/7854030/…
Constantino Cronemberger

Antworten:

116

Wenn Sie Spring Boot 1.3 verwenden, können Sie die Einbeziehung der Serialisierung über Folgendes konfigurieren application.properties:

spring.jackson.serialization-inclusion=non_null

Nach Änderungen in Jackson 2.7 verwendet Spring Boot 1.4 spring.jackson.default-property-inclusionstattdessen eine Eigenschaft mit dem Namen :

spring.jackson.default-property-inclusion=non_null

Siehe " Anpassen des Jackson ObjectMapper " in der Spring Boot-Dokumentation.

Wenn Sie eine frühere Version von Spring Boot verwenden, können Sie die Serialisierungseinbeziehung in Spring Boot am einfachsten konfigurieren, indem Sie Ihre eigene, entsprechend konfigurierte Jackson2ObjectMapperBuilderBean deklarieren . Beispielsweise:

@Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
    Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
    builder.serializationInclusion(JsonInclude.Include.NON_NULL);
    return builder;
}
Andy Wilkinson
quelle
1
Okay, das scheint zu funktionieren. Wo würden Sie eine solche Methode setzen? Vielleicht in einem Hauptanwendungsklasse (mit @ComponentScan, @EnableAutoConfigurationetc)?
Jonik
2
Ja. Diese Methode kann in jeder @ConfigurationKlasse Ihrer App angewendet werden . Die Hauptklasse Applicationist ein guter Ort dafür.
Andy Wilkinson
2
Wichtiger Hinweis: Die Jackson2ObjectMapperBuilder-Klasse ist Teil der Spring-Web-Komponente und wurde in Version 4.1.1 hinzugefügt.
Gaoagong
2
@ Veraltet setSerializationInclusion
Dimitri Kopriwa
6
Veraltete: ObjectMapper.setSerializationInclusion wurde in Jackson 2.7 ... Verwendung veraltet stackoverflow.com/a/44137972/6785908 spring.jackson.default-Eigenschaft-Aufnahme = non_null statt
so random-Geck
24

Ich beantworte diese Frage etwas spät, aber jemand könnte dies in Zukunft nützlich finden. Der folgende Ansatz funktioniert neben vielen anderen Ansätzen am besten, und ich persönlich denke, er passt besser zu einer Webanwendung.

@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
quelle
1
In der letzten Version müssen Sie implementierenWebMvcConfigurer
João Pedro Schmitt
Ja, Leute, probieren Sie diese Lösung aus. Ich habe versucht, Bean für ObjectMapper bereitzustellen, dann Bean für Jackson2ObjectMapperBuilder. Der Kauf hat nicht funktioniert, und ich habe immer noch keine Ahnung, warum. Appending Coverter hat funktioniert!
Somal Somalski
23

In den Anwendungseigenschaften können viele Dinge konfiguriert werden. Leider ist diese Funktion nur in Version 1.3 verfügbar, aber Sie können eine Konfigurationsklasse hinzufügen

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

[UPDATE: Sie müssen am ObjectMapper arbeiten, da die build()-method aufgerufen wird, bevor die Konfiguration ausgeführt wird.]

nbank
quelle
Das war die Lösung, die mir den Tag gerettet hat. Außer ich habe diese Methode dem REST-Controller selbst und nicht der Konfigurationsklasse hinzugefügt.
Nestor Milyaev
20

In der Dokumentation werden verschiedene Möglichkeiten angegeben, dies zu tun.

Wenn Sie die Standardeinstellung ObjectMappervollständig ersetzen möchten , definieren Sie eine @Beandieser Art und markieren Sie sie als @Primary.

Ein Definieren @Beandes Typs Jackson2ObjectMapperBuilderermöglicht es Ihnen , sowohl Standard anpassen ObjectMapperund XmlMapper(-abfluss MappingJackson2HttpMessageConverterund MappingJackson2XmlHttpMessageConverterrespectively).

Predrag Maric
quelle
2
Wie mache ich dasselbe, ohne die Standardeinstellung zu ersetzen ObjectMapper? Ich meine, die Standardeinstellung auch eine Gewohnheit zu behalten.
Soufrk
12

Sie können Ihrer Bootstrap-Klasse eine folgende Methode hinzufügen, die mit Anmerkungen versehen ist @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
quelle
Dieser hat bei mir mit Boot 2.1.3 funktioniert. Die Eigenschaften von spring.jackson hatten keine Auswirkung.
Richtopia
9

spring.jackson.serialization-inclusion=non_null pflegte für uns zu arbeiten

Als wir jedoch die Spring Boot-Version auf 1.4.2.RELEASE oder höher aktualisierten, funktionierte sie nicht mehr.

Nun noch eine Eigenschaft spring.jackson.default-property-inclusion=non_null die Magie.

in der Tat serialization-inclusionist veraltet. Das ist es, was meine Intelligenz auf mich wirft.

Veraltet: ObjectMapper.setSerializationInclusion war in Jackson 2.7 veraltet

Beginnen Sie also spring.jackson.default-property-inclusion=non_nullstattdessen mit der Verwendung

so zufälliger Typ
quelle
4

Ich bin auf eine andere Lösung gestoßen, die ganz nett ist.

Führen Sie grundsätzlich nur Schritt 2 des genannten Blogs aus und definieren Sie einen benutzerdefinierten ObjectMapper als Spring @Component. ( Die Dinge begannen zu arbeiten , wenn ich nur entfernt alle AnnotationMethodHandlerAdapter Sachen aus Schritt 3)

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

Funktioniert, solange sich die Komponente in einem von Spring gescannten Paket befindet. (Verwenden von@Primary ist in meinem Fall nicht obligatorisch, aber warum nicht die Dinge explizit machen?)

Für mich gibt es zwei Vorteile gegenüber dem anderen Ansatz:

  • Das ist einfacher; Ich kann nur eine Klasse von Jackson verlängern und muss nicht über hochfrühlingsspezifische Dinge wie Bescheid wissen Jackson2ObjectMapperBuilder.
  • Ich möchte dieselben Jackson-Konfigurationen zum Deserialisieren von JSON in einem anderen Teil meiner App verwenden, und auf diese Weise ist es sehr einfach: new CustomObjectMapper()statt new ObjectMapper().
Jonik
quelle
Der Nachteil dieses Ansatzes ist, dass Ihre benutzerdefinierte Konfiguration nicht auf ObjectMapperInstanzen angewendet wird, die von Spring Boot erstellt oder konfiguriert wurden.
Andy Wilkinson
Hmm, die benutzerdefinierte Konfiguration wird für die implizite Serialisierung in @RestControllerKlassen verwendet, die derzeit für mich ausreicht. (Sie meinen also, diese Instanzen werden von Spring MVC erstellt, nicht von Spring Boot?) Wenn ich jedoch auf andere Fälle stoße, in denen ObjectMappers instanziiert werden müssen, werde ich den Jackson2ObjectMapperBuilder-Ansatz berücksichtigen!
Jonik
3

Als ich versuchte, ObjectMapper in Spring Boot 2.0.6 primär zu machen, wurden Fehler angezeigt. Daher habe ich den von Spring Boot für mich erstellten geändert

Siehe auch 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
quelle
1

Ich fand die oben beschriebene Lösung mit:

spring.jackson.serialization-inclusive = non_null

Nur ab der Version 1.4.0.RELEASE von Spring Boot arbeiten. In allen anderen Fällen wird die Konfiguration ignoriert.

Ich habe dies durch Experimentieren mit einer Modifikation des Spring Boot-Musters "Spring-Boot-Sample-Jersey" überprüft.

Tim Dugan
quelle
0

Ich kenne die Frage nach dem Frühlingsstiefel, aber ich glaube, dass viele Leute, die danach suchen, wie man das in einem Nicht-Frühlingsstiefel macht, wie ich fast den ganzen Tag suchen.

Oberhalb von Spring 4 ist keine Konfiguration erforderlich, MappingJacksonHttpMessageConverterwenn Sie nur die Konfiguration beabsichtigenObjectMapper .

Sie müssen nur tun:

public class MyObjectMapper extends ObjectMapper {

    private static final long serialVersionUID = 4219938065516862637L;

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

Erstellen Sie in Ihrer Spring-Konfiguration diese Bean:

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