如何强制杰克逊在控制器中调用对象(dto)中所有属性的 Set 方法?

Posted

技术标签:

【中文标题】如何强制杰克逊在控制器中调用对象(dto)中所有属性的 Set 方法?【英文标题】:How to force Jackson to invoke Set methods of all attributes in object (dto) in controller? 【发布时间】:2019-01-15 04:44:54 【问题描述】:

我的“命令”中有一些验证,控制器的参数在每个属性的集合内执行,问题是没有通知属性,杰克逊没有调用 set 方法进行验证。是否可以强制 Jackson 在缺少属性的情况下调用 Set 方法?

例如 Payload 没有代理字段:


   "bank": "237",
   "account": "20772-1",
   "taxId": "36456155800",
   "paidAmount": 30.00

我的控制器:

public Return confirmTransfer(@RequestBody RechargeTransferConfirmationCommand command) 
    System.out.println(command);

Java 类:

public class RechargeTransferConfirmationCommand 

    public static final String ERR_INVALID_AGENCY = "Agency number can not be null.";

    private String bank;
    private String account;
    private String agency;
    private String taxId;

    public RechargeTransferConfirmationCommand(BigDecimal paidAmount, String bank, String account,
            String agency, String taxId) 

        setPaidAmount(paidAmount);
        setBank(bank);
        setAccount(account);
        setAgency(agency);
        setTaxId(taxId);
    

    public void setRechargeId(RechargeId rechargeId) 
        assertArgumentNotNull(rechargeId, Recharge.ERR_RECHARGE_ID_INVALID);
        this.rechargeId = rechargeId;
    

    private void setPaidAmount(BigDecimal paidAmount) 
        if (paidAmount == null || paidAmount.compareTo(BigDecimal.ZERO) < 0)
            throw new IllegalArgumentException(Recharge.ERR_INVALID_AMOUNT);
        this.paidAmount = paidAmount;
    

    private void setBank(String bank) 
        assertArgumentNotEmpty(bank, TransferInformation.ERR_INVALID_BANK_NUMBER);
        this.bank = bank;
    

    private void setAccount(String account) 
        assertArgumentNotEmpty(account, TransferInformation.ERR_INVALID_ACCOUNT);
        this.account = account;
    

    private void setAgency(String agency) 
        assertArgumentNotEmpty(agency, ERR_INVALID_AGENCY);
        this.agency = agency;
    

    private void setTaxId(String taxId) 
        assertArgumentNotEmpty(taxId, ERR_INVALID_TAX_ID);
        this.taxId = taxId;
    


在这种情况下,对于每个字段,都会调用 set 方法进行验证,除了负载中未通知的代理字段,它应该很快抛出方法 assertArgumentNotEmpty 中包含的异常。

【问题讨论】:

为什么不使用@NotNull@Min 注释来处理验证? 因为使用这个注解,客户端的返回对象是特定于框架的,我需要一个自定义的返回。这是我的异常,我可以在 @ControllerAdvice 类中捕获并返回更友好的响应。 如果您的 javax 验证失败,那么您可以拦截 @ControllerAdvice。即使在 javax 验证中,您也可以使用传递验证失败消息的消息 【参考方案1】:

使用@JsonCreator解决问题,谢谢大家的帮助

@JsonCreator
public RechargeTransferConfirmationCommand(@JsonProperty("paidAmount") BigDecimal paidAmount,
        @JsonProperty("bank") String bank, @JsonProperty("account") String account,
        @JsonProperty("agency") String agency, @JsonProperty("taxId") String taxId) 

    setPaidAmount(paidAmount);
    setBank(bank);
    setAccount(account);
    setAgency(agency);
    setTaxId(taxId);

【讨论】:

【参考方案2】:

在方法中使用@JsonSetter && 也声明no args constructor

public RechargeTransferConfirmationCommand()

@JsonSetter
    public void setRechargeId(RechargeId rechargeId) 
        assertArgumentNotNull(rechargeId, Recharge.ERR_RECHARGE_ID_INVALID);
        this.rechargeId = rechargeId;
    

但最好使用 javax 验证。查看this了解详情。

另一种方式: 您可以在一种方法中编写所有验证并使用@AssertTrue:

@AssertTrue
public boolean isValid()
//if all field is valid then return true;
return false;

【讨论】:

【参考方案3】:

是的,jackson 不会调用未传入有效负载的字段的 setter 方法,如果要验证缺少的字段,则需要自定义 Deserializer

class RechargeTransferConfirmationCommandDeserializer extends JsonDeserializer<RechargeTransferConfirmationCommand> 

@Override
public RechargeTransferConfirmationCommand deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException 
    RechargeTransferConfirmationCommand responseModel = jp.readValueAs(RechargeTransferConfirmationCommand.class);

    RechargeTransferConfirmationCommand finalModel = new RechargeTransferConfirmationCommand();
    finalModel.set agency(responseModel. agency == null || age.trim().isEmpty() ? "agency" : responseModel.agency);

    return finalModel;
   


你的模特应该是

@JsonDeserialize(using = DefaultModelDeserializer.class)
public class DefaultModel 

...

【讨论】:

以上是关于如何强制杰克逊在控制器中调用对象(dto)中所有属性的 Set 方法?的主要内容,如果未能解决你的问题,请参考以下文章

杰克逊 Kotlin 控制器中请求正文的序列化不与 restTemplate 一起使用,与邮递员一起使用

杰克逊:当调用不同的 Rest EndPoint 时,同一实体上的多个序列化器

Spring框架中的Jackson反序列化错误处理

杰克逊 - 必需的财产?

DTO对象

通过 id 实例化 dto 对象,其中对象作为有效负载 C#