how to fix while converting xml to java object is response null - java

Hi guys i am trying to convert soap xml to java classes fields but response gives null values into targeted class fields. I haven't worked with soap before, I work with soap for the first time, so I did a bit of google in 2 days and tried to use it. Attached screenshots from intellij debug
It is a SOAP XML file. In fact this is the value of xml that comes to me as a response from another API because I got it via SOAPUI because I had a request for it. So I tried to convert this xml in a new project without working on a real project. I also mentioned the classes in this project below
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<ns1:queryTransactionHistoryResponse soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns1="urn:IssuingWS/binding">
<ResponseInfo>
<response_code>0</response_code>
<error_description xsi:nil="true"/>
<error_action xsi:nil="true"/>
<EXTERNAL_SESSION_ID>TEST111_20211111111111212201191210111511</EXTERNAL_SESSION_ID>
</ResponseInfo>
<Details>
<row>
<item>
<name>CARD_ACCT</name>
<value>0901095000004266</value>
</item>
<item>
<name>ACCOUNT_NO</name>
<value>8186</value>
</item>
<item>
<name>CL_ACCT_KEY</name>
<value>8096</value>
</item>
<item>
<name>CLIENT</name>
<value>00003660</value>
</item>
</row>
<row>
<item>
<name>CARD2_ACCT</name>
<value>0901095000004266</value>
</item>
<item>
<name>ACCOUNTSSO</name>
<value>8186</value>
</item>
<item>
<name>CL_ACCT_KEY22</name>
<value>8096</value>
</item>
<item>
<name>SOMENAME</name>
<value>00003660</value>
</item>
</row>
</Details>
</ns1:queryTransactionHistoryResponse>
</soapenv:Body>
</soapenv:Envelope>
It is rootclass parent class got it from the name after ns1 in the XML, but I don't know if the situation I'm using is correct and one of the things I don't understand is the xmlns.
package uz.paynet.test.Xml;
import lombok.ToString;
import javax.xml.bind.annotation.*;
#ToString
#XmlRootElement(namespace = "urn:IssuingWS/binding")
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "queryTransactionHistoryResponse")
public class QueryTransactionHistoryResponse {
#XmlElement(name = "ResponseInfo", required = true,nillable = true)
private ResponseInfo responseInfo;
#XmlElement(name = "Details", required = true,nillable = true)
private Details details;
public QueryTransactionHistoryResponse() {
}
public QueryTransactionHistoryResponse(ResponseInfo responseInfo, Details details) {
this.responseInfo = responseInfo;
this.details = details;
}
public ResponseInfo getResponseInfo() {
return responseInfo;
}
public void setResponseInfo(ResponseInfo responseInfo) {
this.responseInfo = responseInfo;
}
public Details getDetails() {
return details;
}
public void setDetails(Details details) {
this.details = details;
}
}
it is my ResponseInfo class but this class get value you show on screenshop
package uz.paynet.test.Xml;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
#XmlAccessorType(XmlAccessType.FIELD)
public class ResponseInfo{
#XmlElement(name = "response_code")
private int responseCode;
#XmlElement(name = "error_description",nillable = true)
private String errorDescription;
#XmlElement(name="error-action",nillable = true)
private String errorAction;
#XmlElement(name = "EXTERNAL_SESSION_ID")
private String externalSessionId;
public ResponseInfo(int responseCode, String errorDescription, String errorAction, String externalSessionId) {
this.responseCode = responseCode;
this.errorDescription = errorDescription;
this.errorAction = errorAction;
this.externalSessionId = externalSessionId;
}
public ResponseInfo() {
}
public int getResponseCode() {
return responseCode;
}
public void setResponseCode(int responseCode) {
this.responseCode = responseCode;
}
public String getErrorDescription() {
return errorDescription;
}
public void setErrorDescription(String errorDescription) {
this.errorDescription = errorDescription;
}
public String getErrorAction() {
return errorAction;
}
public void setErrorAction(String errorAction) {
this.errorAction = errorAction;
}
public String getExternalSessionId() {
return externalSessionId;
}
public void setExternalSessionId(String externalSessionId) {
this.externalSessionId = externalSessionId;
}
}
this is Details class which Covers Row class
package uz.paynet.test.Xml;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import java.util.List;
#XmlAccessorType(XmlAccessType.FIELD)
public class Details{
#XmlElement(name = "row")
List<Row> rowList;
public Details() {
}
public Details(List<Row> rowList) {
this.rowList = rowList;
}
public List<Row> getRowList() {
return rowList;
}
public void setRowList(List<Row> rowList) {
this.rowList = rowList;
}
}
and last is Row class which covered value and name
which i need to get xml value and name
package uz.paynet.test.Xml;
import javax.xml.bind.annotation.*;
import java.io.Serializable;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(namespace = "urn:IssuingWS/binding")
public class Row {
#XmlElement(name = "name")
public String name;
#XmlElement(name = "value")
private String value;
public Row(String name, String value) {
this.name = name;
this.value = value;
}
public Row() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

Related

NULL values in JAXB unmarshalling

HI I want to fetch the value of nested xml using un-marshaling using maven dependency but the final output is returning me null values.I have used the 3 packages in maven project and vehicle.xml contains the values of car after fetching the values I have to insert them into access Database
MY XML file---> Vehicle.xml
<?xml version="1.0" encoding="UTF-8"?>
<Vehicle>
<Car>
<manufacturer>Maruti</manufacturer>
<cost>675000</cost>
<name>Ciaz</name>
<fueType>Petrol</fueType>
<driverTye>Manual</driverTye>
</Car>
<Car>
<manufacturer>Maruti</manufacturer>
<cost>575000</cost>
<name>Dezire</name>
<fueType>Petrol</fueType>
<driverTye>Manual</driverTye>
</Car>
</Vehicle>
POJO CLASS
Vehicle.java
package jaxb;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name="Vehicle")
public class Vehicle {
#XmlElement
private List<Car> car;
public List<Car> getCar() {
return car;
}
/*
* public Vehicle(List<Car> car) { super(); this.car = car; }
*/
#Override
public String toString() {
return "Vehicle[ Car="+car+"]";
}
}
Car.java (This is child POJO)
package jaxb;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name="Car")
public class Car {
private String manufacturer;
private String name;
private String driverType;
private String fuelType;
private int cost;
#XmlElement
public String getManufacturer() {
return manufacturer;
}
public void setManufacturer(String manufacturer) {
this.manufacturer = manufacturer;
}
#XmlElement
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#XmlElement
public String getDriverType() {
return driverType;
}
public void setDriverType(String driverType) {
this.driverType = driverType;
}
#XmlElement
public String getFuelType() {
return fuelType;
}
public void setFuelType(String fuelType) {
this.fuelType = fuelType;
}
#XmlElement
public int getCost() {
return cost;
}
public void setCost(int cost) {
this.cost = cost;
}
#Override
public String toString() {
return "Car [name=" + name + ", fuelType=" + fuelType + ", cost=" + cost+",driverType="+driverType +"]";
}
VehicleJxb.java
This file contains the implementation of our unmarshalling method
package jaxb;
import java.io.File;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
public class VehicleJxb {
public void unmarhalling() {
try {
JAXBContext jaxbContext = JAXBContext.newInstance(Vehicle.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
Vehicle vehicle = (Vehicle) jaxbUnmarshaller.unmarshal(new File("src\\main\\java\\Data\\Vehicle.xml"));
System.out.println(vehicle);
} catch (JAXBException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
App.java
package com.project.XMLDB;
import jaxb.*;
public class App
{
public static void main( String[] args )
{
VehicleJxb obj= new VehicleJxb();
obj.unmarhalling();
}
}
My Output is coming
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by com.sun.xml.bind.v2.runtime.reflect.opt.Injector (file:/C:/Users/Shivam%20Sharma/.m2/repository/com/sun/xml/bind/jaxb-impl/2.2.11/jaxb-impl-2.2.11.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int)
WARNING: Please consider reporting this to the maintainers of com.sun.xml.bind.v2.runtime.reflect.opt.Injector
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
Vehicle[ Car=null]
I want to get the values as final output is returned null
Unmarshaller is case sensitive. In your pojo, you have variable name car but in XML you Car. Change it to the following and it will work.
<?xml version="1.0" encoding="UTF-8"?>
<Vehicle>
<car>
<manufacturer>Maruti</manufacturer>
<cost>675000</cost>
<name>Ciaz</name>
<fueType>Petrol</fueType>
<driverTye>Manual</driverTye>
</car>
<car>
<manufacturer>Maruti</manufacturer>
<cost>575000</cost>
<name>Dezire</name>
<fueType>Petrol</fueType>
<driverTye>Manual</driverTye>
</car>
</Vehicle>
Or you need to mention that explicitly.
#XmlElement(name = "Car")
private List<Car> car;

unmarshaling always shows 0 and null

I think the best way is to copy my code so you can understand it.
Here are my POJO classes:
import javax.xml.bind.annotation.XmlAttribute;
public class Product {
private String name;
public Product() {
}
public Product(String name) {
this.name = name;
}
#XmlAttribute(name = "productName")
public String getProduct() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name="ShoppingCart")
public class Cart {
private List<Product> productList = new ArrayList<>();
private long CartIdentifier;
public Cart() {
}
public Cart(long id) {
this.CartIdentifier=id;
}
#XmlAttribute(name="CartIdentifier")
public long getId() {
return CartIdentifier;
}
public void setType(long id) {
this.CartIdentifier=id;
}
public Cart(List<Product> list) {
this.productList = list;
}
public void addElement(Product element) {
this.productList.add(element);
}
#XmlElement(name = "CurrentProducts")
public List<Product> getCart() {
return this.productList;
}
public void setCart(List<Product> cart) {
this.productList = cart;
}
}
import java.util.ArrayList;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "CashRegister")
public class Register {
#XmlElement(name = "ShoppingCart")
private ArrayList<Cart> listCart;
long CartIdentifier;
public Register() {
}
public Register(long id) {
this.CartIdentifier=id;
}
public long getId() {
return CartIdentifier;
}
public void addElementforBonus(Cart element) {
listCart.add(element);
}
public void setType(long id) {
this. CartIdentifier=id;
}
public void setList(ArrayList<Cart> list) {
this.listCart = list;
}
public ArrayList<Cart> getCartList() {
return listCart;
}
}
So those are my POJO classes and in main they create perfect XML, but when it comes to converting from XML to normal output something goes wrong. Can someone tell me where am I making mistake and why is my output than alway 0 and null?
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
public class Main {
public static void main(String[] args) throws JAXBException, IOException {
ArrayList<Cart> list = new ArrayList<Cart>();
Register store = new Register(1);
store.setType(1);
store.setList(list);
Product first = new Product("Burger");
Product second = new Product("Banana");
Cart cart1 = new Cart(1);
cart1.setType(1);
cart1.addElement(first);
cart1.addElement(second);
store.getCartList().add(cart1);
// create JAXB context and instantiate marshaller
JAXBContext context = JAXBContext.newInstance(Register.class);
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
// Write to System.out
m.marshal(store, System.out);
m.marshal(store, new File("mrs.xml"));
System.out.println("Output from our XML File: ");
Unmarshaller um = context.createUnmarshaller();
Register reg = (Register) um.unmarshal(new FileReader("mrs.xml"));
ArrayList<Cart> list1 = reg.getCartList();
for(Cart x:reg.getCartList()) {
System.out.println(x.getId());
}
for (Cart cart : list1) {
System.out.println("Cart: " + cart.getId());
for(Product product : cart.getCart()) {
System.out.println("Product: " + product.getProduct());
}
}
}
}
So here is my output:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<CashRegister>
<ShoppingCart CartIdentifier="1">
<CurrentProducts productName="Burger"/>
<CurrentProducts productName="Banana"/>
</ShoppingCart>
</CashRegister>
Output from our XML File:
0
Cart: 0
Product: null
Product: null
There is one cart with two products but not with 0 and null.
No, my question isn't duplicate. I don't want to have that as Element or show the name of that element in my xml. My xml looks perfect but i can't unmarshal it
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<CashRegister>
<ShoppingCart CartIdentifier="1">
<CurrentProducts productName="Burger"/>
<CurrentProducts productName="Banana"/>
</ShoppingCart>
</CashRegister>
Output from our XML File:
Exception in thread "main" java.lang.ClassCastException: bonusxml.Register cannot be cast to bonusxml.Register1
at bonusxml.Main.main(Main.java:45)
If you are hell bent on preserving your XML structure, you will have to pay a price for that. Below, are classes that can get you de-serializing the XML you have created. But, you will end up using two different set of classes for serializing and de-serializing.
Cart.java
#XmlRootElement(name="ShoppingCart")
public class Cart1 {
#XmlElement(name = "CurrentProducts")
private List<Product1> productList = new ArrayList<>();
#XmlAttribute(name="CartIdentifier")
private long CartIdentifier;
public Cart1() {}
public Cart1(long id) {
this.CartIdentifier=id;
}
// setters & getters
}
Product.java
public class Product1 {
#XmlAttribute(name = "productName")
private String name;
public Product1() {
}
public Product1(String name) {
this.name = name;
}
// setters & getters
}
Register.java
#XmlRootElement(name = "CashRegister")
public class Register1 {
#XmlElement(name = "ShoppingCart")
private ArrayList<Cart1> listCart;
long CartIdentifier;
public Register1() {
}
public Register1(long id) {
this.CartIdentifier=id;
}
//setters & getters
}

How to Use Jaxb for nested POJO class to generate XML model in Java

I'm using JAXB to create an xml data model and it seems to be working really well for non-complex XML model but as soon as I get into a slightly complex XML, JAXB doesn't seem to work as well.
This is the XML-generation I would like to acheive:
<?xml version="1.0" encoding="UTF-8"?>
<SampleRoot xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="Sample.xsd">
<Name>Sample Name</Name>
<Description>This is a description</Description>
<Graph>Graph test</Graph>
<VerifyAttr>
<Data>id</Data>
<Value>32</Value>
</VerifyAttr>
<VeirfyXpath>
<MemXpath>/Root/Name</MemXpath>
<Value>Mosawi</Value>
</<VeirfyXpath>>
</SampleRoot>
This is the Nested Pojo class I created (should I not create a nested pojo? Perhaps separate them out?):
package com.sample.model;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name="SampleRoot")
public class Sample {
private String name;
private String description;
private String graph;
public String getName() {
return name;
}
#XmlElement
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
#XmlElement
public void setDescription(String description) {
this.description = description;
}
public String getGraph() {
return graph;
}
#XmlElement
public void setGraph(String graph) {
this.graph = graph;
}
#XmlAccessorType(XmlAccessType.FIELD)
public class VerifyAttr {
private String data;
private String value;
public String getData() {
return data;
}
#XmlElement
public void setData(String data) {
this.data = data;
}
public String getValue() {
return value;
}
#XmlElement
public void setValue(String value) {
this.value = value;
}
}
#XmlAccessorType(XmlAccessType.FIELD)
public class VerifyXpath {
private String memXpath;
private String value;
public String getMemXpath() {
return memXpath;
}
#XmlElement
public void setMemXpath(String memXpath) {
this.memXpath = memXpath;
}
public String getValue() {
return value;
}
#XmlElement
public void setValue(String value) {
this.value = value;
}
}
}
And here is the demo to exercise the pojo model:
public static void main(String[] args) {
Sample sample = new Sample();
sample.setName("Name");
sample.setDescription("This is a description");
sample.setGraph("Graph Test");
VerifyAttr va = sample.new VerifyAttr();
va.setData("id");
va.setValue("32");
VerifyXpath vx = sample.new VerifyXpath();
vx.setMemXpath("/Root/Name");
vx.setValue("Mosawi");
try {
JAXBContext jaxbContext = JAXBContext.newInstance(Sample.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
jaxbMarshaller.marshal(sample, new File("Sample.xml") );
jaxbMarshaller.marshal(sample, System.out);
} catch(Exception e) {
e.printStackTrace();
}
}
The problem I have is that the full XML as defined above is not generated. This is what is generated:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<SampleRoot>
<description>This is a description</description>
<graph>Graph Test</graph>
<name>Name</name>
</SampleRoot>
What am I doing wrong? Why am I not getting the model XML as I should above? The problem is with demo main?
Okay so I was able to figure this out by reading some JaxB documentation. I've decoupled the POJOs into 3 classes and adjusted the Jaxb annotations:
Sample.java
VerifyAttr.java
VerifyXpath.java
VerifyAttr.java
#XmlRootElement
#XmlAccessorType(XmlAccessType.PROPERTY)
#XmlType(propOrder={"dataAttr","valueAttr"})
public class VerifyAttr {
//...
}
VerifyXpath.java
#XmlRootElement
#XmlAccessorType(XmlAccessType.PROPERTY)
#XmlType(propOrder={"dataXpath","valueXpath"})
public class VerifyAttr {
//...
}
and a top level for root node:
Sample.java
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class VerifyAttr {
#XmlElement(name="VerifyAttr", type=VerifyAttr.class)
private ArrayList<VerifyAttr> va;
#XmlElement(name="VerifyXpath", type=VerifyXpath.class)
private ArrayList<VerifyXpath> vx;
// some other fields
//..getter and setters
}
Hopefully this helps someone else new to Jaxb

JAXB - Reference to id with interface,abstract and list<abstract> [duplicate]

I'm wondering if it's possible to annotate my classes so that the first time the marshaller encounters an object, it generates an XML element of the appropriate type, but any subsequent reference to this object by anything else will have an XML IDREF entry created?
You can leverage the concept of JAXB's XmlAdapter to do something like the following:
input.xml
The following is the XML document I will use for this example. The 3rd phone-number entry is a reference to the 1st phone-number entry, and the 5th phone-number entry is a reference to the 4th.:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customer>
<phone-number id="A">
<number>555-AAAA</number>
</phone-number>
<phone-number id="B">
<number>555-BBBB</number>
</phone-number>
<phone-number id="A"/>
<phone-number xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="work-phone-number" id="W">
<number>555-WORK</number>
<extension>1234</extension>
</phone-number>
<phone-number xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="work-phone-number" id="W"/>
</customer>
Customer
The customer class maintains a collection of PhoneNumber objects. The same instance of PhoneNumber may appear multiple times in the collection.
package forum7587095;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Customer {
private List<PhoneNumber> phoneNumbers;
#XmlElement(name="phone-number")
public List<PhoneNumber> getPhoneNumbers() {
return phoneNumbers;
}
public void setPhoneNumbers(List<PhoneNumber> phoneNumbers) {
this.phoneNumbers = phoneNumbers;
}
}
PhoneNumber
This is a class that can either appear in the document itself or as a reference. This will be handled using an XmlAdapter. An XmlAdapter is configured using the #XmlJavaTypeAdapter annotation. Since we have specified this adapter at the type/class level it will apply to all properties referencing the PhoneNumber class:
package forum7587095;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlJavaTypeAdapter(PhoneNumberAdapter.class)
public class PhoneNumber {
private String id;
private String number;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
#Override
public boolean equals(Object arg0) {
if(null == arg0 || arg0.getClass() != this.getClass()) {
return false;
}
PhoneNumber test = (PhoneNumber) arg0;
if(!equals(id, test.getId())) {
return false;
}
return equals(number, test.getNumber());
}
protected boolean equals(String control, String test) {
if(null == control) {
return null == test;
} else {
return control.equals(test);
}
}
#Override
public int hashCode() {
return id.hashCode();
}
}
WorkPhoneNumber
Based on your comment I have added a subclass of PhoneNumber.
package forum7587095;
public class WorkPhoneNumber extends PhoneNumber {
private String extension;
public String getExtension() {
return extension;
}
public void setExtension(String extension) {
this.extension = extension;
}
#Override
public boolean equals(Object arg0) {
if(!super.equals(arg0)) {
return false;
}
return equals(extension, ((WorkPhoneNumber) arg0).getExtension());
}
}
PhoneNumberAdapter
Below is the implementation of the XmlAdapter. Note that we must maintain if the PhoneNumber object has been seen before. If it has we only populate the id portion of the AdaptedPhoneNumber object.
package forum7587095;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class PhoneNumberAdapter extends XmlAdapter<PhoneNumberAdapter.AdaptedPhoneNumber, PhoneNumber>{
private List<PhoneNumber> phoneNumberList = new ArrayList<PhoneNumber>();
private Map<String, PhoneNumber> phoneNumberMap = new HashMap<String, PhoneNumber>();
#XmlSeeAlso(AdaptedWorkPhoneNumber.class)
#XmlType(name="phone-number")
public static class AdaptedPhoneNumber {
#XmlAttribute public String id;
public String number;
public AdaptedPhoneNumber() {
}
public AdaptedPhoneNumber(PhoneNumber phoneNumber) {
id = phoneNumber.getId();
number = phoneNumber.getNumber();
}
public PhoneNumber getPhoneNumber() {
PhoneNumber phoneNumber = new PhoneNumber();
phoneNumber.setId(id);
phoneNumber.setNumber(number);
return phoneNumber;
}
}
#XmlType(name="work-phone-number")
public static class AdaptedWorkPhoneNumber extends AdaptedPhoneNumber {
public String extension;
public AdaptedWorkPhoneNumber() {
}
public AdaptedWorkPhoneNumber(WorkPhoneNumber workPhoneNumber) {
super(workPhoneNumber);
extension = workPhoneNumber.getExtension();
}
#Override
public WorkPhoneNumber getPhoneNumber() {
WorkPhoneNumber phoneNumber = new WorkPhoneNumber();
phoneNumber.setId(id);
phoneNumber.setNumber(number);
phoneNumber.setExtension(extension);
return phoneNumber;
}
}
#Override
public AdaptedPhoneNumber marshal(PhoneNumber phoneNumber) throws Exception {
AdaptedPhoneNumber adaptedPhoneNumber;
if(phoneNumberList.contains(phoneNumber)) {
if(phoneNumber instanceof WorkPhoneNumber) {
adaptedPhoneNumber = new AdaptedWorkPhoneNumber();
} else {
adaptedPhoneNumber = new AdaptedPhoneNumber();
}
adaptedPhoneNumber.id = phoneNumber.getId();
} else {
if(phoneNumber instanceof WorkPhoneNumber) {
adaptedPhoneNumber = new AdaptedWorkPhoneNumber((WorkPhoneNumber)phoneNumber);
} else {
adaptedPhoneNumber = new AdaptedPhoneNumber(phoneNumber);
}
phoneNumberList.add(phoneNumber);
}
return adaptedPhoneNumber;
}
#Override
public PhoneNumber unmarshal(AdaptedPhoneNumber adaptedPhoneNumber) throws Exception {
PhoneNumber phoneNumber = phoneNumberMap.get(adaptedPhoneNumber.id);
if(null != phoneNumber) {
return phoneNumber;
}
phoneNumber = adaptedPhoneNumber.getPhoneNumber();
phoneNumberMap.put(phoneNumber.getId(), phoneNumber);
return phoneNumber;
}
}
Demo
To ensure the same instance of the XmlAdapter is used for the entire marshal and unmarshal operations we must specifically set an instance of the XmlAdapter on both the Marshaller and Unmarshaller:
package forum7587095;
import java.io.File;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Customer.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
unmarshaller.setAdapter(new PhoneNumberAdapter());
File xml = new File("src/forum7587095/input.xml");
Customer customer = (Customer) unmarshaller.unmarshal(xml);
System.out.println(customer.getPhoneNumbers().get(0) == customer.getPhoneNumbers().get(2));
System.out.println(customer.getPhoneNumbers().get(3) == customer.getPhoneNumbers().get(4));
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setAdapter(new PhoneNumberAdapter());
marshaller.marshal(customer, System.out);
}
}
Output
true
true
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customer>
<phone-number id="A">
<number>555-AAAA</number>
</phone-number>
<phone-number id="B">
<number>555-BBBB</number>
</phone-number>
<phone-number id="A"/>
<phone-number xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="work-phone-number" id="W">
<number>555-WORK</number>
<extension>1234</extension>
</phone-number>
<phone-number xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="work-phone-number" id="W"/>
</customer>
For More Information
http://blog.bdoughan.com/2011/09/mixing-nesting-and-references-with.html
http://blog.bdoughan.com/2010/10/jaxb-and-shared-references-xmlid-and.html
http://blog.bdoughan.com/search/label/XmlAdapter
http://blog.bdoughan.com/2010/11/jaxb-and-inheritance-using-xsitype.html
http://blog.bdoughan.com/2010/11/jaxb-and-inheritance-using-substitution.html

EclipseLink MOXy with recursive data structures / child elements of same type

I am using EclipseLink MOXy and have a data structure that has child elements of the same data type. Now I don't want to serialize the datastructure with infinite depth, but only the first level.
Here is some example code of the data structure:
package test;
import java.util.Collection;
import java.util.Vector;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
#XmlAccessorType(XmlAccessType.PROPERTY)
#XmlRootElement
public class MyClass {
private int id;
private String details;
private Collection<MyClass> children = new Vector<MyClass>();
public MyClass() {
}
public MyClass(int id, String details) {
this.id = id;
this.details = details;
}
#XmlElementWrapper
#XmlElementRef
public Collection<MyClass> getChildren() {
return children;
}
public void addChild(MyClass child) {
children.add(child);
}
public String getDetails() {
return details;
}
#XmlAttribute
public int getId() {
return id;
}
public void setChildren(Collection<MyClass> children) {
this.children = children;
}
public void setDetails(String details) {
this.details = details;
}
public void setId(int id) {
this.id = id;
}
}
And my test program:
package test;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
public class Test {
public static void main(String[] args) throws Exception {
MyClass l1 = new MyClass(1, "Level 1");
MyClass l2 = new MyClass(2, "Level 2");
l1.addChild(l2);
MyClass l3 = new MyClass(3, "Level 3");
l2.addChild(l3);
JAXBContext jc = JAXBContext.newInstance(MyClass.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(l1, System.out);
}
}
The following XML is generated:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<myClass id="1">
<children>
<myClass id="2">
<children>
<myClass id="3">
<children/>
<details>Level 3</details>
</myClass>
</children>
<details>Level 2</details>
</myClass>
</children>
<details>Level 1</details>
</myClass>
However, I'd like the xml too look like:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<myClass id="1">
<children>
<myClass id="2">
<details>Level 2</details>
</myClass>
</children>
<details>Level 1</details>
</myClass>
Thanks.
To accomplish this use case we will leverage two concepts from JAXB: XmlAdapter and Marshaller.Listener.
MyClassAdapter
We will leverage the default JAXB behaviour of not marshalling an element for a null value. To do this we will implement an XmlAdapter that returns null after a specified level has been reached. To count the levels we will create a Marshaller.Listener.
package forum11769758;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class MyClassAdapter extends XmlAdapter<MyClass, MyClass>{
private int levels;
private MyMarshallerListener marshallerListener;
public MyClassAdapter() {
}
public MyClassAdapter(int levels) {
this.levels = levels;
}
public Marshaller.Listener getMarshallerListener() {
if(null == marshallerListener) {
marshallerListener = new MyMarshallerListener();
}
return marshallerListener;
}
#Override
public MyClass marshal(MyClass myClass) throws Exception {
if(null == marshallerListener || marshallerListener.getLevel() < levels) {
return myClass;
}
return null;
}
#Override
public MyClass unmarshal(MyClass myClass) throws Exception {
return myClass;
}
static class MyMarshallerListener extends Marshaller.Listener {
private int level = 0;
public int getLevel() {
return level;
}
#Override
public void afterMarshal(Object object) {
if(object instanceof MyClass) {
level--;
}
}
#Override
public void beforeMarshal(Object object) {
if(object instanceof MyClass) {
level++;
}
}
}
}
MyClass
The #XmlJavaTypeAdapter annotation is used to specify that an XmlAdapter should be used.
package forum11769758;
import java.util.*;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlAccessorType(XmlAccessType.PROPERTY)
#XmlRootElement
public class MyClass {
private int id;
private String details;
private Collection<MyClass> children = new Vector<MyClass>();
public MyClass() {
}
public MyClass(int id, String details) {
this.id = id;
this.details = details;
}
#XmlElementWrapper
#XmlElementRef
#XmlJavaTypeAdapter(MyClassAdapter.class)
public Collection<MyClass> getChildren() {
return children;
}
public void addChild(MyClass child) {
children.add(child);
}
public String getDetails() {
return details;
}
#XmlAttribute
public int getId() {
return id;
}
public void setChildren(Collection<MyClass> children) {
this.children = children;
}
public void setDetails(String details) {
this.details = details;
}
public void setId(int id) {
this.id = id;
}
}
Test
Since we need to use the XmlAdapter in a stateful way, we will set an instance of it on the Marshaller, we will also set the instance of Marshaller.Listener we created on the Marshaller.
package forum11769758;
import javax.xml.bind.*;
public class Test {
public static void main(String[] args) throws Exception {
MyClass l1 = new MyClass(1, "Level 1");
MyClass l2 = new MyClass(2, "Level 2");
l1.addChild(l2);
MyClass l3 = new MyClass(3, "Level 3");
l2.addChild(l3);
JAXBContext jc = JAXBContext.newInstance(MyClass.class);
Marshaller marshaller = jc.createMarshaller();
MyClassAdapter myClassAdapter = new MyClassAdapter(2);
marshaller.setAdapter(myClassAdapter);
marshaller.setListener(myClassAdapter.getMarshallerListener());
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(l1, System.out);
}
}
Output
<?xml version="1.0" encoding="UTF-8"?>
<myClass id="1">
<children>
<myClass id="2">
<children/>
<details>Level 2</details>
</myClass>
</children>
<details>Level 1</details>
</myClass>
For More Information
The following articles expand on the topics discussed in this answer:
http://blog.bdoughan.com/2012/04/binding-to-json-xml-handling-null.html
http://blog.bdoughan.com/2011/09/mixing-nesting-and-references-with.html

Categories

Resources