Java Custom Serialization
Asked Answered
E

3

66

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?

Entirely answered 3/9, 2011 at 2:29 Comment(1)
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!Easily
L
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

}

Lett answered 3/9, 2011 at 2:36 Comment(9)
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?Entirely
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.Lett
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.Entirely
Oh yes, you are right, I miss that :) Sorry, I was typing it in hurryLett
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.Margalit
By the way, what's the assumes "static java.util.Date aDate;" declared bit for?Margalit
@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.Lett
@Lett whats's the crafty part here? I didn't get itFluor
@Fluor 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).Coke
S
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

}
Subchloride answered 3/9, 2011 at 8:29 Comment(1)
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.Entirely
Q
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 ...

Quicksand answered 3/9, 2011 at 7:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.