简体   繁体   English

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

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

I am trying to parse an XML file using JAXB which contains a list of items. 我试图使用包含项列表的JAXB解析XML文件。 The class of the items depends on the value of an element in the XML. 项的类取决于XML中元素的值。

This is a legacy system and I can't easily change the input format. 这是一个遗留系统,我不能轻易改变输入格式。

For example, given the following XML and class definitions: 例如,给定以下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;
}

As it works now, the items list contains two Item objects. 现在可以使用,项目列表包含两个Item对象。

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". 我需要生成的Root对象中的项列表包含两个ItemA对象,一个对象为a“a1”,另一个对象为“a2”。

If the type element is "b", I need the items list to contain ItemB objects. 如果type元素是“b”,我需要item列表包含ItemB对象。

There will only be one type element specified in a single XML file. 在单个XML文件中只指定一个类型元素。

I have seen several solutions using attribute values, but none using element values. 我已经看到了几个使用属性值的解决方案,但没有使用元素值。

Following the tip of Blaise Doughan you could create an XmlAdapter. 按照Blaise Doughan的提示,你可以创建一个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. Root / Item / ItemA / ItemB是普通的POJO,没有任何注释。

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>

I came up with a solution using @XmlAnyElement and a technique described by Blaise Doughan here . 我想出了一个使用@XmlAnyElement的解决方案和Blaise Doughan 在这里描述的技术。

The Root class becomes: Root类变为:

@XmlRootElement(name = "root")
public class Root {
    @XmlElement
    String type;

    @XmlAnyElement
    List<Element> other;

    List<Item> items = new ArrayList<Item>();
}

Then after unmarshaling the Root object, I iterate over the other Elements and populate my items list by unmarshaling each Element for the proper class: 然后在解组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