使用FasterXML遍历含重复键JSON数组仅获最后值的问题排查
嘿,我来帮你拆解下问题所在,一步步搞定:
先修正DTO字段与JSON键的匹配问题
你后端返回的JSON里是小写的"state",但你的Countries类字段是States(大写开头还多了个s);Test类的字段countries和JSON里的"Countries"(大写C)也不匹配。这虽然不是只取最后一个值的核心原因,但会导致Jackson映射异常,先把这个基础错误改了:
修正后的DTO类(记得加@JsonProperty明确映射关系,还有getter/setter,Jackson需要它们访问字段):
// 类名建议用单数Country更符合语义,你也可以按自己习惯来 private class Countries { @JsonProperty("state") // 绑定JSON里的"state"键 private String state; public String getState() { return state; } public void setState(String state) { this.state = state; } } private class Test { @JsonProperty("Countries") // 绑定JSON里的"Countries"键 private List<Countries> countries; public List<Countries> getCountries() { return countries; } public void setCountries(List<Countries> countries) { this.countries = countries; } }
核心问题:Jackson默认会覆盖重复键的值
标准JSON规范不允许同一个对象内出现重复键,所以Jackson的默认行为是遇到重复键时,直接用后面的值覆盖前面的——这就是你只能拿到"Tasmania"的原因。如果后端没法修改返回格式,咱们得自定义反序列化逻辑来收集所有重复的state值。
解决方案:自定义反序列化器+开启重复键支持
要实现把单个对象里的重复state拆成列表里的多个Countries对象,需要两步:
1. 配置ObjectMapper允许重复键
在你的Mapper类里,初始化ObjectMapper时必须开启ALLOW_DUPLICATE_NAMES特性,否则Jackson会直接跳过前面的重复键:
private class Mapper { public Test parseJson(String json) throws IOException { ObjectMapper mapper = new ObjectMapper(); // 关键:允许JSON对象存在重复键,并保留所有键值对 mapper.enable(JsonParser.Feature.ALLOW_DUPLICATE_NAMES); return mapper.readValue(json, Test.class); } }
2. 写自定义反序列化器收集所有重复的state
我们需要给Test类的countries字段写一个反序列化器,把单个对象里的多个state值拆成独立的Countries对象:
import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import java.io.IOException; import java.util.ArrayList; import java.util.List; public class CountriesListDeserializer extends JsonDeserializer<List<Countries>> { @Override public List<Countries> deserialize(JsonParser parser, DeserializationContext context) throws IOException { List<Countries> countriesList = new ArrayList<>(); // 跳过JSON数组的START标记 parser.nextToken(); // 进入数组里的单个对象 parser.nextToken(); // 遍历对象内所有键值对 while (parser.nextToken() != JsonToken.END_OBJECT) { String key = parser.getCurrentName(); parser.nextToken(); // 跳转到对应值的位置 if ("state".equals(key)) { // 把每个state值封装成Countries对象加入列表 Countries country = new Countries(); country.setState(parser.getText()); countriesList.add(country); } } // 跳过JSON数组的END标记 parser.nextToken(); return countriesList; } }
3. 在DTO上绑定反序列化器
修改Test类的countries字段,指定使用我们写的反序列化器:
private class Test { @JsonProperty("Countries") @JsonDeserialize(using = CountriesListDeserializer.class) // 绑定自定义反序列化器 private List<Countries> countries; // getter/setter 同上 }
最终效果
这样处理后,解析你给的JSON字符串时,Test.countries会变成包含两个对象的列表:
[ Countries{state='Queens Land'}, Countries{state='Tasmania'} ]
额外建议
如果能和后端团队沟通的话,最好让他们把JSON改成标准格式,比如:
"Countries": [ {"state":"Queens Land"}, {"state":"Tasmania"} ]
这样完全不用自定义反序列化器,Jackson默认就能正常解析,也避免了不符合规范的JSON带来的其他潜在问题。
内容的提问来源于stack exchange,提问作者Jreddy

