You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

接收空TransactionDto时,如何用Jackson映射嵌套JSON请求?

如何用Jackson将嵌套JSON映射到简单TransactionDto对象?

看起来你碰到了Jackson映射嵌套JSON到扁平DTO的问题——因为请求是嵌套结构,但你的TransactionDto是扁平的,Jackson默认找不到对应字段,才会输出全空/默认值的对象。我给你几个实用的解决办法:

方法1:用@JsonProperty指定嵌套字段路径

这是最省心的方式,直接在TransactionDto的字段上用@JsonProperty标注对应的嵌套JSON路径。

举个例子,假设你的请求JSON结构是这样的:

{
  "user": {
    "name": "JohnDoe",
    "api_key": "XYZ-123-ABC"
  },
  "payment_info": {
    "amount": 99.99,
    "account_num": "87654321",
    "expire_date": "2026-05",
    "csc": "456"
  },
  "billing_address": {
    "first_name": "John",
    "last_name": "Doe",
    "street": "456 Oak Ave",
    "city": "Chicago",
    "state": "IL",
    "zip": "60601",
    "phone": "555-6789"
  },
  "description": "Monthly subscription"
}

那你可以给TransactionDto的字段加上路径注解:

public class TransactionDto {
    private Long id;
    
    @JsonProperty("user.name")
    private String userName;
    
    @JsonProperty("user.api_key")
    private String apiKey;
    
    @JsonProperty("payment_info.amount")
    private BigDecimal amount; // 金额建议用BigDecimal避免精度问题
    
    @JsonProperty("payment_info.account_num")
    private String accountNumber;
    
    @JsonProperty("payment_info.expire_date")
    private String expirationDate;
    
    @JsonProperty("payment_info.csc")
    private String cscCode;
    
    private String customerAccountCode;
    
    @JsonProperty("billing_address.first_name")
    private String firstName;
    
    @JsonProperty("billing_address.last_name")
    private String lastName;
    
    @JsonProperty("billing_address.street")
    private String street;
    
    @JsonProperty("billing_address.city")
    private String city;
    
    @JsonProperty("billing_address.state")
    private String state;
    
    @JsonProperty("billing_address.zip")
    private String zipCode;
    
    @JsonProperty("billing_address.phone")
    private String phoneNumber;
    
    private String description;
    
    // 记得添加无参构造、getter/setter,Jackson默认需要无参构造
}

这样Jackson就会自动按照你指定的路径,从嵌套JSON里提取值映射到扁平的DTO字段中。

方法2:用@JsonCreator构造函数手动映射

如果不想给每个字段加注解,也可以用带@JsonCreator的构造函数,手动从JSON节点里提取值,适合需要做额外判断(比如空值处理)的场景:

public class TransactionDto {
    // 所有字段和之前一致
    
    // 无参构造还是要保留,Jackson可能会用到
    public TransactionDto() {}
    
    @JsonCreator
    public TransactionDto(JsonNode rootNode) {
        // 提取user节点的字段
        JsonNode userNode = rootNode.get("user");
        if (userNode != null) {
            this.userName = userNode.has("name") ? userNode.get("name").asText() : null;
            this.apiKey = userNode.has("api_key") ? userNode.get("api_key").asText() : null;
        }
        
        // 提取payment_info节点的字段
        JsonNode paymentNode = rootNode.get("payment_info");
        if (paymentNode != null) {
            this.amount = paymentNode.has("amount") ? paymentNode.get("amount").decimalValue() : BigDecimal.ZERO;
            this.accountNumber = paymentNode.has("account_num") ? paymentNode.get("account_num").asText() : null;
            this.expirationDate = paymentNode.has("expire_date") ? paymentNode.get("expire_date").asText() : null;
            this.cscCode = paymentNode.has("csc") ? paymentNode.get("csc").asText() : null;
        }
        
        // 提取billing_address节点的字段
        JsonNode billingNode = rootNode.get("billing_address");
        if (billingNode != null) {
            this.firstName = billingNode.has("first_name") ? billingNode.get("first_name").asText() : null;
            this.lastName = billingNode.has("last_name") ? billingNode.get("last_name").asText() : null;
            this.street = billingNode.has("street") ? billingNode.get("street").asText() : null;
            this.city = billingNode.has("city") ? billingNode.get("city").asText() : null;
            this.state = billingNode.has("state") ? billingNode.get("state").asText() : null;
            this.zipCode = billingNode.has("zip") ? billingNode.get("zip").asText() : null;
            this.phoneNumber = billingNode.has("phone") ? billingNode.get("phone").asText() : null;
        }
        
        this.description = rootNode.has("description") ? rootNode.get("description").asText() : null;
    }
    
    // getter/setter省略
}

方法3:自定义JsonDeserializer

如果上面两种方式都满足不了复杂需求(比如特殊类型转换、多格式兼容),可以自定义反序列化器:

public class TransactionDtoDeserializer extends StdDeserializer<TransactionDto> {

    public TransactionDtoDeserializer() {
        super(TransactionDto.class);
    }

    @Override
    public TransactionDto deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
        JsonNode rootNode = jsonParser.readValueAsTree();
        TransactionDto dto = new TransactionDto();
        
        // 手动提取并设置各个字段,逻辑和构造函数类似
        JsonNode userNode = rootNode.get("user");
        if (userNode != null) {
            dto.setUserName(userNode.get("name").asText());
            dto.setApiKey(userNode.get("api_key").asText());
        }
        
        // 其他字段的提取逻辑...
        
        return dto;
    }
}

然后在TransactionDto类上标注使用这个反序列化器:

@JsonDeserialize(using = TransactionDtoDeserializer.class)
public class TransactionDto {
    // 字段、getter/setter省略
}

额外注意事项

  • 确保项目中已经引入Jackson依赖(Spring Boot项目的spring-boot-starter-web默认包含)
  • 检查JSON字段名和你指定的路径是否完全匹配,大小写、下划线/驼峰差异可以通过Jackson配置调整(比如开启驼峰转下划线的自动匹配)
  • 金额字段优先用BigDecimal,避免double类型的精度丢失问题

内容的提问来源于stack exchange,提问作者Sustav

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 08:58:09