原创

Long和Long类型集合前端精度丢失解决办法锦集以及自定义JSON序列化方法

温馨提示:
本文最后更新于 2020年02月24日,已超过 1,733 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我

因为JS解析整型的时候是有最大值的,Number.MAX_SAFE_INTEGER 常量表示在 JavaScript 中最大的安全整数(maxinum safe integer)(253 - 1)

Number.MAX_SAFE_INTEGER // 9007199254740991
Math.pow(2, 53) - 1     // 9007199254740991

最大长度是16位数,超过了就解析不正常了,会丢失精度。

我后端是用的雪花算法生成的20位的唯一ID,我返回给前端的时候,例如:

我返回的是Long类型的,但是前端接收之后精度丢失,导致和我后端给的不一致,解决办法就是使用String类型的。

方法1-后端传输JSON格式化为String类型的

@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long aliyunOssFileId;

@JsonFormat(shape = JsonFormat.Shape.STRING)作用就是将JSON数据的此字段格式化为字符串类型,保证前端超过16位不会出现精度丢失问题!

但是,如果有很多Long类型的话,要一个一个去改,也太累了,Spring MVC中默认是使用了Jackson的,可以通过重写转换器解决。

方法2-重写Jackson转换器(配置全局生效)

@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
    /**
     * Long类型转String类型
     *
     * 解决前端Long类型精度丢失问题(js解析只能解析到16位)
     *
     * @param converters
     * @author Zhaopo Liu
     */
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        MappingJackson2HttpMessageConverter jackson2HttpMessageConverter =
                new MappingJackson2HttpMessageConverter();

        ObjectMapper objectMapper = new ObjectMapper();
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(BigInteger.class, ToStringSerializer.instance);
        // simpleModule.addSerializer(long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        objectMapper.registerModule(simpleModule);
        jackson2HttpMessageConverter.setObjectMapper(objectMapper);
        converters.add(jackson2HttpMessageConverter);
        converters.add(new StringHttpMessageConverter(StandardCharsets.UTF_8));
    }
}

方法3-实现Jackson2ObjectMapperBuilderCustomizer作为Bean(配置全局生效)

和前面说的方法重写Jackson转换器也是一个道理的。

参考:

@Configuration
public class JacksonConfig {

  /**
   * Callback interface that can be implemented by beans wishing to further customize the {@link
   * ObjectMapper} via {@link Jackson2ObjectMapperBuilder} retaining its default auto-configuration.
   *
   * <pre>
   * 在此处{@link Jackson2ObjectMapperBuilderCustomizer}有两种方法,
   * 设置Long以及BigInteger类型序列化为String类型,避免雪花算法Long类型返回前端可能导致精度丢失问题。
   * - 方式1:
   * {@code
   *   SimpleModule simpleModule = new SimpleModule();
   *   // simpleModule.addSerializer(long.class, ToStringSerializer.instance);
   *   simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
   *   simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
   *   simpleModule.addSerializer(BigInteger.class, ToStringSerializer.instance);
   *   builder.modules(simpleModule);
   * }
   * - 方式2:
   * {@code
   *   // builder.serializerByType(long.class, ToStringSerializer.instance);
   *   builder.serializerByType(Long.class, ToStringSerializer.instance);
   *   builder.serializerByType(Long.TYPE, ToStringSerializer.instance);
   *   builder.serializerByType(BigInteger.class, ToStringSerializer.instance)
   * }
   * </pre>
   *
   * @return {@link Jackson2ObjectMapperBuilderCustomizer}
   */
  @Bean
  public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
    return builder -> {
      SimpleModule simpleModule = new SimpleModule();
      // simpleModule.addSerializer(long.class, ToStringSerializer.instance);
      simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
      simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
      simpleModule.addSerializer(BigInteger.class, ToStringSerializer.instance);
      builder.modules(new Jdk8Module(), new JavaTimeModule(), simpleModule);
    };
  }
}

方法4-如果是FastJson的话(配置全局生效)

在Spring Boot中将Jackson替换为fastjson一般会有两种方式:
第一种:

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
 
    @Bean
    public HttpMessageConverters fastJsonHttpMessageConverter() {
        return new HttpMessageConverters(new FastJsonHttpMessageConverter());
    }
}

第二种:

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
 
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        FastJsonHttpMessageConverter fastConverter = 
        new FastJsonHttpMessageConverter();
 
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
        fastConverter.setFastJsonConfig(fastJsonConfig);
        converters.add(fastConverter);
    }
}

替换成fastjson之后,对于精度丢失问题,解决方法如下:

@EnableWebMvc
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
 
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        FastJsonHttpMessageConverter fastConverter = 
        new FastJsonHttpMessageConverter();
 
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        SerializeConfig serializeConfig = SerializeConfig.globalInstance;
        serializeConfig.put(BigInteger.class, ToStringSerializer.instance);
        // simpleModule.addSerializer(long.class, ToStringSerializer.instance);
        serializeConfig.put(Long.class, ToStringSerializer.instance);
        serializeConfig.put(Long.TYPE, ToStringSerializer.instance);
        fastJsonConfig.setSerializeConfig(serializeConfig);
        fastConverter.setFastJsonConfig(fastJsonConfig);
        converters.add(fastConverter);
    }
}

方法5-List<Long>类型精度丢失问题

最好的方式就是,将List<Long>改为List<String>方式,这样子啥事没有,性能也高,但是我就是想多折腾:

1.数组转换为String显示(不推荐)

直接使用官方的即可:

@JsonSerialize(using = ToStringSerializer.class)
private List<Long> roleIds;

前端显示的,这明显需要前端特殊处理,个人不太喜欢这样子:

{
  "roleIds": "[1333010224414613506, 1333010224481722369]"
}

2.自定义序列化方式转换为String数组(推荐)

自定义一个JSON序列化方式:

public class ListLongToStringArrayJsonSerializer extends JsonSerializer<List<Long>> {

    @Override
    public void serialize(List<Long> values, JsonGenerator gen, SerializerProvider serializers) throws IOException {
      String[] newValues =
        ObjectUtil.defaultIfNull(values, Collections.emptyList()).stream()
          .map(String::valueOf)
          .toArray(String[]::new);
      gen.writeArray(newValues, 0, newValues.length);
    }
}

特别注意:此处的gen.writeArray(newValues, 0, newValues.length);,类型为String的,Jackson在2.11版本之后才支持此String类型的。

使用方式:

@JsonSerialize(using = LongToJsonSerializer.class)
private List<Long> roleIds;

结果:

{
  "roleIds": [
    "1333010224414613506",
    "1333010224481722369"
  ]
}

方法6-前端使用String类型来接收

e.g.

aliyunOssFileId: ''
本文目录