I have the following XML structure, which is modelling a single concept across multiple XML elements. This format is not in my control.
<Output>
<Wrapper>
<Channel>
<id>1</id>
<type>x</type>
</Channel>
<Channel>
<id>2</id>
<type>y</type>
</Channel>
<ChannelName>
<id>1</id>
<name>Channel name</name>
</ChannelName>
<ChannelName>
<id>2</id>
<name>Another channel name</name>
</ChannelName>
</Wrapper>
</Output>
I want to model this in a database that I do have control over and can have a more simple Channel
table with id
, type
and name
fields. Therefore I would like to unmarshal into a single List<Channel>
on the Wrapper
class.
Can this be done with @Xml...
annotations automatically? I am currently using JAXB to unmarshal into separate @XmlElement(name="Channel")
and @XmlElement(name="ChannelName")
class lists and then post-processing the transient ChannelName/name
on the Channel
but I am thinking there must be an easier automated way to map these elements. Or is it a job for XSLT?
It might help to know that the XML is coming in as an HTTP file POST file and I'm using Spring 3, Java and Hibernate. I'm hoping something in EclipseLink JAXB (MOXy) might help :)
@XmlElementWrapper will do the job:
@XmlElementWrapper(name="Wrapper")
@XmlElement(name="Channel")
private List<Channel> channels;
For more advanced cases you can use the @XmlPath extension in EclipseLink JAXB (MOXy):
Here is what I have so far. I'm still trying to eliminate the need for the helper objects. This example requires EclipseLink JAXB (MOXy).
Model Objects
Your model objects are:
package example;
import java.util.ArrayList;
import java.util.List;
public class Wrapper {
private List<Channel> channels = new ArrayList<Channel>();
public List<Channel> getChannels() {
return channels;
}
public void setChannels(List<Channel> channels) {
this.channels = channels;
}
}
and:
package example;
import javax.xml.bind.annotation.XmlID;
public class Channel {
private String id;
private String type;
private String name;
@XmlID
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Helper Objects
My current solution involves some helper objects:
package example.adapted;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;
import example.Channel;
import example.Wrapper;
@XmlRootElement(name="Output")
@XmlType(propOrder={"channels", "channelNames"})
public class AdaptedWrapper {
private Wrapper wrapper = new Wrapper();
private List<ChannelName> channelNames;
@XmlTransient
public Wrapper getWrapper() {
for(ChannelName channelName : channelNames) {
channelName.getChannel().setName(channelName.getName());
}
return wrapper;
}
@XmlElementWrapper(name="Wrapper")
@XmlElement(name="Channel")
public List<Channel> getChannels() {
return wrapper.getChannels();
}
public void setChannels(List<Channel> channels) {
wrapper.setChannels(channels);
}
@XmlElementWrapper(name="Wrapper")
@XmlElement(name="ChannelName")
public List<ChannelName> getChannelNames() {
return channelNames;
}
public void setChannelNames(List<ChannelName> channelNames) {
this.channelNames = channelNames;
}
}
and:
package example.adapted;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlIDREF;
import example.Channel;
public class ChannelName {
private String name;
private Channel channel;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@XmlIDREF
@XmlElement(name="id")
public Channel getChannel() {
return channel;
}
public void setChannel(Channel channel) {
this.channel = channel;
}
}
Demo Code
package example;
import java.io.File;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import example.adapted.AdaptedWrapper;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(AdaptedWrapper.class);
File xml = new File("input.xml");
Unmarshaller unmarshaller = jc.createUnmarshaller();
AdaptedWrapper adaptedWrapper = (AdaptedWrapper) unmarshaller.unmarshal(xml);
Wrapper wrapper = adaptedWrapper.getWrapper();
for(Channel channel : wrapper.getChannels()) {
System.out.println(channel.getName());
}
}
}