Simple framework. Do not serialize some variables to xml - java

I have problem serializing java object to XML. My classes are shown below:
#Root(strict = false, name = "Detail")
public class ProductList {
#ElementList(inline = true, entry = "Product")
private List<Product> products;
}
#Root(strict = false)
public class Product {
#Element(name = "ProductCode")
private String productCode;
#Element(name = "ProductPrice")
private double productPrice;
#Element(name = "Currency")
private String currency;
#Element(name = "ConversionRate")
private int conversionRate;
#Element(name = "ProductPoints", required = false)
private int productPoints;
#Element(name = "ProductCount", required = false)
private int productCount;
#Element(name = "ProductName", required = false)
private String productName;
#Element(name = "MinPrice", required = false)
private double minPricet;
#Element(name = "MaxPrice", required = false)
private double maxPricet;
#Element(name = "CanChangePrice", required = false)
private String canChangePrice;
}
The XML below is sent from server and it's deserialized without any problem:
<?xml version="1.0" encoding="UTF-8"?>
<Detail>
<Product>
<ProductCode>0001</ProductCode>
<ProductPrice>0.90</ProductPrice>
<Currency>GEL</Currency>
<ConversionRate>200</ConversionRate>
<ProductName>Bread</ProductName>
<MinPrice>0.9</MinPrice>
<MaxPrice>0.9</MaxPrice>
<CanChangePrice>N</CanChangePrice>
</Product>
<Product>
...
</Product>
</Detail>
I try to generate the XML document that will have this structure:
<?xml version="1.0" encoding="UTF-8"?>
<Detail>
<Product>
<ProductCode>0001</ProductCode>
<ProductPrice>0.90</ProductPrice>
<Currency>GEL</Currency>
<ConversionRate>200</ConversionRate>
<ProductPoints>180</ProductPoints>
<ProductCount>1</ProductCount>
</Product>
<Product>
...
</Product>
</Detail>
But I get this:
<Detail>
<Product>
<ProductCode>0001</ProductCode>
<ProductPrice>0.9</ProductPrice>
<Currency>GEL</Currency>
<ConversionRate>200</ConversionRate>
<productPoints>180</productPoints>
<ProductCount>1</ProductCount>
<ProductName>Bread</ProductName>
<MinPrice>0.9</MinPrice>
<MaxPrice>0.9</MaxPrice>
<CanChangePrice>N</CanChangePrice>
</Product>
<Product>
...
</Product>
</Detail>
Tags <ProductName>, <MinPrice>, <MaxPrice>, <CanChangePrice> mustn't be included in the serialized XML.
Is there any way I can tell the framework not to include specific tags\variables while serializing?

The problem
Your class members are not initialized to null and so required=false has not the effect of not serializing them.
ints are serialized by org.simpleframework.xml.transform.IntegerTransform. The write(Integer) method of this class is simple:
public String write(Integer value) {
return value.toString();
}
As you can see, simple autoboxing is used.
Your primitive int is initialized to 0 by the constructor.
It is boxed into an Integer.
The String value of this Integer is "0" which is not null.
Solution
Use Integer, not int for your class members.
Edit
If you don't want to serialize a member, don't annotate it with #Element. Simple is not about 'generating' XML but about mapping instances to/from XML. Every member you want to map needs an annotation. Every member with an annotation will be mapped.

My solution is pretty ugly. I created another class for XML serialization, that contains every field from the original class except the fields I wanted to omit in the XML.

You can override default serialization mechanism with converter to customize the output. For example that you provide it will be:
public class CustomProductConverter implements Converter<Product> {
private void createProductPropertyNode(OutputNode productNode, String propertyName, String value) throws Exception {
productNode.getChild(propertyName).setValue(value);
}
private void createProductPropertyNode(OutputNode productNode, String propertyName, int value) throws Exception {
createProductPropertyNode(productNode, propertyName, String.valueOf(value));
}
private void createProductPropertyNode(OutputNode productNode, String propertyName, double value) throws Exception {
createProductPropertyNode(productNode, propertyName, String.valueOf(value));
}
#Override
public Product read(InputNode inputNode) throws Exception {
throw new UnsupportedOperationException();
}
#Override
public void write(OutputNode outputNode, Product product) throws Exception {
createProductPropertyNode(outputNode, "ProductCode" , product.getProductCode());
createProductPropertyNode(outputNode, "ProductPrice" , product.getProductPrice());
createProductPropertyNode(outputNode, "Currency" , product.getCurrency());
createProductPropertyNode(outputNode, "ConversionRate", product.getConversionRate());
createProductPropertyNode(outputNode, "ProductPoints" , product.getProductPoints());
createProductPropertyNode(outputNode, "ProductCount" , product.getProductCount());
outputNode.commit();
}
}
And then use a RegistryStrategy for serialization:
Registry registry = new Registry();
registry.bind(Product.class, CustomProductConverter.class);
Strategy strategy = new RegistryStrategy(registry);
Serializer serializer = new Persister(strategy);
// serialize your object with serializer
PROS: you can dynamically serialize same object with different converters and get different output without modification of the model classes.
CONS: in the case of a complex model there will be a lot of similar code in converter.

Related

Jackson: deserialize custom attributes in XML to POJO

I would like to deserialize and map to class following values by name attribute.
This is piece of my XML file.
<custom-attributes>
<custom-attribute name="Name1" dt:dt="string">VALUE</custom-attribute>
<custom-attribute name="Name2" dt:dt="string">
<value>1111</value>
<value>1111</value>
<value>1111</value>
</custom-attribute>
<custom-attribute name="Name3" dt:dt="string">VALUE2</custom-attribute>
<custom-attribute dt:dt="boolean" name="Name3">VALUE3</custom-attribute>
<custom-attribute dt:dt="boolean" name="Name4">VALUE4</custom-attribute>
</custom-attributes>
And This is piece of my pojo class
#JsonIgnoreProperties(ignoreUnknown = true)
public class CustomAttributes {
#JacksonXmlProperty(localName="name3", isAttribute = true)
private String roleID;
public String getRoleID() {
return roleID;
}
public void setRoleID(String roleID) {
this.roleID = roleID;
}
}
Do you know ho to properly read values from those attribues by name ? Currently im receiving null
I am not sure what the result is supposed to look like, but if you want
to parse the complete xml into matching objects they would look like this:
public class CustomAttributeList {
#JacksonXmlProperty(localName = "custom-attributes")
private List<CustomAttributes> list;
...
}
#JacksonXmlRootElement(localName = "custom-attribute")
public class CustomAttributes {
// the name attribute
#JacksonXmlProperty
private String name;
// the datatype from the dt:dt field
#JacksonXmlProperty(namespace = "dt")
private String dt;
// the content between the tags (if available)
#JacksonXmlText
private String content;
// the values in the content (if available)
#JacksonXmlProperty(localName = "value")
#JacksonXmlElementWrapper(useWrapping = false)
private List<String> values;
...
}
Note that the localName="name3" from your question is not referring to a property at all.

Unmarshal element attribute and text to separate fields with jaxb

How do I annotate the externalValue and companyId fields in the Root class so that "abc" gets mapped to externalValue and "123" gets mapped to companyId?
Do I need the #XmlJavaTypeAdapter annotation? Where? I'm hoping that if I do, it can just handle those 2 fields and I can leave the annotations for title and countryCodes as-is.
XML:
<item>
<externalValue companyId="123">abc</externalValue>
<title>My Title</title>
<country>US</country>
<country>CA</country>
</item>
POJO:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Item {
private String externalValue;
private String companyId;
#XmlElement
private String title;
#XmlElement(name = "country")
public List<String> countryCodes;
// getters and setters...
}
I am afraid that this is not possible to achieve only with annotations (so without extra POJO and some adapter) in general case namely JAXB specs. However if your happen to use MOXy as your JAXB implementation it is easy as adding annotation #XmlPath like this:
#XmlPath("externalValue/#companyId")
private String companyId;
Related question: Unmarshalling an XML using Xpath expression and jaxb
You have to define the class in the following manner.
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Item {
private CompanyIdValue companyIdValue;
#XmlElement
private String title;
#XmlElement(name = "country")
public List<String> countryCodes;
//getter and setter
}
In case of both attribute in an XML element tag, you have to define a separate class. Define a separate class called CompanyIdValue, for XML element, you have to define #XmlValue and for attribute you have to annotate with #XmlAttribute
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlValue;
public class CompanyIdValue {
#XmlElement(name = "externalValue")
private String externalValue;
private String companyId;
public String getExternalValue() {
return externalValue;
}
#XmlValue
public void setExternalValue(String externalValue) {
this.externalValue = externalValue;
}
public String getCompanyId() {
return companyId;
}
#XmlAttribute
public void setCompanyId(String companyId) {
this.companyId = companyId;
}
}
I provide below a test program also for testing.
public class Test {
public static void main(String[] args) {
try {
Item item = new Item();
CompanyIdValue companyIdValue = new CompanyIdValue();
companyIdValue.setCompanyId("SomeId");
companyIdValue.setExternalValue("Some External value");
item.setCompanyIdValue(companyIdValue);
item.setCountryCodes(Arrays.asList("A", "B"));
item.setTitle("Some Title");
JAXBContext jaxbContext = JAXBContext.newInstance(Item.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
jaxbMarshaller.marshal(item, System.out);
} catch (JAXBException e) {
e.printStackTrace();
}
}
}

Use JAXB to create Object from XML String with Multiple Groups in XML file

I need to convert XML string into java object.
This is the XML File
<?xml version="1.0" encoding="UTF-8"?>
<DATA_DS>
<G_1>
<TERM_ID>4</TERM_ID><NAME>30 Net</NAME>
</G_1>
</DATA_DS>
I have created Class like this;
#XmlRootElement(name = "DATA_DS")
#XmlAccessorType(XmlAccessType.FIELD)
public class PaymentTerm {
#XmlElement(name = "TERM_ID")
private double termId;
#XmlElement(name = "NAME")
private String termName;
public double getTermId() {
return termId;
}
public void setTermId(double termId) {
this.termId = termId;
}
public String getTermName() {
return termName;
}
public void setTermName(String termName) {
this.termName = termName;
}
}
In Main Class
jaxbContext = JAXBContext.newInstance(PaymentTerm.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
StringReader reader = new StringReader(xmlString);
PaymentTerm paymentTerm = (PaymentTerm) unmarshaller.unmarshal(reader);
This doesn't unmarshell the XML string properly because of nested groups in XML file.
If I remove the G_1 group from XML file then it convert perfectly. I need to do conversion with G_1 group
Where I have to fix the code?
<DATA_DS> contains one element, <G_1>, which itself contains two elements, <TERM_ID> and <NAME>, so your objects needs to reflect that, i.e. the class representing <DATA_DS> must have one field, typed to be a class representing <G_1>, which must have two fields.
Where I have to fix the code?
You need to create a class for <G_1>:
#XmlRootElement(name = "DATA_DS")
#XmlAccessorType(XmlAccessType.FIELD)
public class PaymentTerm {
#XmlElement(name = "G_1", required = true)
private PaymentGroup group;
}
#XmlAccessorType(XmlAccessType.FIELD)
public class PaymentGroup {
#XmlElement(name = "TERM_ID", required = true)
private double termId;
#XmlElement(name = "NAME", required = true)
private String termName;
}
You should also consider why <G_1> exists, e.g. can there be more than one <G_1> inside <DATA_DS>? If so, make it a list:
#XmlElement(name = "G_1", required = true)
private List<PaymentGroup> groups;

Missing name, in state: START_OBJECT parsing XML using Jackson

I'm trying to parse some XML that looks like this:
<correlationMatrix>
<assetMatrix numAssets="45">
<correlations asset="Name1" />
<correlations asset="Name2">
<correlation asset="Name3">1.23</correlation>
</correlations>
<correlations asset="Name4">
<correlation asset="Name5">2.34</correlation>
<correlation asset="Name6">3.45</correlation>
</correlations>
</assetMatrix>
</correlationMatrix>
I've created 3 classes:
#JsonIgnoreProperties(ignoreUnknown = true)
public class CorrelationMatrix {
private List<Correlations> assetMatrix;
public List<Correlations> getAssetMatrix() {
return assetMatrix;
}
public void setAssetMatrix(List<Correlations> assetMatrix) {
this.assetMatrix = assetMatrix;
}
}
And
#JsonIgnoreProperties(ignoreUnknown = true)
public class Correlations {
private String asset;
private List<Correlation> correlation;
public String getAsset() {
return asset;
}
public void setAsset(String asset) {
this.asset = asset;
}
public List<Correlation> getCorrelation() {
return correlation;
}
public void setCorrelations(List<Correlation> correlation) {
this.correlation = correlation;
}
}
Then finally
#JsonIgnoreProperties(ignoreUnknown = true)
public class Correlation {
}
As you can see I've removed everything from the final inner class, but it still fails to parse. I've tried removing <correlations asset="Name1" /> from the input but that's not the source of the problem. If I remove private List<Correlation> correlation; from Correlations then that does then parse successfully but obviously doesn't have the information I need.
What is it that I need to do differently here to parse what is essentially a 2 dimensional array from XML into Java using Jackson (2.2.0 if that matters)?
The error I get is:
Missing name, in state: START_OBJECT (through reference chain: CorrelationMatrix["assetMatrix"]->Correlations["correlation"])
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(
Update:
The problem seems to be associated with the values inside correlation. If I remove 1.23, 2.34 and 3.45 from my example data then it parses - so I need to somehow tell Jackson how to map them.
I was able to parse all the elements in the example xml with these modified classes (add getters, setters and use correct name setCorrelation in Correlations):
class CorrelationMatrix {
private AssetMatrix assetMatrix;
}
class AssetMatrix {
#JacksonXmlProperty(isAttribute = true)
private int numAssets;
#JacksonXmlElementWrapper(useWrapping = false)
private List<Correlations> correlations;
}
class Correlations {
#JacksonXmlProperty(isAttribute = true)
private String asset;
#JacksonXmlElementWrapper(useWrapping = false)
private List<Correlation> correlation;
}
class Correlation {
#JacksonXmlProperty(isAttribute = true)
private String asset;
#JacksonXmlText
private double correlation;
}
I didn't need #JsonIgnoreProperties(ignoreUnknown = true) anywhere
#JacksonXmlProperty(isAttribute = true) is needed for attributes like asset and numAssets
There are 2 types of lists in the xml that are both unwrapped so specify it with this #JacksonXmlElementWrapper(useWrapping = false)
You can parse the innermost double numbers with this #JacksonXmlText although the field in Java is not text.
I introduced a wrapper class AssetMatrix to capture numAssets

Byte array is not working in JAXB classes

I am trying to use byte array like this (JAXB class). However, I am getting all 0s in the msg field even though I pass valid characters. The "id" and "myid" fields are parsed successfully and it is failing for the byte array field.
#XmlRootElement(name = "testMessage")
#XmlAccessorType(XmlAccessType.FIELD)
public class TestMessage
{
#XmlAttribute
private Integer id;
#XmlElement(name = "myid")
private Long myid;
#XmlElement(name = "msg")
private byte[] msg;
}
Using JAXB of Java 1.6.0_23 i get the following xml file for a TestMessage instance:
TestMessage testMessage = new TestMessage();
testMessage.id = 1;
testMessage.myid = 2l;
testMessage.msg = "Test12345678".getBytes();
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<testMessage id="1">
<myid>2</myid>
<msg>VGVzdDEyMzQ1Njc4</msg>
</testMessage>
If you unmarshall this xml content you should get back the TestMessage instance including the msg byte array (which is base64 encoded in the xml file).
You can use xml adapters for your byte array xml element. As you now, every element get marshalling/unmarshalling and adapters are use for situations such as converting date time with specified format, type convertions etc. while marshalling/unmarshalling.
HexBinaryAdapter class is one of those adapters belongs to javax.xml.bind.annotation.adapters so you can use it.
public class TestMessage {
#XmlAttribute
private Integer id;
#XmlElement(name = "myid")
private Long myid;
#XmlJavaTypeAdapter(HexBinaryAdapter.class)
#XmlElement(name = "msg")
private byte[] msg;
}
Yet, if you prefer a custom convertion, you can create your own adapter for converting bytes for a specified format such as base64 etc.
To do that you must write your own unmarshalling/marshalling methods,
public final class MyAdapter extends XmlAdapter<String, byte[]> {
public byte[] unmarshal(String s) {
if (s == null)
return null;
return decode()); // your way to decode.
}
public String marshal(byte[] bytes) {
if (bytes == null)
return null;
return encode(); //your way to encode
}
}
then you give your marshaller/unmarshaller in #XmlJavaTypeAdapter anotation ;
public class TestMessage {
#XmlAttribute
private Integer id;
#XmlElement(name = "myid")
private Long myid;
#XmlJavaTypeAdapter(MyAdapter.class)
#XmlElement(name = "msg")
private byte[] msg;
}

Categories