Jackson's custom serialization annotations solve the problem of losing the precision of JS received values

1 background

In the current project, the snowflake algorithm is widely used to generate the ID as the primary key of the database table. The long type snowflake ID has 19 digits, while the front-end uses the number type to receive the Long type, but the precision of the number type is only 16 digits. This leads to the problem of loss of precision when the snowflake ID is transmitted to the front end. as follows:

数据库的值:1461642036950462475 
前端接收的值:1461642036950462500

(Extended)
Advantages of using Snowflake Algorithm ID:

  1. The algorithm is simple and the calculation efficiency is high. Generate incremental unique IDs in a high-concurrency distributed environment, and can generate millions of unique IDs per second.
  2. Based on the timestamp, and the serial number auto-increment under the same timestamp, it is basically guaranteed that the id is incremented in an orderly manner, which is beneficial to database storage.
  3. Does not rely on third-party libraries and middleware (the middleware method generates IDs, relying on databases or Redis caches to achieve global ID increments)

Disadvantages:
1. Depending on the server time, duplicate ids may be generated when the server clock is called back. In the algorithm, it can be solved by recording the timestamp when the last id was generated, and comparing whether the current server clock has been dialed back before generating the id each time, so as to avoid generating duplicate ids.

The obvious disadvantage of the UUID method is that due to its disordered rules, the database insertion efficiency is low. The obvious disadvantage of the auto-increment method is that it is not suitable for the scenario of sub-table and sub-database, and the primary key conflict will occur in the distributed storage scenario. Moreover, the security is low, because the ID growth is regular, and it is easy to obtain data illegally. Therefore, after comprehensive consideration, it is more recommended to use the snowflake algorithm to generate IDs.

2 @JsonSerialize annotation

If you want to change the id field from a Long type to a String type, it involves code changes such as tables, entity classes, etc., and the impact is relatively large, so this approach is not advocated.

2.1 Basic introduction

Jackson is the default serialization framework of the Spring framework. Using Jackson's @JsonSerialize annotation can perfectly solve this problem. Specific method: the ID (Long) of the backend ==> Jackson (Long to String) ==> the front-end uses the String type to receive, and the 19-digit field of the String type returned by the front-end supported by Jackson deserialization by default is received by the Long type .

In terms of usage, just define the @JsonSerialize annotation on the field. Jackson provides many Json serializers. In addition, we can also customize the serializer.

Need to pay attention not to introduce Gson again, During my use, I found that if the Gson package is introduced at the same time, the @JsonSerialize annotation will fail.

2.2 use

In the project, annotations are marked on the corresponding fields, and Long is automatically converted to String when Json is serialized.

    @JsonSerialize(using = ToStringSerializer.class)
    private Long id;

It should be noted that the field to be converted must be a wrapper class type, otherwise the conversion will fail.

@JsonSerialize(using = ToStringSerializer.class)
private Long parentId;    //转化成功
@JsonSerialize(using = ToStringSerializer.class)
private long parentId;    //转化失败

2.3 Custom serializer

For the snowflake ID collection, we continue to use ToStringSerializer. After the front-end parses and receives it, special processing is required. Therefore, it is necessary to customize the serializer for the Snowflake ID collection of List type, and realize the conversion of Long into String type and pass it to the front end.

Custom serializer ListLongToStringArrayJsonSerializer, inherit JsonSerializer

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);
    }
}

Specific use:

    @JsonSerialize(using = ListLongToStringArrayJsonSerializer.class)
    private List<Long> idList;

3 global configuration

The id field of each entity class needs to be annotated with @JsonSerialize, which is a bit cumbersome. We can modify the Jackson converter first to achieve global unified processing of Long type fields. As follows:

@EnableWebMvc
@Configuration
public class MvcConfig implements WebMvcConfigurer {
    
    
    /**
     * 重写Jackson转换器
     * Long类型转String类型
     *
     * 解决前端Long类型精度丢失问题(js解析只能解析到16位)
     *
     * @param converters
     */
    @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.TYPE, ToStringSerializer.instance);
        objectMapper.registerModule(simpleModule);
        jackson2HttpMessageConverter.setObjectMapper(objectMapper);
        converters.add(jackson2HttpMessageConverter);
        converters.add(new StringHttpMessageConverter(StandardCharsets.UTF_8));
    }
}

However, because the global configuration here is to convert all Long-type fields into Strings and pass them to the front-end, it may cause type incompatibility in the front-end interface, so the global configuration here needs to be used with caution.

Guess you like

Origin blog.csdn.net/huangjhai/article/details/128430840