37

I am trying to marshall a list of objects implementing a common interface. There are 3 classes and 1 interface involved:

Community class (has one method: List<Person> getPeople();)

Person interface (has one method: String getName();)

Girl class (implements Person)

Boy class (implements Person)

See code below.

I want an XML that looks something like this:

<community>
  <people>
    <girl>
      <name>Jane</name>
    </girl>
    <boy>
      <name>John</name>
    </boy>
    <girl>
      <name>Jane</name>
    </girl>
    <boy>
      <name>John</name>
    </boy>
  </people>
</community>

or possibly:

<community>
  <people>
   <person>
      <girl>
        <name>Jane</name>
      </girl>
    </person>
    <person>
      <boy>
        <name>John</name>
      </boy>
    </person>
  </people>
</community>

So far what I get is this:

<community>
    <people>
        <person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="girl">
            <name>Jane</name>
        </person>
        <person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="boy">
            <name>John</name>
        </person>
        <person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="girl">
            <name>Jane</name>
        </person>
        <person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="boy">
            <name>John</name>
        </person>
    </people>
</community>

I realize I can change the element to something else, but I want the element name to be the name spesified in the Girl or Boy class.

Can this be done? Thanks.

@XmlRootElement(name = "community")
public class Community {

 private List<Person> people;

 @XmlElementWrapper
 @XmlElement(name="person")
 public List<Person> getPeople() {
  return people;
 }

 public Community() {
  people = new ArrayList<Person>();
  people.add(new Girl());
  people.add(new Boy());
  people.add(new Girl());
  people.add(new Boy());
 }
}







@XmlRootElement(name = "girl")
public class Girl implements Person {

 @XmlElement
 public String getName() {
  return "Jane";
 }
}


@XmlRootElement(name = "boy")
public class Boy implements Person {

 @XmlElement
 public String getName() {
  return "John";
 }
}



@XmlJavaTypeAdapter(AnyTypeAdapter.class)
public interface Person {
 public String getName();
}



public class AnyTypeAdapter extends XmlAdapter<Object, Object> {

 @Override
   public Object marshal(Object v) throws Exception {
    return v;
   }

 @Override
   public Object unmarshal(Object v) throws Exception {
    return v;
   }

}

2 Answers 2

53

For this scenario I would recommend the use of @XmlElements. @XmlElements is used to represent the XML schema concept of choice:

Here is how it would look for your example:

@XmlElements({ 
    @XmlElement(name="girl", type=Girl.class),
    @XmlElement(name="boy", type=Boy.class)
})
@XmlElementWrapper
public List<Person> getPeople() {
    return people;
}

@XmlElementRef corresponds to the concept of substitution groups in XML schema. This is why the previous answer requires Person to be changed from an interface to a class.

Sign up to request clarification or add additional context in comments.

Comments

13

OK, if you're prepared to change Person from an interface into an abstract base class, then you're golden. Here's the code:

public class Main {


  public static void main(String[] args) throws Exception {

    Community community = new Community();

    JAXBContext context = JAXBContext.newInstance(Community.class);
    Marshaller marshaller = context.createMarshaller();
    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
    marshaller.marshal(community, System.out);

  }
}

@XmlRootElement(name = "community")
@XmlSeeAlso({Person.class})
public class Community {

 private List<Person> people;

 @XmlElementWrapper(name="people")
 @XmlElementRef()
 public List<Person> getPeople() {
  return people;
 }

 public Community() {
  people = new ArrayList<Person>();
  people.add(new Girl());
  people.add(new Boy());
  people.add(new Girl());
  people.add(new Boy());
 }
}

@XmlRootElement(name="boy")
public class Boy extends Person {

 public String getName() {
  return "John";
 }
}

@XmlRootElement(name="girl")
public class Girl extends Person {

 public String getName() {
  return "Jane";
 }
}

@XmlRootElement(name = "person")
@XmlSeeAlso({Girl.class,Boy.class})
public abstract class Person {

  @XmlElement(name="name")
 public abstract String getName();
}

The main trick was the use of @XmlElementRef in the List of Community. This identifies the type of the class through it's @XmlRootElement. You may also be interested in the @XmlSeeAlso which helps to organise context declarations.

And the output is

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<community>
    <people>
        <girl>
            <name>Jane</name>
        </girl>
        <boy>
            <name>John</name>
        </boy>
        <girl>
            <name>Jane</name>
        </girl>
        <boy>
            <name>John</name>
        </boy>
    </people>
</community>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.