Jak dostosować program mapujący Jackson JSON niejawnie używany przez Spring Boot?

102

Używam Spring Boot (1.2.1), w podobny sposób, jak w ich samouczku Tworzenie usługi sieci Web RESTful :

@RestController
public class EventController {

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

}

Tak więc powyżej, Spring MVC niejawnie używa Jacksona do serializacji mojego EventListobiektu do formatu JSON.

Ale chcę zrobić kilka prostych dostosowań do formatu JSON, na przykład:

setSerializationInclusion(JsonInclude.Include.NON_NULL)

Pytanie brzmi, jaki jest najprostszy sposób dostosowania niejawnego mapowania JSON?

Wypróbowałem podejście opisane w tym poście na blogu , tworząc CustomObjectMapper i tak dalej, ale krok 3, „Rejestracja klas w kontekście Spring”, kończy się niepowodzeniem:

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

Wygląda na to, że te instrukcje dotyczą starszych wersji Spring MVC, a ja szukam prostego sposobu, aby to działało z najnowszym Spring Boot.

Jonik
źródło
Czy wstawiłeś tę adnotację ?: @SuppressWarnings ({"SpringJavaAutowiringInspection"})
sven.kwiotek,
Zauważ, że jeśli używasz również Spring Web, będziesz musiał ręcznie powiedzieć mu, aby używał tego ObjectMapper, w przeciwnym razie utworzy własną instancję, która nie zostanie skonfigurowana. Zobacz stackoverflow.com/questions/7854030/…
Constantino Cronemberger

Odpowiedzi:

117

Jeśli używasz Spring Boot 1.3, możesz skonfigurować włączenie serializacji poprzez application.properties:

spring.jackson.serialization-inclusion=non_null

Po zmianach w Jackson 2.7, Spring Boot 1.4 używa właściwości o nazwie spring.jackson.default-property-inclusion:

spring.jackson.default-property-inclusion=non_null

Zobacz sekcję „ Dostosuj Jackson ObjectMapper ” w dokumentacji Spring Boot.

Jeśli używasz wcześniejszej wersji Spring Boot, najłatwiejszym sposobem skonfigurowania włączenia serializacji do Spring Boot jest zadeklarowanie własnego, odpowiednio skonfigurowanego Jackson2ObjectMapperBuilderkomponentu bean. Na przykład:

@Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
    Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
    builder.serializationInclusion(JsonInclude.Include.NON_NULL);
    return builder;
}
Andy Wilkinson
źródło
1
W porządku, to wydaje się działać. Gdzie umieściłbyś taką metodę? Być może w głównej klasie Application (z @ComponentScan, @EnableAutoConfigurationetc)?
Jonik
2
Tak. Ta metoda może być stosowana w dowolnej @Configurationklasie w Twojej aplikacji. ApplicationKlasa główna jest do tego dobrym miejscem.
Andy Wilkinson
2
Ważna uwaga: Klasa Jackson2ObjectMapperBuilder jest częścią komponentu spring-web i została dodana w wersji 4.1.1.
gaoagong
2
@Deprecated setSerializationInclusion
Dimitri Kopriwa
6
Przestarzałe: ObjectMapper.setSerializationInclusion został wycofany w Jackson 2.7 ... użyj stackoverflow.com/a/44137972/6785908 spring.jackson.default-property-Włączenie = non_null zamiast tego
so-random-dude
24

Trochę późno odpowiadam na to pytanie, ale ktoś w przyszłości może uznać to za przydatne. Poniższe podejście, oprócz wielu innych podejść, działa najlepiej i osobiście uważam, że lepiej pasowałoby do aplikacji internetowej.

@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
źródło
1
W najnowszej wersji musisz wdrożyćWebMvcConfigurer
João Pedro Schmitt
tak, ludzie, wypróbuj to rozwiązanie, próbowałem dostarczyć Bean dla ObjectMapper, a następnie Bean dla kupna Jackson2ObjectMapperBuilder, który nie zadziałał i nadal nie mam pojęcia, dlaczego. Dołączanie osłony zadziałało!
Somal Somalski
23

Wiele rzeczy można skonfigurować we właściwościach aplikacji. Niestety ta funkcja jest dostępna tylko w wersji 1.3, ale można dodać ją w klasie Config

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

[AKTUALIZACJA: Musisz pracować na ObjectMapper, ponieważ build()-method jest wywoływana przed uruchomieniem konfiguracji.]

nbank
źródło
To było rozwiązanie, które uratowało mi dzień. Tyle że dodałem tę metodę do samego kontrolera REST, a nie do klasy konfiguracji.
Nestor Milyaev
21

Dokumentacja podaje kilka sposobów, aby to zrobić.

Jeśli chcesz ObjectMappercałkowicie zastąpić domyślne ustawienie , zdefiniuj @Beanten typ i oznacz go jako @Primary.

Zdefiniowanie @Beantypu Jackson2ObjectMapperBuilderpozwoli ci dostosować zarówno domyślne, jak ObjectMapperi XmlMapper(używane odpowiednio w MappingJackson2HttpMessageConverteri MappingJackson2XmlHttpMessageConverter).

Predrag Maric
źródło
2
Jak zrobić to samo bez wymiany domyślnej ObjectMapper? Mam na myśli zachowanie wartości domyślnej, jak również zwyczaju.
suk
13

Możesz dodać następującą metodę wewnątrz swojej klasy bootstrap, która jest opatrzona adnotacją @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
źródło
Ten działał dla mnie przy użyciu Boot 2.1.3. Właściwości spring.jackson nie przyniosły żadnego efektu.
Richtopia
9

spring.jackson.serialization-inclusion=non_null pracował dla nas

Ale kiedy zaktualizowaliśmy wersję Spring Boot do 1.4.2.RELEASE lub nowszej, przestała działać.

Teraz kolejna nieruchomość spring.jackson.default-property-inclusion=non_null robi magię.

w rzeczywistości serialization-inclusionjest przestarzały. To właśnie rzuca we mnie mój intellij.

Przestarzałe: ObjectMapper.setSerializationInclusion został wycofany w Jackson 2.7

Więc zacznij używać spring.jackson.default-property-inclusion=non_nullzamiast tego

tak-przypadkowy-koleś
źródło
4

Natknąłem się na inne rozwiązanie, które jest całkiem miłe.

Zasadniczo wykonaj tylko krok 2 ze wspomnianego blogu i zdefiniuj niestandardowy obiekt ObjectMapper jako Spring @Component. (Wszystko zaczęło działać, gdy właśnie usunąłem wszystkie elementy AnnotationMethodHandlerAdapter z kroku 3).

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

Działa tak długo, jak długo komponent znajduje się w pakiecie skanowanym przez Spring. (Za pomocą@Primary nie jest obowiązkowe w moim przypadku, ale dlaczego nie wyjaśnić rzeczy).

Dla mnie są dwie korzyści w porównaniu z innym podejściem:

  • To jest prostsze; Mogę po prostu przedłużyć zajęcia z Jacksona i nie muszę wiedzieć o rzeczach związanych z wiosną, takich jak Jackson2ObjectMapperBuilder.
  • Chcę użyć tych samych konfiguracji Jacksona do deserializacji JSON w innej części mojej aplikacji i dzięki temu jest to bardzo proste: new CustomObjectMapper()zamiast new ObjectMapper().
Jonik
źródło
Wadą tego podejścia jest to, że Twoja konfiguracja niestandardowa nie będzie stosowana do żadnych ObjectMapperinstancji, które są tworzone lub konfigurowane przez Spring Boot.
Andy Wilkinson
Hmm, niestandardowa konfiguracja jest używana do niejawnej serializacji w @RestControllerklasach, które obecnie mi wystarczają. (Więc masz na myśli, że te instancje są tworzone przez Spring MVC, a nie Spring Boot?) Ale jeśli napotkam inne przypadki, w których trzeba utworzyć instancję ObjectMappers, będę pamiętać o podejściu Jackson2ObjectMapperBuilder!
Jonik
3

Kiedy próbowałem ustawić ObjectMapper jako podstawowy w Spring Boot 2.0.6, otrzymałem błędy Więc zmodyfikowałem ten, który utworzył dla mnie Spring Boot

Zobacz też 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
źródło
1

Znalazłem rozwiązanie opisane powyżej z:

spring.jackson.serialization-inclusive = non_null

Działa tylko od wersji 1.4.0.RELEASE buta sprężynowego. We wszystkich innych przypadkach konfiguracja jest ignorowana.

Sprawdziłem to, eksperymentując z modyfikacją próbki buta sprężynowego „spring-boot-sample-jersey”

Tim Dugan
źródło
0

Znam pytanie dotyczące butów wiosennych, ale wydaje mi się, że wiele osób, które szukają sposobu na zrobienie tego w butach innych niż wiosenne, jak ja, szukam prawie cały dzień.

Powyżej Spring 4 nie ma potrzeby konfigurowania, MappingJacksonHttpMessageConverterjeśli zamierzasz tylko konfigurować ObjectMapper.

Musisz tylko:

public class MyObjectMapper extends ObjectMapper {

    private static final long serialVersionUID = 4219938065516862637L;

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

A w konfiguracji Spring utwórz tę fasolkę:

@Bean 
public MyObjectMapper myObjectMapper() {        
    return new MyObjectMapper();
}
Sam YC
źródło