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

Delphi 2010, Delphi Dependency Injection, Design Patterns, MVC, MVP, Uncategorized 4 Comments »

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:

  1. procedure TfrmCalculatorView.FormCreate(Sender: TObject);
  2. begin
  3.   //Link controls with related interface
  4.   IFirstOperand := TGUIEdit.Create(EditFirstOp);
  5.   ISecondOperand := TGUIEdit.Create(EditSecondOp);
  6.   ICalcResult := TGUIEdit.Create(EditResult);
  7.   IOperators := TGUISelectableList.Create(ComboOperators);
  8.   IError := TGUIEdit.Create(EditError);
  10.   //link view and presenter
  11.   FPresenter := TCalculatorPresenter.Create(Self); //<<– THIS IS THE BAD LINE
  12. 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.

  1. var
  2.   MainPresenter: ICalculatorPresenter;
  3.   CalculatorView: TForm;
  4. begin
  5.   Application.Initialize;
  6.   Application.MainFormOnTaskbar := True;
  8.   Application.CreateForm(TfrmCalculatorView, CalculatorView);  
  10.   MainPresenter := TCalculatorPresenter.Create(CalculatorView as ICalculatorView, TCalculatorService.Create);
  11.   //LETS START!
  12.   Application.Run;
  13. end.

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

  1. constructor TCalculatorPresenter.Create(CalculatorView: ICalculatorView;
  2.   CalculatorService: ICalculatorService);
  3. begin
  4.   inherited Create;
  5.   FCalculatorService := CalculatorService;
  6.   FCalculatorView := CalculatorView;
  7.   FCalculatorView.SetPresenter(self);
  8.   InitView;  //does the links
  9. 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.

  1.     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

DataSnap Filters Compendium, Delphi 2010 1 Comment »

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


Happy testing!

A simple Dependency Injection Container for Delphi

Delphi 2010, Delphi Dependency Injection, Design Patterns 10 Comments »

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.

  1. program Test01;
  4. uses
  5.   SysUtils,
  6.   DIContainer in '..\..\src\DIContainer.pas',
  7.   ServiceTestObjectsU in '..\..\UnitTest\ServiceTestObjectsU.pas';
  9. var
  10.   DIContainer: TDIContainer;
  11.   s1: TService1;
  12.   s2: TService2;
  13.   s3: TService3;
  14.   s6: TService6;
  15.   s7: TService7;
  16. begin
  17.   try
  18.     DIContainer := TDIContainer.Create;
  19.     try
  20.       // AddComponent with TClass with and   InitType = Singleton
  21.       DIContainer.AddComponent(TService1, TDIContainerInitType.Singleton);
  22.       // AddComponent with QualifiedName and InitType = Singleton
  23.       DIContainer.AddComponent('ServiceTestObjectsU.TService2',
  24.         TDIContainerInitType.Singleton);
  25.       // AddComponent with QualifiedName and InitType = CreateNewInstance
  26.       DIContainer.AddComponent('ServiceTestObjectsU.TService3',
  27.         TDIContainerInitType.CreateNewInstance);
  29.       // GetComponent with QualifiedName
  30.       s1 := DIContainer.GetComponent('ServiceTestObjectsU.TService1')
  31.         as TService1;
  32.       s1.Message := 'I''m the first message';
  33.       WriteLn(s1.Message);
  35.       // GetComponent with TClass
  36.       s2 := DIContainer.GetComponent(TService2) as TService2;
  37.       s2.Message := 'I''m the second message';
  38.       WriteLn(s2.Message);
  40.       // GetComponent with a dependent service (TService3 depends upon TService1 and TService2)
  41.       s3 := DIContainer.GetComponent('ServiceTestObjectsU.TService3')
  42.         as TService3;
  43.       WriteLn(s3.GetCompoundMessage);
  44.       // s3 is not created as Singleton, so after use it I must free it
  45.       s3.Free;
  47.       // AddComponent with QualifiedClassName, a custom initializer, an alias.
  48.       // Component will be created as singleton (single instance managed by Container)
  50.       DIContainer.AddComponent(DIContainerUtils.GetQualifiedClassName
  51.           (TService6),
  52.           function: TObject
  53.           begin
  54.             Result := TService6.Create(DIContainer.Get(TService1) as TService1,DIContainer.Get(TService1) as TService1);
  55.           end,
  56.           'srv6',
  57.         TDIContainerInitType.Singleton);
  59.       s6 := DIContainer.Get('srv6') as TService6;
  60.       WriteLn(s6.ToString);
  61.       s6 := DIContainer.Get('srv6') as TService6;
  62.       WriteLn(s6.ToString);
  64.       // AddComponent with QualifiedClassName, a custom initializer, an alias.
  65.       // Component will be created as singleton (single instance managed by Container)
  66.       DIContainer.AddComponent(DIContainerUtils.GetQualifiedClassName
  67.           (TService7),
  68.             function: TObject
  69.             begin
  70.               Result := TService7.Create(DIContainer.Get(TService1) as TService1,DIContainer.Get(TService1) as TService1);
  71.             end,
  72.             'srv7intf',
  73.           TDIContainerInitType.Singleton);
  75.       s7 := DIContainer.Get('srv7intf') as TService7;
  76.       WriteLn(s7.ToString);
  77.     finally
  78.       DIContainer.Free;
  79.     end;
  80.   except
  81.     on E: Exception do
  82.       WriteLn(E.ClassName, E.Message);
  83.   end;
  84.   readln;
  85. 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 4 Comments »

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

CodeGear, Delphi 2010, ITDevCon2009, Programming, Uncategorized 24 Comments »

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.

  1. program JsonTypes;
  3. uses
  4.   SysUtils,
  5.   IOUtils,
  6.   DBXJSON;
  7. var
  8.   Obj, RestoredObject, JSON: TJSONObject;
  9.   arr: TJSONArray;
  10. begin
  11.   json := TJSONObject.Create;
  12.   try
  13.     WriteLn('Empty JSON Object: ' + sLineBreak,json.ToString);
  14.     json.AddPair('FirstName', TJSONString.Create('Daniele'));
  15.     WriteLn('Simple JSON Object with property: ' + sLineBreak, json.ToString);
  16.     json.AddPair(TJSONPair.Create('LastName', 'Teti'));
  17.     WriteLn('Simple JSON Object with 2 property: ' + sLineBreak, json.ToString);
  18.     arr := TJSONArray.Create;
  19.     arr.Add(9871238712);
  20.     arr.Add('peter.parker@bittime.it');
  21.     arr.Add('Via Roma, 12');
  22.     json.AddPair(TJSONPair.Create('Contacts', arr));
  23.     WriteLn('JSON Object with a property array: ' + sLineBreak, json.ToString);
  24.     obj := TJSONObject.Create;
  25.     obj.AddPair(TJSONPair.Create('John','Doe'));
  26.     json.AddPair('MyUndefinedPersonObject', obj);
  27.     WriteLn('JSON Object with a nested Object: ' + sLineBreak,json.ToString);
  28.     Write('Writing JSON object to file [jsonfile]…');
  29.     TFile.WriteAllText('jsonfile',json.ToString);
  30.     WriteLn('DONE!');
  31.     RestoredObject := TJSONObject.ParseJSONValue(
  32.       TEncoding.ASCII.GetBytes(TFile.ReadAllText('jsonfile')),0
  33.       ) as TJSONObject;
  34.     WriteLn('Readed JSON object from file: ' + sLineBreak, RestoredObject.ToString);
  35.   finally
  36.     json.Free;  //Every contained object is automatically freed
  37.   end;
  38.   readln; //If you want to see the output while in Delphi IDE
  39. end.

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

My Speeches at ITDevCon 2009 (Italy, Verona)

Delphi 2010, bit Time Software 1 Comment »

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

DataSnap Filters Compendium, Delphi 2010, Delphi for Win32, Uncategorized 33 Comments »

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 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 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.

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.

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

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).

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 :-)

  1. == HASH FILTERS == 1000 iterations
  2. MD5             =  161ms (stream size:  8304 byte, filtered stream size:  4184 byte)
  3. MD4             =   99ms (stream size:  8304 byte, filtered stream size:  4184 byte)
  4. SHA1            =  145ms (stream size:  8304 byte, filtered stream size:  4192 byte)
  5. SHA512          =  344ms (stream size:  8304 byte, filtered stream size:  4280 byte)
  7. == CIPHER FILTERS == 1000 iterations
  8. Blowfish        =  898ms (stream size:  8304 byte, filtered stream size:  8304 byte)
  9. Rijndael        =  941ms (stream size:  8304 byte, filtered stream size:  8304 byte)
  10. 3TDES           = 1729ms (stream size:  8304 byte, filtered stream size:  8304 byte)
  11. 3DES            = 1757ms (stream size:  8304 byte, filtered stream size:  8304 byte)
  13. == COMPRESS FILTERS == 1000 iterations
  14. LZO             =   79ms (stream size:  8304 byte, filtered stream size:  1113 byte)
  15. 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

Delphi 2010, Programming, RTTI, Uncategorized 20 Comments »

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).”

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:

  1.   TKid = class
  2.     FirstName: String;
  3.     LastName: String;
  4.     Age: Integer;
  5.   end;

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

  1.   Mar: TJSONMarshal;  //Serializer
  2.   UnMar: TJSONUnMarshal;  //UnSerializer
  3.   Kid: TKid;  //The Object to serialize
  4.   SerializedKid: TJSONObject;  //Serialized for of object
  5. begin
  6.   Mar := TJSONMarshal.Create(TJSONConverter.Create);
  7.   try
  8.     Kid := TKid.Create;
  9.     try
  10.       Kid.FirstName := 'Daniele';
  11.       Kid.LastName := 'Teti';      
  12.       Kid.Age := 29;      
  13.       SerializedKid := Mar.Marshal(Kid) as TJSONObject;
  14.     finally
  15.       FreeAndNil(Kid);
  16.     end;
  17.   finally
  18.     Mar.Free;
  19.   end;
  20.   //Output the JSON version of the Kid object
  21.   WriteLn(SerializedKid.ToString);  
  22.   // UnMarshalling Kid
  23.   UnMar := TJSONUnMarshal.Create;
  24.   try
  25.     Kid := UnMar.UnMarshal(SerializedKid) as TKid;
  26.     try
  27.       //now kid is the same as before marshalling
  28.       Assert(Kid.FirstName = 'Daniele');
  29.       Assert(Kid.LastName = 'Teti');
  30.       Assert(Kid.Age = 29);
  31.     finally
  32.       Kid.Free;
  33.     end;
  34.   finally
  35.     UnMar.Free;
  36.   end;
  37. 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:

  1.   TKid = class
  2.     FirstName: String;
  3.     LastName: String;
  4.     Age: Integer;
  5.     BornDate: TDateTime;
  6.   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:

  1. Marshaller.RegisterConverter(TKid, 'BornDate',
  2.   function(Data: TObject; Field: string): string
  3.   var
  4.     ctx: TRttiContext; date : TDateTime;
  5.   begin
  6.     date := ctx.GetType(Data.ClassType).GetField(Field).GetValue(Data).AsType&lt;TDateTime&gt;;
  7.     Result := FormatDateTime('yyyy-mm-dd hh:nn:ss', date);
  8.   end);
  10. //UnMarshaller
  11. UnMarshaller.RegisterReverter(TKid, 'BornDate',
  12.   procedure(Data: TObject; Field: string; Arg: string)
  13.   var
  14.     ctx: TRttiContext;
  15.     datetime:TDateTime;
  16.   begin
  17.     datetime := EncodeDateTime(StrToInt(Copy(Arg, 1, 4)),
  18.                                StrToInt(Copy(Arg, 6, 2)),
  19.                                StrToInt(Copy(Arg, 9, 2)),
  20.                                StrToInt(Copy(Arg, 12, 2)),
  21.                                StrToInt(Copy(Arg, 15, 2)),
  22.                                StrToInt(Copy(Arg, 18, 2)), 0);
  23.     ctx.GetType(Data.ClassType).GetField(Field).SetValue(Data, datetime);
  24.   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:

  1.   TTeenager = class(TKid)
  2.     Phones: TStringList;
  3.     constructor Create; virtual;
  4.     destructor Destroy; virtual;
  5.   end;

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

  1.   Marshaller: TJSONMarshal;
  2.   UnMarshaller: TJSONUnMarshal;
  3.   Teenager: TTeenager;
  4.   Value, JSONTeenager: TJSONObject;
  5. begin
  6.   Marshaller := TJSONMarshal.Create(TJSONConverter.Create);
  7.   try
  8.     Marshaller.RegisterConverter(TTeenager, 'BornDate',
  9.       function(Data: TObject; Field: string): string
  10.       var
  11.         ctx: TRttiContext; date : TDateTime;
  12.       begin
  13.         date := ctx.GetType(Data.ClassType).GetField(Field).GetValue(Data).AsType&lt;TDateTime&gt;;
  14.         Result := FormatDateTime('yyyy-mm-dd hh:nn:ss', date);
  15.       end);
  17.     Marshaller.RegisterConverter(TStringList, function(Data: TObject): TListOfStrings
  18.                                               var
  19.                                                 i, count: integer;
  20.                                               begin
  21.                                                 count := TStringList(Data).count;
  22.                                                 SetLength(Result, count);
  23.                                                 for i := 0 to count - 1 do
  24.                                                   Result[i] := TStringList(Data)[i];
  25.                                               end);  //TStringList Converter
  26.     Teenager := TTeenager.CreateAndInitialize;
  27.     try
  28.       Value := Marshaller.Marshal(Teenager) as TJSONObject;
  29.     finally
  30.       Teenager.Free;
  31.     end;
  32.   finally
  33.     Marshaller.Free;
  34.   end;
  35.   // UnMarshalling Teenager
  36.   UnMarshaller := TJSONUnMarshal.Create;
  37.   try
  38.     UnMarshaller.RegisterReverter(TTeenager, 'BornDate',
  39.       procedure(Data: TObject; Field: string; Arg: string)
  40.       var
  41.         ctx: TRttiContext;
  42.         datetime: TDateTime;
  43.       begin
  44.         datetime := EncodeDateTime(StrToInt(Copy(Arg, 1, 4)),
  45.                                    StrToInt(Copy(Arg, 6, 2)),
  46.                                    StrToInt(Copy(Arg, 9, 2)),
  47.                                    StrToInt(Copy(Arg, 12, 2)),
  48.                                    StrToInt(Copy(Arg, 15, 2)),
  49.                                    StrToInt(Copy(Arg, 18, 2)), 0);
  50.         ctx.GetType(Data.ClassType).GetField(Field).SetValue(Data, datetime);
  51.       end);
  52.     UnMarshaller.RegisterReverter(TStringList, function(Data: TListOfStrings): TObject
  53.                                                var
  54.                                                  StrList: TStringList;
  55.                                                  Str: string;
  56.                                                begin
  57.                                                  StrList := TStringList.Create;
  58.                                                  for Str in Data do
  59.                                                    StrList.Add(Str);
  60.                                                  Result := StrList;
  61.                                                end);  //TStringList Reverter
  63.     Teenager := UnMarshaller.Unmarshal(Value) as TTeenager;
  64.     try
  65.       Assert('Daniele' = Teenager.FirstName);
  66.       Assert('Teti' = Teenager.LastName);
  67.       Assert(29 = Teenager.Age);
  68.       Assert(EncodeDate(1979, 11, 4) = Teenager.BornDate);
  69.       Assert(3 = Teenager.Phones.Count);
  70.       Assert('NUMBER01'=Teenager.Phones[0]);
  71.       Assert('NUMBER02'=Teenager.Phones[1]);
  72.       Assert('NUMBER03'=Teenager.Phones[2]);
  73.     finally
  74.       Teenager.Free;
  75.     end;
  76.   finally
  77.     UnMarshaller.Free;
  78.   end;
  79. end;

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

  1.   //Convert a field in an object array
  2.   TObjectsConverter = reference to function(Data: TObject; Field: String): TListOfObjects;
  3.   //Convert a field in a strings array
  4.   TStringsConverter = reference to function(Data: TObject; Field: string): TListOfStrings;
  6.   //Convert a type in an objects array
  7.   TTypeObjectsConverter = reference to function(Data: TObject): TListOfObjects;
  8.   //Convert a type in a strings array  
  9.   TTypeStringsConverter = reference to function(Data: TObject): TListOfStrings;
  11.   //Convert a field in an object
  12.   TObjectConverter = reference to function(Data: TObject; Field: String): TObject;
  13.   //Convert a field in a string  
  14.   TStringConverter = reference to function(Data: TObject; Field: string): string;
  16.   //Convert specified type in an object
  17.   TTypeObjectConverter = reference to function(Data: TObject): TObject;
  18.   //Convert specified type in a string  
  19.   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:

  1.   TLaptop = class
  2.     Model: String;
  3.     Price: Currency;
  4.     constructor Create(AModel: String; APrice: Currency);
  5.   end;
  6.   TLaptops = TObjectList&lt;TLaptop&gt;;
  7.   TProgrammer = class(TTeenager)
  8.     Laptops: TLaptops;
  9.     constructor Create; override;
  10.     destructor Destroy; override;
  11.     class function CreateAndInitialize: TProgrammer;
  12.   end;
  13. // Implementation code…
  14. var
  15.   Marshaller: TJSONMarshal;
  16.   UnMarshaller: TJSONUnMarshal;
  17.   Programmer: TProgrammer;
  18.   Value, JSONProgrammer: TJSONObject;
  19. begin
  20.   Marshaller := TJSONMarshal.Create(TJSONConverter.Create);
  21.   try
  22.     Marshaller.RegisterConverter(TProgrammer, 'BornDate', ISODateTimeConverter);
  23.     Marshaller.RegisterConverter(TStringList, StringListConverter);
  24.     Marshaller.RegisterConverter(TProgrammer, 'Laptops', LaptopListConverter);
  25.     Programmer := TProgrammer.CreateAndInitialize;
  26.     try
  27.       Value := Marshaller.Marshal(Programmer) as TJSONObject;
  28.     finally
  29.       Programmer.Free;
  30.     end;
  32.     // UnMarshalling Programmer
  33.     UnMarshaller := TJSONUnMarshal.Create;
  34.     try
  35.       UnMarshaller.RegisterReverter(TProgrammer, 'BornDate', ISODateTimeReverter);
  36.       UnMarshaller.RegisterReverter(TStringList, StringListReverter);
  37.       UnMarshaller.RegisterReverter(TProgrammer, 'Laptops', LaptopListReverter);
  39.       Programmer := UnMarshaller.Unmarshal(Value) as TProgrammer;
  40.       try
  41.         Assert('Daniele' = Programmer.FirstName);
  42.         Assert('Teti' = Programmer.LastName);
  43.         Assert(29 = Programmer.Age);
  44.         Assert(EncodeDate(1979, 11, 4) = Programmer.BornDate);
  45.         Assert(3 = Programmer.Phones.Count);
  46.         Assert('NUMBER01' = Programmer.Phones[0]);
  47.         Assert('NUMBER02' = Programmer.Phones[1]);
  48.         Assert('NUMBER03' = Programmer.Phones[2]);
  49.         Assert('HP Presario C700' = Programmer.Laptops[0].Model);
  50.         Assert(1000 = Programmer.Laptops[0].Price);
  51.         Assert('Toshiba Satellite Pro' = Programmer.Laptops[1].Model);
  52.         Assert(800 = Programmer.Laptops[1].Price);
  53.         Assert('IBM Travelmate 500' = Programmer.Laptops[2].Model);
  54.         Assert(1300 = Programmer.Laptops[2].Price);
  55.       finally
  56.         Programmer.Free;
  57.       end;
  58.     finally
  59.       UnMarshaller.Free;
  60.     end;
  61.   finally
  62.     Marshaller.Free;
  63.   end;
  64. end;

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

unit CustomConverter;
  2. interface
  4. uses
  5.   DBXJSONReflect,
  6.   MyObjects; //Needed by converter and reverter for TLaptops
  8. var
  9.   ISODateTimeConverter: TStringConverter;
  10.   ISODateTimeReverter: TStringReverter;
  12.   StringListConverter: TTypeStringsConverter;
  13.   StringListReverter: TTypeStringsReverter;
  15.   LaptopListConverter: TObjectsConverter;
  16.   LaptopListReverter: TObjectsReverter;
  18. implementation
  20. uses
  21.   SysUtils, RTTI, DateUtils, Classes;
  23. initialization
  25. LaptopListConverter := function(Data: TObject; Field: String): TListOfObjects
  26. var
  27.   Laptops: TLaptops;
  28.   i: integer;
  29. begin
  30.   Laptops := TProgrammer(Data).Laptops;
  31.   SetLength(Result, Laptops.Count);
  32.   if Laptops.Count &gt; 0 then
  33.     for I := 0 to Laptops.Count - 1 do
  34.       Result[I] := Laptops[i];
  35. end;
  38. LaptopListReverter := procedure(Data: TObject; Field: String; Args: TListOfObjects)
  39. var
  40.   obj: TObject;
  41.   Laptops: TLaptops;
  42.   Laptop: TLaptop;
  43.   i: integer;
  44. begin
  45.   Laptops := TProgrammer(Data).Laptops;
  46.   Laptops.Clear;
  47.   for obj in Args do
  48.   begin
  49.     laptop := obj as TLaptop;
  50.     Laptops.Add(TLaptop.Create(laptop.Model, laptop.Price));
  51.   end;
  52. end;
  54. StringListConverter := function(Data: TObject): TListOfStrings
  55. var
  56.   i, count: integer;
  57. begin
  58.   count := TStringList(Data).count;
  59.   SetLength(Result, count);
  60.   for i := 0 to count - 1 do
  61.     Result[i] := TStringList(Data)[i];
  62. end;
  65. StringListReverter := function(Data: TListOfStrings): TObject
  66. var
  67.   StrList: TStringList;
  68.   Str: string;
  69. begin
  70.   StrList := TStringList.Create;
  71.   for Str in Data do
  72.     StrList.Add(Str);
  73.   Result := StrList;
  74. end;
  76. ISODateTimeConverter := function(Data: TObject; Field: string): string
  77. var
  78.   ctx: TRttiContext; date : TDateTime;
  79. begin
  80.   date := ctx.GetType(Data.ClassType).GetField(Field).GetValue(Data).AsType&lt;TDateTime&gt;;
  81.   Result := FormatDateTime('yyyy-mm-dd hh:nn:ss', date);
  82. end;
  84. ISODateTimeReverter := procedure(Data: TObject; Field: string; Arg: string)
  85. var
  86.   ctx: TRttiContext;
  87.   datetime :
  88.   TDateTime;
  89. begin
  90.   datetime := EncodeDateTime(StrToInt(Copy(Arg, 1, 4)), StrToInt(Copy(Arg, 6, 2)), StrToInt(Copy(Arg, 9, 2)), StrToInt
  91.       (Copy(Arg, 12, 2)), StrToInt(Copy(Arg, 15, 2)), StrToInt(Copy(Arg, 18, 2)), 0);
  92.   ctx.GetType(Data.ClassType).GetField(Field).SetValue(Data, datetime);
  93. end;
  95. end.

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

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 :-)

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

WP Theme & Icons by N.Design Studio
Entries RSS Comments RSS Log in