CSB
CSB

Reputation: 15

How to marshall XML with dynamic element using JAXB2

I'm developing an SOAP client and I'm looking for more sophisticated solution to marshalling objects into XML-string using Jaxb2 library.

The goal is to marshall an object, which acts as a wrapper for any-type element. Example:

<Action id="5">
   <Employee id="10">
      <Name>John</Name>
   </Employee>
</Action>

or.

 <Action id="5">
    <Department id="ABC">
       <Name>Economy Department</Name>
       <ParentId>CDE</ParentId>
    </Department>
 </Action>

Note: The xml root (Action) contains either "Employee" or "Department" or anything else.

My current working solution is as follows:

@XmlRootElement(name = "Action")
abstract class Action {

   @XmlAttribute(name = "id")
   protected String id;

}

class EmployeeAction extends Action {
    
   @XmlElement(name = "Employee")
   protected Employee employee;

}

class DepartmentAction extends Action {
    
   @XmlElement(name = "Department")
   protected Department department;

}

This works fine, but I'm looking for more universal solution, without the need to create class for each type (*Action extends Action). Name of the element must always be the same as the className of the (dynamic) type. My idea is something like this:

public class Action<T> {

   @XmlAttribute(name = "id")
   protected String id;

   @XmlElement(name = "getClass().getSimpleName()") //???
   protected T element;

}

... and marshalling something like:

Action<?> action = ...;

JAXBContext context = JAXBContext.newInstance(Action.class, action.getElement().getClass());
Marshaller marshaller = context.createMarshaller();

try(ByteArrayOutputStream outStream = new ByteArrayOutputStream()) {
   marshaller.marshal(action, outStream);
   return outStream.toString();
}

Is something like this possible?

Thanks in advance.

Upvotes: 0

Views: 567

Answers (1)

BATMAN_2008
BATMAN_2008

Reputation: 3530

You can do something like this for the above-provided XML:

METHOD-1

Action.class;

@XmlRootElement(name = "Action")
@Data
@XmlAccessorType(XmlAccessType.FIELD)
public class Action {
    @XmlAttribute(name = "id")
    protected String id;

    @XmlElements({
            @XmlElement(name = "Employee", type = Employee.class),
            @XmlElement(name = "Department", type = Department .class),
    })
    private ActionItem type;
}

ActionItem.class;

@Data
@XmlAccessorType(XmlAccessType.NONE)
public class ActionItem {
    @XmlElement(name = "Name")
    protected String name;
}

Employee.class;

@Data
@XmlAccessorType(XmlAccessType.NONE)
public class Employee extends ActionItem {

    @XmlAttribute(name = "id")
    private String id;
}

Department.class;

@Data
@XmlAccessorType(XmlAccessType.NONE)
public class Department extends ActionItem {
    @XmlAttribute
    private String id;

    @XmlElement(name = "ParentId")
    private String parentID;
}

Main.class:


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

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

If you provided Employee XML then it produces the following result:

Action(id=5, type=Employee(id=10))
<Action id="5">
   <Employee id="10">
      <Name>John</Name>
   </Employee>
</Action>

If you provide the Department XML then it produces the following result:

Action(id=5, type=Department(parentID=CDE))
<Action id="5">
   <Department>
      <Name>Economy Department</Name>
      <ParentId>CDE</ParentId>
   </Department>
</Action>

METHOD-2

Creating the interface and using it:

public interface ActionItem2 {
}

Action.class uses the created interface.

@XmlRootElement(name = "Action")
@Data
@XmlAccessorType(XmlAccessType.FIELD)
public class Action {
    @XmlAttribute(name = "id")
    protected String id;

    @XmlElements({
            @XmlElement(name = "Employee", type = Employee.class),
            @XmlElement(name = "Department", type = Department .class),
    })
    private ActionItem2 type;
}

Employee.class which implements the created interface

@Data
@XmlAccessorType(XmlAccessType.NONE)
public class Employee implements ActionItem2 {

    @XmlAttribute(name = "id")
    private String id;

    @XmlElement(name = "Name")
    protected String name;
}

Department.class which implements the created interface

@Data
@XmlAccessorType(XmlAccessType.NONE)
public class Department implements ActionItem2 {
    @XmlAttribute
    private String id;

    @XmlElement(name = "ParentId")
    private String parentID;

    @XmlElement(name = "Name")
    protected String name;
}

Main.class(No changes)

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

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

The result would be the same.

Method - 3

If you don't want to modify your POJO then you can do something like this:

@XmlRootElement(name = "Action")
@Data
@XmlAccessorType(XmlAccessType.FIELD)
public class Action {
    @XmlAttribute(name = "id")
    protected String id;

    @XmlElements({
            @XmlElement(name = "Employee", type = Employee.class),
            @XmlElement(name = "Department", type = Department .class),
    })
    private Object type;
}

Employee.class:

@Data
@XmlAccessorType(XmlAccessType.NONE)
public class Employee {

    @XmlAttribute(name = "id")
    private String id;

    @XmlElement(name = "Name")
    protected String name;
}

Department.class:


@Data
@XmlAccessorType(XmlAccessType.NONE)
public class Department {

    @XmlAttribute
    private String id;

    @XmlElement(name = "ParentId")
    private String parentID;

    @XmlElement(name = "Name")
    protected String name;
}

This will provide the same output.

Upvotes: 1

Related Questions