我正在尝试使用包含项列表的JAXB来解析XML文件。项的类取决于XML中元素的值。
这是遗留系统,我无法轻易更改输入格式。
例如,给定以下XML和类定义:
<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;
}
现在可以使用,项目列表包含两个Item对象。
我需要在生成的Root对象中包含项目列表,以包含两个ItemA对象,一个具有=“a1”,另一个具有=“a2”。
如果type元素是“b”,我需要项目列表包含ItemB对象。
在单个XML文件中只会指定一个类型元素。
我见过几个使用属性值的解决方案,但没有使用元素值。
答案 0 :(得分:2)
按照Blaise Doughan的提示,您可以创建一个XmlAdapter。遗憾的是,根级别不提供适配器,因此您必须在取消/编组期间添加一些额外的代码。
Root / Item / ItemA / ItemB是普通的POJO,没有任何注释。
适配类型的适配器:
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;
}
}
取消/编组可以这样做:
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 );
}
预期输出:
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>
答案 1 :(得分:1)
我想出了一个使用@XmlAnyElement和Blaise Doughan here描述的技术的解决方案。
Root类变为:
@XmlRootElement(name = "root")
public class Root {
@XmlElement
String type;
@XmlAnyElement
List<Element> other;
List<Item> items = new ArrayList<Item>();
}
然后在解组Root对象之后,迭代其他Elements并通过解组每个Element来填充我的项目列表:
Class unmarshalClass = null;
switch (root.type.toLowerCase()) {
case "a":
unmarshalClass = ItemA.class;
break;
case "b":
unmarshalClass = ItemB.class;
break;
default:
throw new Exception("Unknown type " + root.type);
}
for (Element element : root.other) {
if (element.getNodeName().equalsIgnoreCase("item")) {
root.items.add(
(Item)jaxbUnmarshaller
.unmarshal(element, unmarshalClass).getValue());
}
}