JAXB Unmarsharller: XMLAttributes are mapped, but its XmlElements are not

Viewed 235

TL;DR: When I unmarshall from XML to POJO I only have the XmlAttributes well mapped, however all XmlElement are null.

Hi there!

I have the following problem. This class was generated with JAXB

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
    "activity"
})
@XmlRootElement(name = "activityDetails", namespace = "http://lorem.ipsum.com/")
public class ActivityDetails {

    @XmlElement(required = true)
    protected Activity activity;
    @XmlAttribute(name = "schemaVersion", required = true)
    protected float schemaVersion;
    @XmlAttribute(name = "actionType")
    protected ActionTypes actionType;
    @XmlSchemaType(name = "dateTime")
    protected XMLGregorianCalendar timestamp;

This is an example XML

<activityDetails 
    actionType="CREATE" 
    schemaVersion="2.0" 
    timestamp="2020-01-02T15:31:50.549Z" 
    xmlns="http://lorem.ipsum.com/">
    <activity>
        <activityId>
            <start>2020-01-01T03:00:00Z</start>
            <end>2020-01-02T02:59:00Z</end>
        </activityId>
    </activity>
</activityDetails>

But, when this code is executed (please don't judge me, it's legacy code):

    Object xmlClass = Class.forName("com.lorem.ipsum." + className).getConstructor().newInstance();
    final JAXBContext jaxbContext = JAXBContext.newInstance(xmlClass.getClass());
    Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
    Object object = unmarshaller.unmarshal(new StringReader(element));

The result "object" have all XmlAttribute well mapped, but no one of their XmlElement IntelliJ Debug

PS: The namespace in the generated class was added manually, if I don't do that I have this exception:

javax.xml.bind.UnmarshalException: unexpected element (uri:"http://lorem.ipsum.com/", local:"activityDetails"). Expected elements are <{}activityDetails>

Thanks in advance.

UPDATED: If I set all @XmlElement the namespace property I finally map the elements, but I must intervene all the classes. Is there another way to achieve that without having to modify all the fields of all the classes?

2 Answers

I guess I am able to figure out the issue. This is happening because you have not provided any prefix to your namespace in XML. Following code would work for your provided sample XML:

XML:

<activityDetails
        actionType="CREATE"
        schemaVersion="2.0"
        timestamp="2020-01-02T15:31:50.549Z"
        xmlns:ns0="http://lorem.ipsum.com/">
    <activity>
        <activityId>
            <start>2020-01-01T03:00:00Z</start>
            <end>2020-01-02T02:59:00Z</end>
        </activityId>
    </activity>
</activityDetails>

ActivityDetails.class:

@XmlAccessorType(XmlAccessType.FIELD)
@Data
@XmlRootElement(name = "activityDetails", namespace = "http://lorem.ipsum.com/")
public class ActivityDetails {
    private Activity activity;

    @XmlAttribute
    private float schemaVersion;

    @XmlAttribute
    private String actionType;

    @XmlAttribute
    private String timestamp;
}


Activity.class:

@Data
@XmlAccessorType(XmlAccessType.FIELD)
public class Activity {
    private ActivityID activityId;
}

ActivityID.class:

@Data
@XmlAccessorType(XmlAccessType.FIELD)
public class ActivityID {
    private String start;
    private String end;
}

Main.class:

public class Main {
    public static void main(String[] args) throws JAXBException, XMLStreamException {
        final InputStream inputStream = Main.class.getClassLoader().getResourceAsStream("activity.xml");
        final XMLStreamReader xmlStreamReader = XMLInputFactory.newInstance().createXMLStreamReader(inputStream);
        final Unmarshaller unmarshaller = JAXBContext.newInstance(ActivityDetails.class).createUnmarshaller();
        final ActivityDetails activityDetails = unmarshaller.unmarshal(xmlStreamReader, ActivityDetails.class).getValue();
        System.out.println(activityDetails.toString());

        Marshaller marshaller = JAXBContext.newInstance(ActivityDetails.class).createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
        marshaller.marshal(activityDetails, System.out);
    }
}

Following is your output:

ActivityDetails(activity=Activity(activityId=ActivityID(start=2020-01-01T03:00:00Z, end=2020-01-02T02:59:00Z)), schemaVersion=2.0, actionType=CREATE, timestamp=2020-01-02T15:31:50.549Z)
<ns0:activityDetails xmlns:ns0="http://lorem.ipsum.com/" schemaVersion="2.0" actionType="CREATE" timestamp="2020-01-02T15:31:50.549Z">
   <activity>
      <activityId>
         <start>2020-01-01T03:00:00Z</start>
         <end>2020-01-02T02:59:00Z</end>
      </activityId>
   </activity>
</ns0:activityDetails>

Finally I found this solution: Instead of putting the namespace in each of the XMLElements I put the following package-info.java in the package of the generated classes.

@XmlSchema(
    elementFormDefault=XmlNsForm.QUALIFIED,
    namespace="http://lorem.ipsum.com/",
    xmlns={@XmlNs(prefix="", namespaceURI="http://lorem.ipsum.com/")})
package com.lorem.ipsum.generated;

import javax.xml.bind.annotation.XmlNs;
import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;
Related