A Simple start with MVP in Delphi for Win32, Part 2

Some month ago I wrote a simple article about an MVP variant called PassiveView.
That example was very simple. Now I’ll present a more “advanced” version of that example.

The main problem with first example was the following method:

procedure TfrmCalculatorView.FormCreate(Sender: TObject);
begin
  //Link controls with related interface
  IFirstOperand := TGUIEdit.Create(EditFirstOp);
  ISecondOperand := TGUIEdit.Create(EditSecondOp);
  ICalcResult := TGUIEdit.Create(EditResult);
  IOperators := TGUISelectableList.Create(ComboOperators);
  IError := TGUIEdit.Create(EditError);

  //link view and presenter
  FPresenter := TCalculatorPresenter.Create(Self); //<<-- THIS IS THE BAD LINE
end;

The “BAD” line links the View with the Presenter but it’s in the view code, so this is meaning that View KNOWS the presenter… and this is not a good thing becouse the view is not so “passive”.

In a more advanced (and complex) version the View should be completely ignorant about the class that implement the presenter and the service.

In the main dpr file now the code now looks like the following.

var
  MainPresenter: ICalculatorPresenter;
  CalculatorView: TForm;
begin
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  //SETUP THE MAIN APPLICATION FORM FOR VCL PURPOSE
  Application.CreateForm(TfrmCalculatorView, CalculatorView);  
  //SETUP ALL THE LINKS BETWEEN THE MVP TRIAD
  MainPresenter := TCalculatorPresenter.Create(CalculatorView as ICalculatorView, TCalculatorService.Create);
  //LETS START!
  Application.Run;
end.

Now the presenter take care about all links between the MVP triad.

constructor TCalculatorPresenter.Create(CalculatorView: ICalculatorView;
  CalculatorService: ICalculatorService);
begin
  inherited Create;
  FCalculatorService := CalculatorService;
  FCalculatorView := CalculatorView;
  FCalculatorView.SetPresenter(self);
  InitView;  //does the links
end;

There is another addition to the previous example. Now there is only one constructor in the presenter, and using dependency injection take 2 interface for the view and the service.

    constructor Create(CalculatorView: ICalculatorView; CalculatorService: ICalculatorService);

Another plus is the possibility to open the same form a number of times without change the code for create it.

This is the GUI for this simple application.

3 instance of the same view with different presenter and service
3 instance of the same view with different presenter and service

As bonus, unit tests and mock object arent changed.

As usual the source code is here.

New beta version for DataSnap Filters Compendium

I’ve been very busy lately with some Delphi related events here in Italy

so I havent too much time for my personal open source project. But now I’ve released new beta version for DSFC.

This release address a problem with the cypher filters.

Cypher filters arent completely compatible with the previuous version, so you need to rebuild the server and the client if you want to use this new version of DSFC.

There are one known issue:

  • Cannot have multiple connections in a client with the same filters but different cipher keys connecting to different servers.

Some users asked to me a little tutorial to setup a demo application with the cypher filters. Now there is a project group illustrating how do it.

There is also a new net speed test.

I’m waiting for some testers feedback comment before of 1.1 version.

As usual you can find the source code in the GoogleCode SVN repository

http://dsfc.googlecode.com

Happy testing!

A simple Dependency Injection Container for Delphi

As wikipedia says:

“Dependency injection (DI) in computer programming refers to the process of supplying an external dependency to a software component. It is a specific form of inversion of control where the concern being inverted is the process of obtaining the needed dependency. The term was first coined by Martin Fowler to describe the mechanism more clearly.

Many of us have already read this historical article from Martin Fowler about dependency injection pattern, but actually there isn’t a real framework for implement dependency injection in Delphi.

There are already the following implementation for DI in Delphi

  • Emballo (work with pre-D2010 too, but the implementation require changes in the service classes. I really hate it)
  • Delphi Spring Framework (very nice, but still not realeased)

So, I decided to write my own simple DI framework.

You can find the code at google code project here: http://code.google.com/p/delphidicontainer/

This is the first public version and come with sample, documentation and unit tests.

Folow some sample code.

program Test01;
{$APPTYPE CONSOLE}

uses
  SysUtils,
  DIContainer in '....srcDIContainer.pas',
  ServiceTestObjectsU in '....UnitTestServiceTestObjectsU.pas';

var
  DIContainer: TDIContainer;
  s1: TService1;
  s2: TService2;
  s3: TService3;
  s6: TService6;
  s7: TService7;
begin
  try
    DIContainer := TDIContainer.Create;
    try
      // AddComponent with TClass with and   InitType = Singleton
      DIContainer.AddComponent(TService1, TDIContainerInitType.Singleton);
      // AddComponent with QualifiedName and InitType = Singleton
      DIContainer.AddComponent('ServiceTestObjectsU.TService2',
        TDIContainerInitType.Singleton);
      // AddComponent with QualifiedName and InitType = CreateNewInstance
      DIContainer.AddComponent('ServiceTestObjectsU.TService3',
        TDIContainerInitType.CreateNewInstance);

      // GetComponent with QualifiedName
      s1 := DIContainer.GetComponent('ServiceTestObjectsU.TService1')
        as TService1;
      s1.Message := 'I''m the first message';
      WriteLn(s1.Message);

      // GetComponent with TClass
      s2 := DIContainer.GetComponent(TService2) as TService2;
      s2.Message := 'I''m the second message';
      WriteLn(s2.Message);

      // GetComponent with a dependent service (TService3 depends upon TService1 and TService2)
      s3 := DIContainer.GetComponent('ServiceTestObjectsU.TService3')
        as TService3;
      WriteLn(s3.GetCompoundMessage);
      // s3 is not created as Singleton, so after use it I must free it
      s3.Free;

      // AddComponent with QualifiedClassName, a custom initializer, an alias.
      // Component will be created as singleton (single instance managed by Container)

      DIContainer.AddComponent(DIContainerUtils.GetQualifiedClassName
          (TService6),
          function: TObject
          begin
            Result := TService6.Create(DIContainer.Get(TService1) as TService1,DIContainer.Get(TService1) as TService1);
          end,
          'srv6',
        TDIContainerInitType.Singleton);

      s6 := DIContainer.Get('srv6') as TService6;
      WriteLn(s6.ToString);
      s6 := DIContainer.Get('srv6') as TService6;
      WriteLn(s6.ToString);

      // AddComponent with QualifiedClassName, a custom initializer, an alias.
      // Component will be created as singleton (single instance managed by Container)
      DIContainer.AddComponent(DIContainerUtils.GetQualifiedClassName
          (TService7),
            function: TObject
            begin
              Result := TService7.Create(DIContainer.Get(TService1) as TService1,DIContainer.Get(TService1) as TService1);
            end,
            'srv7intf',
          TDIContainerInitType.Singleton);

      s7 := DIContainer.Get('srv7intf') as TService7;
      WriteLn(s7.ToString);
    finally
      DIContainer.Free;
    end;
  except
    on E: Exception do
      WriteLn(E.ClassName, E.Message);
  end;
  readln;
end.

DelphiDIContainer also handle constructor injection with automatic dependency resolution for service.

Enjoy and stay tuned.

News and small fixes for DataSnap Filters compendium

DataSnap Filters Compendium has been used by many users so far. I’m planning to do some change in hash filters (that do not will affect already written code) but until new version will ready I have 3 nice news about DSFC:

1. Now the project is on Google Code.

2. Franco Perana, the first contributor to the project, has fixed and optimized the LZO filter.

3. Have you used DSFC and do you want to see your application listed in the “Application Using DSFC”? If yes, leave a comment on this post with some details of your application amd I’ll add it in the proper list.

JSON support in Delphi2010, a simple example

During last ITDevCon in Verona (ITALY) I talked in a session with title “Marshal and UnMarshal in Delphi 2010″ (we have published some photos about this great conference here).
In the first part of the session I’ve talked about JSON and many attendees were very interested in JSON and his application in everyday programming. I’m a real JSON fan so I decided to write this post about JSON support in Delphi2010. In my session I talked about serialization and deserialization, in this post I’ll show an example of code to do some JSON generation.

program JsonTypes;
{$APPTYPE CONSOLE}
uses
  SysUtils,
  IOUtils,
  DBXJSON;
var
  Obj, RestoredObject, JSON: TJSONObject;
  arr: TJSONArray;
begin
  json := TJSONObject.Create;
  try
    WriteLn('Empty JSON Object: ' + sLineBreak,json.ToString);
    json.AddPair('FirstName', TJSONString.Create('Daniele'));
    WriteLn('Simple JSON Object with property: ' + sLineBreak, json.ToString);
    json.AddPair(TJSONPair.Create('LastName', 'Teti'));
    WriteLn('Simple JSON Object with 2 property: ' + sLineBreak, json.ToString);
    arr := TJSONArray.Create;
    arr.Add(9871238712);
    arr.Add('peter.parker@bittime.it');
    arr.Add('Via Roma, 12');
    json.AddPair(TJSONPair.Create('Contacts', arr));
    WriteLn('JSON Object with a property array: ' + sLineBreak, json.ToString);
    obj := TJSONObject.Create;
    obj.AddPair(TJSONPair.Create('John','Doe'));
    json.AddPair('MyUndefinedPersonObject', obj);
    WriteLn('JSON Object with a nested Object: ' + sLineBreak,json.ToString);
    Write('Writing JSON object to file [jsonfile]...');
    TFile.WriteAllText('jsonfile',json.ToString);
    WriteLn('DONE!');
    RestoredObject := TJSONObject.ParseJSONValue(
      TEncoding.ASCII.GetBytes(TFile.ReadAllText('jsonfile')),0
      ) as TJSONObject;
    WriteLn('Readed JSON object from file: ' + sLineBreak, RestoredObject.ToString);
  finally
    json.Free;  //Every contained object is automatically freed
  end;
  readln; //If you want to see the output while in Delphi IDE
end.

JSON is a lightweight data-interchange format and in Delphi 2010 you have full support for it with the built-in DBXJSON unit.
Enjoy!

My Speeches at ITDevCon 2009 (Italy, Verona)

Tomorrow I travel to Verona to the ITDevCon the Italian Delphi Conference.
The conference will be at 11-12 november, and will be REALLY great!

You can see the time table here.

At this conference I will speak about:

  • Marshal e UnMarshal in Delphi 2010
  • Enterprise Data Access patterns in Delphi
  • Delphi e i messaging systems… ZeroMQ + AMQP e ActiveMQ + STOMP

My speeches will be in italian language only.

See you in Verona!

DataSnap Filters Compendium

What’s DataSnap Filters Compendium

DataSnap Filters Compendium (DSFC) is a compendium of 9 filters for DataSnap 2010.
The filters are divided into 3 groups:

HASH
MD5
MD4
SHA1
SHA512

CIPHER
Blowfish
Rijndael
3TDES
3DES

COMPRESS
LZO

HASH filters
The HASH filters helps avoid to any spiteful person to modify datasnap message through an “Man in the middle” attack (http://en.wikipedia.org/wiki/Man-in-the-middle_attack).
Functioning is basing on an easy principle. After sending the message, the filter calculates the hash of the message and tags along it to the message. When the message gets to destination, the filter recovers the hash calculated by the client and recalculates it on the remaining part of the message.
If the part of the extrated hash (calculated at the beginning) and the hash recalculated to the end are equal, the message hasn’t change.
To avoid someone could modify the message and also recalculates the hash, after calculating the hash, a GUID is tagged along to the message, which just the sender and the receiver know.
This kind of filters DOES NOT AVOID THE UNAVOIDED READING OF DATA,it avoids just the modification.

CIPHER filters
The CIPHER filters are the most interesting filters. Many datasnap users have requested a built-in system to get the data transmission safe. This set of filters colud be the answer.
In the actual version I’ve implemented Symmetric-key algorithms. Maybe I’m going to develop of Asymmetric-key algorithms filters.
Implemented algorithms features are well known, I list them as follows just to be completed:

Blowfish
Blowfish has a 64-bit block size and a variable key length from 32 up to 448 bits.
The filter version has a keysize = 56 byte.

Rijndael
AES Round 2.
AES has a fixed block size of 128 bits and a key size of 128, 192, or 256 bits, whereas Rijndael can be specified with block and key sizes in any multiple of 32 bits, with a minimum of 128 bits and a maximum of 256 bits.
The filter version has a keysize = 32 byte.

3TDES
Triple DES with 24 byte Blocksize, 24 byte Keysize 168 bits relevant
The filter version has a keysize = 24 byte.

3DES
Triple DES with 8 byte Blocksize, 24 byte Keysize 168 bits relevant
The filter version has a keysize = 24 byte.

COMPRESS filters
Actually the LZO compression is the only one that exists, and is one of the faster compression algorithms.
The compression ratio compared to the ZLib is worse but about 3 times faster (as the table in next paragraph shows).

Tests
DSFC has a huge suite of unit tests and speed tests.
The speed tests show how filters are fast and how the data stream size is affected by their work. Follow table contains results of speed test execution (DSFCSpeedTest) on my workstation. If you want give a meaning to those numbers, see the code of the speed test :-)

== HASH FILTERS == 1000 iterations
MD5             =  161ms (stream size:  8304 byte, filtered stream size:  4184 byte)
MD4             =   99ms (stream size:  8304 byte, filtered stream size:  4184 byte)
SHA1            =  145ms (stream size:  8304 byte, filtered stream size:  4192 byte)
SHA512          =  344ms (stream size:  8304 byte, filtered stream size:  4280 byte)

== CIPHER FILTERS == 1000 iterations
Blowfish        =  898ms (stream size:  8304 byte, filtered stream size:  8304 byte)
Rijndael        =  941ms (stream size:  8304 byte, filtered stream size:  8304 byte)
3TDES           = 1729ms (stream size:  8304 byte, filtered stream size:  8304 byte)
3DES            = 1757ms (stream size:  8304 byte, filtered stream size:  8304 byte)

== COMPRESS FILTERS == 1000 iterations
LZO             =   79ms (stream size:  8304 byte, filtered stream size:  1113 byte)
ZLibCompression =  295ms (stream size:  8304 byte, filtered stream size:   799 byte)

Last speed test is for the only filter included in Delphi2010. It’s included only for compare times and data stream size with the other filters.

After you install DataSnapFiltersCompendium.bpl you will see following filters into the “Filters” property

New filters registered by DSFC
New filters registered by DSFC

Cipher filters have only one property for the encription key

Encryption key for the cipher filters
Encryption key for the cipher filters

Project Source Contains

  • Full Filters Code
  • Unit Test
  • SpeedTest

Download DataSnap Filters Compendium

P.S. I’ll be at ITDevCon… and you? :-)

Custom Marshalling/UnMarshalling in Delphi 2010

Introduction
Some days ago, Embarcadero has presented the new version of RAD Studio, 2010.
The are many new features, but you can find in a lot places around the web, so
I won’t repeat them here.

One of the things widely requested from all Delphi programmers all over the world over the past few years, including myself, is
certainly a new and more powerful RTTI.

The new system of RTTI has finally arrived, and pave the way for a large number of applications.
One area that has benefited from the new RTTI is for sure the marshaled objects.

Marshaling is defined as follows:

“In computer science, marshalling (similar to serialization) is the process of
transforming the memory representation of an object to a data format suitable for
storage or transmission. It is typically used when data must be moved between
different parts of a computer program or from one program to another.
The opposite, or reverse, of marshalling is called unmarshalling (demarshalling) (similar to deserialization).”
–WikiPedia

In Delphi 2010 the process of serialization and deserialization is handled respectively by a Marshaller and an Unmarshaller.

The built-in format for the serialization of any Delphi object is JSON.
There are 2 main classes responsible for serializing objects into JSON, both present in the unit DBXJSONReflect:
– TJSONMarshal
– TJSONUnMarshal

Let’s say you have an object defined as follow:

type
  TKid = class
    FirstName: String;
    LastName: String;
    Age: Integer;
  end;

To serialize and deserialize an instance of TKid it requires the following steps:

var
  Mar: TJSONMarshal;  //Serializer
  UnMar: TJSONUnMarshal;  //UnSerializer
  Kid: TKid;  //The Object to serialize
  SerializedKid: TJSONObject;  //Serialized for of object
begin
  Mar := TJSONMarshal.Create(TJSONConverter.Create);
  try
    Kid := TKid.Create;
    try
      Kid.FirstName := 'Daniele';
      Kid.LastName := 'Teti';      
      Kid.Age := 29;      
      SerializedKid := Mar.Marshal(Kid) as TJSONObject;
    finally
      FreeAndNil(Kid);
    end;
  finally
    Mar.Free;
  end;
  //Output the JSON version of the Kid object
  WriteLn(SerializedKid.ToString);   
  // UnMarshalling Kid
  UnMar := TJSONUnMarshal.Create;
  try
    Kid := UnMar.UnMarshal(SerializedKid) as TKid;
    try
      //now kid is the same as before marshalling
      Assert(Kid.FirstName = 'Daniele');
      Assert(Kid.LastName = 'Teti');
      Assert(Kid.Age = 29);
    finally
      Kid.Free;
    end;
  finally
    UnMar.Free;
  end;
end;

Simple, isn’t it?
To access the JSON string that is our object, we must call the method ToString.
The JSON representation of this object SerializedKid can be saved to file,
sent to a remote server, used by a Web page from a web service, stored on a database or sent into space (!!!).
The Delphi application re-read the JSON string, you can recreate the object as it was at the time of serialization.
But anyone with a JSON parser can still read the data in our object, even non Delphi client.
These are the advantages of having used an open format and standard.

So far the simple part …
How serialize a field differently from the default?

Suppose we add the date of birth to our TKid:

type
  TKid = class
    FirstName: String;
    LastName: String;
    Age: Integer;
    BornDate: TDateTime;
  end; 

Serialize a TDateTime, localized and that I have in JSON string is a float, because for Delphi TDateTime is a decimal number.
If I read the data from another program Delphi, no problem, but if I wanted to read a script in JavaScript? or. NET? or Ruby?
Then I use a format “DATA” to understand, even for these languages.
The new engine provides the serialization too.
Is needed, however, to tell the Marshaller and UnMarsheller how to represent and reconstruct a particular
object field by two statements like the following:

//marshaller
Marshaller.RegisterConverter(TKid, 'BornDate', 
  function(Data: TObject; Field: string): string
  var
    ctx: TRttiContext; date : TDateTime;
  begin
    date := ctx.GetType(Data.ClassType).GetField(Field).GetValue(Data).AsType<TDateTime>;
    Result := FormatDateTime('yyyy-mm-dd hh:nn:ss', date);
  end);
  
//UnMarshaller
UnMarshaller.RegisterReverter(TKid, 'BornDate', 
  procedure(Data: TObject; Field: string; Arg: string)
  var
    ctx: TRttiContext;
    datetime:TDateTime;
  begin
    datetime := EncodeDateTime(StrToInt(Copy(Arg, 1, 4)), 
                               StrToInt(Copy(Arg, 6, 2)), 
                               StrToInt(Copy(Arg, 9, 2)), 
                               StrToInt(Copy(Arg, 12, 2)), 
                               StrToInt(Copy(Arg, 15, 2)), 
                               StrToInt(Copy(Arg, 18, 2)), 0);
    ctx.GetType(Data.ClassType).GetField(Field).SetValue(Data, datetime);
  end);

The anonymous method is called when the marshaller serializes the field ‘BornDate’ is called “Converter” while Unmarshaller anonymous method that calls when he has to reconstruct the object from the JSON string is the “Reverter”.
Thus serializing a TKid assure you that my object is readable both by Delphi from another language without loss of information.

But what happens when I have to serialize a complex type?

Suppose we extend TKid this:

type
  TTeenager = class(TKid)
    Phones: TStringList;
    constructor Create; virtual;
    destructor Destroy; virtual;
  end; 

We must define a Converter and a Reverter for the TStringList class.
We can do it this way:

var
  Marshaller: TJSONMarshal;
  UnMarshaller: TJSONUnMarshal;
  Teenager: TTeenager;
  Value, JSONTeenager: TJSONObject;
begin
  Marshaller := TJSONMarshal.Create(TJSONConverter.Create);
  try
    Marshaller.RegisterConverter(TTeenager, 'BornDate', 
      function(Data: TObject; Field: string): string
      var
        ctx: TRttiContext; date : TDateTime;
      begin
        date := ctx.GetType(Data.ClassType).GetField(Field).GetValue(Data).AsType<TDateTime>;
        Result := FormatDateTime('yyyy-mm-dd hh:nn:ss', date);
      end);
      
    Marshaller.RegisterConverter(TStringList, function(Data: TObject): TListOfStrings
                                              var
                                                i, count: integer;
                                              begin
                                                count := TStringList(Data).count;
                                                SetLength(Result, count);
                                                for i := 0 to count - 1 do
                                                  Result[i] := TStringList(Data)[i];
                                              end);  //TStringList Converter 
    Teenager := TTeenager.CreateAndInitialize;
    try
      Value := Marshaller.Marshal(Teenager) as TJSONObject;
    finally
      Teenager.Free;
    end;
  finally
    Marshaller.Free;
  end;
  // UnMarshalling Teenager
  UnMarshaller := TJSONUnMarshal.Create;
  try
    UnMarshaller.RegisterReverter(TTeenager, 'BornDate', 
      procedure(Data: TObject; Field: string; Arg: string)
      var
        ctx: TRttiContext;
        datetime: TDateTime;
      begin
        datetime := EncodeDateTime(StrToInt(Copy(Arg, 1, 4)), 
                                   StrToInt(Copy(Arg, 6, 2)), 
                                   StrToInt(Copy(Arg, 9, 2)), 
                                   StrToInt(Copy(Arg, 12, 2)), 
                                   StrToInt(Copy(Arg, 15, 2)), 
                                   StrToInt(Copy(Arg, 18, 2)), 0);
        ctx.GetType(Data.ClassType).GetField(Field).SetValue(Data, datetime);
      end);
    UnMarshaller.RegisterReverter(TStringList, function(Data: TListOfStrings): TObject
                                               var
                                                 StrList: TStringList;
                                                 Str: string;
                                               begin
                                                 StrList := TStringList.Create;
                                                 for Str in Data do
                                                   StrList.Add(Str);
                                                 Result := StrList;
                                               end);  //TStringList Reverter

    Teenager := UnMarshaller.Unmarshal(Value) as TTeenager;
    try
      Assert('Daniele' = Teenager.FirstName);
      Assert('Teti' = Teenager.LastName);
      Assert(29 = Teenager.Age);
      Assert(EncodeDate(1979, 11, 4) = Teenager.BornDate);
      Assert(3 = Teenager.Phones.Count);
      Assert('NUMBER01'=Teenager.Phones[0]);
      Assert('NUMBER02'=Teenager.Phones[1]);
      Assert('NUMBER03'=Teenager.Phones[2]);
    finally
      Teenager.Free;
    end;
  finally
    UnMarshaller.Free;
  end;
end;

There are different types of Converter and Reverter.
In the the DBXJSONReflect there are 8 types of converters:

  //Convert a field in an object array
  TObjectsConverter = reference to function(Data: TObject; Field: String): TListOfObjects;
  //Convert a field in a strings array
  TStringsConverter = reference to function(Data: TObject; Field: string): TListOfStrings;

  //Convert a type in an objects array
  TTypeObjectsConverter = reference to function(Data: TObject): TListOfObjects;
  //Convert a type in a strings array  
  TTypeStringsConverter = reference to function(Data: TObject): TListOfStrings;

  //Convert a field in an object
  TObjectConverter = reference to function(Data: TObject; Field: String): TObject;
  //Convert a field in a string  
  TStringConverter = reference to function(Data: TObject; Field: string): string;

  //Convert specified type in an object
  TTypeObjectConverter = reference to function(Data: TObject): TObject;
  //Convert specified type in a string  
  TTypeStringConverter = reference to function(Data: TObject): string;

Each of them deals with a particular conversion object representation in the final serialization, in our case we will use them to convert to JSON.

Also in the DBXJSONReflect unit are defined many “Reverter” dealing with retrieving
the serialized version of the data and use it to reconstruct the object previously serialized.
Because they are complementary to the Converter, I will not copy them here.

As a final example, we derive from TProgrammer by TTeenager adding a list of Laptops in the properties.

Is therefore necessary to introduce a new pair of Converter / Reverter.
In this example I have defined all the converter and reverter in another unit in
order to have more readable code:

type
  TLaptop = class
    Model: String;
    Price: Currency;
    constructor Create(AModel: String; APrice: Currency);
  end;
  TLaptops = TObjectList<TLaptop>;
  TProgrammer = class(TTeenager)
    Laptops: TLaptops;
    constructor Create; override;
    destructor Destroy; override;
    class function CreateAndInitialize: TProgrammer;
  end;
// Implementation code...
var
  Marshaller: TJSONMarshal;
  UnMarshaller: TJSONUnMarshal;
  Programmer: TProgrammer;
  Value, JSONProgrammer: TJSONObject;
begin
  Marshaller := TJSONMarshal.Create(TJSONConverter.Create);
  try
    Marshaller.RegisterConverter(TProgrammer, 'BornDate', ISODateTimeConverter);
    Marshaller.RegisterConverter(TStringList, StringListConverter);
    Marshaller.RegisterConverter(TProgrammer, 'Laptops', LaptopListConverter);
    Programmer := TProgrammer.CreateAndInitialize;
    try
      Value := Marshaller.Marshal(Programmer) as TJSONObject;
    finally
      Programmer.Free;
    end;

    // UnMarshalling Programmer
    UnMarshaller := TJSONUnMarshal.Create;
    try
      UnMarshaller.RegisterReverter(TProgrammer, 'BornDate', ISODateTimeReverter);
      UnMarshaller.RegisterReverter(TStringList, StringListReverter);
      UnMarshaller.RegisterReverter(TProgrammer, 'Laptops', LaptopListReverter);

      Programmer := UnMarshaller.Unmarshal(Value) as TProgrammer;
      try
        Assert('Daniele' = Programmer.FirstName);
        Assert('Teti' = Programmer.LastName);
        Assert(29 = Programmer.Age);
        Assert(EncodeDate(1979, 11, 4) = Programmer.BornDate);
        Assert(3 = Programmer.Phones.Count);
        Assert('NUMBER01' = Programmer.Phones[0]);
        Assert('NUMBER02' = Programmer.Phones[1]);
        Assert('NUMBER03' = Programmer.Phones[2]);
        Assert('HP Presario C700' = Programmer.Laptops[0].Model);
        Assert(1000 = Programmer.Laptops[0].Price);
        Assert('Toshiba Satellite Pro' = Programmer.Laptops[1].Model);
        Assert(800 = Programmer.Laptops[1].Price);
        Assert('IBM Travelmate 500' = Programmer.Laptops[2].Model);
        Assert(1300 = Programmer.Laptops[2].Price);
      finally
        Programmer.Free;
      end;
    finally
      UnMarshaller.Free;
    end;
  finally
    Marshaller.Free;
  end;
end;


Unit CustomConverter.pas contains all needed Converters/Reverts as anon methods.

unit CustomConverter;

interface

uses
  DBXJSONReflect,
  MyObjects; //Needed by converter and reverter for TLaptops

var
  ISODateTimeConverter: TStringConverter;
  ISODateTimeReverter: TStringReverter;

  StringListConverter: TTypeStringsConverter;
  StringListReverter: TTypeStringsReverter;

  LaptopListConverter: TObjectsConverter;
  LaptopListReverter: TObjectsReverter;

implementation

uses
  SysUtils, RTTI, DateUtils, Classes;

initialization

LaptopListConverter := function(Data: TObject; Field: String): TListOfObjects
var
  Laptops: TLaptops;
  i: integer;
begin
  Laptops := TProgrammer(Data).Laptops;
  SetLength(Result, Laptops.Count);
  if Laptops.Count > 0 then
    for I := 0 to Laptops.Count - 1 do
      Result[I] := Laptops[i];
end;


LaptopListReverter := procedure(Data: TObject; Field: String; Args: TListOfObjects)
var
  obj: TObject;
  Laptops: TLaptops;
  Laptop: TLaptop;
  i: integer;
begin
  Laptops := TProgrammer(Data).Laptops;
  Laptops.Clear;
  for obj in Args do
  begin
    laptop := obj as TLaptop;
    Laptops.Add(TLaptop.Create(laptop.Model, laptop.Price));
  end;
end;

StringListConverter := function(Data: TObject): TListOfStrings
var
  i, count: integer;
begin
  count := TStringList(Data).count;
  SetLength(Result, count);
  for i := 0 to count - 1 do
    Result[i] := TStringList(Data)[i];
end;


StringListReverter := function(Data: TListOfStrings): TObject
var
  StrList: TStringList;
  Str: string;
begin
  StrList := TStringList.Create;
  for Str in Data do
    StrList.Add(Str);
  Result := StrList;
end;

ISODateTimeConverter := function(Data: TObject; Field: string): string
var
  ctx: TRttiContext; date : TDateTime;
begin
  date := ctx.GetType(Data.ClassType).GetField(Field).GetValue(Data).AsType<TDateTime>;
  Result := FormatDateTime('yyyy-mm-dd hh:nn:ss', date);
end;

ISODateTimeReverter := procedure(Data: TObject; Field: string; Arg: string)
var
  ctx: TRttiContext;
  datetime :
  TDateTime;
begin
  datetime := EncodeDateTime(StrToInt(Copy(Arg, 1, 4)), StrToInt(Copy(Arg, 6, 2)), StrToInt(Copy(Arg, 9, 2)), StrToInt
      (Copy(Arg, 12, 2)), StrToInt(Copy(Arg, 15, 2)), StrToInt(Copy(Arg, 18, 2)), 0);
  ctx.GetType(Data.ClassType).GetField(Field).SetValue(Data, datetime);
end;

end.

Last hint…
Every serialization/unserialization process can create “warnings”.
Those warnings are collected into the “Warnings” property of the Ser/UnSer Object.

Conclusions
In this post I tried to introduce the basics of the new serialization engine in Delphi 2010.
During the next ITDevCon to be held in Italy next November 11.12, I’ll have a talk in which I will extensively talk about serialization and RTTI.
All interested smart developers are invited :-)

ITALIAN P.S.
Se qualche programmatore italiano volesse avere la versione in italiano di questo post può lasciare un commento e vedrò di accontentarlo :-)

You can find the DUnit project Source Code