JAXB unmarshalling multiple XML elements into single class
Solution 1
@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());
}
}
}
Solution 2
You can save your coding time by automating this process in JAXB:
Create a XML schema for your XML using the link below as save it as output.xsd file: http://www.xmlforasp.net/CodeBank/System_Xml_Schema/BuildSchema/BuildXMLSchema.aspx
Run the batch script file (name it as output.bat) below from the project root folder (.) using JDK as only JDK has xjc.exe tool (fill in the necessary details):
"C:\Program Files\Java\jdk1.6.0_24\bin\xjc.exe" -p %1 %2 -d %3
where...
syntax: output.bat %1 %2 %3
%1 = target package name
%2 = full file path name of the generated XML schema .xsd
%3 = root source folder to store generated JAXB java files
Example:
let say project folder is organized as follows:
.
\_src
Run following at command prompt from (.):
output.bat com.project.xml .\output.xsd .\src
It will create a few files:
.
\_src
\_com
\_project
\_xml
|_ObjectFactory.java
|_Output.java
Then, you can create a few useful methods below to manipulate Output
objects:
private JAXBContext jaxbContext = null;
private Unmarshaller unmarshaller = null;
private Marshaller marshaller = null;
public OutputManager(String packageName) {
try {
jaxbContext = JAXBContext.newInstance(packageName);
unmarshaller = jaxbContext.createUnmarshaller();
marshaller = jaxbContext.createMarshaller();
} catch (JAXBException e) {
}
}
public Output loadXML(InputStream istrm) {
Output load = null;
try {
Object o = unmarshaller.unmarshal(istrm);
if (o != null) {
load = (Output) o;
}
} catch (JAXBException e) {
JOptionPane.showMessageDialog(null, e.getLocalizedMessage(), e.getClass().getSimpleName(), JOptionPane.ERROR_MESSAGE);
}
return load;
}
public void saveXML(Object o, java.io.File file) {
Output save = null;
try {
save = (Output) o;
if (save != null) {
marshaller.marshal(save, file);
}
} catch (JAXBException e) {
JOptionPane.showMessageDialog(null, e.getLocalizedMessage(), e.getClass().getSimpleName(), JOptionPane.ERROR_MESSAGE);
}
}
public void saveXML(Object o, FileOutputStream ostrm) {
Output save = null;
try {
save = (Output) o;
if (save != null) {
marshaller.marshal(save, ostrm);
}
} catch (JAXBException e) {
JOptionPane.showMessageDialog(null, e.getLocalizedMessage(), e.getClass().getSimpleName(), JOptionPane.ERROR_MESSAGE);
}
}
andyb
Coding since 1993. Always try to remember that the first implementation is almost never the most elegant solution. I like writing clever but not overly complex code. "Simplicity is prerequisite for reliability" - Edsger W. Dijkstra Most of the principles I have adopted are listed in The Principles of Good Programming.
Updated on March 17, 2020Comments
-
andyb about 4 years
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 withid
,type
andname
fields. Therefore I would like to unmarshal into a singleList<Channel>
on theWrapper
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 transientChannelName/name
on theChannel
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 :)
-
andyb about 13 yearsWas hoping you'd be on @Blaise :) I had a look at XmlPath but couldn't work out how to look up a name for a given id. So when unmarshalling the first
Channel
, the XPath I want to pick up the name isChannelName[id="1"]/name
but couldn't see how to plug theid
in. I'll have a look at your blog right now. -
bdoughan about 13 years@andyb - We're currently working on a related feature (see bugs.eclipse.org/339596). The initial phase is aimed at conditions on the attribute (i.e. ChannelName[@id="1"]/Name). Do you need to marshal to or just unmarshal?
-
andyb about 13 years@Blaise - I only need to unmarshal and it looks like the predicate feature might be the solution since I need to grab data from elements elsewhere in the document? Also, I've looked at your (excellent) blog and have almost got MOXy working in Spring. Took a while for me to understand about the jaxb.properties and jaxb.index files. I am currently getting a
class org.springframework.oxm.jaxb.Jaxb2Marshaller$ByteArrayDataSource requires a zero argument constructor
exception though. I'm using Eclipse Persistence Services 2.3.0.v20110312-r9123 -
andyb about 13 yearsOK, so I have solved the zero argument constructor error. It was due to my misunderstanding of what should go in the
jaxb.index
file and I had listed my XMLHelper class as well! See MOXy Spring JAXB Annotations guide if anyone wants to know the example I was following. -
andyb about 13 yearsMany thanks for this @Blaise. I am away from my code at the moment but will try this out tomorrow. I might be almost there with the predicate solution you suggested. I am planning to run the XML through a simple XSLT first to add the attributes necessary to link the elements together.
-
bdoughan about 13 yearsXSLT is a good approach that integrates cleanly with JAXB: stackoverflow.com/questions/5112404/…
-
andyb about 13 yearsThis is useful but I do not think it will solve my problem as I really want to write as little code as possible but also manipulate the XML structure at bind time. Writing as little code as possible is not because I am lazy, I just truly think it is unnecessary and also adds complexity and requires more extensive testing. Simple, elegant code makes me happy. However, +1 for the answer as I was not aware of the link or tool you mentioned.
-
andyb about 13 yearsThanks for the XSLT link and the answer update. I suspected that it might require some helper objects. I have decided to use XSLT to bend the XML into a nicer structure before binding, mainly as I know XSLT/XPath well but it also doesn't involve extra objects and is less complex. I don't think that the MOXy predicate mapping feature is suitable since I need to combine XML elements based on a variable attribute value match on the predicate. i.e. For
Channel@id=1 I
would need to findChannelName[@id="1"]/Name
and then a different element for @id=2 -
andyb about 13 yearsThat said, I'm still going to use MOXy as I like the @XmlPath annotation. I'll also follow it's development. I just think I am attempting to bind and re-organise a very flat XML structure at the same time is too complex without either using helper objects or transformation. I thought I could get away with some very clever annotations. Anyway, thanks again and answer accepted :-)
-
eee about 13 years@andyb: Maybe you can automate first and later edit the generated files.
-
bdoughan about 13 years@andyb - The predicate feature is now available, for more information see: bdoughan.blogspot.com/2011/03/…