如何序列化/反序列化“飞溅”的复杂类型?

iva*_*ant 2 java xml jackson

我正在连接到外部 XML API,我正在尝试使用 JacksonXmlMapper类将其解析为 POJO 。XML 的一部分如下所示:

<invoice>
    <some>element</some>
    <some_other>element</some_other>
    <currency>USD</currency>
    <cost>10.42</cost>
    <breakdown>
        <item id="1">
            <description>blah blah</description>
            <cost>4.21</cost>
        </item>
    </breakdown>
</invoice>
Run Code Online (Sandbox Code Playgroud)

我想解析单个对象中的currencycost元素Money

更糟糕的是,内部items 仅指定成本并“重用”货币代码。我可以使用 Jackson 以某种智能方式解析它们吗?

Sam*_*rry 5

我想解析单个 Money 对象中的货币和成本元素。

鉴于所提供的XML,你可以解析currency,并cost在一个单一的元素Money通过创建发票的值对象和使用对象@JsonUnwrapped

为发票创建值对象

如果您不想为发票创建对象,则可以将您的对象配置XmlMapper为忽略未知属性,并将整个响应反序列化为Money对象。在我看来,为您的发票创建一个单独的类是一种更简洁的方法。

创建Invoice对象的目的是封装响应的所有元素。现在您可能只需要currencyand cost,但稍后您可能需要访问breakdown例如。对象的结构可以是这样的:

public class Invoice {
    private final String some;
    private final String some_other;
    @JsonUnwrapped
    private final Money money;
    private final List<Item> breakdown;

    @JsonCreator
    public Invoice(@JsonProperty("some") String some,
                   @JsonProperty("some_other") String some_other,
                   @JsonProperty("money") Money money,
                   @JacksonXmlProperty(localName = "item") List<Item> breakdown) {
        this.some = some;
        this.some_other = some_other;
        this.money = money;
        this.breakdown = breakdown;
    }

    public String getSome() {
        return some;
    }

    public String getSome_other() {
        return some_other;
    }

    public Money getMoney() {
        return money;
    }

    public List<Item> getBreakdown() {
        return breakdown;
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,该Money属性带有注释@JsonUnwrapped。这可以在字段上、构造函数内部或 setter 上,它将“解开”Money对象并在与Invoice's相同的级别反序列化其成员。像这样构建您的类,将货币和成本反序列化为单个对象:

public class Money {
    private final String currency;
    private final Double cost;

    @JsonCreator
    public Money(@JsonProperty("currency") String currency,
                 @JsonProperty("cost") Double cost) {
        this.currency = currency;
        this.cost = cost;
    }

    public String getCurrency() {
        return currency;
    }

    public Double getCost() {
        return cost;
    }
}
Run Code Online (Sandbox Code Playgroud)

内部项目仅指定成本并“重用”货币代码。

Money如果可能,分离和“项目”模型

Money为两个不同的模型重用对象不如用一个对象来表示每个视图那么理想。例如,Money对象为currencyand costItem对象为id, description, and cost。如果这在你的项目中是可能的,我会为“item”创建一个这样的对象:

public class Item {
    @JacksonXmlProperty(isAttribute = true)
    public final String id;
    public final String description;
    public final Double cost;

    @JsonCreator
    public Item(@JsonProperty("id") String id,
                @JsonProperty("description") String description,
                @JsonProperty("cost") Double cost) {
        this.id = id;
        this.description = description;
        this.cost = cost;
    }

    public String getId() {
        return id;
    }

    public String getDescription() {
        return description;
    }

    public Double getCost() {
        return cost;
    }
}
Run Code Online (Sandbox Code Playgroud)

重用Money“项目”值

如果您没有自由创建新对象并需要重用Money,您可以配置您XmlMapper的忽略未知属性并将所有属性放在Money对象上。

配置XmlMapper为忽略未知属性

我建议XmlMapper像这样扩展:

public class CustomXmlMapper extends XmlMapper {
    public CustomXmlMapper() {
        configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }
}
Run Code Online (Sandbox Code Playgroud)

将所有可能的属性添加到 Money

当元素存在时,这将填充属性。例如,当“项目”被反序列化时:id, description, 和cost将被填充并且currency将为空。

public class Money {
    @JacksonXmlProperty(isAttribute = true)
    public final String id;
    public final String description;
    private final String currency;
    private final Double cost;

    @JsonCreator
    public Money(@JsonProperty("id") String id,
                 @JsonProperty("description") String description,
                 @JsonProperty("currency") String currency,
                 @JsonProperty("cost") Double cost) {
        this.id = id;
        this.description = description;
        this.currency = currency;
        this.cost = cost;
    }

    public String getId() {
        return id;
    }

    public String getDescription() {
        return description;
    }

    public String getCurrency() {
        return currency;
    }

    public Double getCost() {
        return cost;
    }
}
Run Code Online (Sandbox Code Playgroud)