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

使用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实现动态解析

  1. 定义目标实体类:
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省略
}
  1. 使用时,反序列化后调用parseDeliveryModes()完成转换:
ObjectMapper objectMapper = new ObjectMapper();
ProductOffer offer = objectMapper.readValue(jsonString, ProductOffer.class);
offer.parseDeliveryModes();

方案二:自定义反序列化器

  1. 编写自定义反序列化器:
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;
    }
}
  1. 在实体类上指定反序列化器:
@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省略
}
  1. 直接反序列化即可:
ObjectMapper objectMapper = new ObjectMapper();
ProductOffer offer = objectMapper.readValue(jsonString, ProductOffer.class);

方案对比

  • @JsonAnySetter方案:实现简单,无需编写全量固定字段解析逻辑,但需要额外调用解析方法,适合固定字段频繁变动的场景。
  • 自定义反序列化器方案:逻辑更集中,反序列化一步完成,适合固定字段稳定、动态字段规则明确的场景。

内容的提问来源于stack exchange,提问作者Glenn Van Schil

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 18:30:29