Java后端的 Long(long) 类型字段,在web接口返回这种类型的值给前端时,会发生截断(即js会精度丢失,因为后端的Long的最大值超过了前端js能表示的值)
9223372036854776000
Long longValue
实际值没有超过前端js的限制,也不会出现截断建议项目代码编写的一开始就用String类型不要用Long
如果是接手的代码,并且按1的方法改造不切实际,可以配置全局Long转String
用注解,如下(其实第2点会有问题,我在实际项目中遇到过不生效的情况。原来生效后来可能是jar包版本升级了或者其他未知的原因导致了问题)
@JsonSerialize(using = ToStringSerializer.class)
如上注解加在想要转字串的字段上,比如Long/long/Integer/int等字段上
上述详细的包名是
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
总结:
总体来说我觉得第2中方法应该是最好的,
如果实在没办法用3也不错,用3的话不需要改太多代码。
1这种虽然最彻底,改动也大,容易引发新的bug,虽然 Long 改 String 这种会触发编译上的报错,提示你需要改动的地方,但是也有一些bug很隐蔽
第一个是 Map#get(Object key) 方法,就算你用了泛型,比如 Map
改成了 Map ,但是 map.get(key) 不管这个 key 是 Long 还是 String 都不会报错,但是实际 get 的话会get 不到值。 第二个是equals方法,equals() 的入参是 Object
对于
@Data
public class DemoDTO {
private Long longValue;
private Integer intValue;
private long primitiveLongValue;
private int primitiveIntValue;
}
@ApiOperation("test1 接口")
@GetMapping("/test1")
public DemoDTO test1() {
DemoDTO result = new DemoDTO();
result.setLongValue(Long.MAX_VALUE);
result.setIntValue(Integer.MAX_VALUE);
result.setPrimitiveLongValue(Long.MAX_VALUE);
result.setPrimitiveIntValue(Integer.MAX_VALUE);
return result;
}
@Configuration
public class LongToStringConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder
.serializerByType(Long.class, ToStringSerializer.instance)
.serializerByType(Long.TYPE, ToStringSerializer.instance);
}
实测可以将 Long/long 转字串,对于 Integer/int 等其他类型不会
@Configuration
public class LongToStringConfig {
@Bean
public MappingJackson2HttpMessageConverter jackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
ObjectMapper mapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
mapper.registerModule(simpleModule);
converter.setObjectMapper(mapper);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return converter;
}
}
这种写法也是可以的
这是个无效的方法,网上也列出了很多这种写法,列在这里是为了警示。主要是 @EnableWebMvc 注解导致 swagger 无法用,不加又导致Long转String没法用。
有解决办法,但是实在不想用这么麻烦的方法了,都有前面2种好的方法了,这里第3种的写法就作罢了
package com.example.demo.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import java.util.List;
/**
* 【这是个无效的方法,网上也列出了很多这种写法,列在这里是为了警示。主要是 @EnableWebMvc 注解导致 swagger 无法用,不加又导致Long转String没法用】
* 这是第三种方法,除了 LongToStringConfig 里的二选一,这是第三种方法解决 Long 转 String
* 说明:
* 网上有些资料还是继承 WebMvcConfigurerAdapter 的,这个已经 @Deprecated 了,说明得比较清晰了就是因为有default 关键字后,
* 接口的方法也可以有默认的实现,所以不需要这个 adapter 了 (PS:可以看出,很多的 adapter 的设计就是为了提供方法的空实现,
* 以便避开需要实现接口所有方法的麻烦,使用 extends adapter 只需要重写感兴趣的方法)
*/
@Configuration
// 这个注解会导致swagger打不开(已经打开的页面重新刷新一下就无法再次打开了),参考 https://blog.csdn.net/Apoca_lypse/article/details/115759895。
// 但是不用这个注解又会导致无法将Long转String
// 这里有解决办法,但是实在不想用这么麻烦的方法了,都有前面2种好的方法了,这里第3种的写法就作罢了 (解决:https://blog.csdn.net/axiang_/article/details/107103879)
@EnableWebMvc
public class LongToStringConfig2 implements WebMvcConfigurer /*extends WebMvcConfigurerAdapter*/ {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
objectMapper.registerModule(simpleModule);
converter.setObjectMapper(objectMapper);
converters.add(converter);
}
}
@Data
public class DemoDTO {
private Long longValue;
private Integer intValue;
private long primitiveLongValue;
private int primitiveIntValue;
}
@ApiOperation("test1 接口")
@GetMapping("/test1")
public DemoDTO test1() {
DemoDTO result = new DemoDTO();
result.setLongValue(Long.MAX_VALUE);
result.setIntValue(Integer.MAX_VALUE);
result.setPrimitiveLongValue(Long.MAX_VALUE);
result.setPrimitiveIntValue(Integer.MAX_VALUE);
return result;
}
如果虽然这个字段定义是 Long(long) 但是如果还没超过前端 js 的限制,也不会发生截断
@ApiOperation("test1 接口")
@GetMapping("/test1")
public DemoDTO test1() {
DemoDTO result = new DemoDTO();
result.setLongValue((long) Integer.MAX_VALUE);// 低于阈值,不会被截断 (这个值前端js还不会发生截断)
result.setIntValue(Integer.MAX_VALUE);
result.setPrimitiveLongValue((long) Integer.MAX_VALUE);// 低于阈值,不会被截断
result.setPrimitiveIntValue(Integer.MAX_VALUE);
return result;
}
如下,需要的字段加上 @JsonSerialize(using=ToStringSerializer.class)
即可
@Data
public class DemoDTO {
@JsonSerialize(using = ToStringSerializer.class)
private Long longValue;
private Integer intValue;
@JsonSerialize(using = ToStringSerializer.class)
private long primitiveLongValue;
private int primitiveIntValue;
}
传统 swagger UI 上显示的结果为
{
"longValue": "9223372036854775807",
"intValue": 2147483647,
"primitiveLongValue": "9223372036854775807",
"primitiveIntValue": 2147483647
}
不限制于 Long/long,对于 Integer/int 也可以,实际上对于任何类型的字段都是可以加这个注解的
试一下其他类型加 @JsonSerialize(using=ToStringSerializer.class)
@Data
public class DemoDTO {
@JsonSerialize(using = ToStringSerializer.class)
private Long longValue;
@JsonSerialize(using = ToStringSerializer.class)
private Integer intValue;
@JsonSerialize(using = ToStringSerializer.class)
private long primitiveLongValue;
@JsonSerialize(using = ToStringSerializer.class)
private int primitiveIntValue;
@JsonSerialize(using = ToStringSerializer.class)
private List<String> list;
@JsonSerialize(using = ToStringSerializer.class)
private Date date;
}
@ApiOperation("test1 接口")
@GetMapping("/test1")
public DemoDTO test1() {
DemoDTO result = new DemoDTO();
result.setLongValue(Long.MAX_VALUE);
result.setIntValue(Integer.MAX_VALUE);
result.setPrimitiveLongValue(Long.MAX_VALUE);
result.setPrimitiveIntValue(Integer.MAX_VALUE);
List<String> list = new ArrayList<>();
list.add("one");
result.setList(list);
result.setDate(new Date());
return result;
}
传统 swagger UI 上显示的结果为
{
"longValue": "9223372036854775807",
"intValue": "2147483647",
"primitiveLongValue": "9223372036854775807",
"primitiveIntValue": "2147483647",
"list": "[one]",
"date": "Wed Aug 11 14:46:15 CST 2021"
}
如果都没有注解,可以看到结果:(着重观察下 list 和 date)
{
"longValue": 9223372036854776000,
"intValue": 2147483647,
"primitiveLongValue": 9223372036854776000,
"primitiveIntValue": 2147483647,
"list": [
"one"
],
"date": "2021-08-11T06:49:10.238+00:00"
}