mchen
mchen

Reputation: 10156

How to handle cyclic references in XStream?

The XStream serialization library claims to be robust in serializing complex Java objects out of the box (e.g. no modifications to objects or mapping needed). In particular, according to XStream docs, XStream can handle circular object references.

So I wrote a test to check this out - I tried to serialize a somewhat complex data structure (LinkedHashMultimap) that included a reference to iteself:

import com.google.common.collect.LinkedHashMultimap;
import com.thoughtworks.xstream.XStream;

public class SerializationTest {

    public static void main(String[] args0) {

        // Create LinkedHashMultimap to serialize
        LinkedHashMultimap<String, Object> outObj = LinkedHashMultimap.create();
        outObj.put("x", 1);
        outObj.put("x", "abc");
        outObj.put("y", outObj);   // Add a self-reference

        // Try to serialize
        XStream xstream = new XStream();
        String xml = xstream.toXML(outObj);
        System.out.println(xml);  // Print XML to console for a quick peek

        // Try to deserialize - ERROR HERE!!!
        LinkedHashMultimap<String, Object> inObj = (LinkedHashMultimap<String, Object>)xstream.fromXML(xml);

        System.out.println(inObj);
    }
}

The serialized XML object looks like:

<com.google.common.collect.LinkedHashMultimap serialization="custom">
  <unserializable-parents/>
  <com.google.common.collect.LinkedHashMultimap>
    <default/>
    <int>2</int>
    <int>2</int>
    <string>x</string>
    <string>y</string>
    <int>3</int>
    <string>x</string>
    <int>1</int>
    <string>x</string>
    <string>abc</string>
    <string>y</string>
    <com.google.common.collect.LinkedHashMultimap reference="../.."/>
  </com.google.common.collect.LinkedHashMultimap>
</com.google.common.collect.LinkedHashMultimap>

However, deserialization (i.e. the call to xstream.fromXML()) fails with a NullPointerException exception:

Exception in thread "main" com.thoughtworks.xstream.converters.ConversionException: Could not call com.google.common.collect.LinkedHashMultimap.readObject() : null
---- Debugging information ----
message             : Could not call com.google.common.collect.LinkedHashMultimap.readObject()
cause-exception     : java.lang.NullPointerException
cause-message       : null
class               : com.google.common.collect.LinkedHashMultimap
required-type       : com.google.common.collect.LinkedHashMultimap
converter-type      : com.thoughtworks.xstream.converters.reflection.SerializableConverter
path                : /com.google.common.collect.LinkedHashMultimap/com.google.common.collect.LinkedHashMultimap
line number         : 15
version             : 1.4.7
-------------------------------
    at com.thoughtworks.xstream.converters.reflection.SerializationMethodInvoker.callReadObject(SerializationMethodInvoker.java:119)
    at com.thoughtworks.xstream.converters.reflection.SerializableConverter.doUnmarshal(SerializableConverter.java:454)
    at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.unmarshal(AbstractReflectionConverter.java:257)
    at com.thoughtworks.xstream.core.TreeUnmarshaller.convert(TreeUnmarshaller.java:72)
    at com.thoughtworks.xstream.core.AbstractReferenceUnmarshaller.convert(AbstractReferenceUnmarshaller.java:65)
    at com.thoughtworks.xstream.core.TreeUnmarshaller.convertAnother(TreeUnmarshaller.java:66)
    at com.thoughtworks.xstream.core.TreeUnmarshaller.convertAnother(TreeUnmarshaller.java:50)
Disconnected from the target VM, address: '127.0.0.1:50107', transport: 'socket'
    at com.thoughtworks.xstream.core.TreeUnmarshaller.start(TreeUnmarshaller.java:134)
    at com.thoughtworks.xstream.core.AbstractTreeMarshallingStrategy.unmarshal(AbstractTreeMarshallingStrategy.java:32)
    at com.thoughtworks.xstream.XStream.unmarshal(XStream.java:1185)
    at com.thoughtworks.xstream.XStream.unmarshal(XStream.java:1169)
    at com.thoughtworks.xstream.XStream.fromXML(XStream.java:1040)
    at com.thoughtworks.xstream.XStream.fromXML(XStream.java:1031)
    at SerializationTest3.main(SerializationTest3.java:18)
Caused by: java.lang.NullPointerException
    at com.google.common.collect.AbstractMapBasedMultimap$AsMap.hashCode(AbstractMapBasedMultimap.java:1289)
    at com.google.common.collect.AbstractMultimap.hashCode(AbstractMultimap.java:228)
    at com.google.common.collect.LinkedHashMultimap.hashCode(LinkedHashMultimap.java:81)
    at com.google.common.collect.Hashing.smearedHash(Hashing.java:51)
    at com.google.common.collect.LinkedHashMultimap$ValueSet.add(LinkedHashMultimap.java:416)
    at com.google.common.collect.LinkedHashMultimap.readObject(LinkedHashMultimap.java:574)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at com.thoughtworks.xstream.converters.reflection.SerializationMethodInvoker.callReadObject(SerializationMethodInvoker.java:113)
    ... 13 more

So does anyone know why this is happening? And how to resolve this?

Upvotes: 6

Views: 1915

Answers (1)

Sachin Verma
Sachin Verma

Reputation: 3802

Use:

XStream xstream = new XStream();
xstream.setMode(XStream.XPATH_RELATIVE_REFERENCES);

Upvotes: 3

Related Questions