67

I have an object that contains a few unserializable fields that I want to serialize. They are from a separate API that I cannot change, so making them Serializable is not an option. The main problem is the Location class. It contains four things that can be serialized that I'd need, all ints. How can I use read/writeObject to create a custom serialization method that can do something like this:

// writeObject:
List<Integer> loc = new ArrayList<Integer>();
loc.add(location.x);
loc.add(location.y);
loc.add(location.z);
loc.add(location.uid);
// ... serialization code

// readObject:
List<Integer> loc = deserialize(); // Replace with real deserialization
location = new Location(loc.get(0), loc.get(1), loc.get(2), loc.get(3));
// ... more code

How can I do this?

Alexis King
  • 43,109
  • 15
  • 131
  • 205
  • 2
    please note that it is better to accept the best answer and not the first correct one. you stated yourself that the answer by peter lawrey might be better. for the sake of all people coming here in the future looking for the best answer: please consider accepting a different answer! – Neuron Oct 26 '17 at 07:40

3 Answers3

69

Java supports Custom Serialization. Read the section Customize the Default Protocol.

To quote:

There is, however, a strange yet crafty solution. By using a built-in feature of the serialization mechanism, developers can enhance the normal process by providing two methods inside their class files. Those methods are:

  • private void writeObject(ObjectOutputStream out) throws IOException;
  • private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException;

In this method, what you could do is serialize it into other forms if you need to such as the ArrayList for Location that you illustrated or JSON or other data format/method and reconstruct it back on readObject()

With your example, you add the following code:



private void writeObject(ObjectOutputStream oos)
throws IOException {
    // default serialization 
    oos.defaultWriteObject();
    // write the object
    List loc = new ArrayList();
    loc.add(location.x);
    loc.add(location.y);
    loc.add(location.z);
    loc.add(location.uid);
    oos.writeObject(loc);
}

private void readObject(ObjectInputStream ois)
throws ClassNotFoundException, IOException {
    // default deserialization
    ois.defaultReadObject();
    List loc = (List)ois.readObject(); // Replace with real deserialization
    location = new Location(loc.get(0), loc.get(1), loc.get(2), loc.get(3));
    // ... more code

}

Michael Butscher
  • 10,028
  • 4
  • 24
  • 25
momo
  • 21,233
  • 8
  • 39
  • 38
  • Okay, thanks, this is all well and good. But as far as I can tell the ArrayList is never added to the serialization in `writeObject`. What would I do to correctly add the List to the serialization? – Alexis King Sep 03 '11 at 03:06
  • 1
    I don't think that's true. ArrayList implements Serializable. The important thing is that the members of the ArrayList are also Serializable. And all your members are int that is Serializable in its Integer (object form). If by chance, what I said is not true, then you serialize to different form such as JSON or if you really need just four integers, you could even do a String in the form of int1,int2,int3,int4 and do split during read. With custom serialization, you have many options to write and retrieve back the data. – momo Sep 03 '11 at 03:13
  • No, I understand that, but it would appear that I need to also do `oos.writeObject(loc)` I can do `ois.readObject()`, which you seem to have omitted. – Alexis King Sep 03 '11 at 03:39
  • Oh yes, you are right, I miss that :) Sorry, I was typing it in hurry – momo Sep 03 '11 at 03:40
  • 6
    You don't *have* to go to all the trouble of wrapping things up in an `ArrayList` or JSON object or whatever. You could just do `oos.writeInt(x); oos.writeInt(y); ...` and the corresponding input methods in the same order. – Cameron Skinner Sep 03 '11 at 04:49
  • By the way, what's the `assumes "static java.util.Date aDate;" declared` bit for? – Cameron Skinner Sep 03 '11 at 04:51
  • @Cameron The code was to illustrate using the sample that he put up. All I want to say that there is a way in Java to do what he wanted to do and there is more than one way to do it (for example, you have just proposed another way to do so). And the static java.util.Date was a comment left out from my code where I template the snippet from. I just cleaned that up. – momo Sep 03 '11 at 05:39
  • @momo whats's the crafty part here? I didn't get it – KNU Jan 16 '15 at 12:48
  • @KNU There are several "crafty" or "tricky" aspects, but they're subjective of course. Notice `defaultWriteObject()` and `defaultReadObject()` is always called first - and the order is the same for reading and writing; there is no "reversal" needed as is done in a byte stream dealt in a network transport protocol. The other aspect is that these two methods are both private, and can only be called by the JVM (and yet the serialization API exposed to the calling method remain unchanged). – flow2k Mar 26 '19 at 23:48
36

Similar to @momo's answer but without using a List and auto-boxed int values which will make it much more compact.

private void writeObject(ObjectOutputStream oos) throws IOException {
    // default serialization 
    oos.defaultWriteObject();
    // write the object
    oos.writeInt(location.x);
    oos.writeInt(location.y);
    oos.writeInt(location.z);
    oos.writeInt(location.uid);
}

private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
    // default deserialization
    ois.defaultReadObject();
    location = new Location(ois.readInt(), ois.readInt(), ois.readInt(), ois.readInt());
    // ... more code

}
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • 4
    Thanks, this is a good point. However, in my actual code, I'm using more than just an array, so I used `writeObject` instead. I accepted momo's answer because it was first, but for the situation given, this may be a better answer. – Alexis King Sep 03 '11 at 14:23
1

If it must be Java serialization, the only way I know of is to redefine the readObject() and writeObject() in all classes having a reference to an instance of Location as shown in Momo's answer. Note that this will not allow you to serialize a Location[], and require you to subclass all Collection<Location> appearing in your code. Moreover, it requires the fields of type Location to be marked transient, which will exclude their definitions from being written to the serialization stream, possible foiling the detection of incompatible class changes.

A better way would be to simply override ObjectOutputStream.writeObject. Alas, that method is final. You could override ObjectOutputStream.writeObjectOverride() instead, but that method can not delegate the default implementation, ObjectOutputStream.writeObject0() because that method is private. Of course, you could invoke the private method using reflection, but ...

Therefore, I recommend verifying your constraints. Does it have to be Java serialization? Can you really not change the definition of class Location?

If you have the source code to class Location, it's quite trivial to add implements Serializable and add it to your classpath. Yes, you'll have to do this again whenever you upgrade the library, but it might be better than the alternative ...

meriton
  • 68,356
  • 14
  • 108
  • 175