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"}

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

如果采用字段注解方式:

fastjson:

java 复制代码
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;
    }
}

jackson:

java 复制代码
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;
    }
}

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

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

fastjson在指定序列化方法时,需要定义一个序列化器,然后在序列化时引入这个序列化方法:

java 复制代码
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;
    }
}

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

java 复制代码
JSONObject.toJSONString(data, new FastjsonDateSerializer());

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

java 复制代码
JsonMapper jsonMapper = JsonMapper.builder()
        .defaultDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))
        .defaultTimeZone(TimeZone.getDefault())
        .build();
jsonMapper.writeValueAsString(data);

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

java 复制代码
Gson gsonMapper = new GsonBuilder()
        .setDateFormat("yyyy-MM-dd HH:mm:ss")
        .create();
gsonMapper.toJson(data);

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

在java8中引入了LocalDateTime、LocalDate、LocalTime三种新的数据类型,这三种数据类型在json序列化时还不能很好的支持,要实现他们的序列化和反序列化,需要自己实现序列化和反序列化接口然后再代码中使用。

对于fastjson中支持LocalDateTime再上面代码中已经实现,这里就不再赘述。

jackson中对LocalDateTime的序列化有两种方式:第一种是注解方式;第二种是在mapper中注入,注解方式只需要定义序列化类和反序列化类,然后就可以添加到字段的注解上:

java 复制代码
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));
    }
}
java 复制代码
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);
    }
}

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

java 复制代码
@JsonSerialize(using = JacksonLocalDateTimeSerializer.class)
@JsonDeserialize(using = JacksonLocalDateTimeDeserializer.class)
private LocalDateTime addTime;

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

java 复制代码
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);

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

java 复制代码
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));
    }
}

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

java 复制代码
Gson gsonMapper = new GsonBuilder()
        .setDateFormat("yyyy-MM-dd HH:mm:ss")
        .registerTypeAdapter(LocalDateTime.class, new GsonLocalDateTimeSerializer())
        .create();

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

spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
    locale: zh_CN

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

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);
    }
}

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

java 复制代码
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";
    }
}
相关推荐
q5673152316 分钟前
在 Bash 中获取 Python 模块变量列
开发语言·python·bash
是萝卜干呀17 分钟前
Backend - Python 爬取网页数据并保存在Excel文件中
python·excel·table·xlwt·爬取网页数据
代码欢乐豆18 分钟前
数据采集之selenium模拟登录
python·selenium·测试工具
狂奔solar1 小时前
yelp数据集上识别潜在的热门商家
开发语言·python
Tassel_YUE1 小时前
网络自动化04:python实现ACL匹配信息(主机与主机信息)
网络·python·自动化
聪明的墨菲特i1 小时前
Python爬虫学习
爬虫·python·学习
hjjdebug1 小时前
linux 下 signal() 函数的用法,信号类型在哪里定义的?
linux·signal
其乐无涯1 小时前
服务器技术(一)--Linux基础入门
linux·运维·服务器
Diamond技术流1 小时前
从0开始学习Linux——网络配置
linux·运维·网络·学习·安全·centos
斑布斑布1 小时前
【linux学习2】linux基本命令行操作总结
linux·运维·服务器·学习