POJO object for this XML response in kotlin - java

I'm working with an API which returns XML something like this:
<xyz-objects version="1.0">
<object pk="1" model="roll">
<field type="BigIntegerField" name="roll_number">1000000714</field>
<field type="CharField" name="status">DL</field>
<field name="scans">
<object pk="1" model="scan_stages">
<field type="DateTimeField" name="updated_on">11 Jul, 2017, 17:40</field>
</object>
</field>
</object>
</xyz-objects>
I'm using Jackson's XML mapper.
I tried writing a POJO object for this but that doesn't work.
How do I handle attributes of elements like type and name?
Can you exemplify?

Based on the investigation I did in https://github.com/FasterXML/jackson-module-kotlin/issues/138 here's a workaround that's a bit more typed that the JsonNode workaround by madhead
data class Field(
#JacksonXmlProperty(isAttribute = true)
val type: String,
#JacksonXmlProperty(isAttribute = true)
val name: String,
) {
#JacksonXmlText
lateinit var value: String private set
}

As you didn't provide the schema, it's hard to guess some parts of your model. Especially, it's hard to guess field model. So I will deserialize it into a JsonNode, dynamic object that can handle literally everything. JsonIgnoreProperties just in case you have some extra fields, not mentioned here.
First, create some data classes:
#XmlRootElement(name = "xyz-objects")
#JsonIgnoreProperties(ignoreUnknown = true)
data class XyzObjects(
#JacksonXmlProperty(isAttribute = true)
val version: String,
#JsonProperty(value = "object")
#JacksonXmlElementWrapper(useWrapping = false)
val objects: List<Object>
)
#XmlRootElement(name = "object")
#JsonIgnoreProperties(ignoreUnknown = true)
data class Object(
#JacksonXmlProperty(isAttribute = true)
val pk: String,
#JacksonXmlProperty(isAttribute = true)
val model: String,
#JsonProperty(value = "field")
#JacksonXmlElementWrapper(useWrapping = false)
val fields: List<JsonNode>
)
Answering your question: you can handle attrributes with by using isAttribute attribute of JacksonXmlProperty. Lists can be handled with JacksonXmlElementWrapper + JsonProperty (don't be fooled by name, it is used to handle plurals in names, thus making them more human readable in XML too).
In order to be able to work with data classes, you'll need to register Kotlin module with registerKotlinModule:
val text = """<xyz-objects version="1.0">
<object pk="1" model="roll">
<field type="BigIntegerField" name="roll_number">1000000714</field>
<field type="CharField" name="status">DL</field>
<field name="scans">
<object pk="1" model="scan_stages">
<field type="DateTimeField" name="updated_on">11 Jul, 2017, 17:40</field>
</object>
</field>
</object>
</xyz-objects>"""
val mapper = XmlMapper().registerKotlinModule()
val node = mapper.readValue<XyzObjects>(text)
println(node)
It will print:
XyzObjects(version=1.0, objects=[Object(pk=1, model=roll, fields=[{"type":"BigIntegerField","name":"roll_number","":"1000000714"}, {"type":"CharField","name":"status","":"DL"}, {"name":"scans","object":{"pk":"1","model":"scan_stages","field":{"type":"DateTimeField","name":"updated_on","":"11 Jul, 2017, 17:40"}}}])])

Related

Deserialization of xml with attribute AND value

i have little bit trouble with deserialization of XML. I am only able to deserialize this xml by:
#JacksonXmlProperty(localName = "field")
#JacksonXmlElementWrapper(useWrapping = false)
List<Object> field;
This is my xml:
<Response>
<user>
<field attribute="x"></field>
<field attribute="y">false</field>
<field attribute="z">string</field>
</user>
<user>
<field attribute="x"></field>
<field attribute="y">false</field>
<field attribute="z">string</field>
</user>
</Response>
Problem is, that i want to replace Object in List<Object> field; with some specific class so i could access attribute and the value in field.
With Object, i am able to create something like this:
user='[{attribute=x}, {attribute=y, =false}, {name=z, =string}]
thanks a lot.
Found answer. I created new class that contains this elements:
#JacksonXmlProperty(isAttribute = true, localName = "attribute")
String attribute;
#JacksonXmlText
String value;
And replace the Object with this new class.

Cannot parse list of elements from xml with Jackson

when I try to parse the following XML I get an error with Jackson:
<root>
<aCollection>
<language xml:lang="en">
<entry id="1" value="foo"/>
<entry id="2" value="bar"/>
<entry id="3" value="blah"/>
</language>
</aCollection>
</root>
The classes I use are:
#Data
#JacksonXmlRootElement(localName = "root")
public class RootDTO {
#JacksonXmlProperty(localName = "aCollection")
private CollectionDTO collection;
}
#Data
public class CollectionDTO {
#JacksonXmlProperty(localName = "language")
LanguageDTO language;
}
#Data
public class LanguageDTO {
#JacksonXmlProperty(localName = "entry")
EntryDTO[] entries;
}
#Data
public class EntryDTO {
#JacksonXmlProperty(isAttribute = true)
private String id;
#JacksonXmlProperty(isAttribute = true)
private String value;
}
The error is:
Can not instantiate value of type [simple type, class com.tinkerdesk.viewer.model.DTO.EntryDTO] from JSON String; no single-String constructor/factory method
If I remove the array brackets it works but of course only parses the first entry element. I found that I should maybe use #JacksonXmlElementWrapper(useWrapping = false) on top of EntryDTO[] entries; since the elements are not wrapped. However this does not comile for me because there is no useWrapping in the com.fasterxml.jackson.xml.annotate.JacksonXmlElementWrapper. My Jackson core version is 2.9.6 and xml-databind 0.6.2. Is there now a newer way to do this?
Ok nevermind I made a stupid error. The 0.6.2 version of Jackson Dataformat Xml should have made me suspicious in the first place. It seems at some point they moved to a new namespace and I was using the old namespace in gradle which sadly resolved to a maven repository that has last been updated in 2011.
Updating the namespace made it resolve 2.9.5 which has the useWrapping property. Setting this resolved the issue.

Transform from SolrDocument to class

I need to convert data retrieved from Solr to specific class, I've tried 3 different ways, but each of them has some problem.
Method 1 : using 3rd party library - JSONUtil.
SolrDocument solrDocument = (SolrDocument) iterator.next();
String jsonString = JSONUtil.toJSON(solrDocument);
EventObject eventObject2 = gson.fromJson(jsonString, EventObject.class);
Method 2 : transform solrDocument to JsonString directly.
String jsonString2 = gson.toJson(solrDocument);
EventObject eventObject3 = gson.fromJson(jsonString2, EventObject.class);
Method 3 : transform solrDocument to JSONObject then convert to JsonString.
JSONObject object = new JSONObject(solrDocument);
String jsonStringFromJsonObject = object.toString();
EventObject eventObjectFromJsonObject = gson.fromJson(jsonStringFromJsonObject, EventObject.class);
Method 4 : using Java Beans
List<EventObject> EventObjects = response.getBeans(EventObject.class);
The result of Method1 can't handle fields which is Date or ArrayList Type. The result of Method2, Method3 and Method4 are all the same, they handle all fields well, however, ArrayList field are still empty.
Edit :
My Solr schema is as follows(only mention the 2 fields which can't transform correctly) :
<dynamicField name="*_Image_Infos" type="string" indexed="true" stored="true" required="false" multiValued="true" />
<dynamicField name="*_ss" type="string" indexed="true" stored="true" required="false" multiValued="true" />
My EventObject is as follows :
public List<EventObjectImageInfo> ImageInfos = new ArrayList<EventObjectImageInfo>();
public List<EventObjectColor> Colors = new ArrayList<EventObjectColor>();
Both of EventObjectImageInfo & EventObjectColor are my own classes.
Did I do anything wrong? Or is there any other way should I try? Thanks!
You can use Solr JavaBeans
Let's say i have the solr schema :
<field name="id" type="string" multiValued="false" indexed="true" required="true" stored="true"/>
<field name="name" type="string" indexed="false" stored="true"/>
<field name="marks" type="long" multiValued="true" indexed="true" stored="true"/>
I have the Student class
public class Student {
#Field
String id;
#Field
String name;
#Field
ArrayList<Long> marks;
}
Now I can convert SolrDocumentList to List with javabeans
SolrDocumentList list; // you already have that
DocumentObjectBinder binder = new DocumentObjectBinder();
List<Student> dataList = binder.getBeans(Student.class, list);

Dozer - From List of entities to list of entities' id

I have a domain object called User:
public class User {
private long id;
private String username;
private String email;
private List<Profile> profiles;
// getters & setters
}
And I have the related DTO (UserDTO) which is
public class UserDTO {
private long id;
private String username;
private String email;
private List<Long> profilesId;
// getters & setters
}
I'd like to use Dozer to convert from domain object to DTO. The Profile class has a property
Long id;
What I want is that Dozer takes the profile's id for each profile in the list and save it in the DTO's list. Can I do something like that? Do I have to use custom converters?
Here's my actual mapping file
<mapping>
<class-a>common.model.User</class-a>
<class-b>common.model.dto.UserDTO</class-b>
<field>
<a>legalEntity.id</a>
<b>legalEntityId</b>
</field>
<field type="one-way">
<a>profiles.id</a>
<b>profilesId</b>
</field>
</mapping>
Solved
just add to the source class this method
public List<Long> getProfilesId() {
List<Long> profilesId = new ArrayList<Long>();
for(Profile p : this.profiles) {
profilesId.add(p.getId());
}
return profilesId;
}
and to the mapping file
<field type="one-way">
<a get-method="getProfilesId">profiles</a>
<b>profilesId</b>
</field>
which says Dozer which method use to make the conversion.

Dozer Mapping Confusion

I ve a confusion regarding how the dozer maps the source object to destination object. I have the following scenario:
Source Object:
public class Rule {
private String id;
private String name;
private String group;
private String content;
private RuleType ruleType;
private String drlContent;
private boolean enabled;
private Strategy strategy;
// getters and setters
}
Destination Object:
public class RuleActivity {
private String id;
private String name;
private Strategy strategy;
// getters and setters
}
XML Mapping:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<stop-on-errors>true</stop-on-errors>
<date-format>MM/dd/yyyy HH:mm</date-format>
</configuration>
<mapping wildcard="false">
<class-a>com.magick.models.shared.Rule</class-a>
<class-b>com.magick.models.shared.log.RuleActivity</class-b>
<field>
<a>id</a>
<b>ruleId</b>
</field>
<field>
<a>strategy.name</a>
<b>strategy.name</b>
</field>
<field>
<a>name</a>
<b>name</b>
</field>
</mapping>
Now How these would be mapped ? I mean , does the destination Object contains the Complete Strategy Object or only the strategy.name field of it.
First of all, by default dozer mappings are bi-directional. So,
mapping from class-a to class-b and vice-versa is permitted.
As you have done your mapping as follows:
<field>
<a>strategy.name</a>
<b>strategy.name</b>
</field>
If the source object is having a Strategy object which is not null and have all the relevant field's value. Then dozer will create a new Strategy object for destination as well and will only populate the name field of newly created Strategy object.
Further, dozer also works on retrospection so suppose the name and type of all fields of Strategy object in source and destination is same. Dozer will map or copy all the fields automatically. So you don't have to map each field individually. you just have to write as below.
<field>
<a>strategy</a>
<b>strategy</b>
</field>
But if your field names or type is not same, you need to define mapping for each field as you did for id field for Rule class and ruleId field for RuleActivity class.
<field>
<a>id</a>
<b>ruleId</b>
</field>
Hope this clarifies your doubts.

Categories

Resources