I am trying to parse an XML file using JAXB which contains a list of items. The class of the items depends on the value of an element in the XML.
This is a legacy system and I can't easily change the input format.
For example, given the following XML and class definitions:
<root>
<type>a</type>
<item>
<a>a1</a>
</item>
<item>
<a>a2</a>
</item>
</root>
@XmlRootElement(name = "root")
public class Root {
@XmlElement
String type;
@XmlElement(name="item")
List<Item> items;
}
public class Item {}
public class ItemA extends Item {
@XmlElement
String a;
}
public class ItemB extends Item {
@XmlElement
String b;
}
As it works now, the items list contains two Item objects.
I need the items list in the resulting Root object to contain two ItemA objects, one with a="a1" and the other with a="a2".
If the type element is "b", I need the items list to contain ItemB objects.
There will only be one type element specified in a single XML file.
I have seen several solutions using attribute values, but none using element values.
Following the tip of Blaise Doughan you could create an XmlAdapter. Unfortunately Adapters are not available on the root level so you would have to add a bit extra code during un-/marshalling.
Root/Item/ItemA/ItemB are plain POJOs without any annotations here.
The Adapter with the adapted types:
public class RootAdapter extends XmlAdapter<AdaptedRoot, Root>
{
@Override
public Root unmarshal( AdaptedRoot v ) throws Exception
{
Root root = new Root();
root.type = v.type;
for ( AdaptedItem adaptedItem : v.items )
{
if ( v.type.equals( "a" ) )
{
ItemA a = new ItemA();
a.a = adaptedItem.a;
root.items.add( a );
}
if ( v.type.equals( "b" ) )
{
ItemB b = new ItemB();
b.b = adaptedItem.b;
root.items.add( b );
}
}
return root;
}
@Override
public AdaptedRoot marshal( Root v ) throws Exception
{
AdaptedRoot adapted = new AdaptedRoot();
adapted.type = v.type;
for ( Item item : v.items )
{
AdaptedItem adaptedItem = new AdaptedItem();
if ( v.type.equals( "a" ) )
{
adaptedItem.a = ((ItemA) item).a;
}
if ( v.type.equals( "b" ) )
{
adaptedItem.b = ((ItemB) item).b;
}
adapted.items.add( adaptedItem );
}
return adapted;
}
@XmlRootElement( name = "root" )
public static class AdaptedRoot
{
@XmlElement
String type;
@XmlElement( name = "item" )
List<AdaptedItem> items = new ArrayList<>();
}
public static class AdaptedItem
{
@XmlElement
String a;
@XmlElement
String b;
}
}
Un-/marshalling could be done like this:
public static void main( String[] args ) throws Exception
{
String rawRootA = "<root><type>a</type><item><a>a1</a></item><item><a>a2</a></item></root>";
String rawRootB = "<root><type>b</type><item><b>b1</b></item><item><b>b2</b></item></root>";
Root rootA = unmarshal( rawRootA );
for ( Item item : rootA.items )
{
System.out.println( item.getClass().getSimpleName() );
}
print( rootA );
Root rootB = unmarshal( rawRootB );
for ( Item item : rootB.items )
{
System.out.println( item.getClass().getSimpleName() );
}
print( rootB );
}
public static Root unmarshal( String xml ) throws Exception
{
JAXBContext context = JAXBContext.newInstance( AdaptedRoot.class );
Unmarshaller unmarshaller = context.createUnmarshaller();
XmlAdapter<AdaptedRoot, Root> adapter = new RootAdapter();
AdaptedRoot adapted = (AdaptedRoot) unmarshaller.unmarshal( new StringReader( xml ) );
return adapter.unmarshal( adapted );
}
public static void print( Root root ) throws Exception
{
JAXBContext context = JAXBContext.newInstance( AdaptedRoot.class );
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, true );
XmlAdapter<AdaptedRoot, Root> adapter = new RootAdapter();
AdaptedRoot adaptedRoot = adapter.marshal( root );
marshaller.marshal( adaptedRoot, System.out );
}
with the expected output:
ItemA
ItemA
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<type>a</type>
<item>
<a>a1</a>
</item>
<item>
<a>a2</a>
</item>
</root>
ItemB
ItemB
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<type>b</type>
<item>
<b>b1</b>
</item>
<item>
<b>b2</b>
</item>
</root>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With