2011年10月26日星期三

datasnap的进阶 传递自定义类型的类

前面我写了一个返回自定义类的例子,将要返回的类marshal后装进TJSONArray,今天看来真是多余,因为datasnap的对自定义类的marshal做得很自动,直接返回就行。
前面我定义了
type
  TPerson = class
    FirstName: string;
    LastName: string;
    Age: Integer;
  end;
  TPersonArray = array of TPerson; 
然后写了服务方法
function TServerMethods1.GetPersons: TJSONArray; 
这样写没问题,如果直接写成
function TServerMethods1.GetPersons: TPersonArray;
是不可以的,因为TPersonArray不是类,它的TTypeInfo的TypeKind不是tkClass而是tkDynArray,目前是不datasnap支持的。
于是可以写一个集合类
  TPersonCollection = class
  private
    FPersons: TArray;
  public
    destructor Destroy; override; //别忘了free掉元素
    property Persons: TArray read FPersons write FPersons;
  end;
然后写服务方法
function TServerMethods1.GetPersons: TPersonCollection;
这样就可以了,服务器端可以直接写,客户端可以直接读,不用自己去marshal和unmarshal了。

那是不是说,我们的Field只要封到类里就一定ok呢,不是的。
将上面的类修改为如下
  TPerson = class
    FirstName: string;
    LastName: string;
    Age: Integer;
    Birthday: TDateTime;
    Memo: TStringList;
  end;

TDateTime其实是一个double类型,在datasnap里面是被支持的,TStringList却不行了。但是如果客户端是其他语言REST来的,TDataTime在客户端就难被解释了。怎么办呢
其实Datasnap里,我们可以自己定义marshal的规则。
XE2里面,有两个类TConverterEvent给编码,和TReverterEvent来解码。给EMBT封装后,用法也很简单。
var
  DateTimeDecodeFunc: TStringReverter;
  DateTimeEncodeFunc: TStringConverter;
procedure RegisterReverterConverters;
var
  decode: TReverterEvent;
  encode: TConverterEvent;
begin
  DateTimeEncodeFunc := function(Data: TObject; Field: string): string
    var
      ctx: TRttiContext;
      date: TDateTime;
    begin
      date := ctx.GetType(Data.ClassType).GetField(Field).GetValue(Data).AsType;
      Result := FormatDateTime('yyyy-mm-dd hh:nn:ss', date);
    end;
  DateTimeDecodeFunc := procedure(Data: TObject; Field: string; Arg: string)
    var
      ctx: TRttiContext;
      datetime: TDateTime;
    begin
      datetime := EncodeDateTime(StrToInt(Copy(Arg, 1, 4)), //yyyy
        StrToInt(Copy(Arg, 6, 2)), //mm
        StrToInt(Copy(Arg, 9, 2)), //dd
        StrToInt(Copy(Arg, 12, 2)),//hh
        StrToInt(Copy(Arg, 15, 2)),//nn
        StrToInt(Copy(Arg, 18, 2)),//ss
        0);
      ctx.GetType(Data.ClassType).GetField(Field).SetValue(Data, datetime);
    end;

  decode := TReverterEvent.Create(TPerson, 'Birthday');
  decode.StringReverter := DateTimeDecodeFunc;
  TJSONConverters.AddReverter(R);

  encode := TConverterEvent.Create(TPerson, 'Birthday');
  encode.StringConverter := DateTimeEncodeFunc;
  TJSONConverters.AddConverter(encode);
end;
简单的说,就是定义转换规则后,加给TJSONConverters,让TJSONConverters知道要这么转换来转换回去。 至于TStringList的转换,道理明白了,所以也一样可以写了。Delphi2010的DataSnap可以看这里。 转换的一些普通的函数,EMBT已经提供得有。在Data.DBXJSONReflect单元里,能找到
/// Converts a TStringList into a TSerStringList
function StringListConverter(Data: TObject): TObject;
/// Reverts a TSerStringList into a TStringList
function StringListReverter(Ser: TObject): TObject;
/// converts the pair list of a JSON Object into a serializable structure
function JSONObjectPairListConverter(Data: TObject; Field: String): TListOfObjects;
function JSONArrayElementsConverter(Data: TObject; Field: String): TListOfObjects;
procedure JSONObjectPairListReverter(Data: TObject; Field: String; Args: TListOfObjects);
procedure JSONArrayElementsReverter(Data: TObject; Field: String; Args: TListOfObjects);
除了这个方法,XE2里面另外还用到了描述类,来自动告诉JSON怎么转换。
将上面的TPerson修改为
 TPerson = class
    FirstName: string;
    LastName: string;
    Age: Integer;
    [JSONReflect(ctString, rtString, TDateTimeInterceptor, nil, True)] 
    Birthday: TDateTime;
    Memo: TStringList;
  end;
给Birthday增加描述,JSONReflect是个描述类,继承自TCustomAttribute。
看JSONReflect的代码
 
   constructor Create(ConverterType: TConverterType;
      ReverterType: TReverterType; InterceptorType: TClass = nil;
      PopulationCustomizerType: TClass = nil;
      IsMarshalOwned: boolean = false); overload;

  TConverterType = (ctObjects, ctStrings, ctTypeObjects, ctTypeStrings,
    ctObject, ctString, ctTypeObject, ctTypeString); 
  TReverterType = (rtObjects, rtStrings, rtTypeObjects, rtTypeStrings, rtObject,
    rtString, rtTypeObject, rtTypeString);
这几种类型
顾名思义,
第一个参数是表示进去的数据类型,
第二个是出来的类型,
第三个是执行转换的类,必须从TJSONInterceptor继承
第四个先不管,必须从TJSONPopulationCustomizer继承
第五个表示了是否拥有创建出来的类

目前只管写第3个执行转换的类。由于我们只是要转字符串,所以只覆盖字符的转换就行。
  TDateTimeInterceptor = class(TJSONInterceptor)
  public
    function StringConverter(Data: TObject; Field: string): string; override;
    procedure StringReverter(Data: TObject; Field: string; Arg: string); override;
  end;
怎么转换,和上面的一样的了。我是觉得这个方法比上面的法子好。

没有评论:

发表评论