如何通过Jackson根据DTO使用场景排除嵌套属性,避免定义重复DTO类?
兄弟,我太懂你这种痛点了——为了不同的序列化场景定义一堆几乎重复的DTO类,不仅代码冗余,维护起来也闹心。好在Jackson有好几招能帮你解决这个问题,不用再搞那些“NotNested”后缀的重复类了,我给你唠几个实用的方案:
一、最直观的方案:用@JsonView做场景化序列化
这是我最推荐的方案,代码侵入性低,逻辑清晰,完全适配你这种“同一个DTO在不同场景下序列化不同字段”的需求。
步骤1:定义序列化视图
先搞几个空接口当标记,用来区分不同的序列化场景:
// 序列化CountryDTO时用这个视图 interface CountrySerializationView // 单独序列化SubContinentDTO时用这个视图 interface SubContinentFullView
步骤2:给目标字段绑定视图
修改你的SubContinentDTO,把continent字段绑定到SubContinentFullView上——意思就是只有在使用这个视图序列化时,这个字段才会被输出:
data class SubContinentDTO( val name: String, @JsonView(SubContinentFullView::class) val continent: ContinentDto )
你的CountryDTO和ContinentDTO保持原样就行,不用改:
data class CountryDTO( val name: String, val isoAlpha3: String, val subContinent: SubContinentDTO, val continent: ContinentDto ) data class ContinentDTO(val name: String)
步骤3:序列化时指定视图
- 当你要返回
CountryDTO时,指定用CountrySerializationView,这样SubContinentDTO里的continent字段就会被自动忽略:
val mapper = ObjectMapper().registerModule(KotlinModule()) // 指定视图 val writer = mapper.writerWithView(CountrySerializationView::class.java) val json = writer.writeValueAsString(yourCountryDto)
输出的JSON里,subContinent只会有name字段,不会带重复的continent。
- 当你单独返回
SubContinentDTO时,指定用SubContinentFullView,就能正常输出continent字段:
val writer = mapper.writerWithView(SubContinentFullView::class.java) val json = writer.writeValueAsString(yourSubContinentDto)
如果是Spring Boot项目,更简单——直接在控制器方法上加@JsonView注解就行,不用手动搞ObjectWriter:
@GetMapping("/country/{id}") @JsonView(CountrySerializationView::class) fun getCountry(@PathVariable id: String): CountryDTO { // 业务逻辑返回CountryDTO } @GetMapping("/subcontinent/{id}") @JsonView(SubContinentFullView::class) fun getSubContinent(@PathVariable id: String): SubContinentDTO { // 业务逻辑返回SubContinentDTO }
二、更灵活的方案:用@JsonFilter做动态字段过滤
如果你的场景更复杂,比如需要根据业务逻辑动态决定要不要过滤字段,那@JsonFilter就更适合你。
步骤1:给DTO标记过滤器
给SubContinentDTO加上@JsonFilter注解,指定一个过滤器名称:
@JsonFilter("subContinentFieldFilter") data class SubContinentDTO( val name: String, val continent: ContinentDto )
步骤2:序列化时动态配置过滤器
- 当序列化
CountryDTO时,配置过滤器排除continent字段:
val mapper = ObjectMapper().registerModule(KotlinModule()) // 创建过滤器,指定要排除的字段 val filterProvider = SimpleFilterProvider().addFilter( "subContinentFieldFilter", SimpleBeanPropertyFilter.serializeAllExcept("continent") ) // 绑定过滤器到ObjectWriter val writer = mapper.writer(filterProvider) val json = writer.writeValueAsString(yourCountryDto)
- 当单独序列化
SubContinentDTO时,直接用默认的序列化逻辑(或者不设置过滤器,会输出所有字段):
val json = mapper.writeValueAsString(yourSubContinentDto)
这个方案的好处是,你可以在运行时动态调整要过滤的字段,比如根据用户权限、请求参数来决定要不要输出某些字段,灵活性拉满。
三、不推荐的方案:自定义序列化器
当然你也可以写自定义的JsonSerializer来手动控制序列化逻辑,但这种方式代码量太大,维护起来麻烦,除非你有非常特殊的需求,否则不建议用——毕竟前面两种方案已经能解决90%的场景了。
总结
优先用@JsonView,简单直观,代码改动最小;如果需要动态调整过滤规则,再上@JsonFilter。这样你就不用再定义那些冗余的“NotNested”DTO类了,代码清爽多了!
内容来源于stack exchange

