0

I have a JSON file that I am trying to convert into XML using the JAXB annotation approach. Everything is working fine now and I able to convert the JSON to XML. Now I am trying to refactor the code a little bit so that my class would look clean. Hence, I am trying to remove the method which is present in my class and make it JAXB XMLAdapter so that it can be reused by other classes.

Basically I would like to move the XMLSupport method from CarInfo class to XMLAdapter. I am not sure how to populate the CarInfo objects when I move them to the XMLAdapter.

Following is my JSON file (it has been modified for simplicity purpose):

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

Following is the XML that I expect JAXB to provide: (Observe the carInfo tag which is not present in JSON but I need in XML to match the standard XSD)

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

Following are the classes that I have: (Tha Car class that matches the JSON elements)

@XmlAccessorType(XmlAccessType.FIELD)
@XmlTransient
@XmlSeeAlso({MyCar.class});
public class Car{
    private String brand;
    private String build;
    
    @XmlTransient
    private String engine;

    @XmlTransient
    private String year;

    //Getter, Setters and other consturctiores ommited
}

Following is MYCar class that builds the XML by adding the carInfo tag:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "Car")
@XmlType(name = "Car", propOrder = {"brand","build", "carInfo"})
public class MyCar extends Car{
    
    @XmlElement(name="carInfo")
    private CarInfo carInfo;
    
    public MyCar xmlSupport() {
        if(carInfo == null){
            carInfo = new Carinfo();
        }
        
        carInfo.setEngine(getEngine);
        carInfo.setYear(getYear());
        return this;
    }
}

Following is my CarInfo class which acts as a helper to build the additional tag around MyCar class:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = {"engine","year"})
public class Carinfo{
    private String engine;
    private String year;
    //Getter, Setters and other consturctiores ommited
}

Following is my Main class which actually builds the XML by using the JAXBCOntext

public class Main{
    public static void main(String[] args){
        JAXBContext context = JAXBContext.newInstance(MyCar.class);
        Marshaller mar = context.createMarshaller();
        mar.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
        mar.marshal((MyCar).xmlSupport(), System.out);
        System.out.println("-----------------");
    }
}

Now coming back to my main question: As we can see from MyCar class I have the XMLSupport method which is actually populating the CarInfo objects and then using that method I am creating the XML. Is there a way I can move this to XMLAdapter?

I tried creating the XMLAdapter but I am not sure how can I populate the CarInfo objects from the adapter:

public class MyCar extends Car{
    
    @XmlElement(name="carInfo")
    @XmlJavaTypeAdapter(ExtensionAdapter.class)
    @XmlElement(name = "carInfo")
    private CarInfo carInfo;
}

Following is my Adapter class I've tried: public class ExtensionAdapter extends XmlAdapter<CarInfo, CarInfo> {

    @Override
    public CarInfo unmarshal(CarInfo valueType) throws Exception {
        System.out.println("UN-MARSHALLING");
        return null;
    }

    @Override
    public CarInfo marshal(CarInfo boundType) throws Exception {
        System.out.println("MARSHALLING");
        System.out.println(boundType);
        //I get boundType as NULL so I am not sure how to convert the xmlSupport Method to Adapter so I can use this adapter with multiple class
        return null;
    }
}
BATMAN_2008
  • 2,788
  • 3
  • 31
  • 98

1 Answers1

1

You don't need any adapters, you just need a well-defined POJO.

The trick is using getters and setters, not field access, so we can do delegation, and then use @JsonIgnore and @XmlTransient to control which getter/setter methods are used for JSON vs XML.

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;

@XmlRootElement(name = "Car")
@XmlType(propOrder = { "brand", "build", "carinfo" })
@JsonPropertyOrder({ "brand", "build", "engine", "year" })
public final class Car {

    @XmlType(propOrder = { "engine", "year" })
    public static final class Info {
        private String engine;
        private String year;

        public String getEngine() {
            return this.engine;
        }
        public void setEngine(String engine) {
            this.engine = engine;
        }

        public String getYear() {
            return this.year;
        }
        public void setYear(String year) {
            this.year = year;
        }

        @Override
        public String toString() {
            return "Info[engine=" + this.engine + ", year=" + this.year + "]";
        }
    }

    private String brand;
    private String build;
    private Info carinfo;

    public Car() {
        // Nothing to do
    }
    public Car(String brand, String build, String engine, String year) {
        this.brand = brand;
        this.build = build;
        this.carinfo = new Info();
        this.carinfo.setEngine(engine);
        this.carinfo.setYear(year);
    }

    public String getBrand() {
        return this.brand;
    }
    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getBuild() {
        return this.build;
    }
    public void setBuild(String build) {
        this.build = build;
    }

    @JsonIgnore // For XML, not JSON
    public Info getCarinfo() {
        if (this.carinfo == null)
            this.carinfo = new Info();
        return this.carinfo;
    }
    public void setCarinfo(Info info) {
        this.carinfo = info;
    }

    @XmlTransient // For JSON, not XML
    public String getEngine() {
        return getCarinfo().getEngine();
    }
    public void setEngine(String engine) {
        getCarinfo().setEngine(engine);
    }

    @XmlTransient // For JSON, not XML
    public String getYear() {
        return getCarinfo().getYear();
    }
    public void setYear(String year) {
        getCarinfo().setYear(year);
    }

    @Override
    public String toString() {
        return "Car[brand=" + this.brand + ", build=" + this.build + ", carinfo=" + this.carinfo + "]";
    }
}

Test

Car car = new Car("Ferari", "Italy", "Mercedes", "2021");

// Generate JSON
ObjectMapper jsonMapper = new ObjectMapper();
jsonMapper.enable(SerializationFeature.INDENT_OUTPUT);
String json = jsonMapper.writeValueAsString(car);

// Generate XML
JAXBContext jaxbContext = JAXBContext.newInstance(Car.class);
Marshaller xmlMarshaller = jaxbContext.createMarshaller();
xmlMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
String xml;
try (StringWriter writer = new StringWriter()) {
    xmlMarshaller.marshal(car, writer);
    xml = writer.toString();
}

// Print generated results
System.out.println(car);
System.out.println(json);
System.out.println(xml);

// Parse JSON
Car carFromJson = jsonMapper.readValue(json, Car.class);
System.out.println(carFromJson);

// Parse XML
Unmarshaller xmlUnmarshaller = jaxbContext.createUnmarshaller();
Car carFromXml = xmlUnmarshaller.unmarshal(new StreamSource(new StringReader(xml)), Car.class).getValue();
System.out.println(carFromXml);

Outputs

Car[brand=Ferari, build=Italy, carinfo=Info[engine=Mercedes, year=2021]]
{
  "brand" : "Ferari",
  "build" : "Italy",
  "engine" : "Mercedes",
  "year" : "2021"
}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Car>
    <brand>Ferari</brand>
    <build>Italy</build>
    <carinfo>
        <engine>Mercedes</engine>
        <year>2021</year>
    </carinfo>
</Car>
Car[brand=Ferari, build=Italy, carinfo=Info[engine=Mercedes, year=2021]]
Car[brand=Ferari, build=Italy, carinfo=Info[engine=Mercedes, year=2021]]

As you can see, the generated JSON and XML is exactly what you wanted, and the last two lines of output shows that parsing works as well.

Andreas
  • 154,647
  • 11
  • 152
  • 247
  • Thanks a lot for taking your time to analyze the code providing the detailed answer. I understand how you are doing here to get the desired output. Actually, I am using the Standard POJO from an existing application which I am not allowed to change. Also, my class is pretty big (I just used a sample here for simplicity) and I want to keep my POJO clean as it standard so that there is no conflict to other existing applications and for future integration. – BATMAN_2008 Apr 28 '21 at 14:18
  • Hence, I was thinking to use the `XMLAdapter` so that I can just use the annotation on the Objects in my POJO and get the desired output. Just out of curiosity if I would like to use `XML Adapter` then how can I achieve the same without modifying the POJO. Any suggestions? – BATMAN_2008 Apr 28 '21 at 14:19