*本文针对的是Java语言及Jackson库,其它语言或库中应该也有类似的技巧。
出于一些原因,我们偶尔需要往数据库中插入序列化后的JSON数据:
在把该类数据取出,返回给前端时,我发现大家通常的做法如下:
后端返回给前端如下json数据:
{
"ageNature": "[{\"ageId\":54,\"isChecked\":true}]"
}
前端拿到数据后手工解析(因为 ageNature
只是个字符串):
const data = ...;
const ageNature = JSON.parse(data.ageNature);
后端:
Bean bean = ...;
// 手工解析
bean.ageNature = JSON.parse(ageNature);
return bean;
前端收到的数据,因为ageNature
已经是对象了,可以直接使用:
{
"ageNature": [
{
"ageId": 54,
"isChecked": true
}
]
}
方式一增加了前端的工作量,返回的JSON文本需要他们手工解析。我们尽量要方便前端,给他们的数据让他们拿来即用。尤其是当业务涉及多端时(web/native),可避免重复性的解析工作。
方式二,增加后端的工作量只是其一,关键是反序列化之后,数据返回给前端之前,其实还要经过一步序列化(将Java Bean序列化为JSON返回给前端)。这意味着先前的解析变得多余了。我们知道,无论是序列化还是反序列化,通常都涉及到反射的调用,而这将损失掉部分性能。
所以终究的问题是,后端从db中取出JSON文本后,能否在不反序列化的条件下,在返回给前端时,前端看到的仍是JSON对象,而不是字符串。?
答案是可以!
多亏了@JsonRawValue
注解。它可以以 as is 的形式将注解的字段原封不动地返回给前端。即,若字段值是[1,2,3]
,则返回[1,2,3]
,而不是返回"[1,2,3]"
。[1,2,3]
是JSON数组,而"[1,2,3]"
仅仅是JSON字符串。
我们给出如下方法:
@GetMapping("raw_json")
User raw() {
return new User();
}
@Data
class User {
String name = "Whatever";
@JsonRawValue
String ageNature = "[{\"ageId\":54,\"isChecked\":true}]";
}
因为ageNature
字段加了@JsonRawValue
注解,所以访问该方法后,返回如下信息:
{
"name": "Whatever",
"ageNature": [
{
"ageId": 54,
"isChecked": true
}
]
}
如果不加该注解,我们将得到:
{
"name": "Whatever",
"friendIds": "[{\"ageId\":54,\"isChecked\":true}]"
}
注意二者的区别。
由于我们上例中User.ageNature
被定义成了String
类型,Swagger将做如下显示:
显然这不是我们想要的,我们想显示出ageNature
真实的结构信息。其实借助于@ApiModelProperty
,我们是可以达到目的的:
// 先定义一个封装ageNature的类
@Data
public class AgeNature {
int ageId;
boolean isChecked;
}
@Data
class User {
String name = "Whatever";
// 再利用 dataType 配置该类
@ApiModelProperty(dataType = "[Lyour.package.AgeNature;")
@JsonRawValue
String ageNature = "[{\"ageId\":54,\"isChecked\":true}]";
}
要注意的是,dataType
数组是用Reflection Notation去标注的。后者详细的用法见这里(点我)。
标注完成后,再次检查 swagger,我们得到了我们想要的:
JSON数据从存储、取出、到返回给客户端,这其中涉及的转换,有部分是可以避免的。本文介绍了@JsonRawValue
这一利器,也许对大家有帮助。