Custom JAXB Map for Complex Types

1.4k views Asked by At

I have a schema.xsd and bindings.xjb that I am using to generate classes with xjc. I'm trying to get xjc to generate a field that is a Map where Object vales are one of many simple or complex types (xs:choice). I don't know if what I am trying to accomplish is possible. Here are the key requirements:

  1. Use JAXB to marshall and unmarshall objects
  2. Using a java.util.Map instead of a java.util.List to track the Poc entries in ContactList
  3. Parsing a choice option into values that are stored in the java.util.Map
  4. Choice options may be complex types

I'll give sample code here:
schema.xsd

<?xml version="1.0"?>
<!-- schema.xsd -->
<xs:schema version="1.0"
           xmlns:xs="http://www.w3.org/2001/XMLSchema"
           xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
           xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
           jaxb:extensionBindingPrefixes="xjc"
           elementFormDefault="qualified">

  <xs:simpleType name="phonenumbertype">
    <xs:restriction base="xs:string"/>
  </xs:simpleType>

  <xs:complexType name="addresstype">
    <xs:sequence>
      <xs:element name="street" type="xs:string"/>
      <xs:element name="city" type="xs:string"/>
      <xs:element name="state" type="xs:string"/>
      <xs:element name="zipcode" type="xs:string"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="poctype">
    <xs:sequence>
      <xs:element name="Name" type="xs:string"/>
      <xs:choice>
        <xs:element name="Address" type="addresstype"/>
        <xs:element name="Phone" type="phonenumbertype"/>
      </xs:choice>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="contactlisttype">
    <xs:sequence>
      <xs:element name="Poc" type="poctype" minOccurs="0" maxOccurs="unbounded"/>
    </xs:sequence>
  </xs:complexType>

  <xs:element name="AddressBook">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="ContactList" type="contactlisttype"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>

</xs:schema>

bindings.xjb

<?xml version="1.0" encoding="UTF-8"?>
<!-- bindings.xjb -->
<jxb:bindings version="1.0"
              xmlns:xs="http://www.w3.org/2001/XMLSchema"
              xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
              xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
              jxb:extensionBindingPrefixes="xjc">
  <jxb:bindings schemaLocation="schema.xsd" node="/xs:schema">
    <jxb:globalBindings fixedAttributeAsConstantProperty="true"
                        collectionType="java.util.ArrayList"
                        typesafeEnumBase="xs:NCName"
                        choiceContentProperty="true"
                        typesafeEnumMemberName="generateError"
                        enableFailFastCheck="false"
                        generateIsSetMethod="false"
                        underscoreBinding="asCharInWord">
      <!-- does not work: [ERROR] undefined simple type "contactlisttype". -->
      <xjc:javaType name="java.util.Map" xmlType="contactlisttype" adapter="xjc.MapAdapater"/>
    </jxb:globalBindings>
  </jxb:bindings>
</jxb:bindings>

document.xml

<?xml version="1.0" encoding="UTF-8"?>
<AddressBook>
  <ContactList>
    <Poc>
      <Name>Person1</Name>
      <Address>
        <street>Home Street</street>
        <city>Home Town</city>
        <state>Home State</state>
        <zipcode>99999</zipcode>
      </Address>
    </Poc>
    <Poc>
      <Name>Person2</Name>
      <PhoneNumber>8006667777</PhoneNumber>
    </Poc>
  </ContactList>
</AddressBook>

Right now xjc generates this:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
    "contactList"
})
@XmlRootElement(name = "AddressBook")
public class AddressBook {

    @XmlElement(name = "ContactList", required = true)
    protected ContactList contactList;

But I want it to generate something similar to this:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
    "contactList"
})
@XmlRootElement(name = "AddressBook")
public class AddressBook {

    @XmlElement(name = "ContactList", required = true)
    @XmlJavaTypeAdapter(MapAdapter.class)
    protected Map<String, Object> contactList;

MapAdapter.java

public class MapAdapter extends XmlAdapter<Map<String, Object>, ContactList>
{

  @Override
  public ContactList unmarshal(Map<String, Object> v) throws Exception
  {
    ObjectFactory factory = new ObjectFactory();
    ContactList result = factory.createContactList();
    List<PointOfContact> contacts = result.getPoc();
    for (Map.Entry<String, Object> entry : v.entrySet())
    {
      PointOfContact poc = factory.createPointOfContact();
      String name = entry.getKey();
      Object address = entry.getValue();

      poc.setName(name);
      poc.setAddressOrPhone(address);

      contacts.add(poc);
    }
    return result;
  }

  @Override
  public Map<String, Object> marshal(ContactList v) throws Exception
  {
    // predictable iteration order maintains order of XML elements
    Map<String, Object> result = new LinkedHashMap<>();
    List<PointOfContact> contacts = v.getPoc();
    for (PointOfContact poc : contacts)
    {
      String key = poc.getName();
      Object value = poc.getAddressOrPhone();
      result.put(key, value);
    }
    return result;
  }

}
0

There are 0 answers