系统间通过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:
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:
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在指定序列化方法时,需要定义一个序列化器,然后在序列化时引入这个序列化方法:
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;
}
}
在使用时改成如下的方式:
JSONObject.toJSONString(data, new FastjsonDateSerializer());
jackson在创建mapper时可以指定format格式:
JsonMapper jsonMapper = JsonMapper.builder()
.defaultDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))
.defaultTimeZone(TimeZone.getDefault())
.build();
jsonMapper.writeValueAsString(data);
gson与jackson类似,可以在创建mapper时指定format格式:
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中注入,注解方式只需要定义序列化类和反序列化类,然后就可以添加到字段的注解上:
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));
}
}
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);
}
}
在实体类的字段上添加相应注解:
@JsonSerialize(using = JacksonLocalDateTimeSerializer.class)
@JsonDeserialize(using = JacksonLocalDateTimeDeserializer.class)
private LocalDateTime addTime;
第二种是在创建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);
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));
}
}
在创建mapper时使用这个自定义的序列化类:
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
类中,这里就不展开叙述了,下面这个类是通过配置文件方式配置上面参数,这种配置方式也很好用:
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表单提交时的参数自动转换:
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";
}
}