Unmarshalling of XML key-value pair in java object - java

Unmarshall XML key value part into java object using jaxb. I have tried using map adapter, but I'm not able to do it.
<ACCOUNT_CHANGES>
<TYPE value="Active" />
<RECORD>
<SUBSCRIPTION>
<INFO key="aaa">
<![CDATA[042]]>
</INFO>
<INFO key="bbb">
<![CDATA[45]]>
</INFO>
<INFO key="uuid">
<![CDATA[d9a7e94c-0a9d-c745-82e9-980877cc5043]]>
</INFO>
<INFO key="ccc">
<![CDATA[Active]]>
</INFO>
<INFO key="companyname">
<![CDATA[ltd]]>
</INFO>
</SUBSCRIPTION>
</RECORD>
</ACCOUNT_CHANGES>

You could use an adapter to do that, but as I understand your requirement it is not necessary. If you simply want to unmarshall into an object, not into a Map, you can do the following:
Starting from the root:
#XmlRootElement(name = "ACCOUNT_CHANGES")
#XmlAccessorType(XmlAccessType.FIELD)
public class AccountChanges {
#XmlElement(name = "TYPE")
private Type type;
#XmlElement(name = "RECORD")
private Record record;
}
Let's take Type out of the way:
#XmlAccessorType(XmlAccessType.FIELD)
public class Type {
#XmlAttribute
private String value;
}
Then the record:
#XmlAccessorType(XmlAccessType.FIELD)
public class Record {
#XmlElement(name = "SUBSCRIPTION")
private Subscription subscription;
}
And the subscription:
#XmlAccessorType(XmlAccessType.FIELD)
public class Subscription {
#XmlElement(name = "INFO")
private List<Info> infoList;
}
Info has your key as attribute and then some value. It would look like this:
#XmlAccessorType(XmlAccessType.FIELD)
public class Info {
#XmlAttribute
private String key;
#XmlValue
private String value;
public String getKey() {
return key;
}
public String getValue() {
return value;
}
}
This will unmarshal your xml, and info keys and values will be in the fields. In case you want the key and value in a map, you can use an adapter.
The adapter looks like this:
public class MyMapAdapter extends XmlAdapter<Info, Map<String, String>> {
private HashMap<String, String> hashMap = new HashMap<String, String>();
#Override
public Map<String, String> unmarshal(Info v) throws Exception {
hashMap.put(v.getKey(), v.getValue());
return hashMap;
}
#Override
public Info marshal(Map<String, String> v) throws Exception {
// do here actions for marshalling if u also marshal
return null;
}
}
And you will change the Subscription to use the adapter and have map as a field:
#XmlAccessorType(XmlAccessType.FIELD)
public class Subscription {
#XmlElement(name = "INFO")
#XmlJavaTypeAdapter(MyMapAdapter.class)
private Map<String, String> infoMap;
}
Two ways, both unmarshal your xml payload.
Cheers

Related

How to parse a complex element into a Map, using a attribute as key and the whole element as value, in JAXB Adapter

I would like to parse a list of complex XML elements into a Map, where the key would be an attribute and the value the whole object/element.
Here is an example of my XML:
<product>
<documents>
<document code="100" clazz="DocumentA">
<properties>
<property name="PropA" value="123" />
<property name="PropB" value="qwerty" />
<property name="PropC" value="ABC" />
</properties>
</document>
</documents>
</product>
The example of my class Document:
public class Document {
private Integer code;
private String clazz;
private List<Propertiy> properties;
//getters and setters...
}
I don't know if it's possible, but i would like to parse document elements into a Map, where the key is the attriute code.
Can someone help me?
You could try using an adapter. Let's start building up the POJOs according to your xml. First you have the product:
#XmlRootElement
public class Product {
#XmlElementWrapper(name = "documents")
#XmlElement(name = "document")
private List<Document> documents;
}
Then documents in that product:
#XmlRootElement
public class Document {
#XmlAttribute
private Integer code;
#XmlAttribute
private String clazz;
#XmlElement(name = "properties")
private Properties properties;
}
Inside the properties we use the adapter in order to get the desired Map:
#XmlAccessorType(XmlAccessType.FIELD)
public class Properties {
#XmlElement(name = "property")
#XmlJavaTypeAdapter(MyMapAdapter.class)
private Map<String, String> properties;
}
Our adapter would take the incoming xml in a way that would understand it and turn adapt it to something else (i.e. map). So let's create first a POJO for the property as in xml:
#XmlAccessorType(XmlAccessType.FIELD)
public class Property {
#XmlAttribute
private String name;
#XmlAttribute
private String value;
public String getName() {
return name;
}
public String getValue() {
return value;
}
}
Then we use it in the adapter:
public class MyMapAdapter extends XmlAdapter<Property, Map<String, String>> {
private HashMap<String, String> hashMap = new HashMap<String, String>();
#Override
public Map<String, String> unmarshal(Property v) throws Exception {
hashMap.put(v.getName(), v.getValue());
return hashMap;
}
#Override
public Property marshal(Map<String, String> v) throws Exception {
// do here actions for marshalling if u also marshal
return null;
}
}
Running this, would unmarshal the payload and it would have the values in the map as desired. Hope it helps

JaxB: How Do I Retrieve Text Attribute from Nested Element?

I want the Country class to store the "ISO_3166-1_Alpha-2_Code" code. The code currently gets back the "ISO_3166-1_Numeric-3_Code" code. Can't figure out how to tweak the Country class to get the specific attribute I want.
XML:
<?xml version='1.0' encoding='UTF-8'?>
<wd:Message_Event_Configuration">
<wd:Message_Event_Configuration_Data>
<wd:Country_Reference wd:Descriptor="Saint Martin">
<wd:ID wd:type="WID">66b7082a21e510000961bb6d82b5002a</wd:ID>
<wd:ID wd:type="ISO_3166-1_Alpha-2_Code">MF</wd:ID>
<wd:ID wd:type="ISO_3166-1_Alpha-3_Code">MAF</wd:ID>
<wd:ID wd:type="ISO_3166-1_Numeric-3_Code">663</wd:ID>
</wd:Country_Reference>
<wd:Country_Reference wd:Descriptor="Saint Barthelemy">
<wd:ID wd:type="WID">881527f6cec910000ba81e8dccf61127</wd:ID>
<wd:ID wd:type="ISO_3166-1_Alpha-2_Code">BL</wd:ID>
<wd:ID wd:type="ISO_3166-1_Alpha-3_Code">BLM</wd:ID>
<wd:ID wd:type="ISO_3166-1_Numeric-3_Code">652</wd:ID>
</wd:Country_Reference>
</wd:Message_Event_Configuration_Data>
</wd:Message_Event_Configuration>
Country List:
#XmlRootElement(name = "Message_Event_Configuration")
#XmlAccessorType(XmlAccessType.FIELD)
public class Countries {
#XmlElementWrapper(name = "Message_Event_Configuration_Data")
#XmlElement(name = "Country_Reference")
private List<Country> countries = new ArrayList<Country>();
public List<Country> getCountries() {
return countries;
}
public void setCountries(List<Country> countries) {
this.countries = countries;
}
}
Country:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "Country_Reference")
public class Country {
#XmlElement(name = "ID")
private String isoCode;
public Country() {
}
public Country(String isoCode) {
this.isoCode = isoCode;
}
#XmlAttribute(name = "ISO_3166-1_Alpha-2_Code")
public String getISOCode() {
return isoCode;
}
public void setISOCode(String isoCode) {
this.isoCode = isoCode;
}
}
The <Country_Reference> XML element contains the ISO codes in a rather
sophisticated way within several <wd:ID> XML elements.
It is therefore much too simple to model them as a Java String property.
Instead, you need to model the Java-structure with more similarity to the XML-structure.
The sequence of XML elements <wd:ID> can be modeled by a property List<ID> idList
which needs to be annotated by#XmlElement(name="ID") .
The XML attribute wd:Descriptor="...." can be modeled by a property String descriptor
which needs to be annotated by #XmlAttribute(name="Descriptor").
For your convenience you can add an all-arguments-constructor and some methods for getting
the WID and ISO codes from the List<ID>.
#XmlAccessorType(XmlAccessType.FIELD)
public class Country {
#XmlAttribute(name = "Descriptor")
private String descriptor;
#XmlElement(name = "ID")
private List<ID> idList;
public Country() {
}
public Country(String descriptor, String wid, String isoAlpha2Code, String isoAlpha3Code, String isoNumeric3Code) {
this.descriptor = descriptor;
idList = new ArrayList<>();
idList.add(new ID("WID", wid));
idList.add(new ID("ISO_3166-1_Alpha-2_Code", isoAlpha2Code));
idList.add(new ID("ISO_3166-1_Alpha-3_Code", isoAlpha3Code));
idList.add(new ID("ISO_3166-1_Numeric-3_Code", isoNumeric3Code));
}
public String getWid() {
return getIdByType("WID");
}
public String getIsoAlpha2Code() {
return getIdByType("ISO_3166-1_Alpha-2_Code");
}
public String getIsoAlpha3Code() {
return getIdByType("ISO_3166-1_Alpha-3_Code");
}
public String getIsoNumeric3Code() {
return getIdByType("ISO_3166-1_Numeric-3_Code");
}
private String getIdByType(String idType) {
for (ID id : idList) {
if (id.getType().equals(idType))
return id.getValue();
}
return null;
}
}
The XML elements <wd:ID> are quite complex. Therefore we need a separate POJO class for modeling them.
Let's call the class ID.
The XML text between <wd:ID ..> and </wd:ID> is modeled by the property String value
which needs to be annotated by #XmlValue.
The XML attribute wd:type="..." is modeled by the property String type
which needs to be annotated by #XmlAttribute.
For convenient use by the class Country above, an all-arguments-constructor is added.
#XmlAccessorType(XmlAccessType.FIELD)
public class ID {
#XmlAttribute
private String type;
#XmlValue
private String value;
public ID() {
}
public ID(String type, String value) {
this.type = type;
this.value = value;
}
// public getters and setters (omitted here fro brevity)
}
The screenshot below (taken from within the debugger) visualizes the Java structure
and confirms that the unmarshalling of your XML example works correctly:

JAXB Element With Specific Type But Unknown Names

I have a root Xml document (name = "Entity") that contains one known Xml element (name = "Header") and another Xml element of unknown name but it is known to have an inner XmlElement(name="label")
Here are possible Xmls:
<Entity>
<Header>this is a header</Header>
<a>
<label>this is element A</label>
<otherElements/>
</a>
</Entity>
<Entity>
<Header>this is a different header</Header>
<b>
<label>this is some other element of name b</label>
<others/>
</b>
</Entity>
Here are my JAXB annotated classes:
#XmlRootElement(name = "Entity")
#XmlAccessorType(XmlAccessType.NONE)
public class Entity {
#XmlElement(name = "Header")
private Header header;
#XmlElements( {
#XmlElement(name = "a", type=LabelledElement.A.class),
#XmlElement(name = "b", type=LabelledElement.B.class)
} )
private LabelledElement labelledElement;
// constructors, getters, setters...
}
#XmlAccessorType(XmlAccessType.NONE)
public abstract class LabelledElement {
#XmlElement
private String label;
#XmlAnyElement
private List<Element> otherElements;
public static class A extends LabelledElement {}
public static class B extends LabelledElement {}
}
This was working great! But then I noticed that it isn't only <a> and <b>
It could be <c>, <asd> and even <anything>...
So listing the XmlElement(name = "xyz", type = LabelledElement.xyz.class) is obviously not the right solution.
All I care about is Entity#getLabelledElement()#getLabel() no matter what the LabelledElement name is.
Is this even possible with JAXB?
With EclipseLink JAXB Implementation (MOXy), this should work :
#XmlRootElement(name = "Entity")
#XmlSeeAlso({LabelledElement.class}) //Might not be necessary
#XmlAccessorType(XmlAccessType.NONE)
public class Entity {
#XmlElement(name = "Header")
private Header header;
#XmlPath("child::*[position() = 2]")
#XmlJavaTypeAdapter(MapAdapter.class)
private Map<String,LabelledElement> labelledElementMap;
public LabelledElement getLabelledElement(){
return labelledElementMap.values().get(0);
}
// constructors, getters, setters...
}
The MapAdapter class :
public class MapAdapter extends XmlAdapter<MapAdapter.AdaptedMap, Map<String, LabelledElement>> {
public static class AdaptedMap {
#XmlVariableNode("key")
List<LabbeledElement> entries = new ArrayList<LabbeledElement>();
}
public static class AdaptedEntry {
#XmlTransient
public String key;
#XmlElement
public LabelledElement value;
}
#Override
public AdaptedMap marshal(Map<String, LabelledElement> map) throws Exception {
AdaptedMap adaptedMap = new AdaptedMap();
for(Entry<String, LabelledElement> entry : map.entrySet()) {
AdaptedEntry adaptedEntry = new AdaptedEntry();
adaptedEntry.key = entry.getKey();
adaptedEntry.value = entry.getValue();
adaptedMap.entries.add(adaptedEntry);
}
return adaptedMap;
}
#Override
public Map<String, LabelledElement> unmarshal(AdaptedMap adaptedMap) throws Exception {
List<AdaptedEntry> adaptedEntries = adaptedMap.entries;
Map<String, LabelledElement> map = new HashMap<String, LabelledElement>();
for(AdaptedEntry adaptedEntry : adaptedEntries) {
map.put(adaptedEntry.key, adaptedEntry.value);
}
return map;
}
}
For reference, my solution is inspired by this link.
Apparently it's possible with EclipseLink JAXB (MOXy) implementation, which allows you annotate an interface variable providing a Factory class and method to be used when binding XML to Java, see this answer.
(edited to provide an example of this approach)
For instance, you instead of having an abstract class LabelledElement, have an interface LabelledElement:
public interface LabelledElement {
String getLabel();
}
and then have classes A and B implement it like this:
import javax.xml.bind.annotation.XmlElement;
public class A implements LabelledElement{
private String label;
#Override
#XmlElement(name="label")
public String getLabel() {
return label;
}
}
and Entity class annotated like this:
#XmlRootElement(name = "Entity")
#XmlAccessorType(XmlAccessType.NONE)
public class Entity {
#XmlElement(name = "Header")
private Header header;
#XmlRootElement
#XmlType(
factoryClass=Factory.class,
factoryMethod="createLabelledElement")
private LabelledElement labelledElement;
// constructors, getters, setters...
}
Then, as the answer I linked to suggests, you need a Factory class like:
import java.lang.reflect.*;
import java.util.*;
public class Factory {
public A createA() {
return createInstance(A.class);
}
public B createB() {
return createInstance(B.class);;
}
private <T> T createInstance(Class<T> anInterface) {
return (T) Proxy.newProxyInstance(anInterface.getClassLoader(), new Class[] {anInterface}, new InterfaceInvocationHandler());
}
private static class InterfaceInvocationHandler implements InvocationHandler {
private Map<String, Object> values = new HashMap<String, Object>();
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if(methodName.startsWith("get")) {
return values.get(methodName.substring(3));
} else {
values.put(methodName.substring(3), args[0]);
return null;
}
}
}
}

JAXB collection mapping

I'm new in dealing with XML in Java, but one of services I use returns it as result. Until now, I've dealt with mapping XML into POJO, using #XmlRootElement-like annotations. But now I have absolutely no idea to do with this document:
<?xml version="1.0" encoding="windows-1251"?>
<response>
<status>
<code>0</code>
</status>
<result>
<limit>2</limit>
...
<data>
<row0>
<ID>85427</ID>
<name>Default</name>
<siteID>40628</siteID>
... some elements
</row0>
</data>
</result>
</response>
Until now, I used these classes to bind XML (except 'data' node) into POJO:
#XmlRootElement(name="response")
public class Response {
private Status status;
private String result;
public Status getStatus() {
return status;
}
#XmlElement(name = "status")
public void setStatus(Status status) {
this.status = status;
}
public String getResult() {
return result;
}
#XmlElement(name ="result")
public void setResult(String result) {
this.result = result;
}
}
#XmlRootElement(name = "status")
public class Status {
private String ID;
private String code;
private String error;
public String getID() {
return ID;
}
#XmlElement(name = "ID")
public void setID(String ID) {
this.ID = ID;
}
public String getCode() {
return code;
}
#XmlElement(name = "code")
public void setCode(String code) {
this.code = code;
}
public String getError() {
return error;
}
#XmlElement(name = "error")
public void setError(String error) {
this.error = error;
}
}
But now I need to bind content as collection of elements. I've looked for examples, and everywhere people use specific tag to define root element for collection's item, but in this document, root tags will be as <row0>, <row1> etc.
I use Jackson, which, if I understand correctly, uses JAXB annotations to define XML to POJO bind rules. So could this deal be solved this way, or I have to manipulate this document in DOM-style?
You can solve your problem by using something like this :
Create your Row class that represents your <row0>,<row1> etc... and map it with JAXB like you would do it normally.
Then create a class that extends XmlAdapter<List<Row>,List<Element>> and define the abstracts methods marshall and unmarshall.
Here is some Javadoc to help you :
XmlAdapter : http://docs.oracle.com/javaee/5/api/javax/xml/bind/annotation/adapters/XmlAdapter.html
Element : http://docs.oracle.com/javase/1.5.0/docs/api/org/w3c/dom/Element.html
Then create a Data class :
public class Data{
private List<Row> rows;
public List<Row> getRows() {
return rows;
}
#XmlAnyElement
#XmlJavaTypeAdapter(MyRowsAdapter.class)
public void setRows(List<Row> result) {
this.rows = rows;
}
}
Then you can add this mapping to your Response class :
private Data data;
public Data getData() {
return data;
}
#XmlElement(name="data")
public void setData(Data data) {
this.data = data;
}
Note that for this solution to work, your <data> element must only contains elements like your Row.
Also, you cannot use #XmlElementWrapper instead of using a Data class because of a bug in JAXB which make incompatible #XmlElementWrapper and #XmlJavaTypeAdapter : https://java.net/jira/browse/JAXB-787

java JAXB save subclass when saving

I'm struggling saving all data from my class/subclass using JAXB.
I want to save all accounts from an observableList, but the problem is, the account class
public class Account{
private ObjectProperty<HosterObject> host;
....
}
contains an HosterObject which has 2 attributes:
publicName and privateName also have getter and setter.
#XmlRootElement(name = "hoster")
public class HosterObject {
private final StringProperty publicName;
private final StringProperty privateName;
public HosterObject(String publicName, String privateName){
this.publicName = new SimpleStringProperty(publicName);
this.privateName = new SimpleStringProperty(privateName);
}
#XmlElement(name = "publicName")
public StringProperty publicNameProperty(){
return publicName;
}
#XmlElement(name = "privateName")
public StringProperty privateNameProperty(){
return privateName;
}
How can I save the content from the Hosterobject as Element in the xml-file as well?
At the moment the xml file looks so:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<accounts>
<account>
<hoster/>
<password>123</password>
<status>unchecked</status>
<username>test</username>
</account>
</accounts>
But i should look kinda like this
...
<account>
<hoster>
<publicName>Name</publicName>
<privateName>private Name</privateName>
</hoster>
....
</account>
....
The code for saving:
public void saveAccountDataToFile(File file) {
try {
JAXBContext context = JAXBContext.newInstance(AccountListWrapper.class);
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
// Wrapping our person data.
AccountListWrapper wrapper = new AccountListWrapper();
wrapper.setAccounts(accountData);
// Marshalling and saving XML to the file.
m.marshal(wrapper, file);
} catch (Exception e) {
}
}
Wrapper:
#XmlRootElement(name = "accounts")
public class AccountListWrapper {
private List<Account> accounts;
#XmlElement(name = "account")
public List<Account> getAccounts() {
return accounts;
}
public void setAccounts(List<Account> accounts) {
this.accounts = accounts;
}
}
Thanks in advance!
Add:
tag HosterObject by
#XmlRootElement(name = "hoster")
#XmlElement
for class and set method for HosterObject in Account.
public HosterObject (){}
public Account(){}
JAXB need default empty constructor.
If you want to add class to xml you must tag it and create always default public constructor. Remember to tag only class which are non abstract.

Categories

Resources