1. 程式人生 > 實用技巧 >JsonSerialize重新定義輸出的內容格式&JsonDeserializer定義引數轉換器&PropertyEditorSupport自定義非JSON資料引數處理器

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 extends
JsonSerializer<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繼承該類即可。