最近小明遇到一个需求,写一个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封装的分组校验了。估计这种需要两用的奇葩问题,以后升级了可以实现吧。