Serialization: shallow copying non-serializable fields?

I suspect this is really easy and I’m just being stupid, but is there a way to serialize an object so that some fields are serialized as references rather than being recursively serialised themselves?

As an example:


class World // not serializable
{
  // whole bunch of world data
}

class Quest implements Serializable
{
  // some stuff that should be deep cloned when serialized
  private Goal goal;
  ...etc...

  // Reference to world, which should reference the same object when deserialized
  private World world;
}

Basically I’m (ab)using serialization to do deep copies on rather large graphs of objects. But some of the objects reference the game world which there is only one of and should reference the same world when cloned.

I could mark world as transient, but then I end up with a null reference after serialization. And I guess I could provide a custom readObject that would re-populate the ‘world’ field, but due to it’s lack of scope I’d have to pull it out of a global somewhere, which is pretty hacky. If this was C I’d just write the address of the object into the stream and read it out and cast it to the right object type, but obviously that’s not an option here.

Anyone any suggestions?

why not clone your object correctly?
using serialisation is very hacky on it self, either you hack your hole programm or you just let it be.

If you know of a way of doing a deep clone on a complicated graph of objects via Object.clone() that’s as elegant and robust as using serialization then I’m all ears.

I’m doing exactly this for the savedgames in my SPGL based stuff. The savedgames graphs reference all the stuff in Resources but of course I don’t actually want the stuff in Resources to get serialized otherwise when they get deserialized… well, you know.

Here’s the trick, which you can adapt:

In AbstractResource:


	/**
	 * Serialization support. We completely replace the serialized feature with an
	 * instance of SerializedFeature instead.
	 */
    public final Object writeReplace() throws ObjectStreamException {
    	if (Resources.isRunMode()) {
    		// We're in "Run Mode"
    		if (name == null) {
    			// No name, so have to serialize directly.
    			return this;
    		} else {
    			// Got a name, so send a SerializedFeature that just references this
    			return new SerializedResource(this);
    		}
    	} else {
    		// This is "compile time" so we serialize directly
    		return this;
    	}
    }


    private static final class SerializedResource implements Serializable {

    	private static final long serialVersionUID = 1L;

    	private String resourceName;
    	private transient AbstractResource resource;

    	public SerializedResource(AbstractResource resourceToSerialize) {
    		resourceName = resourceToSerialize.getName();
    	}

    	/**
    	 * Provides a hashcode
    	 */
    	@Override
	public int hashCode() {
		return resourceName.hashCode();
    	}

    	@Override
	public boolean equals(Object obj) {
    		if (obj == null || !(obj instanceof SerializedResource)) {
    			return false;
    		}
    		SerializedResource r = (SerializedResource) obj;
    		if (obj == this) {
    			return true;
    		}
    		if (r.resource != null && resource != null) {
    			return r.resource.equals(resource);
    		} else if (r.resourceName != null && resourceName != null) {
    			return r.resourceName.equals(resourceName);
    		} else {
    			return false;
    		}
    	}

    	/**
    	 * @returns a AbstractResource
    	 */
    	private Object readResolve() throws ObjectStreamException {
    		try {
    			return Resources.get(resourceName);
    		} catch (Exception e) {
    			throw new InvalidObjectException("Failed to deserialize feature "+resourceName+" due to "+e);
    		}
    	}
    }

Cas :slight_smile:

Since I love pimping my own stuff so much, Kryo to the rescue!!
https://code.google.com/p/kryo/#Copying/cloning

Thanks guys. I ended up writing a ShallowHandle class to deal with this for me:

package crimgen;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class ShallowHandle<T> implements Serializable
{
	private transient T target;
	
	public ShallowHandle()
	{
		
	}
	
	public ShallowHandle(T target)
	{
		this.target = target;
	}
	
	public void set(T t)
	{
		this.target = t;
	}
	
	public T get()
	{
		return target;
	}
	
	@Override
	@SuppressWarnings("rawtypes")
	public boolean equals(Object obj)
	{
		ShallowHandle other = (ShallowHandle)obj;
		return this.target.equals(other.target);
	}
	
	private void writeObject(ObjectOutputStream arg) throws IOException
	{
		ShallowCapableObjectOutputStream out = (ShallowCapableObjectOutputStream)arg;
		out.writeShallowObject(target);
	}
	
	@SuppressWarnings("unchecked")
	private void readObject(ObjectInputStream arg) throws IOException
	{
		ShallowCapableObjectInputStream in = (ShallowCapableObjectInputStream)arg;
		target = (T)in.readShallowObject();
	}
}

And having it save out a reference in a custom output/input stream that also has a map of id->shallow objects.

Nate: Kryo did actually spring to mind, but I’m just prototyping for now and I already had the deep copying done with serialization a while ago. I’ll bear it in mind if/when I start making this thing properly.