JsonSerialize重新定義輸出的內容格式&JsonDeserializer定義引數轉換器&PropertyEditorSupport自定義非JSON資料引數處理器
1.JsonSerialize重新定義輸出的內容格式
有時候需要重新定義輸出的內容格式,或者在輸出的JSON資料中增加一個屬性。比如一個場景,日期型別的欄位,通常在返回的JSON資料中會增加一個日期的字串格式,比如原欄位叫createTime,會增加一個createTimeString 欄位。第一種做法是VO中增加getCreateTimeString方法,第二種就是用@JsonSerialize 註解。
比如:
package com.xm.ggn.test; import com.fasterxml.jackson.databind.annotation.JsonSerialize;import lombok.Data; import java.util.Date; @Data public class TestBean { private String name; @JsonSerialize(using = DateJsonSerizlizer.class) private Date createTime; }
序列化程式碼如下:
package com.xm.ggn.test; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer;import com.fasterxml.jackson.databind.SerializerProvider; import org.apache.commons.lang3.time.DateFormatUtils; import java.io.IOException; import java.util.Date; /** * 自定義輸出到responseWriter的內容 * * @author: 喬利強 * @date: 2021/1/18 20:28 * @see TestBean#createTime */ public class DateJsonSerizlizer extendsJsonSerializer<Date> { @Override public void serialize(Date value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { // 先將本欄位寫出去 String fieldName = jsonGenerator.getOutputContext().getCurrentName(); jsonGenerator.writeObject(value); // 多輸出一個欄位(名稱為欄位名+String) String fieldStrName = fieldName + "String"; String dateStr = null; if (value != null) { dateStr = DateFormatUtils.format(value, "yyyy-MM-dd HH:mm:ss"); } jsonGenerator.writeStringField(fieldStrName, dateStr); } }
測試Controller:
/** * 測試@JsonSerialize 的用法,自定義輸出的欄位內容 * * @author 喬利強 * @date 2021/1/18 20:34 * @return: com.xm.ggn.test.TestBean */ @GetMapping("/findTestBean") public TestBean findTestBean() { TestBean testBean = new TestBean(); testBean.setCreateTime(new Date()); testBean.setName("testBean"); return testBean; }
結果:
$ curl http://localhost:8088/findTestBean % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 142 0 142 0 0 6761 0 --:--:-- --:--:-- --:--:-- 7100{"success":true,"data":{"name":"testBean","createTime":1611116695656,"createTimeString":"2021-01-20 12:24:55"},"msg":"鎴愬姛","errorCode":"0"}
補充:這種方式是單獨的配置,相當於每個需要處理的都打註解,程式碼侵入性也比較強,可以增加全域性配置
package com.xm.ggn.test; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import org.apache.commons.lang3.time.DateFormatUtils; import org.springframework.boot.jackson.JsonComponent; import java.io.IOException; import java.util.Date; /** * 自定義輸出到responseWriter的內容 * * @author: 喬利強 * @date: 2021/1/18 20:28 * @see TestBean#createTime */ @JsonComponent public class DateJsonSerizlizer extends JsonSerializer<Date> { @Override public void serialize(Date value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { // 先將本欄位寫出去 String fieldName = jsonGenerator.getOutputContext().getCurrentName(); // 注意不能直接輸出自己,會造成遞迴StackOverFlow // jsonGenerator.writeObject(value); jsonGenerator.writeString(DateFormatUtils.format(value, "yyyy-MM-dd HH:mm:ss")); // 多輸出一個欄位(名稱為欄位名+String) String fieldStrName = fieldName + "String"; String dateStr = null; if (value != null) { dateStr = DateFormatUtils.format(value, "yyyy-MM-dd HH-mm-ss"); } jsonGenerator.writeStringField(fieldStrName, dateStr); } }
注意,這種方式不能再次輸出當前物件,再次輸出會造成StackOverFlow。
測試Controller同上面,測試結果:
$ curl http://localhost:8088/findTestBean % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 150 0 150 0 0 270 0 --:--:-- --:--:-- --:--:-- 271{"success":true,"data":{"name":"testBean","createTime":"2021-01-20 17:57:32","createTimeString":"2021-01-20 17-57-32"},"msg":"鎴愬姛","errorCode":"0"}
2.JsonDeserializer 轉換接受到的引數到bean
同樣使用方式有兩種,第一種是針對單個屬性用@JsonDeserialize 註解,第二種就是全域性的。這種只對@RequestBody 註解有效,對普通form表單提交的引數無效。
1.第一種:針對單個屬性
反序列類:
package com.xm.ggn.test; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.DateUtils; import java.io.IOException; import java.util.Date; /** * JSON字串轉日期 * */ @Slf4j class DateJsonDeserializer extends JsonDeserializer<Date> { @Override public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { Date date = null; String text = jsonParser.getText(); try { if (StringUtils.isNotBlank(text)) { date = DateUtils.parseDate(text, "yyyy-MM-dd HH-mm-ss", "yyyy-MM-dd"); log.info("text: {}, date: {}", text, date); } } catch (Exception e) { // ignore } return date; } @Override public Class<?> handledType() { return Date.class; } }
接收引數的bean:
package com.xm.ggn.test; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import lombok.Data; import java.util.Date; @Data public class TestBean { private String name; // @JsonSerialize(using = DateJsonSerizlizer.class) @JsonDeserialize(using = DateJsonDeserializer.class) private Date createTime; }
測試Controller:
@PostMapping("/findTestBean2") public TestBean findTestBean2(@RequestBody TestBean bean) { return bean; }
測試結果:
$ curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' -d '{"createTime":"1995-02-03 22-22-22"}' 'http://localhost:8088/findTestBean2' % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 180 0 144 100 36 6545 1636 --:--:-- --:--:-- --:--:-- 8571{"success":true,"data":{"name":null,"createTime":"1995-02-03 22:22:22","createTimeString":"1995-02-03 22-22-22"},"msg":"鎴愬姛","errorCode":"0"}
2. 全域性設定
全域性也是用@JsonComponent 註解
package com.xm.ggn.test; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.DateUtils; import org.springframework.boot.jackson.JsonComponent; import java.io.IOException; import java.util.Date; /** * JSON字串轉日期 */ @Slf4j @JsonComponent class DateJsonDeserializer extends JsonDeserializer<Date> { @Override public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { Date date = null; String text = jsonParser.getText(); try { if (StringUtils.isNotBlank(text)) { date = DateUtils.parseDate(text, "yyyy-MM-dd HH-mm-ss", "yyyy-MM-dd"); log.info("text: {}, date: {}", text, date); } } catch (Exception e) { // ignore } return date; } @Override public Class<?> handledType() { return Date.class; } }
bean如下:
package com.xm.ggn.test; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import lombok.Data; import java.util.Date; @Data public class TestBean { private String name; private Date createTime; }
測試Controller同上,測試結果如下:
$ curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' -d '{"createTime":"1995-02-03 22-22-22"}' 'http://localhost:8088/findTestBean2' % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 180 0 144 100 36 218 54 --:--:-- --:--:-- --:--:-- 273{"success":true,"data":{"name":null,"createTime":"1995-02-03 22:22:22","createTimeString":"1995-02-03 22-22-22"},"msg":"鎴愬姛","errorCode":"0"}
補充:還有另一種全域性註冊的方式,如下:
package com.xm.ggn.test; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean; /** * 配置接收JSON資料日期型別轉換器 */ @Configuration public class ConverterConfig { @Bean public Jackson2ObjectMapperFactoryBean jackson2ObjectMapperFactoryBean(DateJsonDeserializer dateJacksonConverter) { Jackson2ObjectMapperFactoryBean jackson2ObjectMapperFactoryBean = new Jackson2ObjectMapperFactoryBean(); jackson2ObjectMapperFactoryBean.setDeserializers(new DateJsonDeserializer()); jackson2ObjectMapperFactoryBean.setSerializers(new DateJsonSerizlizer()); return jackson2ObjectMapperFactoryBean; } }
3. 針對GET引數等非JSON資料轉換器的使用
PropertyEditorSupport 結合WebDataBinder 類的使用。 Spring也內建了許多PropertyEditorSupport ,比如CustomDateEditor、 CustomMapEditor 等。這種針對@RequestBody 的引數無效。
1. 轉換器類:
package com.xm.ggn.test; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.time.DateUtils; import org.springframework.util.StringUtils; import java.beans.PropertyEditorSupport; import java.util.Date; /***/ @Slf4j public class DateEditor extends PropertyEditorSupport { private boolean isAllowEmpty = true; public DateEditor() { } public DateEditor(boolean isAllowEmpty) { this.isAllowEmpty = isAllowEmpty; } @Override @SneakyThrows public void setAsText(String text) throws IllegalArgumentException { Date date = null; if (StringUtils.hasText(text)) { date = DateUtils.parseDate(text, "yyyy-MM-dd HH-mm-ss", "yyyy-MM-dd"); log.info("text: {}, date: {}", text, date); } else { if (!isAllowEmpty) { throw new IllegalArgumentException("日期不允許為空"); } } setValue(date); } public boolean isAllowEmpty() { return isAllowEmpty; } public void setAllowEmpty(boolean isAllowEmpty) { this.isAllowEmpty = isAllowEmpty; } }
2. Controller用@InitBinder 繫結 以及測試:
@GetMapping("/findTestBean3") public TestBean findTestBean3(TestBean bean) { return bean; } @PostMapping("/findTestBean4") public TestBean findTestBean4(TestBean bean) { return bean; } /** * 自定義引數轉換器 * * @param binder */ @InitBinder public void initBinder(WebDataBinder binder) { binder.registerCustomEditor(Date.class, new DateEditor()); // 也可以使用spring內建的一些轉換器。 // SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss"); // binder.registerCustomEditor(Date.class, "createTime", new CustomDateEditor(simpleDateFormat, true)); }
3.測試:
$ curl http://localhost:8088/findTestBean3?createTime=2022-02-22+22-22-22 % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 144 0 144 0 0 5760 0 --:--:-- --:--:-- --:--:-- 6000{"success":true,"data":{"name":null,"createTime":"2022-02-22 22:22:22","createTimeString":"2022-02-22 22-22-22"},"msg":"鎴愬姛","errorCode":"0"} $ curl -X POST http://localhost:8088/findTestBean4?createTime=2022-02-22+22-22-22 % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 144 0 144 0 0 10285 0 --:--:-- --:--:-- --:--:-- 11076{"success":true,"data":{"name":null,"createTime":"2022-02-22 22:22:22","createTimeString":"2022-02-22 22-22-22"},"msg":"鎴愬姛","errorCode":"0"}
注意: 也可以定義一個BaseController, 裡面定義initBinder方法,其他Controller繼承該類即可。