tags:

views:

263

answers:

5

We are using Jersey (Java REST library) for a project for the last few months and loving it. But this week have run into an issue with JAXB.

What I have is an element that has 2 children, each of them have children where some of their children reference each other.

Let me show some code.

Root root = new Root();

Parent parent1 = new Parent();
Parent parent2 = new Parent();

root.add(parent1);
root.add(parent2);

Child child1 = new Child();
Child child2 = new Child();
Child child3 = new Child();

parent1.add(child1);
parent1.add(child2);

parent2.add(child2);
parent2.add(child3);

So we have 1 root, 2 parents and 3 children.

If I send this up and down the JAXB path, I seem to get back 4 children.
Each Parent has their own copy of child2.

Is there anyway to get JAXB to serialise the relationship and show that both parent1 and parent2 point to the same object?

We only found this issue recently, when more complex elements were being transmitted.

If there is no way to get JAXB to do this (thats what I believe at the moment), does anyone have any suggestions of a way I could do some magic in Jersey to re-instate the relationship?

A: 

Just a quick thought: do the Child objects implement a proper equals() method?

Fabian Steeg
+1  A: 

you could potentially do it by writing a adapter class for the Root class, which on unmarshalling can clean up the child objects to remove the duplicates.

jjoshi
+2  A: 

This isn't a problem with JAXB so much as it's a problem with your model. How would you want JAXB to render your relationships in XML, when XML provides no standard mechanism to express this? Both consumer and producer of the XML would need to have a layer of business logic which would agree on the respresentation.

I suggest that you need to remodel your data. For example, rather than have the children as being contained inside the parents, you could flatten things out:

<parent id="parent1">
   <child ref="child1"/>
   <child ref="child2"/>
</parent>

<parent id="parent2">
   <child ref="child2"/>
   <child ref="child3"/>
</parent>

<child id="child1"/>
<child id="child2"/>
<child id="child3"/>

Whatever mechanism reads and writes this structure will have to know what it all means, but I don't see any other nice way to do this.

Another point of interest is that XStream does have support for object references when it serializes/deserializes object graphs, but it's an entirely proprietary mechanism.

skaffman
A: 

As already stated in other answers. This is the design of JAXB.

What you could do is to manually glue the multiple copies of what you believe was the same object before serialization. Then you wouldn't need your own distinct ID for objects, so that you could tell apart clones from just other objects.

Grzegorz Oledzki
+2  A: 

JAXB does support non-containment references between objects in the tree using a combination of @XmlID/@XmlIDREF. A requirement for this is that all objects in the tree must also be referenced by a containment relationship. In your model this might involve giving Root a collection of Child.

Below would be a modified version of your code:

Root root = new Root(); 

Parent parent1 = new Parent(); 
Parent parent2 = new Parent(); 

root.add(parent1); 
root.add(parent2); 

Child child1 = new Child(); 
child1.id = "1";
root.add(child1);
parent1.add(child1); 

Child child2 = new Child();
child2.id = "2";
root.add(child2);
parent1.add(child2); 
parent2.add(child2); 

Child child3 = new Child();
child3.id = "3";
root.add(child3);
parent2.add(child3);

Then your model classes would look like:

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Root {

    public List<Parent> parent = new ArrayList<Parent>();
    public List<Child> child = new ArrayList<Child>();

    public void add(Parent parent1) {
        parent.add(parent1);
    }

    public void add(Child child1) {
        child.add(child1);
    }
}

import javax.xml.bind.annotation.XmlIDREF;

public class Parent {

    @XmlIDREF
    public List<Child> child = new ArrayList<Child>();

    public void add(Child child1) {
        child.add(child1);
    }

}

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlID;

public class Child {

    @XmlID
    @XmlAttribute
    public String id;

}

The resulting XML would look like:

<root>
    <parent>
        <child>1</child>
        <child>2</child>
    </parent>
    <parent>
        <child>2</child>
        <child>3</child>
    </parent>
    <child id="1"/>
    <child id="2"/>
    <child id="3"/>
</root>
Blaise Doughan