繁体   English   中英

JAXB:基于元素值对子类进行解组

[英]JAXB: Unmarshal to subclass based on element value

我试图使用包含项列表的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对象,一个对象为a“a1”,另一个对象为“a2”。

如果type元素是“b”,我需要item列表包含ItemB对象。

在单个XML文件中只指定一个类型元素。

我已经看到了几个使用属性值的解决方案,但没有使用元素值。

按照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>

我想出了一个使用@XmlAnyElement的解决方案和Blaise Doughan 在这里描述的技术。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM