最近小明遇到一个需求,写一个restful的接口,将一大坨json数据解析,并按指定逻辑入库,然后小明花了大量时间写json解析和校验,为了可以早点下班,这里提供springboot+jackson+hibernatevalidator的简单实现。@小明。
废话不多说,直接上案例,解决:1)json自动解析并匹配java bean;2)基于hibernatevalidator注解的校验;3)嵌套json自动解析到嵌套的java bean(含list);4)嵌套的java bean的hibernatevalidator注解的校验;5)大写风格的json到驼峰风格的java属性自动转换。
1、新建一个简单的springboot项目
添加如下依赖即可,会自动将jackson和hibernatevalidator依赖引入。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2、创建三个实体bean,GrandFather、Father、Son,依次嵌套。
Son
@JsonNaming(UpperSnakeCaseStrategy.class)
public class Son implements Serializable{
private static final long serialVersionUID = 1L;
@Length(min = 4, max = 16, message = "用户名长度要求在{min}-{max}之间")
@NotNull(message = "名不可为空")
private String name;
@Min(value = 18, message = "未成年不满足注册要求")
@Max(value = 80, message = "年龄错误")
private Integer age;
@DecimalMin(value = "0.5", message = "钱必须大于0.5")
@DecimalMax(value = "1000.2", message = "钱必须小于1000.2")
private Double money;
@DateTimeFormat(pattern = "yyyy-MM-dd") //将前端传的string转date,指定yyyy-MM-dd格式
@JsonFormat(pattern = "yyyy-MM-dd") //@responseBody标注后,后端往前端返时,json转化指定格式
@Past(message = "出生日期错误")
//@JsonSetter("BIRTH_DAY")
private Date birthDay;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
public Date getBirthDay() {
return birthDay;
}
public void setBirthDay(Date birthDay) {
this.birthDay = birthDay;
}
@Override
public String toString() {
return "Son [name=" + name + ", age=" + age + ", money=" + money + ", birthDay=" + birthDay + "]";
}
}
Father,注意类的属性必须加上@Valid,这样才可以继续递归校验。
@JsonNaming(UpperSnakeCaseStrategy.class)
public class Father implements Serializable{
private static final long serialVersionUID = 1L;
@Length(min = 4, max = 16, message = "用户名长度要求在{min}-{max}之间")
@NotNull(message = "名不可为空")
private String name;
@Min(value = 18, message = "未成年不满足注册要求")
@Max(value = 80, message = "年龄错误")
private Integer age;
@DecimalMin(value = "0.5", message = "钱必须大于0.5")
@DecimalMax(value = "1000.2", message = "钱必须小于1000.2")
private Double money;
@DateTimeFormat(pattern = "yyyy-MM-dd") //将前端传的string转date,指定yyyy-MM-dd格式
@JsonFormat(pattern = "yyyy-MM-dd") //@responseBody标注后,后端往前端返时,json转化指定格式
@Past(message = "出生日期错误")
//@JsonSetter("BIRTH_DAY")
private Date birthDay;
@Valid
//@JsonSetter("SON_LIST")
private List<Son> sonList;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
public Date getBirthDay() {
return birthDay;
}
public void setBirthDay(Date birthDay) {
this.birthDay = birthDay;
}
public List<Son> getSonList() {
return sonList;
}
public void setSonList(List<Son> sonList) {
this.sonList = sonList;
}
@Override
public String toString() {
return "Father [name=" + name + ", age=" + age + ", money=" + money + ", birthDay=" + birthDay + ", sonList="
+ sonList + "]";
}
}
GrandFather
@JsonNaming(UpperSnakeCaseStrategy.class)
public class GrandFather implements Serializable{
private static final long serialVersionUID = 1L;
@Length(min = 4, max = 16, message = "用户名长度要求在{min}-{max}之间")
@NotNull(message = "名不可为空")
private String name;
@Min(value = 18, message = "未成年不满足注册要求")
@Max(value = 80, message = "年龄错误")
private Integer age;
@DecimalMin(value = "0.5", message = "钱必须大于0.5")
@DecimalMax(value = "1000.2", message = "钱必须小于1000.2")
private Double money;
@DateTimeFormat(pattern = "yyyy-MM-dd") //将前端传的string转date,指定yyyy-MM-dd格式
@JsonFormat(pattern = "yyyy-MM-dd") //@responseBody标注后,后端往前端返时,json转化指定格式
@Past(message = "出生日期错误")
//@JsonSetter("BIRTH_DAY")
private Date birthDay;
@Valid
@NotNull(message = "名不可为空")
//@JsonSetter("FATHER")
private Father father;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
public Date getBirthDay() {
return birthDay;
}
public void setBirthDay(Date birthDay) {
this.birthDay = birthDay;
}
public Father getFather() {
return father;
}
public void setFather(Father father) {
this.father = father;
}
@Override
public String toString() {
return "GrandFather [name=" + name + ", age=" + age + ", money=" + money + ", birthDay=" + birthDay
+ ", father=" + father + "]";
}
}
3、创建controller,这里使用springMVC的自动参数绑定,BindingResult会收集校验失败的信息。
@RestController
@RequestMapping("json")
public class JsonValidController {
@PostMapping("insert-order")
public ResultData insertOrder(@Valid @RequestBody GrandFather grandFather, BindingResult bindingResult) {
ResultData rd = new ResultData();
if (bindingResult.hasErrors()) {
rd.setCode("201");
rd.setMessage("校验失败");
List<ObjectError> list = bindingResult.getAllErrors();
rd.setData(list);
return rd;
}else {
System.out.println(grandFather);
return ResultData.ok("ok");
}
}
}
4、重写方法,名称转换策略,这里提供UpperSnakeCaseStrategy,将类似ABC_DEF自动赋值到abcDef的属性上。当然也可以通过类似@JsonSetter("BIRTH_DAY")和@JsonGetter("BIRTH_DAY") 实现具体的每个属性的转化(JsonSetter用于json反序列化,JsonGetter用于json序列化)。
import com.fasterxml.jackson.databind.PropertyNamingStrategy.PropertyNamingStrategyBase;
public class UpperSnakeCaseStrategy extends PropertyNamingStrategyBase
{
/**
*
*/
private static final long serialVersionUID = 1L;
@Override
public String translate(String input)
{
if (input == null) return input; // garbage in, garbage out
int length = input.length();
StringBuilder result = new StringBuilder(length * 2);
int resultLength = 0;
boolean wasPrevTranslated = false;
for (int i = 0; i < length; i++)
{
char c = input.charAt(i);
if (i > 0 || c != '_') // skip first starting underscore
{
if (Character.isUpperCase(c))
{
if (!wasPrevTranslated && resultLength > 0 && result.charAt(resultLength - 1) != '_')
{
result.append('_');
resultLength++;
}
wasPrevTranslated = true;
}
else
{
c = Character.toUpperCase(c);
wasPrevTranslated = false;
}
result.append(c);
resultLength++;
}
}
return resultLength > 0 ? result.toString() : input;
}
}
5、测试,可以实现相关功能。
{
"NAME":"GRANDFATHER",
"AGE":71,
"MONEY":100.09,
"BIRTH_DAY":"1937-08-01",
"FATHER":{
"NAME":"FATHER",
"AGE":41,
"MONEY":200.09,
"BIRTH_DAY":"1963-07-15",
"SON_LIST":[
{
"NAME":"SON",
"AGE":21,
"MONEY":1.09,
"BIRTH_DAY":"1991-01-15"
},
{
"NAME":"SON2",
"AGE":22,
"MONEY":2.09,
"BIRTH_DAY":"1992"
},
{
"NAME":"SON3",
"AGE":23,
"MONEY":3.09,
"BIRTH_DAY":"1993-03-15"
}
]
}
}
最后,使用@Valid实现嵌套校验,就无法使用spring封装的分组校验了。估计这种需要两用的奇葩问题,以后升级了可以实现吧。