使用Jackson动态反序列化含zone和method的JSON属性
动态反序列化带zone和method后缀的JSON字段
问题背景
我收到的JSON请求体格式如下:
{ "productId": "90000011", "offerId": "String", "format": "String", "sellerId": "String", "sellerName": "String", "shippingPrice[zone=BE,method=STD]": 0.0, "deliveryTimeEarliestDays[zone=BE,method=STD]": 1, "deliveryTimeLatestDays[zone=BE,method=STD]": 1, "shippingPrice[zone=NL,method=STD]": 0.0, "deliveryTimeEarliestDays[zone=NL,method=STD]": 1, "deliveryTimeLatestDays[zone=NL,method=STD]": 1 }
其中部分字段仅方括号内的zone和method参数不同,我希望不用每次新增zone或method就修改代码,通过Jackson实现动态反序列化,将这些字段自动转换为如下结构:
{ "productId": "90000011", "offerId": "String", "format": "String", "sellerId": "String", "sellerName": "String", "deliveryModes":[ { "method":"STD", "zone":"BE", "shippingPrice":0.0, "deliveryTimeEarliestDays":1, "deliveryTimeLatestDays":1 },{ "method":"STD", "zone":"NL", "shippingPrice":0.0, "deliveryTimeEarliestDays":1, "deliveryTimeLatestDays":1 }] }
我目前有两个思路:
- 用
@JsonAnySetter注解把所有字段存入Map,再手动解析字段名 - 编写自定义反序列化器,遍历所有属性,筛选出以
shippingPrice、deliveryTimeEarliestDays、deliveryTimeLatestDays开头的字段并映射成目标格式
解决方案
方案一:使用@JsonAnySetter实现动态解析
- 定义目标实体类:
public class ProductOffer { private String productId; private String offerId; private String format; private String sellerId; private String sellerName; private List<DeliveryMode> deliveryModes = new ArrayList<>(); private transient Map<String, Object> extraFields = new HashMap<>(); // 常规getter/setter省略 @JsonAnySetter public void setExtraField(String key, Object value) { extraFields.put(key, value); } @JsonIgnore public void parseDeliveryModes() { // 匹配前缀+[zone=XX,method=YY]格式的字段 Pattern pattern = Pattern.compile("(shippingPrice|deliveryTimeEarliestDays|deliveryTimeLatestDays)\\[zone=(\\w+),method=(\\w+)\\]"); Map<String, DeliveryMode> modeMap = new HashMap<>(); for (Map.Entry<String, Object> entry : extraFields.entrySet()) { Matcher matcher = pattern.matcher(entry.getKey()); if (matcher.matches()) { String fieldName = matcher.group(1); String zone = matcher.group(2); String method = matcher.group(3); String key = zone + "_" + method; DeliveryMode mode = modeMap.computeIfAbsent(key, k -> new DeliveryMode()); mode.setZone(zone); mode.setMethod(method); // 根据字段名赋值 switch (fieldName) { case "shippingPrice": mode.setShippingPrice((Double) entry.getValue()); break; case "deliveryTimeEarliestDays": mode.setDeliveryTimeEarliestDays((Integer) entry.getValue()); break; case "deliveryTimeLatestDays": mode.setDeliveryTimeLatestDays((Integer) entry.getValue()); break; } } } deliveryModes.addAll(modeMap.values()); extraFields.clear(); } } class DeliveryMode { private String method; private String zone; private Double shippingPrice; private Integer deliveryTimeEarliestDays; private Integer deliveryTimeLatestDays; // getter/setter省略 }
- 使用时,反序列化后调用
parseDeliveryModes()完成转换:
ObjectMapper objectMapper = new ObjectMapper(); ProductOffer offer = objectMapper.readValue(jsonString, ProductOffer.class); offer.parseDeliveryModes();
方案二:自定义反序列化器
- 编写自定义反序列化器:
public class ProductOfferDeserializer extends StdDeserializer<ProductOffer> { private static final Pattern FIELD_PATTERN = Pattern.compile("(shippingPrice|deliveryTimeEarliestDays|deliveryTimeLatestDays)\\[zone=(\\w+),method=(\\w+)\\]"); public ProductOfferDeserializer() { super(ProductOffer.class); } @Override public ProductOffer deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { JsonNode root = p.getCodec().readTree(p); ProductOffer offer = new ProductOffer(); // 解析固定字段 offer.setProductId(root.get("productId").asText()); offer.setOfferId(root.get("offerId").asText()); offer.setFormat(root.get("format").asText()); offer.setSellerId(root.get("sellerId").asText()); offer.setSellerName(root.get("sellerName").asText()); // 解析动态配送字段 Map<String, DeliveryMode> modeMap = new HashMap<>(); Iterator<Map.Entry<String, JsonNode>> fields = root.fields(); while (fields.hasNext()) { Map.Entry<String, JsonNode> entry = fields.next(); String key = entry.getKey(); Matcher matcher = FIELD_PATTERN.matcher(key); if (matcher.matches()) { String fieldName = matcher.group(1); String zone = matcher.group(2); String method = matcher.group(3); String modeKey = zone + "_" + method; DeliveryMode mode = modeMap.computeIfAbsent(modeKey, k -> new DeliveryMode()); mode.setZone(zone); mode.setMethod(method); switch (fieldName) { case "shippingPrice": mode.setShippingPrice(entry.getValue().asDouble()); break; case "deliveryTimeEarliestDays": mode.setDeliveryTimeEarliestDays(entry.getValue().asInt()); break; case "deliveryTimeLatestDays": mode.setDeliveryTimeLatestDays(entry.getValue().asInt()); break; } } } offer.setDeliveryModes(new ArrayList<>(modeMap.values())); return offer; } }
- 在实体类上指定反序列化器:
@JsonDeserialize(using = ProductOfferDeserializer.class) public class ProductOffer { private String productId; private String offerId; private String format; private String sellerId; private String sellerName; private List<DeliveryMode> deliveryModes; // getter/setter省略 } class DeliveryMode { private String method; private String zone; private Double shippingPrice; private Integer deliveryTimeEarliestDays; private Integer deliveryTimeLatestDays; // getter/setter省略 }
- 直接反序列化即可:
ObjectMapper objectMapper = new ObjectMapper(); ProductOffer offer = objectMapper.readValue(jsonString, ProductOffer.class);
方案对比
- @JsonAnySetter方案:实现简单,无需编写全量固定字段解析逻辑,但需要额外调用解析方法,适合固定字段频繁变动的场景。
- 自定义反序列化器方案:逻辑更集中,反序列化一步完成,适合固定字段稳定、动态字段规则明确的场景。
内容的提问来源于stack exchange,提问作者Glenn Van Schil
相关产品推荐
相关产品推荐

