前言
在我们前后端联调时,很经常以json作为数据的交互格式,今天我们就来聊聊在开发springboot项目中,使用jackson进行数据渲染一些小技巧
场景一:枚举-JSON互转
在日常开发中我们为了避免过多的魔法值,使用枚举类来封装一些静态的状态代码。
但是在将这些枚举的意思正确而全面的返回给前端却并不是那么顺利,比如有个状态枚举类
public enum StatusEnums {NORMAL(1,"正常"),LOCK(2,"锁定"),DELETE(3,"删除");private Integer code;private String desc;StatusEnums(Integer code, String desc) {this.code = code;this.desc = desc;}public Integer getCode() {return code;}public void setCode(Integer code) {this.code = code;}public String getDesc() {return desc;}public void setDesc(String desc) {this.desc = desc;}}
如果我们直接使用Jackson对枚举进行序列化,将只能简单的输出枚举的String名称
new ObjectMapper().writeValueAsString(StatusEnums.NORMAL);
输出结果为枚举名称:
NORMAL
而我们希望将枚举转为JSON对象,像下面这样:
{"code":200,"desc":"正常"}
那如何达到以上效果
方法一:使用@JsonValue 注解
我们通过@JsonValue注解,来控制枚举序列化结果
@JsonValuepublic Integer getCode() {return code;}
比如StatusEnums.NORMAL最后最终序列化的值:
1
同样我们也可以通过@JsonValue注解进行反序列化,最终1反序列化的值为
NORMAL
方法二:通过自定义序列化/反序列器
a、 自定义序列化器以及反序列化器
public class StatusEnumsJsonSerializer extends JsonSerializer<StatusEnums> {@Overridepublic void serialize(StatusEnums value, JsonGenerator gen, SerializerProvider serializers) throws IOException {gen.writeString(value.getCode().toString());}
}
public class StatusEnumsJsonDerializer extends JsonDeserializer<StatusEnums> {@Overridepublic StatusEnums deserialize(JsonParser p, DeserializationContext ctx) throws IOException, JsonProcessingException {if(p.getText() != null){return StatusEnums.getByCode(Integer.valueOf(p.getText()));}return null;}
}
b、在枚举类上加上自定义序列化、反序列化注解
@JsonSerialize(using = StatusEnumsJsonSerializer.class)@JsonDeserialize(using = StatusEnumsJsonDerializer.class)private StatusEnums status;
除了以上方法,还可以通过@JsonCreator来达到上述效果,因为demo没实现,这边就不演示了
场景二:规避前端JavaScript接收后端Long属性值,精度丢失问题
示例演示
后端示例代码
@GetMapping("get")@ResponseBodypublic User getUser(){return User.builder().name("张三").password("123456").status(StatusEnums.NORMAL).id(987654321123456789L).build();}
前端示例代码
<button id="loadDataButton">Load Data</button>
<div id="dataDisplay"></div><script>$(document).ready(function() {$("#loadDataButton").click(function() {$.ajax({url: "/user/get",type: "GET",data: {},success: function(response) {$("#dataDisplay").html("id: " + response.id + "<br/>name: " + response.name + "<br/>password: " + response.password + "<br/>status: " + response.status);},error: function(xhr, status, error) {console.error("Error: " + error);}});});
});
</script>
浏览器访问结果
可以发现精度丢失了
方法一:通过@JsonSerialize注解,将long序列化为字符串
注: 直接通过jackson自带的序列化器
示例
@JsonSerialize(using = ToStringSerializer.class)private Long id;
加上该注解后,通过浏览器查看效果
可以发现精度没有丢失了
场景三:数据脱敏
敏感数据脱敏展示,应该是挺通用需求,我们可以通过自定义序列化器实现这一效果
1、自定义脱敏序列化器
RequiredArgsConstructor
public class SensitiveJsonSerializer extends JsonSerializer<String> implements ContextualSerializer {private final DesensitizedUtil.DesensitizedType desensitizedType;public SensitiveJsonSerializer(){this.desensitizedType = DesensitizedUtil.DesensitizedType.CLEAR_TO_EMPTY;}@Overridepublic void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {gen.writeString(DesensitizedUtil.desensitized(value,desensitizedType));}@Overridepublic JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {Sensitive sensitive = property.getAnnotation(Sensitive.class);if (sensitive != null){return new SensitiveJsonSerializer(sensitive.type());}return prov.findValueSerializer(property.getType(), property);}
}
2、自定义脱敏注解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveJsonSerializer.class)
public @interface Sensitive {DesensitizedUtil.DesensitizedType type();
}
3、在需要脱敏的字段上,加上脱敏注解
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class User {@JsonSerialize(using = ToStringSerializer.class)private Long id;@Sensitive(type = DesensitizedUtil.DesensitizedType.CHINESE_NAME)private String name;@Sensitive(type = DesensitizedUtil.DesensitizedType.PASSWORD)private String password;}
a、 未加脱敏注解时,通过浏览器访问
发现没有脱敏,现在我们针对名字和密码进行脱敏
b、 名字和密码加上脱敏注解
@Sensitive(type = DesensitizedUtil.DesensitizedType.CHINESE_NAME)private String name;@Sensitive(type = DesensitizedUtil.DesensitizedType.PASSWORD)private String password;
观察浏览器
总结
本文介绍枚举和json转换、long精度问题、数据脱敏三种我们日常开发比较常用的场景,但不知道大家发现没,这三种场景本质上都是通过json的序列化和反序列化实现,因此我们可以通过定制全局json序列化、反序列化器来实现。核心代码如下
@Bean@ConditionalOnMissingBeanpublic Jackson2ObjectMapperBuilderCustomizer customJackson2ObjectMapperBuilderCustomizer(){return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.serializerByType(Long.class,ToStringSerializer.instance).serializerByType(StatusEnums.class,new StatusEnumsJsonSerializer()).deserializerByType(StatusEnums.class,new StatusEnumsJsonDerializer());}
demo链接
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-config-refresh