Skip to main content

Handling Union in JAXB

Posted by kohsuke on March 6, 2006 at 3:52 PM PST

steveor asked How he can map a union simple type nicely to Java. Here's how.

His simple type is an union of three things. (1) tokens that start with "MINOR", (2) tokens that start with "PROP", or (3) one of 10 known constants.

In JAXB 2.0, unfortunately all unions map to java.lang.String by default. This is primarily because otherwise it's very difficult to unmarshal it correctly to typed values if you have to do it just with annotations. In 1.0, just for handling this correctly we had to ship 0.5MB jar file and generate a whole lot of code. With all that price, the union binding you get from 1.0 isn't all that good. For Steve's case, you'd get:

// this property can have java.lang.String or one of the type-safe enum object
// that represents 10 constants.
Object layerRate;

To map this better, we first need to make up our minds about how we want Java code to look like. Perhaps you might want something like this:

interface LayerRate {}

class PropLayerRate extends LayerRate {
  final String value;
  PropLayerRate(String value) { this.value=value; }
  String toString() {
    return "MINOR_"+value;
  }
}

class MinorLayerRate extends LayerRate {
  // the same for MinorLayerRate
  ...
}

enum WellknownLayerRate implements LayerRate {
  LR_DS0_64K, LR_DSL, ...
}

You can define a visitor pattern, or define some numbers associated with each constant. Instead of having MinorLayerRate and PropLayerRate, yuo might choose to have one class with a boolean flag. You might want to put some logic in these code. This is a design problem, and this is where we need human beings to sweat.

Once the design is done, you write what we call "adapter". Adapter is used by JAXB and is responsible for converting XML values and Java values. For the above code, our adapter would look like:

class LayerRateAdapter extends XmlAdapter<String,LayerRate> {
  LayerRate unmarshal(String value) {
    if(value.startsWith("MINOR_"))
      return new MinorLayerRate(value.substring(6));
    if(value.startsWith("PROP_"))
      return new ProplayerRate(value.substring(5));
    else
      return WellknownLayerRate.valueOf(value);
  }
 
  String marshal(LayerRate lr) {
    return lr.toString();
  }
}

As you can see, it defines two method that does the converion. The parameterization tells JAXB what signature it's converting.

Now, you tell JAXB that there's this adapter, by using an annotation like this:

@XmlJavaTypeAdapter(LayerRateAdapter.class)
interface LayerRate {}

If you can afford to modify the generated code (that is, if your schema doesn't change too often), then you can just replace the generated String layerRate; field with LayerRate layerRate; field, and JAXB runtime will take it happily.

If you don't feel comfortable modifying the generated code, then you can use a customization to have XJC generate a field as the LayerRate type by using a customization. This is more work, but it does let you regenerate the java code from the schema:

<br /><xsd:schema xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" jaxb:version="2.0"<br />  xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc/" jaxb:extensionBindingPrefixes="xjc"><br />  ...<br />  <xsd:simpleType name="LayerRate_T"><br />    <xsd:annotation><xsd:appinfo><br />      <xjc:javaType name="org.acme.foo.LayerRate"<br />        adapter="org.acme.foo.LayerRateAdapter" /><br />    </xsd:appinfo></xsd:annotation><br />    ... gory simple type definition here ...<br />  </xsd:simpleType><br /></xsd:schema><br />

This is using a JAXB RI's vendor extension to specify an adapter instance. You can do the equivalent with just the standard but it will be more code, so I won't go in there today:

If you are a really cautious and don't even want to touch the schema, then you can do this by writing a customization externally:

<br /><binding version="2.0" extensionBindingPrefixes="xjc"<br />  xmlns="http://java.sun.com/xml/ns/jaxb"<br />  xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"<br />  xmlns:xsd="http://www.w3.org/2001/XMLSchema"<br />  schemaLocation="relative/path/to/schema.xsd"><br />  <binding node="//xs:simpleType[@name='LayerRate_T']"><br />    <xjc:javaType name="org.acme.foo.LayerRate"<br />                  adapter="org.acme.foo.LayerRateAdapter" /><br />  </binding><br /></binding><br />

In both approaches, this single customization takes effect on all the use of the LayerRate_T simple type.

Related Topics >>

Comments

Re: Handling Union in JAXB

Hi!
Nice article, thank you!
It could be very interesting to see also your recommendations about handling another schema structure, xsd:choice and the way to interact with JAXB Marshaller to let him know which of choise fields should be serialized (not implementing the XmlAdapter as in general case, but smth simpler, good for simple case of xsd:choice)

Sincerely
Jabb.