Lindlof
Lindlof

Reputation: 2162

Preventing Java serialization setting default values

I have a serialized object MyObject that contains integer foo. I set a value 10 to integer foo and save the object to a file using writeObject().

I add integer bar to object MyObject. I set a value 15 to integer bar and then load the old serialized file using readObject().

The old serializable file doesn't contain integer bar so integer bar will get value 0. I want to keep the value 15 in bar if the old serializable file doesn't contain variable bar.

Should I override readObject() or how could I prevent readObject() from setting "default values" to unknown objects?

I want to do this because in the constructor I'm setting my own default values and would like to use my own default values to control versioning.

Upvotes: 3

Views: 3697

Answers (5)

Bene
Bene

Reputation: 1

The problem with only using a method "merge(obj)" is that you cannot distinguish between an already saved old default value of "bar=0" in the stream (Case X) and an even older saved object without that field (Case Y), and then merging with a newly changed default value from "0" to "15".

In that case the method "merge(obj)" would maybe use the new default value "15" for the field that already existed before, but in (Case X) it was already saved/changed earlier with value "0" by the user.

Case X: It should restore "0" from the saved object in the stream, and SHOULD NOT use the new default value "15".

Case Y: For the even older saved object (without the field in the stream), it SHOULD use the new default value "15".

You could (of course) just introduce a new "version"-field, which then can be handled by the merge-method, but that requires much more code.

Case Z: Another problem happens if we have references to other objects. Then the invoker and merge-methods would need to be aware of invoking the merge-methods of all references as well (recursive).

Basically, that's exactly what ObjectOutputStream and ObjectInputStream already do using the methods readObject(ois), etc.

See my workaround with setDefaults(), which will be invoked before the ObjectInputStream restores the values from the stream, and/or use the solution with readObject(ois), which also avoids that problem.

Upvotes: 0

Bene
Bene

Reputation: 11

The problem here is that ObjectInputStream does not invoke the default constructor MyObject() which usually also initializes the field values. (e.g. foo=10, bar=15)

The ObjectInputStream generates its own empty constructor for deserialization, which does not initialize the fields! (Only uses the values in the stream)

But there is a workaround for this problem as follows:

  1. Create a superclass MyParent which is NOT Serializable. (MyObject extends MyParent)

    The default constructor MyParent() will then be invoked by the ObjectInputStream because MyParent is NOT Serializable.

  2. Create an empty method e.g. MyParent.setDefaults() which is called by the MyParent() constructor.

  3. Override that method in the subclass MyObject.setDefaults() to set your default values BEFORE the ObjectInputStream reads the values from the stream.

    Example:

    public class MyParent {
        public MyParent() { // will be invoked by ObjectInputStream !
            super();
            setDefaults();
        }
        public void setDefaults() { // will be invoked by MyParent()
            // to be overwritten by sub-classes.
        }
    }
    
    public class MyObject extends MyParent implements Serializable {
        private int foo;
        private int bar;
        public MyObject() { // will NOT be invoked by ObjectInputStream !
            super();
        }
        public void setDefaults() { // will be invoked by MyParent() before ObjectInputStream reads the values
            super();
            foo = 10;
            bar = 15;
        }
    }
    

Upvotes: 1

chubbsondubs
chubbsondubs

Reputation: 38779

Serialization doesn't set default values it defers to Java's default value initialization scheme.

If I can sum up your question. You want serialization to merge what's in the serialized stream with the values in memory. That's not possible with Java serialization as it controls what objects to create. You can read in your serialized object, then manually write the code to merge what fields you want merged together. If your stuck on Java serialization (I'd steer clear of it if I were you), but let's say you want to continue using it.

public class MyObject {

    public void merge( MyObject that ) {
        // given some other instance of an object merge this with that.
        // write your code here, and you can figure out the rules for which values win.
    }
}

ObjectInputStream stream = new ObjectInputStream( new FileInputStream( file ) );
MyObject that = stream.readObject();
someObject.merge( that );

Viola you control which fields will be merged from that into someObject. If you want a library to do this merge for you check out http://flexjson.sourceforge.net. It uses JSON serialization, and works from Beans rather than POJO. However, there is a way to take an already populated object and overwrite values from a JSON stream. There are limitations to this. The other benefit of this is you can actually read the stream back AFTER your object structure has changed something that Java serialization can technically do, but its very very hard.

Upvotes: 2

spdaley
spdaley

Reputation: 821

Would adding the following method to your MyObject work for you?

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException
{
    bar = 15;  // Set a default value of 15 if it's not in the serialized output file
    ois.defaultReadObject();
}

Upvotes: 2

mre
mre

Reputation: 44240

Use the keyword transient to exclude fields from serialization/deserialization.

Upvotes: -1

Related Questions