Android - SimpleXML framework cannot parse #ElementList - java

I am using SimpleXML framework to parse xmls in my Android application. I have a problem with getting #ElementList parsed correctly.
A fragment of xml:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<SaleToPOIResponse>
<MessageHeader (...) />
<ReconciliationResponse ReconciliationType="SaleReconciliation">
<Response Result="Success"/>
<TransactionTotals PaymentInstrumentType="Card">
<PaymentTotals TransactionType="Debit" TransactionCount="182" TransactionAmount="4.17"/>
<PaymentTotals TransactionType="Credit" TransactionCount="1" TransactionAmount="2.01"/>
</TransactionTotals>
</ReconciliationResponse>
</SaleToPOIResponse>
My classes look:
ReconciliationResponseType.java:
#Root
#Order(elements = {
"Response",
"TransactionTotals"
})
public class ReconciliationResponseType {
#Element(name = "Response", required = true)
protected ResponseType response;
#ElementList(name = "TransactionTotals", inline = true, required = false)
protected List<TransactionTotalsType> transactionTotals;
#Attribute(name = "ReconciliationType", required = true)
protected String reconciliationType;
// getters and setters
}
TransactionTotalsType.java:
#Root
#Order(elements = {
"PaymentTotals",
"LoyaltyTotals"
})
public class TransactionTotalsType {
#ElementList(name = "PaymentTotals", inline = true, required = false)
protected List<PaymentTotalsType> paymentTotals;
#Attribute(name = "PaymentInstrumentType", required = true)
protected String paymentInstrumentType;
// getters and setters
}
I parse it using method:
public static SaleToPOIResponse fromXMLString(String xmlResponse) {
Reader reader = new StringReader(xmlResponse);
Serializer serializer = new Persister();
try {
SaleToPOIResponse response = serializer.read(SaleToPOIResponse.class, reader, false);
return response;
} catch (Exception e) {
Log.e(TAG, "Exception during parsing String XML to SaleToPOIResponse: ", e);
}
return null;
}
But every time I get an exception, that ordered element 'TransactionTotals' is missing, even though 1) it is not required 2) it does exist in the parsed xml
org.simpleframework.xml.core.ElementException: Ordered element 'TransactionTotals' missing for class pl.novelpay.epas.generated.saletopoimessages.ReconciliationResponseType
When I comment the 'TransactionTotals' from #Order the xml is parsed without an exception, but the TransactionTotals filed in result is empty. What am I missing here?

I found what was a problem while reading answer to a similar problem here:
https://sourceforge.net/p/simple/mailman/message/25699359/
I was using name insted of entry. So an ElementList attribute should look like this:
#ElementList(entry= "PaymentTotals", inline = true, required = false)
protected List<PaymentTotalsType> paymentTotals;
Now it works perfectly.

Related

Jackson XML deserialization of abstract type results in fields with null values

When trying to deserialize XML to an object that extends an abstract base class, I'm seeing that the list of references contains the expected number of elements, but all the fields on those objects are null.
This only happens if I create an XML reader for the abstract class. If I deserialize directly to the concrete implementation all the fields have the expected value.
I've added the minimum working example below
Expected output (as json for readability)
{
"References": [ { "id": "1", "Type": "Secondary Image" } ]
}
Actual output (as json for readability)
{
"References": [ { "id": null, "Type": null } ]
}
Test Data
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Foo TypeID="ConcreteA">
<Reference ID="1" Type="Secondary Image">
<Values/>
</Reference>
</Foo>
Abstract base class
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "TypeID", visible = true)
#JsonSubTypes({
#JsonSubTypes.Type(value = ConcreteClassA.class, name = "ConcreteA")
})
public abstract class AbstractBase {
#JacksonXmlProperty(localName = "TypeID", isAttribute = true)
private String typeId;
#JsonIgnore
private List<Reference> references = new ArrayList<>();
#JacksonXmlElementWrapper(useWrapping = false)
#JacksonXmlProperty(localName = "Reference")
public List<Reference> getReferences() {
return references;
}
#JsonSetter
public AbstractBase setReferences(List<Reference> references) {
this.references.addAll(references);
return this;
}
}
Concrete Implementation
public class ConcreteClassA extends AbstractBase {}
Test Cases
public class DeserializationTest {
#Test
public void deserializedAbstractClass_fieldsShouldNotBeNull() throws JsonProcessingException {
var mapper = new XmlMapper()
.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true)
.deactivateDefaultTyping()
.registerModule(new JacksonXmlModule());
var xmlData = readTestData();
var reader = mapper.readerFor(AbstractBase.class);
var deserializedObject = reader.readValue(xmlData);
assert(deserializedObject instanceof ConcreteClassA);
var concreteClassA = (ConcreteClassA) deserializedObject;
assert(concreteClassA.getReferences().get(0).getId() != null);
}
#Test
public void deserializedConcreteClass_fieldsShouldNotBeNull() throws JsonProcessingException {
var mapper = new XmlMapper()
.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true)
.configure(MapperFeature.USE_BASE_TYPE_AS_DEFAULT_IMPL, true)
.registerModule(new JacksonXmlModule());
var xmlData = readTestData();
var reader = mapper.readerFor(ConcreteClassA.class);
var deserializedObject = reader.readValue(xmlData);
assert(deserializedObject instanceof ConcreteClassA);
var concreteClassA = (ConcreteClassA) deserializedObject;
assert(concreteClassA.getReferences().get(0).getId() != null);
}
private String readTestData() {
try {
var datafile = getClass().getClassLoader().getResource("TestData.xml");
return Files.lines(Paths.get(datafile.toURI())).collect(Collectors.joining());
} catch (Exception e) { return ""; }
}
}
Turns out there are multiple problems, that I've managed to solve.
The Jackson version I was using (2.11) had some problems with multiple elements using the same tag, not in a wrapper. This is something I was aware of, and is the reason why my setter does "addAll" instead of just setting the list
This problem was solved by upgrading to 2.12, which means that it's no longer necessary to do this to handle unwrapped elements.
Jackson failed to properly deserialize the items, because the setter accepts a list, which apparently breaks due to some java generic mumbo jumbo (I was never able to figure out exactly why, just that it happens).
I solved this by having the setter accept a single element, and then adding that to the list
#JsonSetter(value = "Reference")
public AbstractBase setReferences(Reference reference) {
this.references.add(references);
return this;
}

Fail to convert xml into JAVA class

I have an xml response and want it to convert into java class for android usage.
<?xml version="1.0" encoding="UTF-8"?>
<title>
<results_info>
<page>1</page>
<total_pages>8</total_pages>
</results_info>
<listing_info>
<id>4</id>
<image></image>
</listing_info>
<listing_info>
<id>4</id>
<image></image>
</listing_info>
<listing_info>
<id>4</id>
<image></image>
</listing_info>
</title>
This is the code use for data fetch
public interface ApiService {
#GET("myUrl")
Call<MyObject> reqProfile();
}
Api WEB_SERVICE = new Retrofit.Builder()
.baseUrl("http://www.baseUrl.com/")
.client(client)
.addConverterFactory(SimpleXmlConverterFactory.create())
.build().create(Api.class);
how can I make a model class of MyObject for it
Thank you developers for interesting in the question, and finally after a lot of struggle I got to the point and solve this problem here is JAVA class of this xml.
I have created three classes for efficient work.
1: MyObject
public class MyObject {
#Element(name = "results_info")
private Results_info results_info;
#ElementList(inline = true)
List<Listing_info> listing_info;
}
2: Results_info
public class Results_info {
private String page;
private String total_pages;
}
3: Listing_info
public class Listing_info {
#Element(required = false)
String id;
#Element(required = false)
String image;
#Element(required = false)
}
#Element(required = false) is due to some time data may be empty.
And in response in call manager of retrofit, we can get all the data from accessing objects of these classes just like
...
if (response != null && response.isSuccessful() && response.body() != null) {
MyObject object = (MyObject) response.body();
String page = object.getResults_info().getPage();
List<Listing_info> myList = object.getList();
String a = myList.get(0).getImage();
}
...
Cheers.

return just part of the bean with toJson()

I am trying to return {"status": its value}´in the case of routeD!=0 currently I am getting {"status":201,"routes":null} I would get the response in this form {"status":201} without "routes":null at the same time I dont want to lost the response of routeD==0 which is for example {"status":230,"routes":[1,9,3]}
I appeciate any help.
Receiver class:
#Path("/data")
public class Receiver {
#POST
#Consumes(MediaType.APPLICATION_JSON)
public Response storeData(Data data) {
Database db = new Database();
String macD = data.getMac();
int routeD = data.getRoute();
double latD = data.getLatitude();
double longD = data.getLongitude();
double speedD = data.getSpeed();
// Jackson class to wrapper the data in JSON string.
SDBean bean = new SDBean();
if (routeD != 0) {
bean.status = db.insertData(macD, routeD, latD, longD);
return Response.status(bean.status).entity(bean.toJson()).build();
} else {
bean.routes = db.detectRoute(latD, longD);
return Response.status(230).entity(bean.toJson()).build();
}
}
}
SDBean class:
public class SDBean {
public int status;
public ArrayList<Integer> routes;
public SDBean(){
status = 230;
}
public String toJson() {
ObjectMapper mapper = new ObjectMapper();
String json = null;
try {
json = mapper.writeValueAsString(this);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
System.out.println(json);
return json;
}
}
Just use #JsonInclude(JsonInclude.Include.NON_NULL)
Annotation used to indicate when value of the annotated property (when used for a field, method or constructor parameter), or all properties of the annotated class, is to be serialized. Without annotation property values are always included, but by using this annotation one can specify simple exclusion rules to reduce amount of properties to write out.
import com.fasterxml.jackson.annotation.JsonInclude;
[...]
#JsonInclude(JsonInclude.Include.NON_NULL)
public class SDBean {

XMLRootElement converting a class to XML in jersey

I'm very good in converting a model class to JSON Array or Object.
But i'm a noob when it comes to XML.
I want my final output to be like this
<Response>
<Say voice="alice">Thanks for trying our documentation. Enjoy!</Say>
</Response>
To achieve it i create a model class
#XmlRootElement(name = "Response")
public class Response {
private Say say = new Say();
public Say getSay() {
return say;
}
public void setSay(Say say) {
this.say = say;
}
#XmlRootElement(name = "Say")
static class Say {
#XmlAttribute
private String voice = "alice";
private String string = "Thanks for trying our documentation. Enjoy!";
public String getString() {
return string;
}
public void setString(String string) {
this.string = string;
}
}
}
Now after converting it to XML with jersey my output was
<Response>
<say voice="alice">
<string>Thanks for trying our documentation. Enjoy!</string>
</say>
</Response>
I got a extra string tag. I'm not sure what attribute to set for the String so that it comes in the body.? Or is there any other way?
Also for say. The 'S' is not capitalised. How can i make it a capital letter?
Thanks in advance
By default properties and public fields will be mapped to elements. What you want to do is use #XmlValue to map the field to the element's value.
#XmlRootElement(name = "Say")
#XmlAccessorType(XmlAccessType.FIELD)
static class Say {
#XmlAttribute
private String voice = "alice";
#XmlValue
private String string = "Thanks for trying our documentation. Enjoy!";
public String getString() {
return string;
}
public void setString(String string) {
this.string = string;
}
}
Note the use of #XmlAccessorType(XmlAccessType.FIELD). This is so the default behavior doesn't "doubly" attempt to map the property defined by the getter and setter. Alternatively, you could place the annotations on the getter, and leave out the the #XmlAccessorType
Result:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Response>
<say voice="alice">Thanks for trying our documentation. Enjoy!</say>
</Response>
public class ResponseTest {
public static void main(String[] args) throws Exception {
JAXBContext context = JAXBContext.newInstance(Response.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
Response response = new Response();
marshaller.marshal(response, System.out);
}
}
UPDATE
but can i know why the 'S' in Say is not capitalised even though #XmlRootElement(name = "Say") is specified?
You need to specify the name with #XmlElement(name = "Say") on the property. If you don't the default naming will kick in.
#XmlElement(name = "Say")
public Say getSay() {
return say;
}
The XmlRootElement(name = "Say") is only for if the element is used as the root element. For instance this:
Response.Say response = new Response.Say();
marshaller.marshal(response, System.out);
Would give you this output
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Say voice="alice">Thanks for trying our documentation. Enjoy!</Say>

Javax Validation Size annotation does not behave correctly

So I have a simple class like this:
import javax.validation.constraints.Size;
public class Temail {
#Size(min = 0, max = 10)
private String json;
public Temail(String json) {
Validate.notEmpty(json, "json can't be empty");
setJson(json);
}
public void setJson(String json) {
this.json = json;
}
public String getJson() {
return json;
}
}
What I am expecting is if I run new Temail("1234123123123"); I expect it to throw an exception but it does not. I looked at the conventions and it does fit into the right convention. So what's the problem here?
It seems that it is not supposed to throw an exception when a constraint is invalid. Instead, you use a validator instance to validate the fields based on the constraints you define. So, something like this:
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Temail t = new Temail("123456789012345");
Set<ConstraintViolation<Temail>> constraintViolations = validator.validateProperty(t, "json");
See more examples and explanations here.

Categories

Resources