DataSnap对象传递

    添加时间:2013-7-1 点击量:

    斗劲简单的办法:


    1.引用DBXJSON,  DBXJSONReflect


    假设有一个类:



    type
    
    TKid = class
    FirstName: String;
    LastName: String;
    Age: Integer;
    BornDate: TDateTime;
    class function CreateAndInitialize: TKid;
    public
    function SayHello(sName:string):String;
    end;


    { TKid }

    class function TKid.CreateAndInitialize: TKid;
    begin
    Result := TKid.Create;
    Result.FirstName := Daniele;
    Result.LastName := Teti;
    Result.Age := 29;
    Result.BornDate := encodedate(1979,11,4);
    end;

    function TKid.SayHello(sName: string): String;
    begin
    Result:=Hello,+sName;
    end;



    好了,我们就可以如许来调用(DUnit测试代码):



    procedure TCustomMarshallerTest.TestSimpleObject;
    
    var
    Marshaller: TJSONMarshal;
    UnMarshaller: TJSONUnMarshal;
    Kid: TKid;
    Value, JSONKid: TJSONObject;
    begin
    Marshaller :
    = TJSONMarshal.Create(TJSONConverter.Create);
    try
    Kid :
    = TKid.CreateAndInitialize;
    try
    Value :
    = Marshaller.Marshal(Kid) as TJSONObject;

    // Full qualified class name
    CheckEquals(
    MyObjects.TKid, Value.Get(0).JsonValue.Value);

    // Object ID
    CheckEquals(
    1, Value.Get(1).JsonValue.Value);

    // Reference to the object fields
    JSONKid :
    = Value.Get(2).JsonValue as TJSONObject;
    CheckEquals(
    FirstName, JSONKid.Get(0).JsonString.Value);
    CheckEquals(
    Daniele, JSONKid.Get(0).JsonValue.Value);

    CheckEquals(
    LastName, JSONKid.Get(1).JsonString.Value);
    CheckEquals(
    Teti, JSONKid.Get(1).JsonValue.Value);

    CheckEquals(
    Age, JSONKid.Get(2).JsonString.Value);
    CheckEquals(
    29, JSONKid.Get(2).JsonValue.Value);

    CheckEquals(
    BornDate, JSONKid.Get(3).JsonString.Value);
    CheckEquals(FloatToStr(EncodeDate(
    1979114)), JSONKid.Get(3).JsonValue.Value);

    finally
    Kid.Free;
    end;

    // UnMarshalling Kid
    UnMarshaller :
    = TJSONUnMarshal.Create;
    try
    Kid :
    = UnMarshaller.Unmarshal(Value) as TKid;
    try
    CheckEquals(
    Daniele, Kid.FirstName);
    CheckEquals(
    Teti, Kid.LastName);
    CheckEquals(
    29, Kid.Age);
    CheckEquals(EncodeDate(
    1979114), Kid.BornDate);

    CheckEquals(
    Hello,Garfield,Kid.SayHello(Garfield));
    finally
    Kid.Free;
    end;
    finally
    UnMarshaller.Free;
    end;
    finally
    Marshaller.Free;
    end;
    end;



    序列化只对于对象本身的传递可以,若是传递的对象引用了其他的对象实例则调用含有引用对象办法时会呈报错误。



    下面是收集上的一些参考材料:



    stackoverflow.com:


    How to return a record in a DataSnap method


    I wish to be able to declare a Data Snap method with the following signature


    type
    
    TLoginInfo
    =record
    Username
    :string;
    Password
    :string;
    LastLogged
    : DateTime;end;function GetLoginInfoconst UserId: Integer): TLoginInfo;

    When I try to call it it says that TLoginInfo is not well known.


    Answer:


    store the record into a stream and pass the stream to the DataSnap method


    //on server side


    function GetLoginInfoconst UserId: Integer): TStream;begin
    
    Result
    := TMemoryStream.Create;
    Result
    .Write loginRec SizeOfTLoginInfo
    Result
    .Seek0 TSeekOrigin.soBeginning);end;

    //on client side


    procedure TfrmMain.getLogInto sUser:string);var
    
    AStr
    : TStream;
    loginRec
    : TLoginInfo;begin// cycleConnection;with TMethodsClient.Create SQLConn.DBXConnectionFalsedobegin

    AStr
    := GetLoginInfo sUser );
    AStr
    .Read loginRec SizeOfTLoginInfo
    Free
    ;end;

    FreeAndNil
    AStr);end;


    http://blogs.embarcadero.com/adrian/2009/08/19/json-types-for-server-methods-in-datasnap-2010/



    JSON Types for Server Methods in DataSnap 2010



    I am very happy I was granted permission to talk about the new features present in Delphi 2010 concerning database area.


    DataSnap 2010 (DS2010) extended the list of server method parameter types with the full suite of JSON types.


    It is now possible to pass a TJSONObject instance for example a client process to a server as input or output parameter and receive a TJSONValue back as an output parameter.


    The unit where JSON types are implemented is DBXJSON. In the paragraphs below will be talking about creating JSON objects, writing server methods with JSON parameters and using JSON objects to marshal in and out user types.


    Creating JSON objects


    JSON objects are regular objects created out of types defined in DBXJSON unit. One can create such objects either instantiating specific types or through parsing of a byte array representing a JSON value according to specs.


    Types such as TJSONObject TJSONArray TJSONNumber TJString TJSONTrue TJSONFalse TJSONull are derived  TJSONValue and can be instantiated. TJSONPair is a type that can be only added to TJSONObject but it is not in itself a TJSONValue.


    The code below creates a TJSONObject with a {Hello:World} object:



    
    
    var
    obj: TJSONObject;
    ...
    begin
    ...
    obj := TJSONObject.Create;
    obj.AddPair(TJSONPair.Create(Hello, World));
    ...


    A JSON pair is formed by a string and a value. The TJSONPair has several constructors that will allow the creation of a pair less verbose. For example, in the example above is assumes that the value is actually a JSON string and creates the appropriate object.


    A JSON value can be also created a byte array using the two TJSONObject class functions ParseJSONValue. They both have similar signatures, one assumes that it has to parse the entire array to create a consistent JSON object while the second expects to use only a part of it for that.



    
    
    class function ParseJSONValue(const Data: TBytes; const Offset: Integer): TJSONValue; overload; static;
    class function ParseJSONValue(const Data: TBytes; const Offset: Integer; const Count: Integer): TJSONValue; overload; static;


    By default a JSON object assumes ownership of its elements. Freeing the root object will cascade the release of all JSON pairs and/or values forming that particular instance. Each JSON object has an Owned property that provides ownership hints when an object is destructed. The default value, true, provides the information to an eventual container that the instance can be released together with the container itself. All objects with a false property will be spared assuming that they are owned by other resources (data stores, parameters, fields). This was done to ease the construction of complex objects based on existing ones but owned by separate entities (save memory and CPU).


    Each object can be cloned through Clone function. This feature can be handy when server method input parameter need to be stored for longer than method execution itself.


    ToString provides a Unicode JSON representation. This should not be used as input for the JSON parser as its content representation is not parser friendly; use ToBytes instead. The following code snippet will demonstrate how to obtain a standard byte representation of a JSON value:



    
    
    SetLength(ToBytes, JSONValue.EstimatedByteSize);
    SetLength(ToBytes, JString.ToBytes(ToBytes, 0));


    Value property provides the Unicode representation for a TJString or a TJSONNumber but it has no use on other types.


    Server method with JSON parameters


    Using JSON parameter types for server methods is as natural as using them for any method or function; input, var, out and return parameters are accepted.


    The following server method uses all possible combination:



    
    
    function JSONPing(data: TJSONObject; var ok: TJSONValue): TJSONObject;


    The DS2010 owns those parameters hence they will be destroyed immediately after method execution.
    One cannot return an object that doesn’t own; here is where Clone method can be useful: if there is a need for any of the input parameters to be preserved outside the scope of the server method then it needs to be cloned.


    It may be possible to keep bits and pieces of them through Owned, but be advised that you are doing it at your own risk.


    Handling JSON objects over the client side


    Clients can invoke server methods by using the proxy code generated for the server methods or directly using DBXConnection.


    The proxy method signature is identical with the server method:



    
    
    function JSONPing(data: TJSONObject; var ok: TJSONValue): TJSONObject;


    The parameter life cycle is controlled by the proxy instance owner flag used for client creation:



    
    
    constructor Create(ADBXConnection: TDBXConnection; AInstanceOwner: Boolean); overload;


    When using the DBXConnection directly, the parameter setter function SetJSONValue accepts a Boolean flag that will control the eventual destruction of its instance upon execution.



    
    
    FJSONPingCommand.Parameters[0].Value.SetJSONValue(data, true);


    Marshaling user types into JSON objects


    DBXJSONReflect unit provides the tools needed to marshal all user types into and JSON values.


    The marshaling technology is implemented by TTypeMarshaller class is based on traversing user objects and emitting specialized events for each action; an example would be when a new object is about to be visited OnTypeStart is called. The events implementation is done through a specialization of TConverter generic class.


    The conversion is left at the latitude of the user; an user type can be marshaled into any representation such as XML or text or binary. Out of the box we provide JSON conversion throughTJSONConverter class that can be coupled with TJSONMarshal class.


    It is important to be able to restore the source content of the user object the marshaled data. Due to the fact that some support classes can be quite complex and some types are not well supported by current RTTI runtime converters can be added. We currently support two categories:



    • Field: when the field is encountered on a user type the provided converter is used instead of the default one

    • Type: when a field of that type is encountered the converter is used instead of the default one


    Each category has 4 types: convert the field value into another object, a string, an array of objects or an array of strings. For example a set or a record can be transformed on an array of strings.


    For each user-defined converter a reverter is required to restore the field value. For example, an array of strings can be restored into a record’s content or a set based on field’s name or type.


    To restore the user object the marshaled data are traversed this time only to generate a deep copy of the source data. Out of the box we provide TJSONUnMarshal that will traverse a JSON object and rebuild the user object.


    We will provide a code snippet that will illustrate the marshaling of user objects. The user object types are:



    
    
    TAddress = record
    FStreet: String;
    FCity: String;
    FCode: String;
    FCountry: String;
    FDescription: TStringList;
    end;

    TPerson = class
    private
    FName: string;
    FHeight: integer;
    FAddress: TAddress;
    FSex: char;
    FRetired: boolean;
    FQualifications: TStringList;
    FChildren: array of TPerson;
    FNumbers: set of 1..10;
    public
    constructor Create;
    destructor Destroy; override;

    procedure AddChild(kid: TPerson);
    end;


    We are using record, sets, string lists all combined to describe a TPerson with fields like name or children list.


    For this particular example we will consider the FNumbers field transient and we will not marshal it in order to illustrate the warnings that are generated during the process.


    The marshal classes used for the user objects and for the JSON equivalent form are declared and instantiated as such:



    
    
    var
    m: TJSONMarshal;
    unm: TJSONUnMarshal;
    ...
    begin
    m := TJSONMarshal.Create(TJSONConverter.Create);
    unm := TJSONUnMarshal.Create;
    ...


    The JSON marshal instance (m) is created with the out-of-the box JSON converter. To restore the user object TJSONUnMarshal instance (unm) is part of the unit.


    Dealing with special cases, such as string lists, arrays and records is done through three user defined converters:



    
    
    m.RegisterConverter(TPerson, FChildren, function(Data: TObject; Field: String): TListOfObjects
    var
    obj: TPerson;
    I: Integer;
    begin
    SetLength(Result, Length(TPerson(Data).FChildren));
    I := Low(Result);
    for obj in TPerson(Data).FChildren do
    begin
    Result[I] := obj;
    Inc(I);
    end;
    end);
    m.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);
    m.RegisterConverter(TPerson, FAddress, function(Data: TObject; Field: String): TListOfStrings
    var
    Person: TPerson;
    I: Integer;
    Count: Integer;
    begin
    Person := TPerson(Data);
    if Person.FAddress.FDescription <> nil then
    Count := Person.FAddress.FDescription.Count
    else
    Count := 0;
    SetLength(Result, Count + 4);
    Result[0] := Person.FAddress.FStreet;
    Result[1] := Person.FAddress.FCity;
    Result[2] := Person.FAddress.FCode;
    Result[3] := Person.FAddress.FCountry;
    for I := 0 to Count - 1 do
    Result[4+I] := Person.FAddress.FDescription[I];
    end);


    The first converter is a field converter that is used in the context of marshaling a TPerson’s FChildren field. The list of children objects is returned as an array; these objects will be individually marshaled later.


    The second converter is a type converter and it will be used for all TStringList fields; it simply returns an array of strings.


    Finally, the last converter is again a field converter that deals with a record type; it transforms it into an array of strings with the record’s FDescription content is appended to it.


    In a similar manner, the reverters are registered with the unm instance:



    
    
    unm.RegisterReverter(TPerson, FChildren, procedure(Data: TObject; Field: String; Args: TListOfObjects)
    var
    obj: TObject;
    I: Integer;
    begin
    SetLength(TPerson(Data).FChildren, Length(Args));
    I := Low(TPerson(Data).FChildren);
    for obj in Args do
    begin
    TPerson(Data).FChildren[I] := TPerson(obj);
    Inc(I);
    end
    end);
    unm.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);
    unm.RegisterReverter(TPerson, FAddress, procedure(Data: TObject; Field: String; Args: TListOfStrings)
    var
    Person: TPerson;
    I: Integer;
    begin
    Person := TPerson(Data);
    if Person.FAddress.FDescription <> nil then
    Person.FAddress.FDescription.Clear
    else if Length(Args) > 4 then
    Person.FAddress.FDescription := TStringList.Create;

    Person.FAddress.FStreet := Args[0];
    Person.FAddress.FCity := Args[1];
    Person.FAddress.FCode := Args[2];
    Person.FAddress.FCountry := args[3];
    for I := 4 to Length(Args) - 1 do
    Person.FAddress.FDescription.Add(Args[I]);
    end);


    Let’s instantiate a couple of user objects and see what the outcome is:



    
    
    var
    ...
    person, kid: TPerson;
    obj: TObject;
    ...
    begin
    ...
    person := TPerson.Create;
    person.FName := John Doe;
    person.FHeight := 167;
    person.FSex := M;
    person.FRetired := false;
    person.FQualifications.Add(Delphi developer);
    person.FQualifications.Add(Chess player);
    person.FAddress.FStreet := 62 Peter St;
    person.FAddress.FCity := TO;
    person.FAddress.FCode := 1334566;
    person.FAddress.FDescription.Add(Driving directions: exit 84 on highway 66);
    person.FAddress.FDescription.Add(Entry code: 31415);

    kid := TPerson.Create;
    kid.FName := Jane Doe;
    person.AddChild(kid);

    v := m.Marshal(person);
    memo1.Lines.Add(v.ToString);

    obj := unm.Unmarshal(v);
    assert( obj is TPerson );
    assert( TPerson(obj).FAddress.FDescription nil );
    assert( TPerson(obj).FAddress.FDescription.Count > 0 );
    assert( TPerson(obj).FAddress.FDescription[0] = Driving directions: exit 84 on highway 66, Description);
    ...
    end;


    The output is displayed into the form’s memo1 component. For convenience and ease of use we formatted a little and presented it below:



    
    
    {type:Converter.TPerson,
    id:1,
    fields:{FName:John Doe,
    FHeight:167,
    FAddress:[62 Peter St,
    TO,
    1334566,

    Driving directions: exit 84 on highway 66,
    Entry code: 31415],
    FSex:M,
    FRetired:false,
    FQualifications:[Delphi developer,
    Chess player],
    FChildren:[{type:Converter.TPerson,
    id:2,
    fields:{FName:Jane Doe,
    FHeight:0,
    FAddress:[,,,],
    FSex:,
    FRetired:false,
    FQualifications:[],
    FChildren:[]}}]}}


    Transient fields will be signaled as warnings. You can check if the JSON representation contains 100% of the user data by checking the HasWarnings function of the marshaller instance.



    
    
    ...
    if m.HasWarnings then
    for I := 0 to Length(m.Warnings) - 1 do
    memo1.Lines.Add(Transient: + m.Warnings[I].FieldName);
    ...


    Conclusion


    JSON introduction to DataSnap 2010 opens this technology to



    • User Objects: server side data can now be accessed by client processes

    • Business Logic: server side rules interact with client side through callbacks

    • Third party applications: Web based applications based on JavaScript can smoothly interact with server methods.


    We illustrated how aspects of the goals above can be implemented using JSON types. They are easy to handle, extensible and they can be used to stream structured data to third party applications.


    For more information please visit Embarcadero RAD Studio 2010


    JSON reflection code can be seen here. 


     


    http://www.danieleteti.it/2009/09/01/custom-marshallingunmarshalling-in-delphi-2010/


     




    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 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 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 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 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 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 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&lt;TDateTime&gt;;
        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 the JSON string is the “Reverter”.
    Thus serializing a TKid assure you that my object is readable both by Delphi 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&lt;TDateTime&gt;;
            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 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&lt;TLaptop&gt;;
      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 &gt; 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&lt;TDateTime&gt;;
      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



     


    我俩之间有着强烈的吸引力。短短几个小时后,我俩已经明白:我们的心是一个整体的两半,我俩的心灵是孪生兄妹,是知己。她让我感到更有活力,更完美,更幸福。即使她不在我身边,我依然还是感到幸福,因为她总是以这样或者那样的方式出现在我心头。——恩里克·巴里奥斯《爱的文明》
    分享到: