When in Rome, do as the Romans do - Part I
While different OOP languages share some common principles and many coding patterns will be the same or at least fairly similar, there are also some significant differences that can make some commonly used patterns from one language quite unsuitable in another—and if used, such patterns can cause a lot of trouble.
One such pattern can be found in their serialization approaches. In Java, object
serialization is performed on fields. In Delphi, the convention is to serialize
published properties. That is also the original purpose of the published
visibility specifier.
Delphi also supports defining custom properties in TPersistent descendants, via
DefineProperties method. When it comes to what kind of properties Delphi can
serialize out of the box, there are some limitations, so you cannot serialize
records, arrays, or indexed properties, and you can only serialize collections
defined as TCollection, whose items are TCollectionItem descendants.
But for simplicity, to show the core difference in serialization conventions and what problems it can cause, I will focus on fields vs properties.
Java
- serializes all fields (private, protected, public)
- has the transientfield modifier—for marking fields that should not be serialized- Java Language Specification 8.3.1.3. transient Fields
 Variables may be marked transient to indicate that they are not part of the persistent state of an object.
 
- Java Language Specification 8.3.1.3. transient Fields
- does not have the publishedvisibility specifier
Delphi
- serializes published properties
- does not have transientnor any equivalent field modifier- newer Delphi versions allow custom attributes that can be used for configuring serialization, but the core Delphi RTL does not define such attributes that could be used across different serialization frameworks.
 
- has the publishedvisibility specifier
How that works in practice
Let's say we have a simple class that holds a single piece of data: A string holding the value 'abc'.
class Foo
{
  String data;
}
Foo foo = new Foo();
foo.data = "abc";
  TFoo = class(TPersistent)
  protected
    FData: string;
  published
    property Data: string read FData write FData;
  end;
var Foo := TFoo.Create;
Foo.Data := 'abc';
And if we serialize the above instances to JSON in Java and in Delphi, we expect to get following output:
{"data":"abc"}
If we serialize them to XML, we expect to get:
<data>abc</data>
If we serialize the Foo instance as part of some TComponent and use the default
component streaming format in Delphi we will get:
object ...
  Foo.Data = 'abc'
end
So far, so good. But let's add another field in the class, that will not be serialized.
class Foo
{
  String data;
  transient String temp;
}
Foo foo = new Foo();
foo.data = "abc";
foo.temp = "temporary";
  TFoo = class(TPersistent)
  protected
    FData: string;
    FTemp: string;
  public
    property Temp: string read FTemp write FTemp;
  published
    property Data: string read FData write FData;
  end;
var Foo := TFoo.Create;
Foo.Data := 'abc';
Foo.Temp := 'temporary';
If we serialize such an object instance in Java, regardless of the format (JSON,
XML, or any other format, using any serialization framework), we will get exactly the
same output like we got when only the data field was declared in the class.
After all that is the whole purpose of the transient modifier in Java, and no
matter which serialization framework you use in Java, the default serialization
output created by those differently declared Java classes and their instances
holding the same value in the data field will always be the same.
Now let's take a look on the Delphi side, using the default serialization behavior from
REST.Json. Well, that is not right... we ended up with a temp field in the output,
even though the property was not published, but public. That is not good.
{"data":"abc","temp":"temporary"}
However, if we use the new TFoo class and serialize it with the Delphi component
streaming system, it will behave as expected and regardless of the new field,
that field will not be serialized because it was not marked as published.
object ...
  Foo.Data = 'abc'
end
The problem is that REST.Json is not a good Delphi citizen. Instead of doing what
Romans—Delphians—do, and serializing published properties, it serializes fields.
And this wreaks havoc if you want to apply that JSON serialization framework on existing classes that use published properties.
Now, you can say there is a solution—we can just use the [JSONMarshalled(False)]
attribute declared in REST.Json.Types—but this opens another can of worms.
Now, you will have to add a hard dependency to your code, as you will need to
include REST.Json.Types in the unit where your class is defined, and add the attribute to
the field. If you still don't see why that is wrong: What if you also need to
serialize that class to XML or another format? If those serialization frameworks
are good citizens and use published property serialization, then you will only
end up with a single hard dependency, but if those other frameworks are also using
field-based serialization and have custom-defined attributes, your units and
classes will soon enough turn into a complete mess and dependency nightmare.
Yes, you can always declare those classes separately, but seriously, if that is not an obvious DRY violation and copy-pasta code, then I don't know what is.
The conclusion is plain and simple. While Java follows the field serialization convention, Delphi uses the published serialization convention. Applying the Java serialization style in Delphi code is not an appropriate coding pattern. It just does not fit well in Delphi, because Delphi was designed around a totally different serialization style from the beginning.
TList<T> story
If you are still not convinced that using field-based serialization in Delphi is
wrong, let me tell you a story about TList<T>...
If you don't know it yet, well, then you are the lucky one...
But, this is also a fairly long story, so I will tell you all about it in another post. For now, when writing Delphi code, just remember to do as the Delphians do... not all coding patterns used in other OOP languages fit well in Delphi and vice versa.

 
 
Excellent article.
ReplyDeleteWhile not relevant to the problem you outline, it's worth mentioning that Delphi takes the "stored" storage specifier into account when persisting published properties.
Thanks!
DeleteTrue, conventional Delphi serialization involves more than just publishing the property and that also includes property specifiers like 'stored', 'default' and 'nodefault'.
I simplified the issue here to show main difference and core issues between field vs property approach.
More detailed serialization articles covering other aspects will follow :)