使用Jaxb解组嵌套Map

时间:2017-08-21 09:23:05

标签: java xml jaxb

我需要以下DTO

@XmlRootElement(name = "exchangerate")
@XmlAccessorType(XmlAccessType.FIELD)
public class ExchRates {

    @XmlJavaTypeAdapter(DateAdapter.class)
    private Date date;

    @XmlJavaTypeAdapter(JaxbExchangeRatesMapAdapter.class)
    private Map<CurrencyUnit, Map<CurrencyUnit, Double>> rates = new HashMap<>();
}

如何将此xml解组到上面的DTO中?

<exchangerate>
    <date>2015-05-04</date>
    <EUR>
        <EUR>1</EUR>
        <GBP>0.73788</GBP>
        <USD>1.1152</USD>
    </EUR>
    <GBP>
        <EUR>1.35523</EUR>
        <GBP>1</GBP>
        <USD>1.51136</USD>
    </GBP>
    <USD>
        <EUR>0.8967</EUR>
        <GBP>0.66166</GBP>
        <USD>1</USD>
    </USD>
</exchangerate>

我阅读了一些教程和示例,但我发现没有人所有的键都是xml的节点值。

修改

几个小时后,我接近解决方案。

我的XmlAdapter:

public class JaxbExchangeRatesMapAdapter extends XmlAdapter<JaxbExchangeRatesMap, Map<CurrencyUnit, Map<CurrencyUnit, Double>>> {

    @Override
    public Map<CurrencyUnit, Map<CurrencyUnit, Double>> unmarshal(JaxbExchangeRatesMap v) throws Exception {
        return null;
    }

    @Override
    public JaxbExchangeRatesMap marshal(Map<CurrencyUnit, Map<CurrencyUnit, Double>> v) throws Exception {
        JaxbExchangeRatesMap map = new JaxbExchangeRatesMap();

        for (CurrencyUnit currencyFrom : v.keySet()) {
            Map<CurrencyUnit, Double> from = v.get(currencyFrom);
            JaxbExchangeRatesEntry entry = new JaxbExchangeRatesEntry();
            for (CurrencyUnit currencyTo : from.keySet()) {
                entry.getEntries().add(new JAXBElement<>(new QName(currencyTo.getCurrencyCode()), Double.class, from.get(currencyTo)));
            }
            JAXBElement<JaxbExchangeRatesEntry> jaxbElement = new JAXBElement<>(new QName(currencyFrom.getCurrencyCode()), JaxbExchangeRatesEntry.class, entry);
            map.getEntires().add(jaxbElement);
        }
        return map;
    }

}

我的映射类:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlSeeAlso(JaxbExchangeRatesEntry.class)
public class JaxbExchangeRatesMap extends Printable {

    private static final long serialVersionUID = 15543456767150881L;

    @XmlAnyElement
    private List<JAXBElement<JaxbExchangeRatesEntry>> entires = new ArrayList<>();

    public List<JAXBElement<JaxbExchangeRatesEntry>> getEntires() {
        return entires;
    }

    public JaxbExchangeRatesMap setEntires(List<JAXBElement<JaxbExchangeRatesEntry>> entires) {
        this.entires = entires;
        return this;
    }
}

@XmlAccessorType(XmlAccessType.FIELD)
public class JaxbExchangeRatesEntry extends Printable {

    private static final long serialVersionUID = -694282168028218725L;

    @XmlAnyElement
    private List<JAXBElement<Double>> entries = new ArrayList<>();

    public List<JAXBElement<Double>> getEntries() {
        return entries;
    }

    public JaxbExchangeRatesEntry setEntries(List<JAXBElement<Double>> entries) {
        this.entries = entries;
        return this;
    }
}

我得到了以下结果:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<exchangerate>
    <rates>
        <USD>
            <USD>9.0</USD>
            <EUR>7.0</EUR>
            <GBP>8.0</GBP>
        </USD>
        <EUR>
            <USD>3.0</USD>
            <EUR>1.0</EUR>
            <GBP>2.0</GBP>
        </EUR>
        <GBP>
            <USD>6.0</USD>
            <EUR>4.0</EUR>
            <GBP>5.0</GBP>
        </GBP>
    </rates>
</exchangerate>

如何删除/跳过费率标记?

2 个答案:

答案 0 :(得分:1)

我建议您构建XML,如:

<exchangerate>
    <date>2015-05-04</date>
    <currency code="EUR">
        <rate code="EUR">1</rate >
        <rate code="GBP">0.73788</rate >
        <rate code="USD">1.1152</rate >
    </currency>
    <currency code="GBP">
        <rate code="EUR">1.35523</rate >
        <rate code="GBP">1</rate >
        <rate code="USD">1.51136</rate >
    </currency>
    <currency code="USD">
        <rate code="EUR">0.8967</rate >
        <rate code="GBP">0.66166</rate >
        <rate code="USD">1</rate >
    </currency>
</exchangerate> 

你有多个Classes:

@XmlAccessorType(XmlAccessType.FIELD)
public class ExchangeRates {
    @XmlJavaTypeAdapter(DateAdapter.class)
    private Date date;

    @XmlElement(name="currency")
    private List<Currency> currencies = new ArrayList<>();

    ....
}

@XmlAccessorType(XmlAccessType.FIELD)
public class Currency {
    @XmlAttribute
    private String code;

    @XmlElement(name="rate")
    private List<Rate> rates= new ArrayList<>();

    ....
}


@XmlAccessorType(XmlAccessType.FIELD)
public class Rate {
    @XmlAttribute
    private String code;

    @XmlValue
    private Double value;

    ....
}

答案 1 :(得分:1)

如果您想坚持问题开头所述的原始XML结构,那么使用@XmlJavaTypeAdapter很难或无法解决。 但是你可以重用替代方法 answer to "JAXB nodes to map"并将其应用于您的情况:

ExchRates类中声明List<Element>注释@XmlAnyElement,以便JAXB将其用于编组/解组。

但您需要Map<CurrencyUnit, Map<CurrencyUnit, Double>>Map<String, Map<String, Double>>(我不知道如何创建CurrencyUnit,因此我的解决方案使用String。) 因此,您也声明了这一点,但使用@XmlTransient进行了注释,以便JAXB不会将其用于编组/解组。

最后实现一个私有方法afterUnmarshal(Unmarshaller unmarshaller, Object parent),您可以在其中将内容从List<Element>铲到Map<String, Map<String, Double>>。 如Unmarshal Event Callbacks中所述 JAXB将在适当的时候调用此方法。

如果您需要编写XML文件,您可能还需要一个私有方法beforeMmarshal(Marshaller marshaller),您可以将Map<String, Map<String, Double>>中的内容反馈回List<Element>。 如Marshal Event Callbacks中所述 JAXB将在适当的时候调用此方法。

@XmlRootElement(name = "exchangerate")
@XmlAccessorType(XmlAccessType.FIELD)
public class ExchRates {

    private Date date;

    @XmlAnyElement
    private List<Element> elements;

    @XmlTransient  // don't participate in JAXB marshalling/unmarshalling
    private Map<String, Map<String, Double>> rates;

    @SuppressWarnings("unused")  // called only by JAXB
    private void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
        rates = new HashMap<>();
        for (Element element : elements) {
            String currencyUnit = element.getTagName();
            NodeList subElements = element.getElementsByTagName("*");
            Map<String, Double> subMap = new HashMap<>();
            for (int i = 0; i < subElements.getLength(); i++) {
                Element subElement = (Element) subElements.item(i);
                String currencyUnit2 = subElement.getTagName();
                double value = Double.parseDouble(subElement.getTextContent());
                subMap.put(currencyUnit2, value);
            }
            rates.put(currencyUnit, subMap);
        }   
    }
}