Jackson 自定義注解實現null值自定義序列化


Jackson 自定義注解實現null值自定義序列化

spring項目中都使用的是Jackson為默認的序列化方式,但是不免有時不滿足項目的需要,例如以下場景:

  1. 返回前端時部分字段不能為null,需要默認值
  2. 某類型的字段需要默認值
  3. 某字段需要特殊默認值

綜上各類場景,總而言之就是要對為 null 值的字段進行一些默認值賦值處理,讓返回的json中不存在未null的字段,針對此需求無非兩種解決方式:一是在業務中手動編碼進行各字段賦值(當然部分也需要這么做),二是進行序列化方式配置,讓系統在序列化時自動進行賦值

第一種方式的弊端我想大家都能一目了然(重復、煩雜),今天我們主講第二種實現方式

目前簡單的方式

  1. 直接使用fastjson,配置簡單,內置配置即可實現
  2. 使用Jackson,使用本次介紹的方式

對比:
使用fastjson的方式配置比較簡單,使用也方便,能滿足需求,但是本身不夠靈活,因為是配置全局生效,要排除的字段需要單獨指定序列化方式

使用本次介紹的方式靈活度很高,默認為原本的序列化方式(null 序列化還是 null),不做任何處理,而針對極少場景再使用自定義注解改變默認序列化方式,讓null值序列化為默認值

原理

開發自定義的null值序列化方式,然后配置全局Jackson null值序列化方式進行使用。

其中:
自定義注解
主要標識某個類、某個字段需要不為null,需要使用自定義序列化方式,然后再根據注解配置進行自定義的序列化

null值自定義序列化器
攔截所有值為null的字段進入自定義序列化器,然后根據自定義注解配置進行不同的序列化寫值

部分實現

  1. 自定義注解 @NeedNotNull
package com.lth.json.jackson;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * <p>
 * json 字段需要不為null
 * 只有字段為null時的序列化才會使用此方式,有值的和正常一樣
 * </p>
 *
 * @author Tophua
 * @since 2021/8/13
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})
public @interface NeedNotNull {

    /**
     * 是否排除,只針對字段
     *
     * @return true:按原默認處理,false:按此注解處理
     */
    boolean isExclude() default false;

    /**
     * 自定義值,有此值時已此值為主
     *
     * @return customV
     */
    String customV() default "";

    /**
     * 是否處理Boolean值
     *
     * @return true:Boolean字段為null時序列化為 boolV 值,false:不處理Boolean字段 null -> null
     */
    boolean boolT() default true;

    /**
     * boolean 默認值
     *
     * @return boolV
     */
    boolean boolV() default false;

    /**
     * 是否處理Number值
     *
     * @return true:Number字段為null時序列化為 numberV 值,false:不處理Number字段 null -> null
     */
    boolean numberT() default true;

    /**
     * Number 默認值
     *
     * @return numberV
     */
    int numberV() default 0;

    /**
     * 是否處理String值
     *
     * @return true:String字段為null時序列化為 stringV 值,false:不處理String字段 null -> null
     */
    boolean stringT() default true;

    /**
     * String 默認值
     *
     * @return stringV
     */
    String stringV() default "";

    /**
     * 是否處理Coll(集合和數組)值,null -> 空集合
     *
     * @return true:Coll字段為null時序列化為 '[]' 值,false:不處理Coll字段 null -> null
     */
    boolean collT() default true;

}

  1. 自定義null值序列化器
package com.lth.json.jackson;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

import java.io.IOException;

/**
 * <p>
 * json 字段為null時的自定義序列化方式
 * 只有字段為null時的序列化才會使用此方式,有值的和正常一樣
 * </p>
 *
 * @author Tophua
 * @since 2021/8/13
 */
@NoArgsConstructor
@AllArgsConstructor
public class NeedNotNullSerialize extends JsonSerializer<Object> {

    private BeanProperty property;

    @Override
    public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        if (value != null) {
            gen.writeObject(value);
            return;
        }
        JavaType javaType = property.getType();
        NeedNotNull needNotNull = property.getAnnotation(NeedNotNull.class);
        if (needNotNull == null) {
            needNotNull = property.getContextAnnotation(NeedNotNull.class);
        }
        if (needNotNull == null) {
            gen.writeObject(null);
            return;
        }
        // 是否排除
        if (needNotNull.isExclude()) {
            gen.writeObject(null);
            return;
        }
        // 有自定義值
        if (StrUtil.isNotEmpty(needNotNull.customV())) {
            gen.writeObject(needNotNull.customV());
            return;
        }
        // bool
        if (needNotNull.boolT() && javaType.isTypeOrSubTypeOf(Boolean.class)) {
            gen.writeObject(needNotNull.boolV());
            return;
        }
        // Number
        if (needNotNull.numberT() && javaType.isTypeOrSubTypeOf(Number.class)) {
            gen.writeObject(needNotNull.numberV());
            return;
        }
        // String
        if (needNotNull.stringT() && javaType.isTypeOrSubTypeOf(String.class)) {
            gen.writeObject(needNotNull.stringV());
            return;
        }
        // 集合、數組
        if (needNotNull.collT() && (javaType.isArrayType() || javaType.isCollectionLikeType())) {
            gen.writeObject(ListUtil.empty());
            return;
        }

        gen.writeObject(null);
    }

}

  1. Jackson 配置
package com.lth.json.config;

import cn.hutool.core.date.DatePattern;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.fasterxml.jackson.datatype.jsr310.PackageVersion;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import com.lth.json.jackson.NeedNotNullSerialize;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;

/**
 * @author skyhua
 */
@Configuration
@ConditionalOnClass(ObjectMapper.class)
public class JacksonConfig {

	@Bean
	public ObjectMapper objectMapper(ApplicationContext applicationContext) {
		Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
		builder.applicationContext(applicationContext);
		builder.locale(Locale.CHINA);
		builder.timeZone(TimeZone.getTimeZone(ZoneId.systemDefault()));
		builder.simpleDateFormat(DatePattern.NORM_DATETIME_PATTERN);
		builder.modules(new JavaTimeModule());
		ObjectMapper mapper = builder.createXmlMapper(false).build();
		// 為mapper注冊一個帶有SerializerModifier的Factory,此modifier主要做的事情為:值為null時序列化為默認值
		mapper.setSerializerFactory(mapper.getSerializerFactory().withSerializerModifier(new NeedNotNullSerializerModifier()));
		return mapper;
	}

	/**
	 * <p>
	 * NeedNotNullSerializerModifier 為bean 設置null值序列化器
	 * </p>
	 *
	 * @author Tophua
	 * @since 2021/8/14
	 */
	public static class NeedNotNullSerializerModifier extends BeanSerializerModifier {

		@Override
		public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
			beanProperties.forEach(b -> b.assignNullSerializer(new NeedNotNullSerialize(b)));
			return beanProperties;
		}
	}

	public static class JavaTimeModule extends SimpleModule {
		public JavaTimeModule() {
			super(PackageVersion.VERSION);
			this.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)));
			this.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)));
			this.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN)));
			this.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)));
			this.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)));
			this.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN)));
		}
	}
}


結果演示

默認

@Data
public class Test {
    private Object obj;
    private Boolean bool;
    private Integer intT;
    private Long longT;
    private BigDecimal decimalT;
    private Double doubleT;
    private Float floatT;
    private String stringV;
    private Object[] arrayT;
    private List<Integer> collT;
    private Set<String> setT;
    private String customT;
    private Integer customT1;
    private LocalDateTime now;
    private Test1 test1;

    @Data
    static class Test1 {
        private Integer id;
        private String value;
    }
}

默認結果

{
  "obj": null,
  "bool": null,
  "intT": null,
  "longT": null,
  "decimalT": null,
  "doubleT": null,
  "floatT": null,
  "stringV": null,
  "arrayT": null,
  "collT": null,
  "setT": null,
  "customT": null,
  "customT1": null,
  "now": "2021-08-15 17:53:14",
  "test1": {
    "id": null,
    "value": null
  }
}

注解用在類上

@Data
@NeedNotNull
public class Test {
    private Object obj;
    private Boolean bool;
    private Integer intT;
    private Long longT;
    private BigDecimal decimalT;
    private Double doubleT;
    private Float floatT;
    private String stringV;
    private Object[] arrayT;
    private List<Integer> collT;
    private Set<String> setT;
    private String customT;
    private Integer customT1;
    private LocalDateTime now;
    private Test1 test1;

    @Data
    static class Test1 {
        private Integer id;
        private String value;
    }
}

注解在類上結果

{
  "obj": null,
  "bool": false,
  "intT": 0,
  "longT": 0,
  "decimalT": 0,
  "doubleT": 0,
  "floatT": 0,
  "stringV": "",
  "arrayT": [],
  "collT": [],
  "setT": [],
  "customT": "",
  "customT1": 0,
  "now": "2021-08-15 17:54:00",
  "test1": {
    "id": null,
    "value": null
  }
}

可見已有默認值

注解使用在字段上

@Data
public class Test {
    private Object obj;
    private Boolean bool;
    private Integer intT;
    private Long longT;
    private BigDecimal decimalT;
    private Double doubleT;
    private Float floatT;
    @NeedNotNull
    private String stringV;
    private Object[] arrayT;
    @NeedNotNull
    private List<Integer> collT;
    private Set<String> setT;
    private String customT;
    private Integer customT1;
    private LocalDateTime now;
    private Test1 test1;

    @Data
    static class Test1 {
        private Integer id;
        @NeedNotNull
        private String value;
    }
}

注解用在字段上結果

{
  "obj": null,
  "bool": null,
  "intT": null,
  "longT": null,
  "decimalT": null,
  "doubleT": null,
  "floatT": null,
  "stringV": "",
  "arrayT": null,
  "collT": [],
  "setT": null,
  "customT": null,
  "customT1": null,
  "now": "2021-08-15 17:58:31",
  "test1": {
    "id": null,
    "value": ""
  }
}

花樣玩法

@Data
@NeedNotNull
public class Test {
    private Object obj;
    @NeedNotNull(boolT = false)
    private Boolean bool;
    private Integer intT;
    @NeedNotNull(isExclude = true)
    private Long longT;
    private BigDecimal decimalT;
    @NeedNotNull(customV = "0.5")
    private Double doubleT;
    private Float floatT;
    private String stringV;
    @NeedNotNull(customV = "[1,2,3,4]")
    private Object[] arrayT;
    @NeedNotNull(customV = "[1,2,3,4]")
    private List<Integer> collT;
    private Set<String> setT;
    @NeedNotNull(customV = "花樣字符串")
    private String customT;
    @NeedNotNull(customV = "100")
    private Integer customT1;
    private LocalDateTime now;
    private Test1 test1;

    @Data
    @NeedNotNull(stringT = false)
    static class Test1 {
        private Integer id;
        private String value;
    }
}

花樣玩法結果

{
  "obj": null,
  "bool": null,
  "intT": 0,
  "longT": null,
  "decimalT": 0,
  "doubleT": "0.5",
  "floatT": 0,
  "stringV": "",
  "arrayT": "[1,2,3,4]",
  "collT": "[1,2,3,4]",
  "setT": [],
  "customT": "花樣字符串",
  "customT1": "100",
  "now": "2021-08-15 18:02:37",
  "test1": {
    "id": 0,
    "value": null
  }
}

各式玩法由各位自行去嘗試,也可以再進行擴展

總結

本人意在解決Jackson序列化時對null值的一些自定義序列化方式,讓使用者變的簡單,使用方式變的靈活,而不是一桿子打死進行全局配置而忽略一些需要null返回值的場景。
此外,個人覺得在spring項目中還是使用Jackson作為序列化方式比較好,因為這是官方默認的方式,fastjson可以使用,但僅限於代碼中做某些json轉換(畢竟靜態調用還是比較香的)。

我是Tophua,歡迎交流

附上源碼

GitHub

Gitee


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM