3

When writing custom deserialization code, what's the better practice: a static method that creates an uninitialised object (e.g. using the default constructor) and then performs deserialization, or a constructor that performs deserialization directly?

For example, deserializing a Foo:

class Foo
{
    int bar;

    this() { }

    static Foo fromMyDataFormat(byte[] serializedData)
    {
        auto foo = new Foo();
        foo.bar = /* deserialize bar from serializedData */;
        return foo;
    }
}

versus

class Foo
{
    int bar;

    this(byte[] serializedData)
    {
        bar = /* deserialize bar from serializedData */;
    }
}

What are the pros and cons of both approaches? Are there other options?

Only deserialization should be implemented.

  • Is an instance of Foo valid if created just via the default constructor? – Philip Kendall Jul 31 '16 at 13:41
  • @PhilipKendall In my example it's public, so yes. An answer that also addresses the case where it wouldn't be valid would be most useful though. – lesderid Jul 31 '16 at 13:50
4

No, it should not.

Serialization is orthogonal to the object and thus should be kept outside. Custom serialization belongs in a customization to a serializer.

  • 1
    I actually whole heartedly agree with this answer. Not sure why it was downvoted. The ctor should take in the actual values it needs, not a byte array. A separate class should be responsible for deserializing the data into values to be passed to the ctor. SRP. – RubberDuck Jul 31 '16 at 22:09
1

It depends on the language and serialization framework. In .NET for example, the serializer specifically looks for a constructor that takes a SerializationInfo and StreamingContext. JSON.NET will look for constructors that take parameters that match JSON field names.

But outside of those cases, I would recommend a static method. Deserialization is one of those things that can commonly fail, and constructors limit your design options around dealing with and reporting errors in a nice way.

  • 1
    Why can deserialization commonly fail? – Robert Harvey Jul 31 '16 at 20:13
  • @RobertHarvey Because the input string can be invalid? – Derek 朕會功夫 Jul 31 '16 at 22:07
  • @Derek朕會功夫: That would only happen if the serialization was generated from some other source than the original serializer, or there's a bug in the serializer or deserializer, or you handed it a type that's different than the one you used for serialization. – Robert Harvey Jul 31 '16 at 22:39
1

Just like you use constructor arguments which are very transparent in the terms of what is needed for the object to be created, one constructor may accept the byte[] data type contaning serialized data for the object to be created.

As Telastyn has already pointed out, there's a problem with deserialization. It is very prone to errors because the byte[] data types gives no hints about the data structure which may easily be corrupted by pretty much anything. Naturally, throwing an exception from a constructor is nothing to be afraid of and some people, myself included, encourage to do so when the input data is not in a valid format. Because of that, the static factory method is really not needed as it pretty much just wraps the constructor anyway.

In my opinion the static method brings nothing to the table and just like you can throw an exception with error info from the constructor you are very likely to do the same from the static method anyway. You could introduce a static getConstructionErrors method returning a list of error strings, but static state is shared and thus you may have issues related to race conditions.

But depending on your needs, you may not want a static method or a constructor either. If you ever want to introduce another way of serialization/deserialization you should create a custom FooSerialization interface (here the interface is the language keyword and may me refered to as a protocol or a pure abstract class in other languages) and have different classes implement it to provide different serialization and deserialization strategies. Serialization and deserialization is usually not considered to be a direct responsibility of an object you are modeling and thus may be extracted to a custom service (see the Single Responsibility Principle). On the other hand, if you will never need more than one way to serialize the object, a direct serialize method and construction from the serialized byte array is probably the way to go.

  • 1
    The "serialization is brittle because byte[] doesn't contain type information" argument is not compelling. Naturally, if you serialize a class to bytes without any metadata to recover the original data structures you're going to have problems, but that's not how serialization normally works, and if binary serialization feels too opaque, use some other serialization medium like XML or JSON. – Robert Harvey Jul 31 '16 at 20:09
  • I like the separate interface to do serialization, and, in many cases, this is best done by a separate class, especially in large projects. (+1) Though it begs the question of how you find the appropriate builder class for your objects. If your project is modest sized, as are the classes, having the classes know how to build themselves may simplify things. – user949300 Jul 31 '16 at 22:50
0

It should be obvious that an arbitrary array of bytes may or may not be a valid serialized representation of the object. So two things must happen: Someone must check the data, which might fail the test, and someone must create an object if the data passes the test.

I personally think it is easiest and cleanest to do the verification and the object creation with the same code - maybe that's the best approach, maybe it's just me. (On stackoverflow you see a frightening number of questions where people ignore the verification part completely).

Question: How easy is it in your language to run a constructor that may fail? If this is very easy, like in Objective-C or Swift, do it in a constructor. In C++, afaik it is easy to throw an exception in a constructor and handle it correctly if the programmer knows how to do this - which is a pretty strong assumption :-( If it is difficult, add a class method or some factory method.

-2

There is a third option to consider: a method.

Foo foo = new Foo();
foo.deserialize(theByteData);

or, if you like immutability and want it to return a new instance

Foo temp = new Foo();  // there's other conventions you can do here
Foo foo = temp.deserialize(theByteData);

The advantage in some languages is that this can be declared in an interface. So if you have many classes to deserialize (or xmlize or serialuze or whatever) this can be a powerful option.

Advantages:

  1. If you have a mixed collection of objects of this interface, you don't need to do instanceofs to find the constructor, builder class, or static method.
  2. Unlike a separate builder or serializer (see @David Packer's answer, which is a good one) you can better maintain private fields and possibly immutability. A separate builder typically (not always) requires setters (or "friend"-like access) for your fields. In effect, instead of a separate builder for each class, the object itself is it's own builder.

If you only have a few classes to deserialize, depends on your language. In Java, the static function valueOf() is a common convention.

  • this mostly seems like a hack to allow for declaration in an interface. are there any other advantages compared to a static method? – sara Jul 31 '16 at 19:54
  • @kai Slightly contrived, but let's say you have a mixed collection of IDeserializables. Its easy to deserialize all of them. With the static method, you'd have to do a lot of instanceof (or similar) to decide which static class to use. And that is gross. – user949300 Jul 31 '16 at 21:42
  • @Telastyn I admit this is unusual. But I've used this in a couple of projects, and, after initial reactions similar to yours (including from myself the first time), once they used it everybody has liked it. – user949300 Jul 31 '16 at 21:46
  • @user949300 well in that case, what is the return type of the Deserialize method? object? or are all classes declared class Foo : IDeserializeable<Foo>? – sara Aug 1 '16 at 7:25
  • Gross because you have instances that have been created with an empty constructor, and instances created by deserialisation. What happens if you write foo.deserialize (theByteData) again? So the deserialisation must be able to clean up all the resources of a previous deserialisation? – gnasher729 Aug 1 '16 at 15:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.