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

Learn how our document data model can map directly to how you program your app, and native database features like secondary indexes, geospatial and text search give you full access to your data. Brought to you in partnership with MongoDB.

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);
    }
 
}


Discover when your data grows or your application performance demands increase, MongoDB Atlas allows you to scale out your deployment with an automated sharding process that ensures zero application downtime. Brought to you in partnership with MongoDB.

Topics:

Published at DZone with permission of Blaise Doughan, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

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

{{ parent.tldr }}

{{ parent.urlSource.name }}