Search code examples
javaxmlparsingjaxbsax

Reuse JAXB classes for similar xml schemas


I have the following XMLs, for each of the XML I have the corresponding Java classes modeled with JAXB.

XML 1:

<counts at="2019-06-27 09:54:31">
    <media id="1" start="1" finish="1000" timeZone="NZST">
        <count id="1" value="1" at="2019-06-27 09:54:31" duration="5"/>
        <count id="2" value="2" at="2019-06-27 09:54:31" duration="5"/>
        <count id="3" value="3" at="2019-06-27 09:54:31" duration="5"/>
    </media>
</counts>

XML 2:

<tags at="2019-06-27 09:54:31">
    <media id="1" start="1" finish="1000" timeZone="NZST">
        <tag id="1" value="1" at="2019-06-27 09:54:31" visible="true" />
        <tag id="2" value="2" at="2019-06-27 09:54:31" visible="true" />
        <tag id="3" value="3" at="2019-06-27 09:54:31" visible="true" />
    </media>
</tags>

XML 3:

<trajectories at="2019-06-27 09:54:31">
    <media id="1" start="1" finish="1000" timeZone="NZST">
        <trajectory id="1" value="1" at="2019-06-27 09:54:31" />
        <trajectory id="2" value="2" at="2019-06-27 09:54:31" />
        <trajectory id="3" value="3" at="2019-06-27 09:54:31" />
    </media>
</trajectories>

Since most of the elements in all the XMLs are more or less the same except for <count>, <tag> and <trajectory>. Is there a way to abstract the classes for <counts>, <tags>, <trajectories> and <media>?

Ideally, I would like something like:

class CountsTagsTrajectories<T extends CommonObjectsOfMedia> {
    private Media<T> media;
}


class Media<T> {
    private List<T> listOfObjects;
}

class Count extends CommonObjectsOfMedia {
    ...
    ...
    ...
}

class Tag extends CommonObjectsOfMedia {
    ...
    ...
    ...
}

class Trajectory extends CommonObjectsOfMedia {
    ...
    ...
    ...
}

Is this approach possible? Or I would have to have classes like:

Counts > CountMedia > Count
Tags > TagMedia > Tag
Trajectories > TrajectoryMedia > Trajectory

The problem that I am facing right now trying to abstract the classes is that I need to define a name in @XmlElement, whether it is counts/count, tags/tag or trajectories/trajectory.

NOTE: I've seen this post but what it does is to add a list of all the possible "CommonObjectsOfMedia" to the Media class and that's not what I am looking for.


Solution

  • You are on the right path. There are two points where you can improve.

    1. The media structure has tags, trajectory and count which are almost the same and use different names. We can handle that:

    First as you have the CommonObjectsOfMedia

    @XmlAccessorType(XmlAccessType.FIELD)
    public class CommonObjectsOfMedia {
    
        @XmlAttribute
        private String id;
        @XmlAttribute
        private String value;
        @XmlAttribute
        private String at;
    
    }
    

    Then tag inside media has an extra attribute from the common. That would look like:

    @XmlAccessorType(XmlAccessType.FIELD)
    public class MediaTag extends CommonObjectsOfMedia {
    
        @XmlAttribute
        private boolean visible;
    }
    

    Similar for count:

    @XmlAccessorType(XmlAccessType.FIELD)
    public class MediaCount extends  CommonObjectsOfMedia {
    
        @XmlAttribute
        private String duration;
    }
    

    and trajectory only has the common.

    Now lets create the Media class that will be able to handle any case of them. The magic is from @XmlElements.

    @XmlAccessorType(XmlAccessType.FIELD)
    public class Media {
    
        @XmlAttribute
        private String id;
        @XmlAttribute
        private String start;
        @XmlAttribute
        private String finish;
        @XmlAttribute
        private String timeZone;
    
        @XmlElements ({
            @XmlElement(name="tag", type = MediaTag.class),
            @XmlElement(name="trajectory", type = CommonObjectsOfMedia.class),
            @XmlElement(name="count", type = MediaCount.class),
        })
        private List<CommonObjectsOfMedia> commonObjectsOfMedia;
    }
    

    I will create a demo class that will show the unmarshalling of all the cases at the same time. The xml would look like this:

    <someroot at="2019-06-27 09:54:31">
        <media id="1" start="1" finish="1000" timeZone="NZST">
            <tag id="1" value="1" at="2019-06-27 09:54:31" visible="true" />
            <tag id="2" value="2" at="2019-06-27 09:54:31" visible="true" />
            <tag id="3" value="3" at="2019-06-27 09:54:31" visible="true" />
        </media>
        <media id="1" start="1" finish="1000" timeZone="NZST">
            <trajectory id="1" value="1" at="2019-06-27 09:54:31" />
            <trajectory id="2" value="2" at="2019-06-27 09:54:31" />
            <trajectory id="3" value="3" at="2019-06-27 09:54:31" />
        </media>
        <media id="1" start="1" finish="1000" timeZone="NZST">
            <count id="1" value="1" at="2019-06-27 09:54:31" duration="5"/>
            <count id="2" value="2" at="2019-06-27 09:54:31" duration="5"/>
            <count id="3" value="3" at="2019-06-27 09:54:31" duration="5"/>
        </media>
    </someroot>
    

    and the root class like this:

    @XmlRootElement
    public class Someroot {
    
        @XmlElement
        private List<Media> media;
    }
    

    This will unmarshall properly. And we go to item 2.

    The roots have different name but same structure inside. Maybe you want to fix this too. This can be done as shown in this reply: Can JAXB handle multiple "root" elements?