Stas
Stas

Reputation: 1059

Unmarshalling collections in JaxB

suppose I have this class:

public class A {

    private HashMap<String, B> map;

    @XmlElement
    private void setB(ArrayList<B> col) {
        ...
    }

    private ArrayList<B> getB() {
        ...
    }

}

When trying to unmarshall an xml document to this class using JaxB I notice that instead of calling the setB() method and sending me the list of B instances JaxB actually calls the getB() and adds the B instances to the returned list. Why?

The reason I want the setter to be called is that the list is actually just a temporary storage from which I want to build the map field, so I thought to do it in the setter.

Thanks.

Upvotes: 14

Views: 20195

Answers (7)

The reason I want the setter to be called is that the list is actually
just a temporary storage from which I want to build the map field,
so I thought to do it in the setter.

JAXB can handle maps directly, hence, this could make the call to setB() a moot point. If that is an acceptable solution for you, see the example I maintain on my blog to create an adaptor for maps in JAXB.

Upvotes: 0

bdoughan
bdoughan

Reputation: 148977

Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB 2 (JSR-222) expert group.

The behaviour you are seeing will vary among JAXB implementations. If you do not initialize a value for the List property then EclipseLink JAXB (MOXy) will call the set method as you expect.

For More Information


EXAMPLE

A

package forum1032152;

import java.util.ArrayList;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class A {

    private ArrayList<B> b;

    @XmlElement
    public void setB(ArrayList<B> col) {
        System.out.println("Called setB");
        for(B b : col) {
            System.out.println(b);
        }
        this.b = col;
    }

    public ArrayList<B> getB() {
        return b;
    }

}

B

package forum1032152;

public class B {

}

Demo

package forum1032152;

import java.io.File;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(A.class);

        File xml = new File("src/forum1032152/input.xml");
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        unmarshaller.unmarshal(xml);
    }

}

input.xml

<?xml version="1.0" encoding="UTF-8"?>
<a>
    <b></b>
    <b></b>
</a>

Output

Called setB
forum1032152.B@8bdcd2
forum1032152.B@4e79f1

Upvotes: 5

LE GALL Beno&#238;t
LE GALL Beno&#238;t

Reputation: 7568

Hy,

you can use it with jaxb, it's work !!! (with Maven....)

<plugin>
            <groupId>org.jvnet.jaxb2.maven2</groupId>
            <artifactId>maven-jaxb2-plugin</artifactId>
            <executions>
                <execution>
                    <goals>
                        <goal>generate</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <args>
                    <arg>-Xcollection-setter-injector</arg>
                </args>
                <plugins>
                    <plugin>
                        <groupId>net.java.dev.vcc.thirdparty</groupId>
                        <artifactId>collection-setter-injector</artifactId>
                        <version>0.5.0-1</version>
                    </plugin>
                </plugins>
                <schemaDirectory>src/schemas</schemaDirectory>
                <generateDirectory>src/main/java</generateDirectory>
                <extension>true</extension>
            </configuration>
        </plugin>

and you get your setter for your Collection

Hope it would help people

bye

Upvotes: 6

alles
alles

Reputation: 11

You can just use an array instead of List )

Upvotes: 1

Justin
Justin

Reputation: 4477

Jaxb2 UnMarshaller defines a listener interface which is called any time an object has been un-marshaled. You can define a custom listener to invoke setter methods on all collections (or on sub-objects). This should be pretty easy to do with any one of the bean utils classes. I'm looking for an existing implementation, though I don't see one.

JAXBContext context = JAXBContext.newInstance( classesToBeBound );
m_unmarshaller = context.createUnmarshaller();
m_unmarshaller.setListener(
  new Unmarshaller.Listener() {
    public void afterUnmarshal(Object target, Object parent) {
     for (Property p : getBeanProperties(target.getClass()))
      if (p.isCollectionType() || p.isCompositeType())
        p.invokeSetter(p.invokeGetter());
    }
  });

If you are using the spring framework, its pretty straightforward:

    new Unmarshaller.Listener() {
         public void afterUnmarshal(Object target, Object parent) {
             BeanWrapper wrapper = new BeanWrapperImpl(target);
             for (PropertyDescriptor pd : wrapper.getPropertyDescriptors()) {
                 if (pd.getPropertyType() != null) {
                         if (!BeanUtils.isSimpleProperty(pd.getPropertyType())) {
                             try {
                                 Method setter = pd.getWriteMethod();
                                 if (setter != null) {
                                     Method getter = pd.getReadMethod();
                                     if (getter != null)
                                         setter.invoke(target, getter.invoke(target));
                                 }
                             }
                             catch (Exception ex) {
                                 s_logger.error("can't invoke setter", ex);
                             }
                         }
                 }
             }
         }
    }

Upvotes: 1

gibbss
gibbss

Reputation: 2033

JAXB has problems supporting interfaces and abstract classes; it usually doesn't know what subclass to instantiate. The problem is, it's a common pattern to have a class along the lines of:

ArrayList list;

@XMLElement
public List getList() {return this.list;}

To get around this, JAXB doesn't even try to instantiate the property class (e.g. List) derived from the getter/setter pair if it's a Collection. It just assumes that it's non-null and modifiable.

Probably the simplest work around is to mark your business interface with @XMLTransient and add a different getter/setter pair with @XMLElement for the view for the data that you want to expose to JAXB. I usually make these protected rather than public, because I don't care to have the somewhat-goofy JAXB behavior as part of my classes' public contract.

Upvotes: 3

silmx
silmx

Reputation: 486

thats the way jaxb is handling collections. you have to be sure you have a non null collection when jaxb try to unmarshal.

there is a plugin (never used it myself) but can be helpful: https://jaxb2-commons.dev.java.net/collection-setter-injector/

Upvotes: 8

Related Questions