Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Mapping Bad XML - Enumerated Collection Elements

DZone's Guide to

Mapping Bad XML - Enumerated Collection Elements

· Java Zone ·
Free Resource

Build vs Buy a Data Quality Solution: Which is Best for You? Gain insights on a hybrid approach. Download white paper now!

In a  previous post I introduced  EclipseLink JAXB (MOXy)'s  @XmlVariableNode extension.  In this post I'll demonstrate how  @XmlVariableNode could be leveraged to handle an  interesting question I came across on Stack Overflow.  In that question instead of a collection being represented with an element that appeared multiple times, the element name contained the index.  While I would never recommend structuring your XML document this way sometimes you encounter it and need to be able to map it.

XML Input (input.xml)/Output 

Below is what the problematic XML looks like.  In this example the number of different elements prefixed with  phone-number is not known.
<?xml version="1.0" encoding="UTF-8"?>
<customer>
    <phone-number1 type="work">555-1111</phone-number1>
    <phone-number2 type="home">555-2222</phone-number2>
    <phone-number3 type="cell">555-3333</phone-number3>
</customer>

Java Model

Below is the Java model that we will use for this example.

Customer

Ultimately we wish to represent the XML in Java as a Customer that has a collection of PhoneNumber instances.  For this use case we will leverage MOXy's @XmlVariableNode extension.  The referenced object does not have a property that we can use to store the node name, so we will use an XmlAdapter to convert PhoneNumber into an object that does.

package blog.variablenode.enumeratedlist;
 
import java.util.*;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.eclipse.persistence.oxm.annotations.XmlVariableNode;
 
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {
 
    @XmlVariableNode("nodeName")
    @XmlJavaTypeAdapter(PhoneNumberAdapter.class)
    private List<PhoneNumber> phoneNumbers = new ArrayList<PhoneNumber>();
 
}
PhoneNumber

Note how PhoneNumber does not have a field to store the node name

package blog.variablenode.enumeratedlist;
 
import javax.xml.bind.annotation.*;
 
@XmlAccessorType(XmlAccessType.FIELD)
public class PhoneNumber {
 
    @XmlAttribute
    private String type;
 
    @XmlValue
    private String number;
 
}
XMLAdapter (PhoneNumberAdapter)

We will use an XmlAdapter to convert the PhoneNumber into an AdaptedPhoneNumberAdaptedPhoneNumber has thenodeName field (line 14) that we referenced in the @XmlVariableNode annotation that we used on the phoneNumbersfield in the Customer class.  We will annotate this field with @XmlTransient to prevent it from being marshalled/unmarshalled (see: JAXB and Unmapped Properties).  We will leverage MOXy's @XmlPath extension to avoid having to copy the contents of PhoneNumber into the AdaptedPhoneNumber (see: XPath Based Mapping).

package blog.variablenode.enumeratedlist;
 
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import org.eclipse.persistence.oxm.annotations.XmlPath;
 
public class PhoneNumberAdapter extends XmlAdapter<PhoneNumberAdapter.AdaptedPhoneNumber, PhoneNumber>{
 
    private int counter = 1;
 
    public static class AdaptedPhoneNumber {
 
        @XmlTransient
        public String nodeName;
 
        @XmlPath(".")
        public PhoneNumber phoneNumber;
 
    }
 
    @Override
    public AdaptedPhoneNumber marshal(PhoneNumber phoneNumber) throws Exception {
        AdaptedPhoneNumber adaptedPhoneNumber = new AdaptedPhoneNumber();
        adaptedPhoneNumber.nodeName = "phone-number" + counter++;
        adaptedPhoneNumber.phoneNumber = phoneNumber;
        return adaptedPhoneNumber;
    }
 
    @Override
    public PhoneNumber unmarshal(AdaptedPhoneNumber adaptedPhoneNumber) throws Exception {
        return adaptedPhoneNumber.phoneNumber;
    }
 
}
Demo

The following demo code can be used to prove that everything works.  Since the PhoneNumberAdapter is stateful we need to set and instance of it on the Marshaller (line 17)

package blog.variablenode.enumeratedlist;
 
import java.io.File;
import javax.xml.bind.*;
 
public class Demo {
 
    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Customer.class);
         
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        File xml = new File("src/blog/variablenode/enumeratedlist/input.xml");
        Customer customer = (Customer) unmarshaller.unmarshal(xml);
         
        Marshaller marshaller = jc.createMarshaller();
        PhoneNumberAdapter phoneNumberAdapter = new PhoneNumberAdapter();
        marshaller.setAdapter(phoneNumberAdapter);
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(customer, System.out);
    }
 
}


Build vs Buy a Data Quality Solution: Which is Best for You? Maintaining high quality data is essential for operational efficiency, meaningful analytics and good long-term customer relationships. But, when dealing with multiple sources of data, data quality becomes complex, so you need to know when you should build a custom data quality tools effort over canned solutions. Download our whitepaper for more insights into a hybrid approach.

Topics:

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}