一起聊聊springboot实现RESTful API中json格式,自定义序列号和反序列号规则

背景

  • 如今炒的火热的前后端分离项目,大多数开发人员选择RESTful设计风格,Java Web 人员经常要设计 RESTful API,这种设计通常使用 json 数据进行交互。那么前端传入的 json 数据如何序列化成 Java 对象,后端返回的结果又如何将Java 对象解析成 json 格式数据返回给前端。
  • spring-boot-stater-web依赖的json解析是jackson,同时也为我们进行了jackson的一系列自动化配置,这样我们不需要导入其他json依赖,就可以直接使用,其中起到关键作用的是MappingJackson2HttpMessageConverter
  • 在整个解析过程,HttpMessageConverter 起到了重要作用,各大公司实现了自己的HttpMessageConverter,如:
    jackson的MappingJackson2HttpMessageConverter;
    gson的GsonHttpMessageConverter;
    fastjson的FastJsonHttpMessageConverter等等。

问题

数据库数据类型datetime,java类型为LocalDateTime,我们来看看springboot默认返回什么时间格式给前端

在这里插入图片描述
可以看到,是世界时间格式,因为LocalDateTime类型是和时区有关的时间,"2022-03-07T15:09:19"格式,T就是表示UTC的世界时间,但是我们并不需要这种格式,我们不想把T返回或者我们只需要返回日期,怎么办呢

源码分析

刚刚我们讲到spring-boot-stater-web依赖的json解析是jackson,其中起到关键作用的是MappingJackson2HttpMessageConverter

@Configuration
@ConditionalOnClass({ObjectMapper.class})
@ConditionalOnBean({ObjectMapper.class})
@ConditionalOnProperty(
    name = {"spring.http.converters.preferred-json-mapper"},
    havingValue = "jackson",
    matchIfMissing = true
)
protected static class MappingJackson2HttpMessageConverterConfiguration {
    protected MappingJackson2HttpMessageConverterConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean(
        value = {MappingJackson2HttpMessageConverter.class},
        ignoredType = {"org.springframework.hateoas.mvc.TypeConstrainedMappingJackson2HttpMessageConverter", "org.springframework.data.rest.webmvc.alps.AlpsJsonHttpMessageConverter"}
    )
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
        return new MappingJackson2HttpMessageConverter(objectMapper);
    }
}

可以看到这里做了MappingJackson2HttpMessageConverter 类的bean声明,会依赖ObjectMapper 注入

/**
 * Construct a new {@link MappingJackson2HttpMessageConverter} with a custom {@link ObjectMapper}.
 * You can use {@link Jackson2ObjectMapperBuilder} to build it easily.
 * @see Jackson2ObjectMapperBuilder#json()
 */
public MappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
  super(objectMapper, MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
}

我们看下MappingJackson2HttpMessageConverter的构造方法,为json类型设置ObjectMapper 映射关系,可以自定义ObjectMapper,可以通过Jackson2ObjectMapperBuilder快速构建ObjectMapper

我们来看下Jackson2ObjectMapperBuilder

构建常规 JSON {@link ObjectMapper} 实例。

/**
 * Obtain a {@link Jackson2ObjectMapperBuilder} instance in order to
 * build a regular JSON {@link ObjectMapper} instance.
 */
public static Jackson2ObjectMapperBuilder json() {
	return new Jackson2ObjectMapperBuilder();
}

自定义反序列化规则

/**
 * Configure custom deserializers. Each deserializer is registered for the type
 * returned by {@link JsonDeserializer#handledType()}, which must not be {@code null}.
 * @since 4.3
 * @see #deserializersByType(Map)
 */
public Jackson2ObjectMapperBuilder deserializers(JsonDeserializer<?>... deserializers) {
	for (JsonDeserializer<?> deserializer : deserializers) {
		Class<?> handledType = deserializer.handledType();
		if (handledType == null || handledType == Object.class) {
			throw new IllegalArgumentException("Unknown handled type in " + deserializer.getClass().getName());
		}
		this.deserializers.put(deserializer.handledType(), deserializer);
	}
	return this;
}

自定义序列化规则

/**
 * Configure custom serializers. Each serializer is registered for the type
 * returned by {@link JsonSerializer#handledType()}, which must not be {@code null}.
 * @see #serializersByType(Map)
 */
public Jackson2ObjectMapperBuilder serializers(JsonSerializer<?>... serializers) {
	for (JsonSerializer<?> serializer : serializers) {
		Class<?> handledType = serializer.handledType();
		if (handledType == null || handledType == Object.class) {
			throw new IllegalArgumentException("Unknown handled type in " + serializer.getClass().getName());
		}
		this.serializers.put(serializer.handledType(), serializer);
	}
	return this;
}

构建ObjectMapper,并设置相关规则

/**
 * Build a new {@link ObjectMapper} instance.
 * <p>Each build operation produces an independent {@link ObjectMapper} instance.
 * The builder's settings can get modified, with a subsequent build operation
 * then producing a new {@link ObjectMapper} based on the most recent settings.
 * @return the newly built ObjectMapper
 */
@SuppressWarnings("unchecked")
public <T extends ObjectMapper> T build() {
	ObjectMapper mapper;
	if (this.createXmlMapper) {
		mapper = (this.defaultUseWrapper != null ?
				new XmlObjectMapperInitializer().create(this.defaultUseWrapper, this.factory) :
				new XmlObjectMapperInitializer().create(this.factory));
	}
	else {
		mapper = (this.factory != null ? new ObjectMapper(this.factory) : new ObjectMapper());
	}
	configure(mapper);
	return (T) mapper;
}

全局配置方式

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;

@Configuration
public class ObjectMapperConfig {

    @Bean
    public ObjectMapper jsonObjectMapper(){
        Jackson2ObjectMapperBuilder objectMapperBuilder = Jackson2ObjectMapperBuilder.json();

        //objectMapperBuilder.serializers(new CustomDateSerialize());
        objectMapperBuilder.serializers(new CustomLocalDateTimeSerialize());

        return objectMapperBuilder.build();
    }

}
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;

public class CustomLocalDateTimeSerialize extends JsonSerializer<LocalDateTime> {
    private DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault());
    private DateTimeFormatter sdf2 = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").withZone(ZoneId.systemDefault());

    public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
        String localTime = df.format(localDateTime);
        jsonGenerator.writeString(localTime);
    }

    @Override
    public Class<LocalDateTime> handledType() {
        return LocalDateTime.class;
    }
}

我们看看自定义序列化后的结果

在这里插入图片描述
可以看到返回前端的是序列化后的格式,其他类型如DateZonedDateTime等等都可以使用这种方式序列化和反序列化

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大佬腿好粗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值