David
David

Reputation: 4285

How to handle different children with XStream?

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?

  1. Check what input you retrieve?
  2. 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;    
}

Upvotes: 1

Views: 528

Answers (1)

Jacob Schoen
Jacob Schoen

Reputation: 14212

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

Upvotes: 2

Related Questions