Is there a a way to add a wrapper element during the marshalling in JAXB for non collection element?

Viewed 246

I am trying to create the XML using the marshaling method from the JAXB library. For this approach, I am using the standard java class and assigning the annotation to it. I would like to have a wrapper element to some of the element in the class which are not Collections.

I am ware of @XmlElementWrapper which can be used for Collections but I do not have a collection. I have some elements for which I want to have an outer XML element so that it can match the standard XSD.

I just want to know if there is a way to add an outer XML element to some of the elements so that the create XML matches the standard XSD format. If there is no direct approach then what alternative approach can I take?

As we can see from the example XML the carinfo tag is a outer (wrapper) tag for the elements engine and year but this carInfo tag is not present within the Parent or Child class. As these are standard classes I cannot modify the fields/ Add new fields. Hence I would like to handle wrapper XML tag addition using the JAXB.

Following is the input JSON:

{
   "brand": "Ferari",
   "build": "Italy",
    "engine": "Mercedes",
    "year": "2021"
   
}

Following is the output XML that I would like to have:

<?xml version="1.0"?>
<Car>
    <brand>Ferari</brand>
    <build>Italy</build>
    <carinfo>
        <engine>Mercedes</engine>
        <year>2021</year>
    </carinfo>
</Car>

Following are my classes:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlTransient
@XmlType(name = "Parent")
public class Parent{
    private String brand;
    private String year;
    
    //Getters and Setters avoided
}

Class based on which XML is being created:

@XmlRootElement(name = "Car")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "Child",propOrder={"brand","engine","build","year"})
public class Child extends Parent{
    private String engine;
    private String build;
    
    //Getter and Setters avoided
}

The main class which will create the XML: I am using the Jackson library to read the values from the Json file and create the XML based on Jaxb annotations.

public class Main{

     public static void main(String []args){
        JsonFactory jsonFactory = new JsonFactory();
        JsonParser jsonParser = jsonFactory.createParser(new File(Main.class.getClassLoader().getResource("input.json").toURI()));
        Child eventInfo = jsonParser.readValueAs(Child.class);
        JAXBContext context = JAXBContext.newInstance(Child.class);
        Marshaller mar = context.createMarshaller();
        mar.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
        mar.marshal(eventInfo, System.out);
     }
     
     
}
2 Answers

From my point of view, it is possible to include a new tag inside your xml preserving the original classes with a combination of jaxb and javax.xml.transform api. The first thing is create a xsl stylesheet file for the transformation like below:

template.xsl

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0"   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<Car>
<brand>
<xsl:value-of   select="/Car/brand"/>
</brand>
<build>
<xsl:value-of   select="/Car/build"/>
</build>
<carinfo>
<engine>
<xsl:value-of   select="/Car/engine"/>
</engine>
<year>
<xsl:value-of   select="/Car/year"/>
</year>
</carinfo>
</Car>
</xsl:template>
</xsl:stylesheet>

Then you can use the set a Transformer and pass to it a new JAXBSource like below:

Child eventInfo = jsonParser.readValueAs(Child.class);
JAXBContext context = JAXBContext.newInstance(Child.class);
           
// Create Transformer
TransformerFactory tf = TransformerFactory.newInstance();
StreamSource xslt = new StreamSource(xslFile);
Transformer transformer = tf.newTransformer(xslt);
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty(OutputKeys.STANDALONE, "yes");

//Print to the stdout the desired xml with the new nested tag 
//including the two existing tags
StreamResult result = new StreamResult(System.out);
JAXBSource source = new JAXBSource(context, eventInfo);
transformer.transform(source, result);

Finally, I was able to get it using the @XmlPath from Moxy which is and library based on the Jaxb. You can use the @XmlPath to any of the elements such as collection, non-collection, String etc. I have spent a lot of time trying to configure and do workarounds so posting the answer here so it can be useful to someone in the future and they do not endup so much time as me :)

Please find the answer for configuring the Moxy here

Also, see how to get the wrapper elements for the XML using the @XmlPath here

You can also read more about it in the EclipseLink documentation.

Related