简体   繁体   中英

Using JAXB to unmarshal xml with map-like structure to object

I'm trying to unmarshal poorly designed XML into an object. The XML was built using a generic type element which can have any number of items with any name . Depending on the value of type in Something below, the include properties will be different. It's basically just bypassing the XSD rulse (yes, it has a XSD, but it's utterly useless).

The XML I get:

<Something type="actualType">
   <Property name="prop1">value1</Property>
   <Property name="prop2">value2</Property>
   ...
</Something>

What it should have been:

<actualType>
   <prop1>Value1</prop1>
   <prop2>Value2</prop1>
</actualType>

How it should be represented in Java:

@XmlType(name="actualType")
public class ActualType
{
   @XmlElement
   public X prop1

   @XmlElement
   public Y prop2

}

The actual question(s):

Is there any basic support for something like this in Jaxb (without external dependencies)? If not, can I write custom annotations such that I will be able to reuse the same logic for other services using this schema?

There are a couple of ways you could support this use case:

OPTION #1 - Any JAXB (JSR-222) Implementation

If you only need to read the XML to objects, then you could leverage a StreamReaderDelegate and do the following. Basically it makes the bad XML appear as if it were the good XML:

import javax.xml.bind.*;
import javax.xml.stream.*;
import javax.xml.stream.util.StreamReaderDelegate;
import javax.xml.transform.stream.StreamSource;

public class Demo {

    public static void main(String[] args) throws Exception {

        XMLInputFactory xif = XMLInputFactory.newFactory();
        XMLStreamReader xsr = xif.createXMLStreamReader(new StreamSource("src/forum16529016/input.xml"));

        xsr = new StreamReaderDelegate(xsr) {

            @Override
            public String getLocalName() {
                String localName = super.getLocalName();
                if(!"Property".equals(localName) && super.getEventType() == XMLStreamReader.START_ELEMENT) {
                    return localName;
                }
                if(super.getEventType() == XMLStreamReader.START_ELEMENT) {
                    return super.getAttributeValue(null, "name");
                }
                return localName;
            }
        };

        JAXBContext jc = JAXBContext.newInstance(ActualType.class);
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        ActualType actualType = unmarshaller.unmarshal(xsr, ActualType.class).getValue();
        System.out.println(actualType.prop1);
        System.out.println(actualType.prop2);
    }
}

OPTION #2 - EclipseLink JAXB (MOXy's) @XmlPath Extension

Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.

MOXy has an @XmlPath extension that enables you to map this use case.

import javax.xml.bind.annotation.XmlType;
import org.eclipse.persistence.oxm.annotations.XmlPath;

@XmlType(name="actualType")
public class ActualType
{
   @XmlPath("Property[@name='prop1']/text())
   public X prop1

   @XmlPath("Property[@name='prop1']/text())
   public Y prop2

}

For More Information

This is not a direct answer to your question, but you could simply read the bad xml's as they are in object Bad and then define the class Good with constructor Good(Bad bad). The Good class would have the properties defined as members and its constructor would fetch them from the Bad object.

An alternative would be to have just one class, but keeping the "bad" members private and then calling some method after the unmarshalling to fill up the good members (which are untouched by jaxb). See callback .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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