Spring Boot自定义嵌套JSON校验:英寸/英尺字段和为0抛异常
Spring Boot 嵌套JSON公共对象全局校验实现
场景需求
项目接收嵌套结构JSON入参,JSON中多处复用两类公共对象:
InchFraction:包含inch、fraction两个数值字段FeetInchFraction:包含feet、inch、fraction三个数值字段
需要实现统一校验规则:- 任意位置的
InchFraction对象如果inch + fraction = 0,直接抛出错误提示:invalid inch and fraction data - 任意位置的
FeetInchFraction对象如果feet + inch + fraction = 0,直接抛出错误提示:invalid feet inch fraction data
参考入参示例
{ "key1": 1, "key2": "xxxxxxxxxxxx", "key3": "xxxxxxxxx", "key4": "", "key5": "", "key6": { "feet": 0, "inch": 1, "fraction": 0 }, "key7": "", "key8": { "inch": 0, "fraction": 0 }, "key9": { "inch": 1, "fraction": 1 }, "key10": { "inch": 1, "fraction": 1 }, "key11": { "inch": 1, "fraction": 1 }, "key12": { "key13": "", "key14": { "inch": 1, "fraction": 1 }, "key15": { "inch": 1, "fraction": 1 } }, "key16": { "key17": "", "key18": { "inch": 1, "fraction": 1 }, "key19": { "inch": 1, "fraction": 1 } }, "key20": "", "key21": "", "key22": "Welded", "key23": "Welded", "key24": { "inch": 1, "fraction": 1 }, "key25": { "inch": 1, "fraction": 1 }, "key26": { "feet": 0, "inch": 1, "fraction": 0 } }
现有模型定义
public class ShopstandardInputJSON { private float key1; private String key2; private String key3; private String key4; private String key5; private FeetInchFraction key6; private String key7; private InchFraction key8; private InchFraction key9; private InchFraction key10; private InchFraction key11; private Key12 key12; private Key16 key16; private String key20; private String key21; private String key22; private String key23; private InchFraction key24; private InchFraction key25; private FeetInchFraction key26; } public class InchFraction { private int inch; private float fraction; } public class FeetInchFraction { private int feet; private int inch; private float fraction; }
实现步骤
不用手写递归遍历JSON的逻辑,直接用Spring Validation的类级别校验+嵌套校验能力,自动覆盖所有层级的公共对象:
1. 定义两类公共对象的自定义校验注解
首先给InchFraction写校验注解:
import jakarta.validation.Constraint; import jakarta.validation.Payload; import java.lang.annotation.*; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = InchFractionValidator.class) @Documented public @interface ValidInchFraction { String message() default "invalid inch and fraction data"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
对应校验逻辑实现:
import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; public class InchFractionValidator implements ConstraintValidator<ValidInchFraction, InchFraction> { @Override public boolean isValid(InchFraction value, ConstraintValidatorContext context) { if (value == null) return true; // 浮点数避免精度问题,用阈值判断 return Math.abs(value.getInch() + value.getFraction()) > 0.0001f; } }
再给FeetInchFraction写校验注解:
import jakarta.validation.Constraint; import jakarta.validation.Payload; import java.lang.annotation.*; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = FeetInchFractionValidator.class) @Documented public @interface ValidFeetInchFraction { String message() default "invalid feet inch fraction data"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
对应校验逻辑实现:
import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; public class FeetInchFractionValidator implements ConstraintValidator<ValidFeetInchFraction, FeetInchFraction> { @Override public boolean isValid(FeetInchFraction value, ConstraintValidatorContext context) { if (value == null) return true; return Math.abs(value.getFeet() + value.getInch() + value.getFraction()) > 0.0001f; } }
Spring Boot 2.x版本把上述代码里的
jakarta.validation替换成javax.validation即可。
2. 修改模型类,添加校验触发标记
- 给两个公共类加上对应的类级别校验注解
- 所有自定义对象类型的字段(不管嵌套多少层)都加
@Valid注解,告诉Spring Validation递归校验该字段内部的约束
修改后的公共类:
// 用Lombok的话直接加@Data省掉getter/setter,手写也可以 @ValidInchFraction public class InchFraction { private int inch; private float fraction; // 补全getter/setter public int getInch() {return inch;} public void setInch(int inch) {this.inch = inch;} public float getFraction() {return fraction;} public void setFraction(float fraction) {this.fraction = fraction;} } @ValidFeetInchFraction public class FeetInchFraction { private int feet; private int inch; private float fraction; // 补全getter/setter public int getFeet() {return feet;} public void setFeet(int feet) {this.feet = feet;} public int getInch() {return inch;} public void setInch(int inch) {this.inch = inch;} public float getFraction() {return fraction;} public void setFraction(float fraction) {this.fraction = fraction;} }
外层入口类修改示例:
import jakarta.validation.Valid; public class ShopstandardInputJSON { private float key1; private String key2; private String key3; private String key4; private String key5; @Valid private FeetInchFraction key6; private String key7; @Valid private InchFraction key8; @Valid private InchFraction key9; @Valid private InchFraction key10; @Valid private InchFraction key11; @Valid private Key12 key12; @Valid private Key16 key16; private String key20; private String key21; private String key22; private String key23; @Valid private InchFraction key24; @Valid private InchFraction key25; @Valid private FeetInchFraction key26; // 补全所有getter/setter }
内部嵌套类比如Key12、Key16同理,内部的InchFraction/FeetInchFraction字段都加上@Valid即可:
public class Key12 { private String key13; @Valid private InchFraction key14; @Valid private InchFraction key15; // 补全getter/setter } public class Key16 { private String key17; @Valid private InchFraction key18; @Valid private InchFraction key19; // 补全getter/setter }
3. 接口层开启校验
在Controller接口的入参前加上@Valid注解,就会自动触发所有校验规则:
@PostMapping("/shop-standard/save") public Object submit(@Valid @RequestBody ShopstandardInputJSON param) { // 正常业务逻辑处理 return "success"; }
校验失败时会抛出MethodArgumentNotValidException,可以在全局异常处理器里捕获该异常,取出默认的错误信息返回给前端即可,不需要额外写递归遍历逻辑,不管两类公共对象嵌套在JSON的哪个位置,只要加了@Valid就会自动触发校验。
内容的提问来源于stack exchange,提问作者Naina Thange
相关产品推荐
相关产品推荐

