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";
    }
}
相关推荐
深蓝海拓4 分钟前
Pyside6(PyQT5)中的QTableView与QSqlQueryModel、QSqlTableModel的联合使用
数据库·python·qt·pyqt
无须logic ᭄12 分钟前
CrypTen项目实践
python·机器学习·密码学·同态加密
Channing Lewis25 分钟前
flask常见问答题
后端·python·flask
Channing Lewis27 分钟前
如何保护 Flask API 的安全性?
后端·python·flask
幻想编织者27 分钟前
Ubuntu实时核编译安装与NVIDIA驱动安装教程(ubuntu 22.04,20.04)
linux·服务器·ubuntu·nvidia
水兵没月1 小时前
钉钉群机器人设置——python版本
python·机器人·钉钉
利刃大大1 小时前
【Linux入门】2w字详解yum、vim、gcc/g++、gdb、makefile以及进度条小程序
linux·c语言·vim·makefile·gdb·gcc
我想学LINUX2 小时前
【2024年华为OD机试】 (A卷,100分)- 微服务的集成测试(JavaScript&Java & Python&C/C++)
java·c语言·javascript·python·华为od·微服务·集成测试
数据小爬虫@5 小时前
深入解析:使用 Python 爬虫获取苏宁商品详情
开发语言·爬虫·python
健胃消食片片片片5 小时前
Python爬虫技术:高效数据收集与深度挖掘
开发语言·爬虫·python