This discussion is archived
5 Replies Latest reply: Dec 22, 2009 5:39 PM by 843790 RSS

Partial deserialization

843790 Newbie
Currently Being Moderated
Hello all...

I've got a (hopefully) quick question regarding deserialization - is there any easy way to perform a partial deserialization of an object? That is, ideally I would like to avoid manual (de)serialization using readExternal and writeExternal, but be able to deserialize only a subset of an object's fields if possible.

As a bit of context, I'm in the process of developing an application for Android. I'd like to present a list of files containing previously serialized objects to the user, and extract some header information display in the list without having to load the entire object to do so. My app incorporates an editor, and I'm essentially using the serialization API as a means to load and save my document objects. These can get quite large, and so to limit memory usage I'd ideally like to avoid loading the entire object just to get a single header field then discard it.

I thought that possibly I could split my implementation into two parts - a base class containing the header information, and a derived class containing the rest of the information. That way (in theory) I could serialize my document by serializing the derived class, but to get just the header information would deserialize to the base class - something like the following (error and exception checking omitted for brevity):
public class Header implements Serializable
{
    private int headerField;
    ...
}

public class Document extends Header
{
    private int documentField;
    ...
}

public void saveDocument (Document document, FileOutputStream fileStream)
{
    ObjectOutputStream objectStream = new ObjectOutputStream(fileStream);
    objectStream.writeObject(document);
    objectStream.close( );
}

public Document loadDocument (FileInputStream fileStream)
{
    Document result = null;
    ObjectInputStream objectStream = new ObjectInputStream(fileStream);
    result = (Document) objectStream.readObject( );
    objectStream.close( );
    return result;
}

public Header loadHeader (FileInputStream fileStream)
{
    Header result = null;
    ObjectInputStream objectStream = new ObjectInputStream(fileStream);
    result = (Header) objectStream.readObject( );
    objectStream.close( );
    return result;
}
As I see it, the problem here is that (in my understanding) in loadHeader we don't actually deserialize a Header object, we instead deserialize a Document object and cast it to Header. Since in the end we actually are deserializing a Document after all, there seems to be no gain as far as my aims are concerned.

Any thoughts?

Cheers!

Mac.
  • 1. Re: Partial deserialization
    843790 Newbie
    Currently Being Moderated
    "Document extends Header" doesn't make a lot of sense to me. It seems flawed semantically (is a Document really a special kind of header) and has serious drawbacks for your concrete problem.

    Why don't you make your Document contain a header instead? Not only does this give you more flexibility in handling your object (you can easily detach an existing Header and handle it independently from the Document which is almost impossible with your current design), but it also matches the semantics more closely ("a document has a header").

    Then you can easily do something like this:
    public class Header implements Serializable
    {
        private int headerField;
        ...
    }
     
    public class Document implements Serializable
    {
        private Header header;
        private int documentField;
        ...
    }
     
    public void saveDocument (Document document,OutputStream outputStream)
    {
        ObjectOutputStream objectStream = new ObjectOutputStream(outputStream);
        objectStream.writeObject(document.getHeader());
        objectStream.writeObject(document);
        objectStream.close( );
    }
     
    public Document loadDocument (InputStream inputStream)
    {
        Document result = null;
        ObjectInputStream objectStream = new ObjectInputStream(inputStream);
        objectStream.readObject(); // read and ignore the Header object, it will be contained in the Document anyway.
        result = (Document) objectStream.readObject( );
        objectStream.close( );
        return result;
    }
     
    public Header loadHeader (FileInputStream fileStream)
    {
        Header result = null;
        ObjectInputStream objectStream = new ObjectInputStream(fileStream);
        result = (Header) objectStream.readObject( );
        objectStream.close( );
        return result;
    }
    Note that all of this is cobbled together from what little I remember about serialization and if I'm not mistaken, then this should work. Try it and see if I remember enough ;-)

    Also note that I replaced all references to FileOutputStream/FileInputStream with the more general OutputStream/InputStream. There's nothing that forces you to take those specific classes as argument (unless Android doesn't haveInputStream/OutputStream, but from what I heard I think that is not the case).
  • 2. Re: Partial deserialization
    843790 Newbie
    Currently Being Moderated
    You make several excellent points.

    You are entirely correct, and now that you have pointed it out it seems terribly obvious. I must be having an off day...

    Thanks so much for your help!

    Mac.
  • 3. Re: Partial deserialization
    843790 Newbie
    Currently Being Moderated
    For reference, I thought I'd share my solution (based on Joachim's advice above).

    My Document class now contains an inner Header class. The document has an instance of Header as a volatile member. This allows it, and the rest of the class to be (de)serialized separately. To serialize the entire document, I first serialize the header, then serialize the rest of the document. To deserialize the entire document, I first deserialize the header, then deserialize the rest of the document, then assign the deserialized header to the deserialized document. Deserializing the header is then as simple as only performing the first step in deserializing the entire document.

    The code is similar to the following:
    public class Document implements Serializable
    {
        public static class Header
        {
            private int headerField;
            ...
        }
        
        private volatile Header documentHeader;
        private int documentField;
        ...
        
        public void saveDocument (OutputStream outputStream)
        {
            ObjectOutputStream objectStream = new ObjectOutputStream(outputStream);
            
            objectStream.writeObject(documentHeader);
            objectStream.defaultWriteObject( );
        }
        
        public static Document loadDocument (InputStream inputStream)
        {
            Document result = null;
            Header header = null;
            ObjectInputStream objectStream = new ObjectInputStream(inputStream);
    
            header = (Header) objectStream.readObject( );        
            result = (Document) objectStream.readObject( );
            
            result.documentHeader = header;
            
            objectStream.close( );
            
            return result;
        }
        
        public static Document loadHeader (InputStream inputStream)
        {
            Header result = null;
            ObjectInputStream objectStream = new ObjectInputStream(inputStream);
    
            result = (Header) objectStream.readObject( );
            
            objectStream.close( );
            
            return result;
        }
    }
    Hopefully that will be of some use to someone else some day...

    Mac.
  • 4. Re: Partial deserialization
    EJP Guru
    Currently Being Moderated
    My Document class now contains an inner Header class.
    A static Header class. Inner classes aren't static. If you had used an inner class it wouldn't work as required, because inner classes have a secret reference to their enclosing instance, so the Document would have been serialized along with the Header. So it's an important distinction and especially so in this case.
    The document has an instance of Header as a volatile member.
    That should be transient.
  • 5. Re: Partial deserialization
    843790 Newbie
    Currently Being Moderated
    Thanks! My apologies - I'm rather new to both serialization and nested classes, so please excuse my confused terminology. My code is fixed as suggested.

    I have also discovered that you're not supposed to call defaultWriteObject from any method except for an implementation of writeObject. To do otherwise causes a java.io.NotActiveException to be thrown. The proper approach is instead to call ObjectOutputStream's writeObject method with this as a parameter:
    objectStream.writeObject(this);
    replaces
    objectStream.defaultWriteObject( );
    in the saveDocument method.

    Mac.

    Edited by: Mac.Coombe on Dec 22, 2009 5:33 PM