SpringMVC Json custom serialization and deserialization

Demand background

Requirement 1: In the microservice system built by SpringMVC, the database stores dates in Long-type timestamps. The front-end used Long-type time by default. Now the front-end framework changes and requires the back-end to respond to data. The Long-type time automatically becomes Standard time format (yyyy-MM-dd HH:mm:ss).

The scope of this conversion is quite large. All entity tables have a creation time createTime and a modification time updateTime. The current main appeal is also for these two fields, and the entity detail data and list data exist, so a unified method is needed , To process these two fields.

Requirement 2: The JSON message uploaded by the front-end request, String type content, may have spaces before and after it, if the front-end framework does not handle this problem, when the JSON request received by the back-end is deserialized into an object, There will be a String type value with spaces before and after it. Now a unified processing method is needed to execute the trim method on the received String type attribute.

solution

The default JSON framework of SpringMVC is jackson, and fastjson can also be used.

jackson frame

Custom serialization

If the project uses jackson framework for json serialization, the recommended solution is to use @JsonSerialize annotation. The sample code is as follows:

@JsonSerialize(using = CustomDateSerializer.class)  
private Long createTime;

@JsonSerialize(using = CustomDateSerializer.class)  
private Long updateTime;

An implementation example of the CustomDateSerializer class is as follows:

public class CustomDateSerializer extends JsonSerializer<Long> {

    @Override
    public void serialize(Long aLong, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date = new Date(aLong);
        jsonGenerator.writeString(sdf.format(date));
    }
}

The benefits of this scheme are as follows:

  1. Custom implementation classes can be reused
  2. Accurate to the fields that need to be converted, not limited to createTime and updateTime, and closer to requirements

The disadvantage is that the fields that need to be converted need to use annotations, and the workload is a bit heavy

Of course, there are other unified processing solutions, so I won't repeat them here.

Custom deserialization

It is also very convenient to implement custom serialization on the jackson framework, just inherit the SimpleModule class:

@Component
public class StringTrimModule extends SimpleModule {

    public StringTrimModule() {
        addDeserializer(String.class, new StdScalarDeserializer<String>(String.class) {
            @Override
            public String deserialize(JsonParser jsonParser, DeserializationContext ctx) throws IOException {
                String value = jsonParser.getValueAsString();
                if (StringUtils.isEmpty(value)) {
                     return value;
                }
                return value.trim();
            }
        });
    }
}

fastjson framework

If this dependency appears in the project:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.62</version>
</dependency>

Explain that the json framework used in this project is fastjson, then jackson’s @JsonSerialize will not have a trigger entry, let’s take a look at the fastjson processing method.

Custom serialization

Correspondingly, there will be a corresponding configuration class when using fastjson, an example is as follows:

/**
 * 统一输出是采用fastJson
 *
 * @return
 */
@Bean
public HttpMessageConverters fastJsonHttpMessageConverters() {
    //convert转换消息的对象
    FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();

    //处理中文乱码问题
    List<MediaType> fastMediaTypes = new ArrayList<>();
    fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
    fastConverter.setSupportedMediaTypes(fastMediaTypes);

    //是否要格式化返回的json数据
    FastJsonConfig fastJsonConfig = new FastJsonConfig();
    fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
    // 添加指定字段的值转换处理
    fastJsonConfig.setSerializeFilters(new CustomerDateFilter());
    // FastJson禁用autoTypeSupport
    fastJsonConfig.getParserConfig().setAutoTypeSupport(false);
    fastConverter.setFastJsonConfig(fastJsonConfig);

    return new HttpMessageConverters(fastConverter);
}

Here you need to add fastjson to the field value processing (the above code has added this line of code), such as

// 添加指定字段的值转换处理
fastJsonConfig.setSerializeFilters(new CustomerDateFilter());

CustomerDateFilter is a self-implemented class, the code is as follows:

public class CustomerDateFilter implements ValueFilter {

    @Override
    public Object process(Object object, String name, Object value) {
        if (FieldConstants.CREATE_TIME.equalsIgnoreCase(name) ||  FieldConstants.UPDATE_TIME.equalsIgnoreCase(name)) {
            // 属性名为createTime, updateTime进行转换处理
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            sdf.setTimeZone(TimeZone.getTimeZone("GMT+8"));

            if(value instanceof Long) {
                Long time = (Long) value;
                Date date = new Date(time);
                return sdf.format(date);
            } else {
                return value;
            }
        }
        return value;
    }
}

In this way, the createTime and updateTime fields appearing in all response objects can be processed uniformly, regardless of list data or single object data, which is very convenient. The disadvantage is that other fields need to be handled separately if they cannot be unified across the system.

SerializeFilter custom serialization

The extended programming interfaces that support SerializeFilter custom serialization are as follows, which can be extended according to actual needs:

  • PropertyPreFilter: Determine whether to serialize according to PropertyName;
  • PropertyFilter: Determine whether to serialize according to PropertyName and PropertyValue;
  • NameFilter: Modify Key, if you need to modify Key, process return value can be;
  • ValueFilter: modify Value;
  • BeforeFilter: Add content at the top during serialization;
  • AfterFilter: Add content at the end when serializing;
Custom deserialization

fastJson provides serialization filters to implement custom serialization transformations, but does not provide deserialization filters to implement corresponding functions.

Solution: @JSONField annotation

Going back to performing trim operations on the String value of JSON messages, the official website supports @JSONField annotation attribute settings (fastJson version 1.2.36 or higher is required):

@JSONField(format="trim")
private String name;

When the JSON message is deserialized, the name attribute of the entity will automatically execute the trim method for processing.

This solution only has to add notes one by one, which requires a lot of work.

Solution: Implement ObjectDeserializer interface

The ObjectDeserializer interface is an interface that can implement custom deserialization. With the global settings of ParserConfig, the expected effect can also be achieved. The StringTrimDeserializer class is jointly built to process String:

/**
 * @title: StringTrimDeserializer
 * @description: 把String类型的内容统一做trim操作
 */
public class StringTrimDeserializer implements ObjectDeserializer {

    @Override
    public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
        // JSON String反序列化的逻辑比较复杂,在StringCodec的基础上,对其结果调用trim方法
        Object obj = StringCodec.instance.deserialze(parser, type, fieldName);
        if (obj instanceof String) {
            String str = (String) obj;
            return (T) str.trim();
        }
        return (T) obj;
    }

    @Override
    public int getFastMatchToken() {
        return JSONToken.LITERAL_STRING;
    }
}

Correspondingly, HttpMessageConvertersadd the deserialization settings of the String class in the fastJsonHttpMessageConverters method of the class:

// 设置String类的全局反序列化规则:自动完成trim操作
ParserConfig.getGlobalInstance().putDeserializer(String.class, new StringTrimDeserializer());

tips:
In the StringTrimDeserializer class implementation method, why not parser.getLexer().stringVal()execute the trim method after getting the value directly , but call the implementation method of StringCodec.instance?

StringCodec is the deserialization logic class of fastJson's default String type. The types to be processed are String, StringBuffer, StringBuilder, etc., as well as various collections and array structures. The nextToken values ​​involved are different. In short, for String text The deserialization, implementation logic and response scenarios are more complicated, and this time the requirement is only to perform trim operations on String, and the complex logic is still handed over to StringCodec to process. Standing on the basis of StringCodec, execute the trim method on the results. You can achieve the desired goal.

summary

Today’s article is to record the practical plan of Json custom serialization and deserialization. Before starting the implementation, confirm which framework is used in the project, otherwise it will appear that the @JsonSerialize annotation will be added, and it will be ineffective for most of the day. Seeing that the framework is fastjson, there is no trigger entry, of course, the expected effect will not be obtained, a small suggestion, I hope it will help you.

Guess you like

Origin blog.51cto.com/2123175/2608888