How to handle different children with XStream? - java

This is the scenario:
<family>
<person>name</person>
<person>
<address> street </adress>
<address> street </address>
</person>
</family>
The person value can be a list of addresses or just the person name.
I think the solution is to use a converter but how do you do that?
Check what input you retrieve?
Tell the converter to just continue using the defaults for class 3?
Example class : (do note that this is for illustration)
public class Person {
private String name;
private List<Address> address;
}
public class Address {
private String street;
}

You do need to use a converter. Here is the converter for your example:
public class PersonConverter implements Converter {
public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) {
Person person = (Person) value;
if(person.name != null){
writer.setValue(person.name);
} else if(person.address != null){
for (Address address : person.address){
writer.startNode("address");
writer.setValue(address.street);
writer.endNode();
}
}
}
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
Person person = new Person();
person.name = reader.getValue();
if(person.name.trim().length()==0){
person.name = null;
}
List<Address> addresses = getAddress(reader, new ArrayList<Address>());
person.address = addresses;
if(person.address.size() == 0){
person.address = null;
}
return person;
}
private List<Address> getAddress(HierarchicalStreamReader reader, List<Address> addresses){
if (!reader.hasMoreChildren()){
return addresses;
}
reader.moveDown();
if(reader.getNodeName().equals("address")){
addresses.add(new Address(reader.getValue()));
reader.moveUp();
getAddress(reader, addresses);
}
return addresses;
}
public boolean canConvert(Class clazz) {
return clazz.equals(Person.class);
}
}
Here is the main method:
public static void main(String[] args) {
List<Person> persons = new ArrayList<Person>();
persons.add(new Person("John"));
List<Address> adds = new ArrayList<Address>();
adds.add(new Address("123 street"));
adds.add(new Address("456 street"));
persons.add(new Person(adds));
Family family = new Family(persons);
XStream stream = new XStream();
stream.registerConverter(new PersonConverter());
stream.processAnnotations(new Class[]{Family.class});
String xml = stream.toXML(family);
System.out.println(xml);
Family testFam = (Family) stream.fromXML(xml);
System.out.println("family.equals(testFam) => "+family.equals(testFam));
}
If you implement the equals method for the Family, Person, and Address classes it should print that they are equal at the end of the method when ran. Also worth noting is that I used some Annotations on the Family. I used #XStreamAlias("family") on the Class itself, and then on the collection of Person objects I used #XStreamImplicit(itemFieldName="person").
And here is my ouput when I run the main method supplied:
<family>
<person>John</person>
<person>
<address>123 street</address>
<address>456 street</address>
</person>
</family>
family.equals(testFam) => true

Related

Loop Through Self List

I have this Person class which has a list of Person (s). How do I loop through persons and check if each object inside of that has a list of Person(s) and if each object inside that has a list and so on and so forth? Everything I can think of is pretty limiting as far as how nested it gets. I can write a recursive loop but that gets me to the first level deep, but not sure how to get x levels deep with recursion. I am sure somebody has come accross this problem in the past and it shouldn't be that difficult but I just can't quite wrap my head around it. Any and all ideas are welcomed!
public class Person {
// other fields removed for simplicity
private long id;
private List<Person> persons;
public List<Person> getPersons() {
return debates;
}
}
// essentially I am looking for a way to make this unlimited level nested looping
private void loopPersons() {
Person person = new Person();
if(person.getPersons() != null && !person.getPersons().isEmpty()) {
for(Person person1 : person.getPersons()) {
if(person1.getPersons() != null && !person1.getPersons().isEmpty()) {
System.out.println(person1.getId());
for(Person person2 : person1.getPersons()) {
if(person2.getPersons() != null && !person2.getPersons().isEmpty()) {
System.out.println(person2.getId());
}
}
}
}
}
}
UPDATE:
The answer by Brian in this other post (scroll down) is essentially what does it. iterate through recursive objects
You might just be looking for some flattening on the lines of making use of recursion with a tail condition. This could be similar to the following implementation
// essentially I am looking for a way to make this unlimited level nested looping
private List<Person> loopPersons(Person person, List<Person> flattened) {
if (person.getPersons() == null || person.getPersons().isEmpty()) {
return flattened;
} else {
flattened.addAll(person.getPersons());
person.getPersons().forEach(p -> loopPersons(p, flattened));
}
return flattened;
}
Note: The code is not tested and is to depict a possible approach that you can take if you think over the same lines.
Do it as follows:
import java.util.List;
class Person {
// other fields removed for simplicity
private long id;
private List<Person> persons;
public Person(long id, List<Person> persons) {
this.id = id;
this.persons = persons;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public void setPersons(List<Person> persons) {
this.persons = persons;
}
public List<Person> getPersons() {
return persons;
}
#Override
public String toString() {
return "Person [id=" + id + ", persons=" + persons + "]";
}
public void showAll() {
if (getPersons() == null || getPersons().isEmpty()) {
return;
}
getPersons().get(0).showAll();
System.out.println(getPersons());
}
}
Demo:
public class Main {
public static void main(String[] args) {
Person p1 = new Person(1,List.of(new Person(11, List.of(new Person(111, List.of(new Person(1111, null))),
new Person(112, List.of(new Person(1121, null))),
new Person(113, List.of(new Person(1131, null))))),
new Person(12, List.of(new Person(121, List.of(new Person(1211, null))))),
new Person(13, List.of(new Person(131, List.of(new Person(1311, null)))))));
p1.showAll();
}
}
Output:
[Person [id=1111, persons=null]]
[Person [id=111, persons=[Person [id=1111, persons=null]]], Person [id=112, persons=[Person [id=1121, persons=null]]], Person [id=113, persons=[Person [id=1131, persons=null]]]]
[Person [id=11, persons=[Person [id=111, persons=[Person [id=1111, persons=null]]], Person [id=112, persons=[Person [id=1121, persons=null]]], Person [id=113, persons=[Person [id=1131, persons=null]]]]], Person [id=12, persons=[Person [id=121, persons=[Person [id=1211, persons=null]]]]], Person [id=13, persons=[Person [id=131, persons=[Person [id=1311, persons=null]]]]]]

JAXB Unmarshall to List

I am trying to unmarshall this XML to Java objects, a Customer object containing a List of EmailAdresses.
<customer>
<emailAddresses>janed#example.com</emailAddresses>
<emailAddresses>jdoe#example.org</emailAddresses>
</customer>
Having an issue with the list, I get the correct number of list items (2), but the value of the emailAddresses tag is null
Customer.java
#XmlRootElement( name = "customer" )
public class Customer
{
private List<EmailAddress> emailAddresses;
public Customer()
{
emailAddresses = new ArrayList<EmailAddress>();
}
public List<EmailAddress> getEmailAddresses()
{
return emailAddresses;
}
public void setEmailAddresses( List<EmailAddress> emailAddresses )
{
this.emailAddresses = emailAddresses;
}
}
EmailAddress.java
public class EmailAddress
{
private String emailAddresses;
public String getEmailAddresses()
{
return emailAddresses;
}
public void setEmailAddresses( String emailAddresses )
{
this.emailAddresses = emailAddresses;
}
}
Failing Unit Test
#Test
public void shouldDeserialiseCusomerXMLToObject() throws JAXBException
{
String xml = "<customer>"
+ " <emailAddresses>janed#example.com</emailAddresses>"
+ " <emailAddresses>jdoe#example.org</emailAddresses>"
+ "</customer>";
JAXBContext jaxbContext = JAXBContext.newInstance( Customer.class );
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
StringReader reader = new StringReader( xml );
Customer msg = ( Customer ) jaxbUnmarshaller.unmarshal( reader );
// This passes, I have 2 emailAddresses
assertEquals( 2, msg.getEmailAddresses().size() );
// This fails, I have a null pointer instead of the email address
assertEquals( "janed#example.com", msg.getEmailAddresses().get( 0 ).getEmailAddresses() );
}
The emailAddresses field of EmailAddress is by default treated as a subelement, expecting the XML to be:
<customer>
<emailAddresses>
<emailAddresses>janed#example.com</emailAddresses>
</emailAddresses>
<emailAddresses>
<emailAddresses>jdoe#example.org</emailAddresses>
</emailAddresses>
</customer>
Since your outer <emailAddresses> element doesn't contain an inner <emailAddresses> element, the field is never assigned.
You want the emailAddresses field of EmailAddress to be the value of the (outer) <emailAddresses> element, so you have to tell JAXB that, by specifying the #XmlValue annotation:
#XmlValue
public String getEmailAddresses()
{
return emailAddresses;
}
The #XmlValue annotation is especially useful when combined with #XmlAttribute, to support XML like this:
<Person sex="male" age="25">John Doe</Person>
Where class would then be:
public class Person {
public enum Sex {
#XmlEnumValue("male") MALE,
#XmlEnumValue("female") FEMALE,
}
#XmlAttribute
private Sex sex;
#XmlAttribute
private int age;
#Value
private String name;
}
You have a level too many of email addresses.
If you should change the list of email addresses to a list of strings, like
#XmlRootElement( name = "customer" )
public class Customer
{
private List<String> emailAddresses;
public Customer()
{
emailAddresses = new ArrayList<String>();
}
public List<String> getEmailAddresses()
{
return emailAddresses;
}
public void setEmailAddresses( List<String> emailAddresses )
{
this.emailAddresses = emailAddresses;
}
}

XStream map null value as empty tag instead of just omit

I use XStream to map Entities to xml String
#XStreamAlias("user")
public class User {
private String name;
private Integer age;
private String address;
private String gender;
...getter()/setter()...
}
The util method I use:
public static String getXmlFromEntity(Object obj, Class rootClazz, String rootAlias){
XStream magicApi = new XStream(new XppDriver(new NoNameCoder()));
magicApi.registerConverter(new MapEntryConverter());
magicApi.alias(rootAlias, rootClazz);
magicApi.autodetectAnnotations(Boolean.TRUE);
String xml = magicApi.toXML(obj);
return xml;
}
And the converter
public static class MapEntryConverter implements Converter {
public boolean canConvert(Class clazz) {
return AbstractMap.class.isAssignableFrom(clazz);
}
public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) {
AbstractMap map = (AbstractMap) value;
for (Object obj : map.entrySet()) {
Map.Entry entry = (Map.Entry) obj;
writer.startNode(entry.getKey().toString());
Object val = entry.getValue();
if ( null != val ) {
writer.setValue(val.toString());
}else {
writer.setValue("bbb");
}
writer.endNode();
}
}
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
Map<String, String> map = new HashMap<String, String>();
while(reader.hasMoreChildren()) {
reader.moveDown();
String key = reader.getNodeName(); // nodeName aka element's name
String value = reader.getValue();
map.put(key, value);
reader.moveUp();
}
return map;
}
}
I want to get a result that for example if the gender is not set, that Tag should also be included as a empty tag instead of omitting it in the result xml string:
<user>
<name>Tom</name>
<address>228 Park Ave S. New York, NY 10003</address>
</user>
I want the result to be:
<user>
<name>Tom</name>
<address>228 Park Ave S. New York, NY 10003</address>
<gender></gender>
<age></age>
</user>
How could I do that?

Attribute name in variable

;How can I use the value of a String variable as attribute or method name?
Want to do something like that:
class Person {
public String firstname;
}
String myAttributeName="firstname";
Person obj = new Person();
String firstNameOfObj = obj.{myAttributeName};
if you really want to do this, you could use reflection:
Person obj = new Person();
Method method = Person.class.getMethod("getFirstname");
String firstname = method.invoke(obj);
but as mentioned in the comments, you better use a map to hold attribute values:
class Person {
private Map<String,Object> attrs = new HashMap<>();
public void setAttribute(String attr, Object value)
{
attrs.put(attr,vaue);
}
public Object getAttribute(String attr)
{
attrs.get(attr);
}
}
Person person = new Person();
person.setAttribute("firstname","patrick");
String firstname = (String)person.getAttribute("firstname");

Fixtures loadModel does not load properly when the yml file contains Object with Map refer to another object

init-datasample.yml
Book(a): &a
title: Play in Action
price: 30.00
Book(b): &b
title: Alice in Wonderland
price: 12.00
Person(b):
name: Bob Joe
ratings:
? *a: 8
? *b: 8
Java Class Definition for Person
#Entity
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Person extends Model
{
public String name;
// choice and importance score
#ElementCollection(targetClass=Integer.class)
#MapKeyClass(Book.class)
public Map<Book, Integer> ratings;
}
The Fixture.loadModels("init-datasample.yml") does not load the initial ratings defined in the above. I tried not to put &a, &b, it didn't work either. Could someone please help?
Thank you.
Ok, got the work around. Would like to share here. And if anyone can take a look and think it is worth commit to the git source, please help to do so.
The problem is the embedded is not support at this moment. So the resolveDependencies in the Fixtures class does not resolve the reference in the map defined.
My work aorund is to extend Fixtures class, and hide the loadModels methods. And
inside the code block of
Fixtures.loadmodels(String name, Map<String, Object> idCache) {
... ...
if (f.getType().isAssignableFrom(Map.class)) {
/* new below */
ElementCollection ec = f.getAnnotation(ElementCollection.class);
MapKeyClass mkc = f.getAnnotation(MapKeyClass.class);
if (ec != null && mkc != null) {
Map mapper = (Map) objects.get(key).get(f.getName());
if (mapper == null) continue;
Class targetClass = ec.targetClass();
Class mapKeyClass = mkc.value();
Map<Object, Object> fieldValue = new HashMap();
for (Object k : mapper.keySet()) {
Object mapKey = retrieveObject(mapKeyClass, k, idCache);
Object targetValue = retrieveObject(targetClass, mapper.get(k), idCache);
fieldValue.put(mapKey, targetValue);
}
f.set(model, fieldValue);
} else {
f.set(model, objects.get(key).get(f.getName()));
}
}
... ...
}
/* new method below */
private static Object retrieveObject(Class targetClass, Object alias, Map<String, Object> idCache)
{
if (targetClass.isAssignableFrom(alias.getClass())) {
return alias;
}
Object targetValue = idCache.get(targetClass.getCanonicalName() + "-"+ alias);
if (targetValue != null) {
targetValue = Model.Manager.factoryFor(targetClass).findById(targetValue);
}else {
try {
targetValue = targetClass.getConstructor(String.class).newInstance(alias.toString());
} catch(Exception ex) {
ex.printStackTrace();
return null;
}
}
return targetValue;
}
In case, someone wanted to refer to an already existing entity (whitch was loaded in an earlier #OnApplicationStart for example) you can refer them by the ids:
Fixture:
User(user01):
email: user01#g.com
userGroup.id: ${models.UserGroup.getGroupForYml(models.UserGroup.GROUP_SALES)}
UserGroup Model:
#Entity
public class UserGroup extends Model {
public static final String GROUP_ADMIN = "admin";
public static final String GROUP_CUSTOMER = "customer";
public static final String GROUP_SALES = "sales";
#OneToMany(mappedBy = "userGroup")
public List<User> userList;
#Column(name="groupId")
public String group;
/**
* Used in the yml fixtures
* #param group - the group id
*/
public static Long getGroupForYml(String group) {
UserGroup ug = UserGroup.find("byGroup", group).first();
return ug.id;
}
}
User Model:
#Entity
#Table(name = "users")
public class User extends BaseModel {
publis String email;
#ManyToOne
public UserGroup userGroup;
}

Categories