Java xml unmarshalling list nested object issue - java

I have a xml with content below:
<ParentClass>
<StringA>A</StringA>
<StringB>B</StringB>
<Items>
<Item ts="2016-03-25T20:00:00+02:00">1.17</Item>
<Item ts="2016-03-25T21:00:00+02:00">1.15</Item>
</Items>
</ParentClass>
I would like to read it but I got stuck on proper mapping. Classes are as below:
#XmlAccessorType(XmlAccessType.FIELD)
#NoArgsConstructor
#AllArgsConstructor
#Data
public class ParentClass {
#XmlElement(name = "StringA", required = true)
private String a;
#XmlElement(name = "StringB", required = true)
private String b;
#XmlElement(name = "Items", required = true)
private List<Item> consumptionList;
}
#NoArgsConstructor
#AllArgsConstructor
#Data
#XmlAccessorType(XmlAccessType.FIELD)
public class Item {
#XmlAttribute(name = "ts", required = true)
#XmlJavaTypeAdapter(value = LocalDateTimeAdapter.class)
private LocalDateTime timestamp;
private double value; //this corrensponds to 1.17 and 1.15 in xml
}
In the actual file there are 100+ items yet when I read it the list is populated with only one instance of Item class and it has both fields null.
I guess the mapping in the Item class is all wrong but tried all and nothing seems to work.
How should I properly map it to achieve the goal?

You need to add #XmlValue to field value, otherwise it defaults to #XmlElement.
Also, you need to change the annotations on consumptionList to
#XmlElementWrapper(name = "Items")
#XmlElement(name = "Item", required = true)
Also be aware that the ts values are OffsetDateTime (or ZonedDateTime) values, not LocalDateTime, unless your LocalDateTimeAdapter applies a time zone, e.g. the JVM default time zone.
I've found that the best way to help apply #Xml... annotations correctly, is to create objects and marshal them to XML to see what you get. Your current code will create this XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ParentClass>
<StringA>A</StringA>
<StringB>B</StringB>
<Items ts="2016-03-25T20:00:00+02:00">
<value>1.17</value>
</Items>
<Items ts="2016-03-25T21:00:00+02:00">
<value>1.15</value>
</Items>
</ParentClass>
If you apply the changes mentioned above, you get:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ParentClass>
<StringA>A</StringA>
<StringB>B</StringB>
<Items>
<Item ts="2016-03-25T20:00:00+02:00">1.17</Item>
<Item ts="2016-03-25T21:00:00+02:00">1.15</Item>
</Items>
</ParentClass>
The above outputs were created by adding this code:
class LocalDateTimeAdapter extends XmlAdapter<String, LocalDateTime> {
#Override
public String marshal(LocalDateTime time) throws Exception {
return time.atZone(ZoneOffset.ofHours(2))
.format(DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ssxxx"));
}
#Override
public LocalDateTime unmarshal(String text) throws Exception {
return ZonedDateTime.parse(text).toLocalDateTime();
}
}
public static void main(String... args) throws Exception {
ParentClass p = new ParentClass("A", "B", Arrays.asList(
new Item(LocalDateTime.parse("2016-03-25T20:00:00"), 1.17),
new Item(LocalDateTime.parse("2016-03-25T21:00:00"), 1.15)));
JAXBContext jaxbContext = JAXBContext.newInstance(ParentClass.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(p, System.out);
}

Related

Jackson XML Serialization - Remove Field Tags

I need to generate this XML:
<CRequest>
<abc:Name>Smith</abc:Name>
<abc:FirstName>John</abc:Surname>
<abc:Age>12</abc:Age>
<abc:Name>Jones</abc:Name>
<abc:FirstName>Jake</abc:Surname>
<abc:Age>10</abc:Age>
<abc:Name>Johnson</abc:Name>
<abc:FirstName>Paul</abc:Surname>
<abc:Age>12</abc:Age>
</CRequest>
However, the best I could do was:
<CRequest>
<children>
<abc:Name>Smith</abc:Name>
<abc:FirstName>John</abc:Surname>
<abc:Age>12</abc:Age>
</children>
<children>
<abc:Name>Jones</abc:Name>
<abc:FirstName>Jake</abc:Surname>
<abc:Age>12</abc:Age>
</children>
<children>
<abc:Name>Johnson</abc:Name>
<abc:FirstName>Paul</abc:Surname>
<abc:Age>12</abc:Age>
</children>
</CRequest>
I have the following Java classes:
#JsonRootName("CRequest")
#XmlAccessorType(XmlAccessType.FIELD)
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public class ChildrenRequest {
#JacksonXmlElementWrapper(useWrapping = false)
private List<Child> children= new ArrayList<>();
...
and
#XmlAccessorType(XmlAccessType.FIELD)
#JsonInclude(JsonInclude.Include.NON_EMPTY)
#JsonPropertyOrder({"Name", "FirstName", "Age"})
public class Child{
#JsonProperty("Name")
#JacksonXmlProperty(localName = "abc:Name")
private String name;
#JsonProperty("Surname")
#JacksonXmlProperty(localName = "FirstName")
private String firstName;
#JsonProperty("Age")
#JacksonXmlProperty(localName = "abc:Age")
private String age;
...
Is there a way to get rid of the children tags?
PS: Without "useWrapping = false" I get two children tags for every child.
You need to implement custom serialiser for request class:
class ChildrenRequestJsonSerializer extends JsonSerializer<ChildrenRequest> {
#Override
public void serialize(ChildrenRequest value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
ToXmlGenerator xmlGen = (ToXmlGenerator) gen;
writeStartObject(xmlGen);
JsonSerializer<Object> childSerializer = serializers.findValueSerializer(Child.class).unwrappingSerializer(NameTransformer.NOP);
for (Child child : value.getChildren()) {
childSerializer.serialize(child, gen, serializers);
}
xmlGen.writeEndObject();
}
private void writeStartObject(ToXmlGenerator xmlGen) throws IOException {
final XmlMapper mapper = (XmlMapper) xmlGen.getCodec();
final PropertyName rootName = mapper.getSerializationConfig().findRootName(ChildrenRequest.class);
xmlGen.setNextName(new QName("", rootName.getSimpleName()));
xmlGen.writeStartObject();
}
}
And you can register serialiser as below:
#JsonRootName("CRequest")
#JsonSerialize(using = ChildrenRequestJsonSerializer.class)
class ChildrenRequest
See also:
#JsonSerialize - How to create a wrapper at runtime and use default serialization for the object fields?
Dynamic root element with Jackson
JacksonXmlRootElement with dynamic localName value

JAXB marshal polymorphic POJO to XML

I am trying to use JAXB to marshal class file(with annotations). Under <profile-set> it can have different tags for e.g.
<organization-information-profile>
<connection-profile>
<user-information-profile>
Sample output XML files are as below
a)
<?xml version="1.0"?>
<request version="2.0" principal="111" credentials="xxxxx">
<target name="TestAPI" operation="create">
<parameter>
<organization>
<qualified-name>some-qualified-name</qualified-name>
<profile-set>
<name>TestOrg</name>
<organization-information-profile>
<name>Organization Information</name>
<qualified-name>/Organization Information</qualified-name>
<last-name>Test</last-name>
<address>some-address</address>
<city>my-city</city>
<province></province>
<postal-code>1111</postal-code>
<country>Timbaktu</country>
<phone-number-day>1111</phone-number-day>
<email-address>some#email.com</email-address>
<attribute name="PhoneNumber1">
<value context="organization">23333</value>
</attribute>
<attribute name="ShortName">
<value context="organization">my company</value>
</attribute>
<attribute name="TaxId">
<value context="organization">myorg</value>
</attribute>
</organization-information-profile>
</profile-set>
</organization>
</parameter>
</target>
</request>
b)
<?xml version="1.0"?>
<request version="2.0" principal="11111" credentials="xxxxx">
<target name="TestAPI" operation="update">
<parameter>
<organization>
<qualified-name>some-qualified-name</qualified-name>
<profile-set>
<name>TestOrg</name>
<connection-profile>
<qualified-name>some-qualified-name</qualified-name>
<service>
<name>some service</name>
</service>
<attribute name="att-1">
<value context="organization" segment="some-segment" subscript="524288">fill-the-value</value>
</attribute>
<attribute name="att-2">
<value context="organization" segment="some-segment" subscript="524288">qedqwe</value>
</attribute>
</connection-profile>
</profile-set>
</organization>
</parameter>
</target>
</request>
Below is the code (only profile-set)
public static class ProfileSet
{
#XmlElement(name = "name")
public String name;
// innerPayLoad is template to store different profile objects
#XmlJavaTypeAdapter(CustomAdaptor.class)
#XmlElement
public InnerPayLoad innerPayLoad;
public ProfileSet(String name, InnerPayLoad innerPayLoad)
{
this.name = name;
this.innerPayLoad = innerPayLoad;
}
}
And CustomAdaptor
public class CustomAdaptor extends XmlAdapter<String,InnerPayLoad<?>>
{
#Override
public InnerPayLoad<?> unmarshal(String v) throws Exception
{
return null;
}
#Override
public String marshal(InnerPayLoad<?> v) throws Exception
{
String value = TestCode.convertToXmlNoHeader(v.whichProfile,v.whichProfile.getClass());
// after converting value becomes
// <organization-information-profile>
// <name>Organization Information</name>
// </organization-information-profile>
return value;
}
}
But the final XML produced is not similar to (a) for organization-information-profile
<?xml version='1.0' encoding='UTF-8'?>
<request version="2.0" principle="11111" credentials="xxxxx">
<target name="TestAPI" operation="create">
<parameter>
<organization>
<qualified-name>newOrg</qualified-name>
<profile-set>
<innerPayLoad><organization-information-profile>
<name>Organization Information</name>
</organization-information-profile></innerPayLoad>
<name>testOrg</name>
</profile-set>
</organization>
</parameter>
</target>
</request>
Is it possible to remove <innerPayLoad> tag and just insert with CustomAdaptor marshal function return value?
Appreciate help and hints to solve this issue.
You don't need to write a custom adapter for the various profile types within your ProfileSet.
Instead, to handle such mixed XML Content the canonical approach goes like this.
In your ProfileSet class you should define a polymorphic Java property profile
which can take the contents of a <organization.information-profile>,
<connection-profile> or <user-information-profile> element.
(I preferred the name profile here instead of innerPayload).
The mapping between these XML element names and Java classes is done
by using the #XmlElements annotation.
#XmlAccessorType(XmlAccessType.FIELD)
public class ProfileSet {
#XmlElement(name = "name")
private String name;
// template to store different profile objects
#XmlElements({
#XmlElement(name = "organization-information-profile", type = OrganizationInfomationProfile.class),
#XmlElement(name = "connection-profile", type = ConnectionProfile.class),
#XmlElement(name = "user-information-profile", type = UserInformationProfile.class)
})
private Profile profile;
// default constructor used by JAXB unmarshaller
public ProfileSet() {
}
public ProfileSet(String name, Profile profile) {
this.name = name;
this.profile = profile;
}
}
You need an abstract super-class Profile containing only the properties common to all kinds of profiles:
#XmlAccessorType(XmlAccessType.FIELD)
public abstract class Profile {
#XmlElement
private String name;
#XmlElement(name = "attribute")
private List<Attribute> attributes;
}
You have one subclass OrganizationInformationProfile for representing the
<organization-information-profile> element
#XmlAccessorType(XmlAccessType.FIELD)
public class OrganizationInfomationProfile extends Profile {
#XmlElement(name = "qualified-name")
private String qualifiedName;
#XmlElement(name = "last-name")
private String lastName;
#XmlElement(name = "address")
private String address;
// ... other properties
}
and another subclass ConnectionProfile for representing the <connection-profile> element
#XmlAccessorType(XmlAccessType.FIELD)
public class ConnectionProfile extends Profile {
#XmlElement(name = "service")
private Service service;
}
and yet another subclass UserInformationProfile for representing the <user-information-profile> element.
By using the above approach you can unmarshal your XML examples
and get the same output again when marshalling.

Deserializing XML with list using Jackson

I have the following XML that I'd like to deserialize to Java POJO.
<testdata>
<foo>
<bar>
<![CDATA[MESSAGE1]]>
</bar>
<bar>
<![CDATA[MESSAGE2]]>
</bar>
<bar>
<![CDATA[MESSAGE3]]>
</bar>
</foo>
</testdata>
I have the following Java classes
public class TestData {
#JacksonXmlProperty(localName = "foo")
private Foo foo;
public Foo getFoo() {
return foo;
}
public void setFoo(Foo foo) {
this.foo = foo;
}
}
I have another class like below
public class Foo {
#JacksonXmlProperty(localName = "bar")
#JacksonXmlCData
private List<String> barList;
public List<String> getBarList() {
return barList;
}
public void setBarList(List<String> barList) {
this.barList = barList;
}
}
Now when I run the code using the class below I get an exception
private void readXml() throws FileNotFoundException, IOException {
File file = new File("/Users/temp.xml");
XmlMapper xmlMapper = new XmlMapper();
String xml = GeneralUtils.inputStreamToString(new FileInputStream(file));
TestData testData = xmlMapper.readValue(xml, TestData.class);
System.out.println(testData.getFoo()
.getBarList());
}
Exception in thread "main" com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of java.util.ArrayList out of VALUE_STRING token
How do I convert bar elements into a List? I tried multiple things but I keep getting some or the other errors
You need to indicate that <bar> is a wrapping element for your collection of String messages:
This should work in your Foo class:
#JacksonXmlProperty(localName = "bar")
#JacksonXmlCData
#JacksonXmlElementWrapper(useWrapping = false)
private List<String> barList;
In case you have in your input xml a list of bar elements with an attribute like
<testdata>
<foo>
<bar name="John">
<![CDATA[MESSAGE1]]>
</bar>
<bar name="Mary">
<![CDATA[MESSAGE2]]>
</bar>
<bar name="Bill">
<![CDATA[MESSAGE3]]>
</bar>
</foo>
<testdata>
you could create a Bar class and include a list of it as a field of the Foo class:
#JacksonXmlProperty(localName = "bar")
#JacksonXmlElementWrapper(useWrapping = false)
private List<Bar> barList;
The Bar class would be:
class Bar {
#JacksonXmlProperty(isAttribute = true)
private String name;
#JacksonXmlCData
private String content;
}
Remember to include getters and setters for the Bar class.

JAXB - how to unmarshal an entity into list of entities with size 1

I have the following JAXB entity:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = EntityConstants.PARTNER)
public class FilePartner
{
#XmlAttribute(name = EntityConstants.IDENTIFIER, required = true)
private String identifier;
#XmlElement(name = EntityConstants.NAME)
private String name;
#XmlElement(name = EntityConstants.ROOT_PATH)
private String rootPath;
...
}
which serialized into a similar structure:
<file-partner identifier="foo">
<name>bar</name>
<root-path>path123</root-path>
...
</file-partner>
I also have an entity which represents a list of partners:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = EntityConstants.PARTNERS)
public class FilePartnerList
{
#XmlElement(name = EntityConstants.PARTNER)
private List<FilePartner> partners = new ArrayList<FilePartner>();
public List<FilePartner> getPartners()
{
return partners;
}
public void addPartners(List<FilePartner> partners)
{
this.partners.addAll(partners);
}
}
which serializes into:
<partners>
<file-partner identifier="foo">
...
</file-partner>
<file-partner identifier="foo2">
...
</file-partner>
...
</partners>
I am looking for a way to force the jaxb unmarshaller to deserialize XMLs in the form of
<file-partner identifier="foo">
<name>bar</name>
<root-path>path123</root-path>
...
</file-partner>
into FilePartnerList instances with list size of 1, i.e:
JAXBContext context = JAXBContext.newInstance(FilePartner.class, FilePartnerList.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
InputStream inputStream = getResourceAsStream(filePartnerAsXml);
FilePartnerList partnerList = (FilePartnerList) unmarshaller.unmarshal(inputStream); // This SHOULD be unmarshalled to FilePartnerList instead of FilePartner
assertTrue(partnerList.getPartners().getSize().equals(1));
How do I achieve that?
private List<FilePartner> partners = new ArrayList<FilePartner>(**1**);
That way you'll get a fixed size input array....
But I'm sure that you will want to get the XSD of that model with the 'maxocurrs=1' so you will end modifying the XSD manually.
Anyway: why don't you, if the list size must be fixed to '1', simply set it as a simple node with a single child? Something like this (untested):
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = EntityConstants.PARTNERS)
public class FilePartnerList
{
#XmlElement(name = EntityConstants.PARTNER)
private FilePartner partners;
public FilePartner getFilePartner()
{
return partner;
}
public void setPartner(FilePartner partner)
{
this.partner = partner;
}
}
This way you will have one and only one partner per parnets-list.
The XML that fullfits the XSD of your service a text and in that text is indistinguible a list with max size 1 and a node.

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