How Are Constructors Called During Serialization and Deserialization

How are constructors called during serialization and deserialization?

Example:

 public class ParentDeserializationTest {

public static void main(String[] args){
try {
System.out.println("Creating...");
Child c = new Child(1);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
c.field = 10;
System.out.println("Serializing...");
oos.writeObject(c);
oos.flush();
baos.flush();
oos.close();
baos.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
System.out.println("Deserializing...");
Child c1 = (Child)ois.readObject();
System.out.println("c1.i="+c1.getI());
System.out.println("c1.field="+c1.getField());
} catch (IOException ex){
ex.printStackTrace();
} catch (ClassNotFoundException ex){
ex.printStackTrace();
}
}

public static class Parent {
protected int field;
protected Parent(){
field = 5;
System.out.println("Parent::Constructor");
}
public int getField() {
return field;
}
}

public static class Child extends Parent implements Serializable{
protected int i;
public Child(int i){
this.i = i;
System.out.println("Child::Constructor");
}
public int getI() {
return i;
}
}
}

Output:

Creating...
Parent::Constructor
Child::Constructor
Serializing...
Deserializing...
Parent::Constructor
c1.i=1
c1.field=5

So if you deserialized your object, its constructors doesn't called, but default constructor of its parent will be called.
And don't forget: all your serializable object should have a standard constructor without parameters.

Why default constructor is not called while deserialization process?

It's worth reading Java Object Serialization Specification: 3 - Object Input Classes where readObject method is described in details along with step by step explanation.

How it works?

An instance of the class is allocated. The instance and its handle are added to the set of known objects.

The contents restored appropriately:

  1. For serializable objects, the no-arg constructor for the first non-serializable supertype is run.

    • For serializable classes, the fields are initialized to the default value appropriate for its type.

    • Then the fields of each class are restored by calling class-specific readObject methods, or if these are not defined, by calling the defaultReadObject method.

    • Note that field initializers and constructors are not executed for serializable classes during deserialization.

    • In the normal case, the version of the class that wrote the stream will be the same as the class reading the stream. In this case, all of the supertypes of the object in the stream will match the supertypes in the currently-loaded class.

    • If the version of the class that wrote the stream had different supertypes than the loaded class, the ObjectInputStream must be more careful about restoring or initializing the state of the differing classes.

    • It must step through the classes, matching the available data in the stream with the classes of the object being restored. Data for classes that occur in the stream, but do not occur in the object, is discarded.

    • For classes that occur in the object, but not in the stream, the class fields are set to default values by default serialization.

  2. For externalizable objects, the no-arg constructor for the class is run and then the readExternal method is called to restore the contents of the object.


Sample code to understand fist point For serializable objects, the no-arg constructor for the first non-serializable supertype is run.

Sample code;

class TestClass1 {
public TestClass1() {
System.out.println("TestClass1");
}
}

class TestClass2 extends TestClass1 implements Serializable {
public TestClass2() {
System.out.println("TestClass2");
}
}

public static void main(String[] args) throws Exception {
System.out.println("Object construction via calling new keyword");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("resources/dummy.dat"));
out.writeObject(new TestClass2());

System.out.println("Object construction via readObject method");
ObjectInputStream is = new ObjectInputStream(new FileInputStream("resources/dummy.dat"));
TestClass2 tc = (TestClass2) is.readObject();
}

output:

Object construction via calling new keyword
TestClass1
TestClass2

Object construction via readObject method
TestClass1

Java: Why doesn't deserialization invoke constructor & what's the best workaround?

Deserialization doesn't invoke the constructor because the purpose of it is to express the state of the object as it was serialized, running constructor code could interfere with that.

How can I call the constructor before deserialisation?

Create a constructor with the signature

protected YourClassName(SerializationInfo info, StreamingContext context)
{
}

and have your class implement ISerializable. On serialization it calls ISerializable.GetObjectData() on de-serialization it calls the above constructor.

See Custom Serialization: Implementing the ISerializable Interface on the MSDN

If you don't actually need a to do the work in the constructor you can use the attribute [OnDeserializing] instead of [OnDeserialized] to get the work done before deserialization instead of after.

[OnDeserializing]
private void SetValuesOnDeserializing(StreamingContext context)
{
// Code not shown.
}

Note: If you have more than one [OnDeserializing] method in your object graph the order they are called in is not dertimistic.

Does the default constructor get called during object deserialization when using ObjectInputStream?

I was wondering if in the following code, when readObject gets called,
does it call the default constructor (if one exists) or not?

No, It doesn't ..

is there a MemoryStream (C#) equivalent in Java?

ByteArrayInputStream and ByteArrayOutputStream

When is the class constructor called while deserialising using XmlSerializer.Deserialize?

No it is not OK to assume the properties will be set when the constructor runs. The opposite is true. The constructor is the very first piece of code which runs when an instance of an object is created. It's not possible for the properties to be set until after the constructor has started executing.

The XML deserialization process roughly looks like the following

  • Call the parameterless constructor
  • Set the properties to their deserialized values

A way to work around this is to use a factory method to do the deserialization and then run the logic which depends on the properties being set. For example

class MyClass {
...
public static MyClass Deserialize(string xmlContents) {
var local = ... // Do the XML deserialization
local.PostCreateLogic();
return local;
}
}

Serialization/Deserialization and non-default constructors

You can solve that in two ways. Use convention over configuration (name the constructor parameters as the properties and do not include a default constructor):

[Persistable]
public sealed class FileMoveTask : TaskBase
{
[PersistMember]
public string SourceFilePath { get; private set;}

[PersistMember]
public string DestFilePath { get; private set;}

public FileMoveTask(string sourceFilePath, string destFilePath)
{
this.SourceFilePath = srcpath;
this.DestFilePath = dstpath;

//possibly other IMPORTANT initializations
}

//code
}

Explicitly tag the constructor arguments with your attribute and search for the constructor which have been tagged:

[Persistable]
public sealed class FileMoveTask : TaskBase
{
[PersistMember]
public string SourceFilePath { get; private set;}

[PersistMember]
public string DestFilePath { get; private set;}

public FileMoveTask([PersistMember("SourceFilePath")]string srcpath, [PersistMember("DestFilePath")]string dstpath)
{
this.SourceFilePath = srcpath;
this.DestFilePath = dstpath;

//possibly other IMPORTANT initializations
}

//code
}

the attribute or the argument name isn't used to know which property to set, but to know which information from the serialize data to use when invoking the constructor.

Each parameter in the deserialization constructor on type must bind to an object property or field on deserialization

You are encountering two separate problems related to deserializing types with parameterized constructors. As explained in the documentation page How to use immutable types and non-public accessors with System.Text.Json:

System.Text.Json can use a public parameterized constructor, which makes it possible to deserialize an immutable class or struct. For a class, if the only constructor is a parameterized one, that constructor will be used. For a struct, or a class with multiple constructors, specify the one to use by applying the [JsonConstructor] attribute. When the attribute is not used, a public parameterless constructor is always used if present. The attribute can only be used with public constructors.

...

The parameter names of a parameterized constructor must match the property names. Matching is case-insensitive, and the constructor parameter must match the actual property name even if you use [JsonPropertyName] to rename a property.

Your first problem is with the type Repository. You don't show it in your question, but I assume it looks something like this:

public class Repository
{
public Repository(string gitDirectory) => this.GitDirectory = new DirectoryInfo(gitDirectory);

[JsonConverter(typeof(DirectoryInfoConverter))]
public DirectoryInfo GitDirectory { get; }
}

public class DirectoryInfoConverter : JsonConverter<DirectoryInfo>
{
public override DirectoryInfo Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
new DirectoryInfo(reader.GetString());
public override void Write(Utf8JsonWriter writer, DirectoryInfo value, JsonSerializerOptions options) =>
writer.WriteStringValue(value.ToString());
}

If so, your problem here is that either the name of the constructor argument corresponding to GitDirectory is not the same as the property name or the type of the argument is not the same. Because, as it turns out, there is an undocumented restriction that types of the constructor arguments and corresponding properties must also match exactly. For confirmation see JsonConstructor fails on IEnumerable property?.

Demo fiddle #1 here.

To fix this, you must either:

  1. Add a public parameterless constructor and make Repository be mutable (i.e. add a setter for GitDirectory), or

  2. Add a constructor with an argument of the same type and name as the property GitDirectory, and mark it with [JsonConstructor].

Adopting option #2, your Repository type should now look like:

public class Repository
{
public Repository(string gitDirectory) => this.GitDirectory = new DirectoryInfo(gitDirectory);
[JsonConstructor]
public Repository(DirectoryInfo gitDirectory) => this.GitDirectory = gitDirectory ?? throw new ArgumentNullException(nameof(gitDirectory));

[JsonConverter(typeof(DirectoryInfoConverter))]
public DirectoryInfo GitDirectory { get; }
}

And now Respository will deserialize successfully. Demo fiddle #2 here.

However, you will now encounter your second problem, namely that the Blob type will not round-trip either. In this case, Blob does have a unique parameterized constructor whose argument names and types correspond precisely to properties -- but the semantics of one of them, data, are completely different:

public class Blob : GitObject
{
public string Data { get; set; }

public Blob(Repository repository, string data = null)
{
if (data != null)
Data = File.ReadAllText(data);
Repository = repository;
}

The property Data corresponds to the textual contents of a file, while the argument data corresponds to the file name of a file. Thus when deserializing Blob your code will attempt to read a file whose name equals the file's contents, and fail.

This inconsistency is, in my opinion, poor programming style, and likely to confuse other developers as well as System.Text.Json. Instead, consider adding factory methods to create a Blob from a file, or from file contents, and remove the corresponding constructor argument. Thus your Blob should look like:

public class Blob : GitObject
{
public string Data { get; set; }

public Blob(Repository repository) => this.Repository = repository ?? throw new ArgumentNullException(nameof(repository));

public static Blob CreateFromDataFile(Repository repository, string dataFileName) =>
new Blob(repository)
{
Data = File.ReadAllText(dataFileName),
};

public static Blob CreateFromDataConents(Repository repository, string data) =>
new Blob(repository)
{
Data = data,
};

public override string Serialize() => JsonSerializer.Serialize(this);

public override void Deserialize(string data)
{
// System.Text.Json does not have a Populate() method so we have to do it manually, or via a tool like AutoMapper
Blob blobData = JsonSerializer.Deserialize<Blob>(data);
this.Repository = blobData.Repository;
this.Data = blobData.Data;
}
}

And you would construct and round-trip it as follows:

var firstBlob = GitObject.Blob.CreateFromDataFile(repository, file.FullName);
var json = firstBlob.Serialize();

var secondBlob = new GitObject.Blob(repository);
secondBlob.Deserialize(json);

Final working demo fiddle here.

How does java serialization deserialize final fields when no default constructor specified?

Deserialization is implemented by the JVM on a level below the basic language constructs. Specifically, it does not call any constructor.



Related Topics



Leave a reply



Submit