JAXB Marshalling and Generics

19,670

Solution 1

You could write a custom adapter (not using JAXB's XmlAdapter) by doing the following:

1) declare a class which accepts all kinds of elements and has JAXB annotations and handles them as you wish (in my example I convert everything to String)

@YourJAXBAnnotationsGoHere
public class MyAdapter{

  @XmlElement // or @XmlAttribute if you wish
  private String content;

  public MyAdapter(Object input){
    if(input instanceof String){
      content = (String)input;
    }else if(input instanceof YourFavoriteClass){
      content = ((YourFavoriteClass)input).convertSomehowToString();
    }else if(input instanceof .....){
      content = ((.....)input).convertSomehowToString();
    // and so on
    }else{
      content = input.toString();
    }
  }
}

// I would suggest to use a Map<Class<?>,IMyObjToStringConverter> ...
// to avoid nasty if-else-instanceof things

2) use this class instead of E in your to-be-marshalled class

NOTES

  • Of course this would not work for complex (nested) data structures.
  • You have to think how to unmarshall this back again, could be more tricky. If it's too tricky, wait for a better proposal than mine ;)

Solution 2

How about

public class Range<**E extends Number**> implements Serializable { ...
  • Number is a class

  • I bet JAXB knows default marshalling/unmarshalling rules for Number

For unmarshalling to specific type, you need XmlAdapter as I described here: JAXB inheritance, unmarshal to subclass of marshaled class

Share:
19,670
tomrtc
Author by

tomrtc

Updated on July 20, 2022

Comments

  • tomrtc
    tomrtc almost 2 years

    I am trying to use JAXB's introspection to marshall and unmashall some existing domain objects marked up with JAXB annotations. Most things work as expected, but I am having quite a bit of trouble getting a fairly simple class to serialize. This class is used as an @XmlElement on a number of beans and looks something like:

    public class Range<E extends Comparable<E>> implements Serializable {
        protected boolean startInclusive, endInclusive;
        protected E       start, end;
    
        public Range(){
                startInclusive = endInclusive = true;
        }
    
        public boolean contains(E value){...}
    
        public E getEnd() {
                return end;
        }
    
        public void setEnd(E end) {
                this.end = end;
        }
    
        public boolean isEndInclusive() {
                return endInclusive;
        }
    
        public void setEndInclusive(boolean endInclusive) {
                this.endInclusive = endInclusive;
        }
    
        public E getStart() {
                return start;
        }
    
        public void setStart(E start) {
                this.start = start;
        }
    
        public boolean isStartInclusive() {
                return startInclusive;
        }
    
        public void setStartInclusive(boolean startInclusive) {
                this.startInclusive = startInclusive;
        }
    }
    

    I have tried to do the following, with no success, JAXB is still angry with the interface Comparable.

    public class DoubleRange extends Range<Double> {}
    

    Using both Range and DoubleRange as return types for the bean getter's yields an exception like:

    java.lang.Comparable is an interface, and JAXB can't handle interfaces.
        this problem is related to the following location:
            at java.lang.Comparable
            at protected java.lang.Comparable com.controlpath.util.Range.start
            at example.util.Range
            at example.util.DoubleRange
            at public example.util.DoubleRange example.domain.SomeBean.getRange()
            at example.domain.SomeBean
    

    I realize that in most cases List<T> and Map<T, U> only work because the JAXB specification has special provisions for those types when they are encountered on beans, but is there any way to convey what I want to the JAXB introspection engine without having to reimplement range with non-generic fields?

  • tomrtc
    tomrtc almost 15 years
    Am I incorrect in assuming that the DoubleRange class would solve the erasure problem? I tried redefining the getters as public Double getEnd() and public Double getStart() but I got the same error. It seems like it is looking too deeply for the types.
  • Brett
    Brett almost 15 years
    IIRC, JAXB can either look at the getters and setter or (better) the fields. If the latter then you'd need to move the field up to the specialised class, and make getStart/getEnd abstract. Possibly the abstract class shouldn't be annotated as JAXB encodeable. I'm not really an expert in this area as you might be able to tell...
  • tomrtc
    tomrtc almost 15 years
    Good suggestion, unfortunately JAXB tries to instantiate java.lang.Number when unmarshalling >.< And Number is not Comparable, but I could have gotten around that with some casting.
  • tomrtc
    tomrtc almost 15 years
    I am trying to get some insight as to why JAXB is upset and avoid a lot of custom marshalling code (as I am sure I will hit this again at some point in the future, potentially on a much more complex object). But this technically IS an answer, and I will mark it as such in a few days if no one else has a better one. Thanks =)