Poulami
Poulami

Reputation: 1087

Handle deserialization when datatype of class field changed

I have a serializable class.

public class Customer  implements Externalizable {

private static final long serialVersionUID = 1L;

    private String id;
    private String name;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public  String getName() {
        return name;
    }


    public void setName( String name) {
        this.name = name;
    }


    @Override
    public String toString() {
        return "id : "+id+" name : "+name ;
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
            this.setId((String) in.readObject());
            this.setName((String) in.readObject());     
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        System.out.println("Reached here");
        out.writeObject(id);
        out.writeObject(name);
    }


}

I have serialized the object of the class into a file. Now I have changed the datatype of name from String to List. So while deserializing, I am getting a class cast exception because it is not able to convert from String to List. I was thinking of changing the version of the class every time some change is made to the class so that in the readExternal I can handle it explicitly. However while this idea might be able to work for simple classes it would fail in case of larger complicated classes. Can anyone please provide a simpler solution to this.

Thanks

Upvotes: 3

Views: 1576

Answers (3)

Brian
Brian

Reputation: 902

You best implement a migration tool for converting serialized objects from one version to another (by deserializing it to an instance of the old class and then creating an instance of the new class and copying the fields). Keep it simple, no need for overly clever code.

I also recommend to not implement a migration algorithm in your readExternal method for a better separation of concerns, not to mention most likely improved performance, because you could and should omit readExternal for the deserialization provider is usually doing a pretty good job and unless you don't deserialize the same old object over and over again the branch (is it version x or y?) would only yield once to the "old" branch but evaluated for every deserialization. And last but not least: Code you don't have is code you don't need to maintain -> improved maintainability.

Btw: The idea of the serialVersionUID field is to give the deserialization implementation a hint that a serialized object cannot be deserialized to an instance of the same class, because it has changed (thus making it a different class with the same name). If you don't change the version field when making changes, it's completely useless and you can set it to 0 or anything constant and never touch it.

Upvotes: 1

LoganMzz
LoganMzz

Reputation: 1619

I suggest you take a look at different different serialization engine such as Protocol Buffers, Apache Avro or Apache Thrift.

Other possiblities : use a Strategy pattern to select serialization algorithm and delegate to it on readExternal/writeExternal. However you still need a "selector". Class identifier (full name ?) and version are generally top candidates but serialization layout (ie String+String ou String+List) is also an alternative.

Upvotes: 0

SJuan76
SJuan76

Reputation: 24895

You just have to manage the different possibilities (and perform the appropiate conversion) yourself.

@Override
public void readExternal(ObjectInput in) throws IOException,
  ClassNotFoundException {
  this.setId((String) in.readObject());
  Object nameField = in.readObject();
  if (nameField != null) {
    boolean resolved = false;
    if (nameField instanceof String) {
      ArrayList<String> list = new ArrayList<String>(); // Or whatever you want to for converting the String to list.
      list.add((String)nameField);
      this.setName(list);
      resolved = true;
    }
    if (nameField instanceof List) {
      this.setName((List<String>) nameField);
      resolved = true;
    }
    if (!resolved) {
      throw new Exception("Could not deserialize " + nameField + " into name attribute");
    }
  }
}

Upvotes: 4

Related Questions