乐趣区

关于springboot:SpringMVC-Json自定义序列化和反序列化

需要背景

需要一:SpringMVC 构建的微服务零碎,数据库对日期的存储是 Long 类型的工夫戳,前端之前是默认应用 Long 类型工夫,当初前端框架改变,要求后端响应数据时,Long 类型的工夫主动变成规范工夫格局(yyyy-MM-dd HH:mm:ss)。

波及到这个转换的范畴挺大,所有的实体表都有创立工夫 createTime 和批改工夫 updateTime,目前的次要诉求也是针对这两个字段,并且在实体详情数据和列表数据都存在,须要一个对立的办法,对这两个字段进行解决。

需要二:前端申请上传的 JSON 报文,String 类型的内容,可能会呈现前后有空格的景象,如果前端框架未对此问题进行解决,后端收到的 JSON 申请反序列化为对象时,就会呈现 String 类型的值,前后有空格,现须要一个对立的解决办法,对接管的 String 类型属性执行 trim 办法。

解决方案

SpringMVC 默认的 JSON 框架为 jackson,也能够应用 fastjson。

jackson 框架

自定义序列化

如果我的项目应用 jackson 框架做 json 序列化,举荐的计划是应用 @JsonSerialize 注解,示例代码如下:

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

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

CustomDateSerializer 类的实现示例如下:

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

这种计划的益处如下:

  1. 自定义的实现类能够复用
  2. 精准到须要转换解决的字段,不受限于 createTime 和 updateTime,更贴近于需要

毛病就是须要转换的字段都须要应用注解,工作量有点大

当然有其余的对立解决计划,这里不赘述。

自定义反序列化

在 jackson 框架上实现自定义序列化,也是十分不便的,继承 SimpleModule 类即可:

@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 框架

如果工程里呈现这个依赖:

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

阐明此工程应用的 json 框架为 fastjson,那么 jackson 的 @JsonSerialize 就不会有触发入口了,咱们来看看 fastjson 的解决形式。

自定义序列化

相应的,应用 fastjson 会有相应的配置类,示例如下:

/**
 * 对立输入是采纳 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);
}

这里须要增加 fastjson 对字段值的解决(上述代码已增加这行代码),如

// 增加指定字段的值转换解决
fastJsonConfig.setSerializeFilters(new CustomerDateFilter());

CustomerDateFilter 为自行实现的类,代码如下:

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

这样就能够把所有响应对象中呈现的 createTime 和 updateTime 字段对立解决了,无论列表数据还是单个对象数据,十分不便。毛病就是除此之外的字段,如果还做不到全零碎对立,就须要独自解决。

SerializeFilter 定制序列化

反对 SerializeFilter 定制序列化的扩大编程接口有以下几个,可依据理论须要进行扩大:

  • PropertyPreFilter:依据 PropertyName 判断是否序列化;
  • PropertyFilter:依据 PropertyName 和 PropertyValue 来判断是否序列化;
  • NameFilter:批改 Key,如果须要批改 Key,process 返回值则可;
  • ValueFilter:批改 Value;
  • BeforeFilter:序列化时在最前增加内容;
  • AfterFilter:序列化时在最初增加内容;
自定义反序列化

fastJson 提供了序列化过滤器,来实现自定义序列化革新,但没有提供反序列化过滤器,来实现对应的性能。

计划:@JSONField 注解

回到对 JSON 报文 String 类型的值执行 trim 操作,官网反对 @JSONField 注解的属性设置(要求 fastJson 版本 1.2.36 以上):

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

在 JSON 报文反序列化时,该实体的 name 属性会主动执行 trim 办法进行解决。

此计划只有一一增加注解,工作量较大。

计划:实现 ObjectDeserializer 接口

ObjectDeserializer 接口为能够实现自定义反序列化实现接口,配合 ParserConfig 的全局设置,也能够达到预期的成果,合建 StringTrimDeserializer 类,对 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;}
}

相应在,在 HttpMessageConverters 类 fastJsonHttpMessageConverters 办法内中减少 String 类的反序列化设置:

// 设置 String 类的全局反序列化规定:主动实现 trim 操作
ParserConfig.getGlobalInstance().putDeserializer(String.class, new StringTrimDeserializer());

tips:
在 StringTrimDeserializer 类实现办法中为什么不间接 parser.getLexer().stringVal() 失去值后执行 trim 办法,而是调用 StringCodec.instance 的实现办法?

StringCodec 是 fastJson 默认的 String 类型的反序列化逻辑类,外面要解决的类型有 String、StringBuffer、StringBuilder 等,还有各种的汇合、数组构造,波及的 nextToken 值都不雷同,总之,对 String 文本的反序列化,实现逻辑和应答的场景都比较复杂,而此次的需要只是对 String 执行 trim 操作,简单的逻辑还是交给 StringCodec 来解决,站在 StringCodec 的根底上,对其后果执行 trim 办法就能够达到预期指标。

小结

明天这篇是记录 Json 自定义序列化和反序列化的实际计划,开始施行前先确认工程里应用的框架是哪个,否则就会呈现增加了 @JsonSerialize 注解,搞了大半天没有成果,回头一看框架是 fastjson,没有触发入口,当然得不到预期成果,小小倡议,心愿对你有帮忙。

退出移动版