接收空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
相关产品推荐
相关产品推荐

