Three ways to solve the JS precision loss problem with SpringBoot Jackson serialization


Hello everyone, I am Yihang!

Yesterday afternoon, a friend asked the following question in the group:

The database uses bigint to store the ID of the record, and the Java code uses the Long type to map the record's corresponding ID value; the front end calls the SpringBoot interface to obtain the data. When debugging the back end, the Long type ID can get the value normally, but after returning to the front end, after the ID Half of the section was "stolen", and it is normal when the query ID is 1, 2, and 3; the data is as follows:
Comparison of the two values:

后端的值:1508733541883731970
前端的值:1508733541883732000

It's obvious that there 's an accuracy issue , but when you don't understand the details, it's easy to get confused.

Why is this so?

reason

This is because the precision of numbers in Javascript is limited, and the precision of Long in Java is beyond the processing range of Javascript. JS follows the IEEE 754 specification, uses double precision storage, and occupies 64 bits. Its structure is as shown in the figure:

  • 1 bit (s) used to represent the sign bit
  • 11 bits (e) are used to represent the exponent
  • 52 bits (f) represents the mantissa

The maximum number of mantissa digits is 52, so the largest integer that can be accurately represented in JS is Math.pow(2, 53). In decimal, 9007199254740992any number greater than 9007199254740992 will cause loss of precision;

In order to verify, we pressed in the browser F12and did the following test in the Console:

the result is the same as what we thought above.

solution

However, in the actual development process, bigint in the database and Long in Java are relatively commonly used data types. It is impossible not to use them because of the accuracy problem of front-end JS. Therefore, in order to avoid loss of accuracy, for this relatively large numerical type , it can be returned in the form of text;

SpringBoot's object serialization is adopted by default Jackson. There are the following three ways to convert numeric types into text strings.

Interfaces and objects used in testing:

@GetMapping("/user")
public User getUser(){
    
    
    User user = new User();
    user.setId(1508733541883731970L);
    user.setAge(10);
    user.setName("zhangsan");
    user.setGender((short) 1);
    return user;
}

@Data
class User{
    
    
    Long id;
    String name;
    Integer age;
    Short gender;
}

Method 1: Attribute serialization annotation @JsonSerialize

Specified properties in the object can be serialized as text

@Data
class User{
    
    
    @JsonSerialize(using = ToStringSerializer.class)
    Long id;
    String name;
    @JsonSerialize(using = ToStringSerializer.class)
    Integer age;
    Short gender;
}

Test Data:

{
    
    
  "id": "1508733541883731970",
  "name": "zhangsan",
  "age": "10",
  "gender": 1
}

@JsonSerializeThe configured idsum ageis converted to text

  • advantage

    Flexible, according to the attribute configuration of the object, you can change the one you want without disturbing other attributes or objects.

  • shortcoming

    Each attribute that needs to be converted needs to be configured, which is a bit of hard work.

Method 2: Global configuration, convert numerical type to text

If you need to convert all number types into text, you can add the following configuration to application.yml:

spring:
  jackson:
    generator:
      write_numbers_as_strings: true #序列化的时候,将数值类型全部转换成字符串返回

Test example:

{
    
    
  "id": "1508733541883731970",
  "name": "zhangsan",
  "age": "10",
  "gender": "1"
}
  • advantage:

    After configuration, all numerical types are converted to text, once and for all;

  • shortcoming

    The above advantages are also part of the disadvantages: they are too general and not flexible enough;

Method three, single type conversion

You can customize a Jackson object conversion constructor to convert the specified type in the specified serialization method. For example, it will be converted to text when it LongencountersDouble

@Bean("jackson2ObjectMapperBuilderCustomizer")
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
    
    
    Jackson2ObjectMapperBuilderCustomizer customizer = new Jackson2ObjectMapperBuilderCustomizer() {
    
    
        @Override
        public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
    
    
            jacksonObjectMapperBuilder.serializerByType(Long.class, ToStringSerializer.instance)
                    .serializerByType(Long.TYPE, ToStringSerializer.instance);
        }
    };
    return customizer;
}

Test Data:

{
    
    
  "id": "1508733541883731970",
  "name": "zhangsan",
  "age": 10,
  "gender": 1
}

After discovery, the id of Long type was converted into text; Integerand Shortthe id type was not affected;

The three methods have their own applicable scenarios. In comparison, the first and third methods are relatively common and can be chosen according to your actual situation;

Guess you like

Origin blog.csdn.net/lupengfei1009/article/details/123857550