关于浏览器精度问题
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于浏览器精度问题相关的知识,希望对你有一定的参考价值。
参考技术A 查找资料之后发现,是浏览器精度问题。js中的number类型不能完全表示long类型的数据,当long长度大于17位时,会出现精度丢失的问题,浏览器会自动把超出部分用0表示。chrome从第17位就开始不正常(有时候正常有时候+1),18位及以后均补0
关于Long转String和js丢失精度截断的问题
背景
Java后端的 Long(long) 类型字段,在web接口返回这种类型的值给前端时,会发生截断(即js会精度丢失,因为后端的Long的最大值超过了前端js能表示的值)
哪些情况下发生截断
- 如果前端不是用 js框架则不会截断,比如用postman调用就不会截断,浏览器地址栏直接请求也不会截断
- 前端用 js 类型的框架可能会发生截断,但是只有在实际值超过js的最大范围的时候才会截断
- 截断的一般特征是 000 结尾,比如
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<Long, Object>改成了 Map<String, Object>,但是 map.get(key) 不管这个 key 是 Long 还是 String 都不会报错,但是实际 get 的话会get 不到值。
第二个是equals方法,equals() 的入参是 Object
全局 Long 转 String 的配置
对于
@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;
}
1、配置一
@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 等其他类型不会
2、配置二
@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;
}
}
这种写法也是可以的
3、配置三
这是个无效的方法,网上也列出了很多这种写法,列在这里是为了警示。主要是 @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;
}
-
原生 swagger UI 发生了截断,因为它是用js解析并展示值的
-
第三方的新的 swagger UI 并不会发生截断,因为它类似postman一样不是将值用js展示
-
浏览器地址栏直接访问(刚好接口是GET,否则也不能直接访问),不截断
-
postman 工具访问,不截断 (要截断就大事情了,一个工具做成这样子)
-
如果虽然这个字段定义是 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" }
以上是关于关于浏览器精度问题的主要内容,如果未能解决你的问题,请参考以下文章
浏览器响应数据long型超长自动转换精度丢失-JavaScript 整数精度丢失问题-springboot解决Long类型数据传入前端损失精度