Spring Boot RestController+Jackson:如何将JSONObject数组反序列化为Map<String,String>
解决Jackson将对象数组映射为Map的解析异常问题
你遇到的核心问题是:前端传递的codesList是一个包含type和code字段的对象数组,但你的DTO里用Map<String, String>来接收,Jackson默认不知道怎么把数组结构转成键值对形式的Map,所以抛出了解析异常。下面给你两种实用的解决方案,按需选择:
方案一:自定义Jackson反序列化器(推荐)
这种方式直接在DTO层面完成结构转换,不需要额外的中间类,代码更紧凑。
步骤1:编写自定义反序列化器
创建一个继承自JsonDeserializer的类,实现将对象数组转为Map的逻辑:
import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; import java.io.IOException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; public class CodesListDeserializer extends JsonDeserializer<Map<String, String>> { @Override public Map<String, String> deserialize(JsonParser jsonParser, DeserializationContext ctx) throws IOException { JsonNode arrayNode = jsonParser.getCodec().readTree(jsonParser); Map<String, String> codesMap = new HashMap<>(); // 遍历数组中的每个对象,提取type作为键,code作为值 if (arrayNode.isArray()) { Iterator<JsonNode> elements = arrayNode.elements(); while (elements.hasNext()) { JsonNode itemNode = elements.next(); String type = itemNode.get("type").asText(); String code = itemNode.get("code").asText(); // 可选:处理重复type的情况,比如抛异常或者跳过 if (codesMap.containsKey(type)) { // throw new IllegalArgumentException("Duplicate code type: " + type); } codesMap.put(type, code); } } return codesMap; } }
步骤2:在DTO字段上指定反序列化器
修改你的ProductDto,在codesList字段上添加@JsonDeserialize注解,指定刚才写的自定义反序列化器:
import com.fasterxml.jackson.databind.annotation.JsonDeserialize; public class ProductDto { private String someField; @JsonDeserialize(using = CodesListDeserializer.class) private Map<String, String> codesList; // 别忘了添加Getter和Setter方法 public String getSomeField() { return someField; } public void setSomeField(String someField) { this.someField = someField; } public Map<String, String> getCodesList() { return codesList; } public void setCodesList(Map<String, String> codesList) { this.codesList = codesList; } }
这样修改后,Jackson就会自动用你写的逻辑把前端的对象数组转成Map,你的REST端点就能正常接收数据了。
方案二:用中间DTO接收数组后转换
如果你不想写自定义反序列化器,可以先把数组接收为对象列表,再手动转成Map,这种方式更直观,适合快速实现。
步骤1:创建数组元素的DTO类
先定义一个用来接收单个code对象的类:
public class CodeItem { private String type; private String code; // Getter和Setter public String getType() { return type; } public void setType(String type) { this.type = type; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } }
步骤2:修改ProductDto或创建请求专用DTO
可以直接修改原DTO,用列表接收后转换,或者创建一个专门用于接收请求的DTO:
import java.util.HashMap; import java.util.List; import java.util.Map; public class ProductRequestDto { private String someField; private List<CodeItem> codesList; // 转换方法:把列表转成Map public Map<String, String> getCodesAsMap() { Map<String, String> codesMap = new HashMap<>(); if (codesList != null) { codesList.forEach(item -> codesMap.put(item.getType(), item.getCode())); } return codesMap; } // Getter和Setter public String getSomeField() { return someField; } public void setSomeField(String someField) { this.someField = someField; } public List<CodeItem> getCodesList() { return codesList; } public void setCodesList(List<CodeItem> codesList) { this.codesList = codesList; } }
步骤3:在REST端点中处理转换
修改你的端点方法,接收请求专用DTO,然后转换为业务用的ProductDto:
public ResponseEntity<ProductDto> addProduct(@RequestBody ProductRequestDto requestDto) { ProductDto productDto = new ProductDto(); productDto.setSomeField(requestDto.getSomeField()); // 调用转换方法得到Map productDto.setCodesList(requestDto.getCodesAsMap()); // 后续业务逻辑处理 return ResponseEntity.ok(productDto); }
注意事项
- 如果前端传递的数组中存在重复的
type值,两种方案中后面的code都会覆盖前面的,如果你需要处理这种情况(比如抛异常、收集所有值),可以在转换逻辑中添加对应的判断。 - 确保你的Spring Boot项目中已经引入了Jackson的依赖(Spring Boot Web starter默认包含)。
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

