• json中时间类型字段的处理


    系统间通过json进行序列化和反序列化时,对于基本数据类型或字符串,通常不会有问题,但是对于比较常用的时间类型,由于不同库处理方式的差异,他们进行序列化时往往不同。比如项目中常用的fastjson、jackson、gson三个库,fastjson和jackson对时间类型默认序列化时会转换为时间戳,而gson则转换为西式格式:

    fastjson : {"createTime":1699325978234,"first_name":"james"}
    jackson : {"createTime":1699325978234,"first_name":"james"}
    gson : {"first_name":"james","createTime":"Nov 7, 2023 10:59:38 AM"}
    
    • 1
    • 2
    • 3

    在系统开发中,我们往往习惯统一时间格式,比如精确到秒的时间格式:yyyy-MM-dd HH:mm:ss;精确到毫秒的时间格式:yyyy-MM-dd HH:mm:ss.SSS。对于不同的库可以有不同的实现方式。
    如果采用字段注解方式:
    fastjson:

    import com.alibaba.fastjson.annotation.JSONField;
    import com.fasterxml.jackson.annotation.JsonProperty;
    import com.google.gson.annotations.SerializedName;
    
    import java.util.Date;
    
    public class DemoVo {
    
        @JSONField(name = "first_name")
        @JsonProperty(value = "first_name")
        @SerializedName(value = "first_name")
        private String firstName;
    
        @JSONField(format = "yyyy-MM-dd HH:mm:ss")
        private Date createTime;
    
        public String getFirstName() {
            return firstName;
        }
    
        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }
    
        public Date getCreateTime() {
            return createTime;
        }
    
        public void setCreateTime(Date createTime) {
            this.createTime = createTime;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    jackson:

    import com.alibaba.fastjson.annotation.JSONField;
    import com.fasterxml.jackson.annotation.JsonFormat;
    import com.fasterxml.jackson.annotation.JsonProperty;
    import com.google.gson.annotations.SerializedName;
    
    import java.util.Date;
    
    public class DemoVo {
    
        @JSONField(name = "first_name")
        @JsonProperty(value = "first_name")
        @SerializedName(value = "first_name")
        private String firstName;
    
        @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
        private Date createTime;
    
        public String getFirstName() {
            return firstName;
        }
    
        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }
    
        public Date getCreateTime() {
            return createTime;
        }
    
        public void setCreateTime(Date createTime) {
            this.createTime = createTime;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    gson没有查找到对应的注解,如果知道的帮忙提供一下。

    上面的处理方式可以解决序列化的格式问题,但是这种处理方式会有一定的风险,如果新添加的类中字段忘记添加注解在序列化时就会产生问题,更好的处理方式是在序列化时统一配置,这样就会避免产生问题,但是这样处理就不够灵活,比如某些字段在序列化时就是要单独指定格式,这样还要单独处理。
    fastjson在指定序列化方法时,需要定义一个序列化器,然后在序列化时引入这个序列化方法:

    import com.alibaba.fastjson.serializer.ValueFilter;
    import java.text.SimpleDateFormat;
    import java.time.LocalDateTime;
    import java.time.format.DateTimeFormatter;
    import java.util.Date;
    
    /**
     * fastjson时间序列化
     *
     * @Author xingo
     * @Date 2023/11/7
     */
    public class FastjsonDateSerializer implements ValueFilter {
        // 针对LocalDateTime类型的处理
        DateTimeFormatter dateTimeFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    
        @Override
        public Object process(Object object, String name, Object value) {
            if(value instanceof Date) {
                return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(value);
            } else if(value instanceof LocalDateTime) {
                return dateTimeFormat.format((LocalDateTime) value);
            }
    
            return value;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    在使用时改成如下的方式:

    JSONObject.toJSONString(data, new FastjsonDateSerializer());
    
    • 1

    jackson在创建mapper时可以指定format格式:

    JsonMapper jsonMapper = JsonMapper.builder()
            .defaultDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))
            .defaultTimeZone(TimeZone.getDefault())
            .build();
    jsonMapper.writeValueAsString(data);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    gson与jackson类似,可以在创建mapper时指定format格式:

    Gson gsonMapper = new GsonBuilder()
            .setDateFormat("yyyy-MM-dd HH:mm:ss")
            .create();
    gsonMapper.toJson(data);
    
    • 1
    • 2
    • 3
    • 4

    如果某些字段单独设置序列化格式,jackson是支持的,fastjson不会生效。

    在java8中引入了LocalDateTime、LocalDate、LocalTime三种新的数据类型,这三种数据类型在json序列化时还不能很好的支持,要实现他们的序列化和反序列化,需要自己实现序列化和反序列化接口然后再代码中使用。
    对于fastjson中支持LocalDateTime再上面代码中已经实现,这里就不再赘述。
    jackson中对LocalDateTime的序列化有两种方式:第一种是注解方式;第二种是在mapper中注入,注解方式只需要定义序列化类和反序列化类,然后就可以添加到字段的注解上:

    import com.fasterxml.jackson.core.JsonGenerator;
    import com.fasterxml.jackson.databind.JsonSerializer;
    import com.fasterxml.jackson.databind.SerializerProvider;
    import java.io.IOException;
    import java.time.LocalDateTime;
    import java.time.format.DateTimeFormatter;
    
    /**
     * jackson对LocalDateTime序列化
     *
     * @Author xingo
     * @Date 2023/11/7
     */
    public class JacksonLocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
    
        private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    
        @Override
        public void serialize(LocalDateTime value, JsonGenerator generator, SerializerProvider provider) throws IOException {
            generator.writeString(formatter.format(value));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    import com.fasterxml.jackson.core.JsonParser;
    import com.fasterxml.jackson.databind.DeserializationContext;
    import com.fasterxml.jackson.databind.JsonDeserializer;
    import java.io.IOException;
    import java.time.LocalDateTime;
    import java.time.format.DateTimeFormatter;
    
    /**
     * jackson对LocalDateTime反序列化
     *
     * @Author xingo
     * @Date 2023/11/7
     */
    public class JacksonLocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
    
        private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    
        @Override
        public LocalDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException {
            String value = parser.getText();
            return LocalDateTime.parse(value, formatter);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在实体类的字段上添加相应注解:

    @JsonSerialize(using = JacksonLocalDateTimeSerializer.class)
    @JsonDeserialize(using = JacksonLocalDateTimeDeserializer.class)
    private LocalDateTime addTime;
    
    • 1
    • 2
    • 3

    第二种是在创建mapper时指定,这种方式一劳永逸:

    JsonMapper jsonMapper = JsonMapper.builder()
            .defaultDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))
            .defaultTimeZone(TimeZone.getDefault())
            .build();
    // 添加对LocalDateTime类型支持
    JavaTimeModule module = new JavaTimeModule();
    LocalDateTimeDeserializer dateTimeDeserializer = new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    LocalDateTimeSerializer dateTimeSerializer = new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    module.addDeserializer(LocalDateTime.class, dateTimeDeserializer);
    module.addSerializer(LocalDateTime.class, dateTimeSerializer);
    // 添加对LocalDate类型支持
    LocalDateDeserializer dateDeserializer = new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
    LocalDateSerializer dateSerializer = new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
    module.addDeserializer(LocalDate.class, dateDeserializer);
    module.addSerializer(LocalDate.class, dateSerializer);
    // 添加对LocalTime类型支持
    LocalTimeDeserializer timeDeserializer = new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss"));
    LocalTimeSerializer timeSerializer = new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss"));
    module.addDeserializer(LocalTime.class, timeDeserializer);
    module.addSerializer(LocalTime.class, timeSerializer);
    // 添加model到mapper中
    jsonMapper.registerModules(module);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    gson中也是需要先定义一个序列化类实现接口:

    import com.google.gson.JsonElement;
    import com.google.gson.JsonPrimitive;
    import com.google.gson.JsonSerializationContext;
    import com.google.gson.JsonSerializer;
    import java.lang.reflect.Type;
    import java.time.LocalDateTime;
    import java.time.format.DateTimeFormatter;
    
    /**
     * gson对LocalDateTime序列化
     *
     * @Author xingo
     * @Date 2023/11/7
     */
    public class GsonLocalDateTimeSerializer implements JsonSerializer<LocalDateTime> {
        DateTimeFormatter dateTimeFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    
        @Override
        public JsonElement serialize(LocalDateTime localDateTime, Type type, JsonSerializationContext jsonSerializationContext) {
            return new JsonPrimitive(localDateTime.format(dateTimeFormat));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在创建mapper时使用这个自定义的序列化类:

    Gson gsonMapper = new GsonBuilder()
            .setDateFormat("yyyy-MM-dd HH:mm:ss")
            .registerTypeAdapter(LocalDateTime.class, new GsonLocalDateTimeSerializer())
            .create();
    
    • 1
    • 2
    • 3
    • 4

    以上就是json中对日期类型的处理方式,对于性能这些网上有很多的文章,这里就不展开了,总体来说,jackson无论是在配置的灵活性还是扩展性方面,都是最好的,而且在与spring整合上也是官方的默认组件库,所以在项目中还是非常推荐使用jackson库作为json处理类库的,在springboot中可以通过全局配置,让接口在序列化和反序列化时自动完成处理:

    spring:
      jackson:
        date-format: yyyy-MM-dd HH:mm:ss
        time-zone: GMT+8
        locale: zh_CN
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这里是最简单的配置,设置了时间字段的序列化和反序列化格式,以及时区信息,更完整全面的配置信息在 org.springframework.boot.autoconfigure.jackson.JacksonProperties.java 类中,这里就不展开叙述了,下面这个类是通过配置文件方式配置上面参数,这种配置方式也很好用:

    import com.fasterxml.jackson.annotation.JsonInclude.Include;
    import com.fasterxml.jackson.databind.*;
    import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
    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 org.springframework.boot.autoconfigure.AutoConfigureBefore;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
    import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.Ordered;
    import org.springframework.core.annotation.Order;
    
    import java.time.LocalDate;
    import java.time.LocalDateTime;
    import java.time.LocalTime;
    import java.time.format.DateTimeFormatter;
    import java.util.LinkedHashMap;
    import java.util.Locale;
    import java.util.Map;
    
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(ObjectMapper.class)
    @AutoConfigureBefore(JacksonAutoConfiguration.class)
    public class JacksonConfiguration {
    
        private final Map<Class<?>, JsonSerializer<?>> serializers = new LinkedHashMap<>(5);
    
        private final Map<Class<?>, JsonDeserializer<?>> deserializers = new LinkedHashMap<>(3);
    
        @Bean
        @Order(Ordered.HIGHEST_PRECEDENCE)
        public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
    
            // 序列化时对Long类型进行处理,避免前端js处理数据时精度缺失
            serializers.put(Long.class, ToStringSerializer.instance);
            serializers.put(Long.TYPE, ToStringSerializer.instance);
    
            // java8日期处理
            serializers.put(LocalDateTime.class,
                    new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            serializers.put(LocalDate.class,
                    new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
            serializers.put(LocalTime.class,
                    new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
            deserializers.put(LocalDateTime.class,
                    new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            deserializers.put(LocalDate.class,
                    new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
            deserializers.put(LocalTime.class,
                    new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
    
            return customizer -> customizer
                    .featuresToDisable(SerializationFeature.FAIL_ON_EMPTY_BEANS,
                            SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,
                            SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS,
                            DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
                    //.propertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)    // 驼峰转下划线
                    .serializationInclusion(Include.NON_NULL)
                    .serializersByType(serializers)
                    .deserializersByType(deserializers)
                    .simpleDateFormat("yyyy-MM-dd HH:mm:ss")
                    .timeZone("GMT+8")
                    .locale(Locale.SIMPLIFIED_CHINESE);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    spring开发中也有一个注解可以实现接口中的时间字符串转换为Date类型对象:@DateTimeFormat。这种方式多用于form表单提交时的参数自动转换:

    import org.springframework.format.annotation.DateTimeFormat;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    import java.util.Date;
    
    @RestController
    public class TestController {
    
        @GetMapping("/test")
        public String test(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date createTime) {
            System.out.println(createTime);
            return "ok";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
  • 相关阅读:
    2022年CCF推荐国际学术会议和期刊(人工智能领域)
    Django——模板层、模型层
    这几个重要协议,是网络工程师加薪的秘密
    spark
    scratch大鱼吃小鱼 电子学会图形化编程scratch等级考试二级真题和答案解析2022年6月
    CSDN有哪些值得学习的专栏?
    SANs证书是什么?
    1870. 准时到达的列车最小时速-二分法
    【第八篇】商城系统-库存管理
    一级造价工程师(安装)- 计量笔记 - 第五章第四节静置设备与工艺金属结构工程
  • 原文地址:https://blog.csdn.net/dream_xin2013/article/details/134262670