วิธีปรับแต่งตัวทำแผนที่ Jackson JSON ที่ Spring Boot ใช้โดยปริยาย?


102

ฉันใช้ Spring Boot (1.2.1) ในลักษณะเดียวกันกับในการสอนการสร้าง RESTful Web Service :

@RestController
public class EventController {

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

}

ข้างต้น Spring MVC ใช้แจ็คสันโดยปริยายสำหรับการทำให้EventListวัตถุของฉันเป็นอนุกรมเป็น JSON

แต่ฉันต้องการปรับแต่งง่ายๆในรูปแบบ JSON เช่น:

setSerializationInclusion(JsonInclude.Include.NON_NULL)

คำถามคือวิธีที่ง่ายที่สุดในการปรับแต่งเครื่องมือทำแผนที่ JSON โดยปริยายคืออะไร?

ฉันลองใช้แนวทางในบล็อกโพสต์นี้สร้าง CustomObjectMapper และอื่น ๆ แต่ขั้นตอนที่ 3 "ลงทะเบียนคลาสในบริบท Spring" ล้มเหลว:

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

ดูเหมือนว่าคำแนะนำเหล่านี้ใช้สำหรับ Spring MVC เวอร์ชันเก่าในขณะที่ฉันกำลังมองหาวิธีง่ายๆในการทำให้สิ่งนี้ทำงานกับ Spring Boot ล่าสุด


คุณใส่คำอธิบายประกอบนี้หรือไม่: @SuppressWarnings ({"SpringJavaAutowiringInspection"})
sven.kwiotek

สังเกตว่าหากคุณใช้ Spring Web ด้วยคุณจะต้องบอกให้ใช้ ObjectMapper นี้ด้วยตนเองมิฉะนั้นมันจะสร้างอินสแตนซ์ของตัวเองซึ่งจะไม่ได้รับการกำหนดค่า ดูstackoverflow.com/questions/7854030/…
Constantino Cronemberger

คำตอบ:


118

คุณสามารถกำหนดค่าการรวมคุณสมบัติและการตั้งค่าอื่น ๆ อีกมากมายผ่านทางapplication.properties:

spring.jackson.default-property-inclusion=non_null

มีตารางในเอกสารที่แสดงรายการคุณสมบัติทั้งหมดที่สามารถใช้ได้

หากคุณต้องการการควบคุมที่มากขึ้นคุณยังสามารถปรับแต่งการกำหนดค่าของ Spring Boot โดยใช้โปรแกรมJackson2ObjectMapperBuilderCustomizerbean ได้ตามที่อธิบายไว้ในเอกสารประกอบ :

บริบทJackson2ObjectMapperBuilderสามารถปรับแต่งได้โดยJackson2ObjectMapperBuilderCustomizerถั่วอย่างน้อยหนึ่งชนิด สามารถสั่งซื้อถั่วปรับแต่งดังกล่าวได้ (เครื่องมือปรับแต่งของ Boot มีลำดับเป็น 0) ทำให้สามารถใช้การปรับแต่งเพิ่มเติมได้ทั้งก่อนและหลังการปรับแต่งของ Boot

สุดท้ายหากคุณไม่ต้องการการกำหนดค่าใด ๆ ของ Boot และต้องการควบคุมวิธีObjectMapperกำหนดค่าอย่างสมบูรณ์ให้ประกาศJackson2ObjectMapperBuilderbean ของคุณเอง:

@Bean
Jackson2ObjectMapperBuilder objectMapperBuilder() {
    Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
    // Configure the builder to suit your needs
    return builder;
}

1
ได้เลยดูเหมือนว่าจะได้ผล คุณจะวางวิธีการดังกล่าวไว้ที่ไหน? บางทีในชั้นการประยุกต์ใช้หลัก (มี@ComponentScan, @EnableAutoConfigurationฯลฯ )?
Jonik

2
ใช่. วิธีนี้สามารถใช้ใน@Configurationคลาสใดก็ได้ในแอปของคุณ Applicationชั้นเรียนหลักเป็นสถานที่ที่ดีสำหรับมัน
Andy Wilkinson

2
หมายเหตุสำคัญ: คลาส Jackson2ObjectMapperBuilder เป็นส่วนหนึ่งขององค์ประกอบสปริงเว็บและถูกเพิ่มในเวอร์ชัน 4.1.1
gaoagong

2
@Deprecated setSerializationInclusion
Dimitri Kopriwa

6
เลิกใช้: ObjectMapper.setSerializationInclusion เลิกใช้แล้วใน Jackson 2.7 ... ใช้stackoverflow.com/a/44137972/6785908 spring.jackson.default-property-inclusion = non_null แทน
ดังนั้นสุ่ม

25

ฉันตอบคำถามนี้ช้าไปหน่อย แต่ในอนาคตอาจมีใครบางคนคิดว่าสิ่งนี้มีประโยชน์ วิธีการด้านล่างนี้นอกเหนือจากวิธีการอื่น ๆ ได้ผลดีที่สุดและโดยส่วนตัวแล้วฉันคิดว่าน่าจะเหมาะกับเว็บแอปพลิเคชันมากกว่า

@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()));
    }
}

1
ในเวอร์ชันล่าสุดคุณต้องใช้WebMvcConfigurer
João Pedro Schmitt

ใช่ทุกคนลองวิธีนี้ฉันพยายามจัดหา Bean สำหรับ ObjectMapper แล้วซื้อ Bean สำหรับ Jackson2ObjectMapperBuilder ที่ไม่ได้ผลและฉันก็ยังไม่รู้ว่าทำไม การเพิ่มผู้ปกปิดใช้งานได้!
Somal Somalski

22

มีหลายสิ่งหลายอย่างที่สามารถกำหนดค่าใน applicationproperties น่าเสียดายที่คุณลักษณะนี้มีเฉพาะในเวอร์ชัน 1.3 แต่คุณสามารถเพิ่มใน Config-Class ได้

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

[UPDATE: คุณต้องทำงานบน ObjectMapper เนื่องจากbuild()-method ถูกเรียกก่อนที่จะเรียกใช้ config]


นั่นคือทางออกที่ช่วยชีวิตฉัน ยกเว้นฉันเพิ่มวิธีนี้ให้กับตัวควบคุม REST เองแทนที่จะเป็นคลาสคอนฟิกูเรชัน
Nestor Milyaev

21

เอกสารระบุหลายวิธีในการดำเนินการนี้

หากคุณต้องการที่จะเปลี่ยนค่าเริ่มต้นObjectMapperสมบูรณ์กำหนดประเภทนั้นและทำเครื่องหมายเป็น@Bean@Primary

การกำหนด@BeanประเภทJackson2ObjectMapperBuilderจะช่วยให้คุณปรับแต่งทั้งค่าเริ่มต้นObjectMapperและXmlMapper(ใช้ในMappingJackson2HttpMessageConverterและMappingJackson2XmlHttpMessageConverterตามลำดับ)


2
จะทำเช่นเดียวกันโดยไม่แทนที่ค่าเริ่มต้นได้ObjectMapperอย่างไร? ฉันหมายถึงการรักษาค่าเริ่มต้นและกำหนดเอง
soufrk

13

คุณสามารถเพิ่มวิธีการต่อไปนี้ภายในคลาส bootstrap ซึ่งมีคำอธิบายประกอบ @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;
}

อันนี้ใช้ได้กับฉันโดยใช้ Boot 2.1.3 คุณสมบัติของ spring.jackson ไม่มีผลกระทบ
Richtopia

10

spring.jackson.serialization-inclusion=non_null เคยทำงานให้เรา

แต่เมื่อเราอัปเกรดเวอร์ชันสปริงบูตเป็น 1.4.2.RELEASE หรือสูงกว่ามันก็หยุดทำงาน

ตอนนี้ทรัพย์สินอื่นspring.jackson.default-property-inclusion=non_nullกำลังทำอาคม

ในความเป็นจริงserialization-inclusionเลิกใช้แล้ว นี่คือสิ่งที่ intellij พ่นใส่ฉัน

เลิกใช้: ObjectMapper.setSerializationInclusion เลิกใช้แล้วใน Jackson 2.7

ดังนั้นเริ่มใช้spring.jackson.default-property-inclusion=non_nullแทน


4

ฉันสะดุดกับวิธีแก้ปัญหาอื่นซึ่งค่อนข้างดี

โดยทั่วไปเพียง แต่ทำขั้นตอนที่ 2 จากบล็อกที่โพสต์ที่กล่าวถึงและกำหนด ObjectMapper @Componentที่กำหนดเองเป็นฤดูใบไม้ผลิ (สิ่งต่างๆเริ่มทำงานเมื่อฉันเพิ่งลบสิ่ง AnnotationMethodHandlerAdapter ทั้งหมดออกจากขั้นตอนที่ 3)

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

ทำงานได้ตราบเท่าที่ส่วนประกอบอยู่ในแพ็คเกจที่ Spring สแกน (การใช้@Primaryไม่บังคับในกรณีของฉัน แต่ทำไมไม่ทำให้สิ่งต่างๆชัดเจน)

สำหรับฉันมีประโยชน์สองประการเมื่อเทียบกับวิธีอื่น:

  • สิ่งนี้ง่ายกว่า Jackson2ObjectMapperBuilderฉันเพียงแค่สามารถขยายชั้นเรียนจากแจ็คสันและไม่จำเป็นต้องรู้ว่าสิ่งที่เกี่ยวกับการขอฤดูใบไม้ผลิที่เฉพาะเจาะจงเช่น
  • ฉันต้องการใช้การกำหนดค่าแจ็คสันเดียวกันสำหรับการกำหนดค่า JSON ในส่วนอื่นของแอปของฉันและวิธีนี้ก็ง่ายมาก: new CustomObjectMapper()แทนที่จะเป็น new ObjectMapper().

ข้อเสียของวิธีนี้คือการกำหนดค่าที่กำหนดเองของคุณจะไม่ถูกนำไปใช้กับObjectMapperอินสแตนซ์ใด ๆที่สร้างหรือกำหนดค่าโดย Spring Boot
Andy Wilkinson

อืมเอง config จะใช้สำหรับเป็นอันดับนัยใน@RestControllerชั้นเรียนซึ่งขณะนี้พอเพียงสำหรับผม (คุณหมายถึงอินสแตนซ์เหล่านั้นสร้างโดย Spring MVC ไม่ใช่ Spring Boot ใช่ไหม) แต่ถ้าฉันเจอกรณีอื่น ๆ ที่ ObjectMappers จำเป็นต้องสร้างอินสแตนซ์ฉันจะคำนึงถึงแนวทางของ Jackson2ObjectMapperBuilder!
Jonik

3

เมื่อฉันพยายามทำให้ ObjectMapper เป็นหลักในสปริงบูต 2.0.6 ฉันได้รับข้อผิดพลาดดังนั้นฉันจึงแก้ไขสิ่งที่สปริงบูตสร้างขึ้นสำหรับฉัน

ดูเพิ่มเติมที่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;
}

1

ฉันพบวิธีแก้ปัญหาที่อธิบายไว้ข้างต้นด้วย:

spring.jackson.serialization-inclusion = non_null

เพื่อให้ทำงานได้โดยเริ่มที่เวอร์ชัน 1.4.0 เท่านั้นของสปริงบูต ในกรณีอื่น ๆ การกำหนดค่าจะถูกละเว้น

ฉันยืนยันสิ่งนี้โดยการทดลองดัดแปลงตัวอย่างรองเท้าสปริง "spring-boot-sample-jersey"


0

ฉันรู้คำถามที่ถามหา Spring boot แต่ฉันเชื่อว่าหลาย ๆ คนที่กำลังมองหาวิธีทำสิ่งนี้ในแบบที่ไม่ใช่ Spring boot เช่นฉันค้นหาเกือบทั้งวัน

เหนือ Spring 4 ไม่จำเป็นต้องกำหนดค่าMappingJacksonHttpMessageConverterหากคุณตั้งใจจะกำหนดค่าObjectMapperเท่านั้น

คุณต้องทำ:

public class MyObjectMapper extends ObjectMapper {

    private static final long serialVersionUID = 4219938065516862637L;

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

และในการกำหนดค่า Spring ของคุณให้สร้าง bean นี้:

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

0

วิธีที่ถูกต้องในการเพิ่มการกำหนดค่าเพิ่มเติมใน Spring Boot peconfigured ObjectMapper คือการกำหนด Jackson2ObjectMapperBuilderCustomizer มิฉะนั้นคุณกำลังเขียนทับการกำหนดค่าสปริงซึ่งคุณไม่ต้องการหลวม

 @Configuration
public class MyJacksonConfigurer implements Jackson2ObjectMapperBuilderCustomizer {
    @Override
    public void customize(Jackson2ObjectMapperBuilder builder) {
        builder.deserializerByType(LocalDate.class, new MyOwnJsonLocalDateTimeDeserializer());
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.