Utilizando generics para transformação genérica de Objetos em DataSnap 2010
No meu post anterior “DataSnap 2010 – Enviando e Recebendo objetos” mostrei como transferir objetos através de JSON objects, nos comentários relacionados a este post Rafael Soares fez uma interessante observação com relação a necessidade de se ter que escrever em toda a classes os métodos de conversão de JSON para TObject e TObject para JSON. Na verdade isso não é necessário, você pode utilizar generics e assim ter apenas dois métodos de conversão para qualquer classe.
O exemplo utilizado no post anterior o qual os fontes foram disponibilizados para download, traz a classe TCustomer como exemplo e implementa os método JSONtoCustomer e CustomerToJSON, este fazem referência direta a classe TCustomer, assim sendo fiz algumas modificações onde nos permitirá utilizar estes dois métodos para qualquer classe, primeiro os métodos foram renomeados para JSONtoObject e ObjectToJSON e adicionados a uma nova classe chamada TBaseObject, este métodos foram definidos como class function, desta forma não precisamos instanciar o objeto. Agora TCustomer passa a herdar desta, assim sendo qualquer objeto terá este método implemetado.
Os métodos da classe TBaseObject agora definem <T> como parâmetro de entrada e retorno, ou seja, o tipo que for definido durante a utilização do método é o que será considerado pelo método, aqui estamos utilizando Generics.
unit BaseObject; interface uses DBXJSON, DBXJSONReflect; type TBaseObject = class public { public declarations } class function ObjectToJSON<T : class>(myObject: T): TJSONValue; class function JSONToObject<T : class>(json: TJSONValue): T; end; implementation { TBaseObject } class function TBaseObject.JSONToObject<T>(json: TJSONValue): T; var unm: TJSONUnMarshal; begin if json is TJSONNull then exit(nil); unm := TJSONUnMarshal.Create; try exit(T(unm.Unmarshal(json))) finally unm.Free; end; end; class function TBaseObject.ObjectToJSON<T>(myObject: T): TJSONValue; var m: TJSONMarshal; begin if Assigned(myObject) then begin m := TJSONMarshal.Create(TJSONConverter.Create); try exit(m.Marshal(myObject)); finally m.Free; end; end else exit(TJSONNull.Create); end; end.
Já a classe TCustomer herda de TBaseObject.
unit Customer; interface uses DBXJSON, DBXJSONReflect, SysUtils, BaseObject; type TMaritalStatus = (msMarried, msEngaged, msEligible); TCustomer = class(TBaseObject) private FName: string; FAge: integer; FMaritalStatus: TMaritalStatus; public property Name: string read FName write FName; property Age: integer read FAge write FAge; property MaritalStatus: TMaritalStatus read FMaritalStatus write FMaritalStatus; function toString : string;override; end;
Agora todos os Server Methods da classe TDSServerMethods passam a utilizar os métodos de conversão de objetos especificando qual o tipo que deverá ser utilizado. O exemplo abaixo retorna uma lista de TJSONArray, cada elemento adicionado ao Array usa a sintaxe TCustomer.ObjectToJSON<TCustomer>.
<TCustomer> define que o tipo do Objeto que está sendo adicionado ao Array.
function TDSServerMethods.ListofCustomer: TJSONArray; var i: Integer; myCustomer: TCustomer; begin Result := TJSONArray.Create; for i := 0 to 19 do begin myCustomer := GetCustomer; myCustomer.Name := 'Customer ' + IntToStr(i); myCustomer.Age := i; Result.AddElement(myCustomer.ObjectToJSON<TCustomer>(myCustomer)); end; end;
Para ler o retorno no lado Client utilizamos o mesmo princípio com Generics, onde cada item é transformado de JSON para TCustomer conforme o exemplo abaixo:
var proxy: TDSServerMethodsClient; mySingleCustomer: TCustomer; allCustomers: TJSONArray; i: Integer; begin proxy := nil; try proxy := TDSServerMethodsClient.Create (DMClientContainer.MyDSServer.DBXConnection); allCustomers := proxy.ListofCustomer; for i := 0 to allCustomers.Size - 1 do begin mySingleCustomer := TCustomer.JSONToObject<TCustomer>(allCustomers.Get(i)); MMLog.Lines.Add(mySingleCustomer.ToString); mySingleCustomer.Free; end; finally proxy.Free; end;
Vimos aqui como Generics nos auxilia na transformação de objetos em DataSnap.
O exemplo atualizado está disponível no CodeCentral – Download do código fonte
Olá, sempre ótimos os seus post, mas neste caso tenho uma duvida como o Marshal se comporta para um objeto com uma lista interna, ou como fazer um objeto que o Marshal consiga funcionar a contento?
Oi Fabio, obrigado.
Se você tem uma lista simples (Array), irá usar passar um JSONArray. De uma lida neste outro post http://www.andreanolanusse.com/pt/datasnap-2010-enviando-e-recebendo-array-de-strings-numbers-e-outros-tipos/
Olá Adreano, muito interessante o seu post, mas completando a pergunta do Fabio, se por ventura a lista for uma lista de objetos, o que devo fazer?
Andreano, boa tarde…
Supomos que eu queira trabalhar com INTERFACE (IBaseObject) no lugar da classe. Neste caso como utilizar Generics com INTERFACE?
Oi Jose Abilio,
Da uma lida neste artigo http://www.andreanolanusse.com/pt/datasnap-2010-enviando-e-recebendo-objetos/
Oi Hugo não seria diferente, da uma lida nesse artigo aqui do meu amigo Malcolm Groves http://www.malcolmgroves.com/blog/?p=420
Andreano, pensando em uma conexao remota utilizando DataSnap, onde meu servidor fica em outro local e acesso meus metodos online, queria saber se a velocidade na hora de trazer uma lista de clientes por exemplo é maior que utilizando o ClientDataSet?
Olá Claudio,
Eu diria que não seria percepitiva a diferente, se você for usar só server method e deixar o clientdataset de lado, vai perder uma produtividade enorme no desenvolvimento e todos os benefícios que o ClientDataSet traz para o desenvolvimento RAD.
Tem que prevalecer o bom senso, para o que seja dataware recomendo manter o ClientDataSet.
Andreano, tenho a seguinte duvida, trabalhando em um ambiente datasnap vou ter meu SqlDataSets e Provider no lado servidor e meus ClientDataSets no lado cliente, estou me perdendo um pouco na hora de trabalhar desta maneira.
Como seria uma boa pratica para esse metodo de trabalho?
Oi Claudio,
Perdendo o que? Só vejo benefícios utilizando SqlDataSet + Provider + ClientDataSet, esta é a melhor forma de se trabalhar de forma RAD.
Andreano, na verdade tambem nao tenho duvidas sobre os beneficios, na verdade minha duvida é no processo mesmo.
Acredito que seja assim; tenho uma tabela de clientes e uma de produto. Acho que a melhor solucao seria um ServerMethod para cada tabela.
No lado servidor ficaria assim:
function TDSServer.ListarClientesporCodigo(pCodCli: string): Boolean;
begin
QryCadCli.Active := False;
QryCadCli.CommandText := ‘Select * From CADCLI Where CODCLI = ‘+
QuotedStr(pCodCli);
QryCadCli.Active := True;
end;
E no lado cliente ficaria assim:
procedure TFrmCliente.Button5Click(Sender: TObject);
var
proxy : TDSServerClient;
begin
proxy := TDSServerClient.Create(DSServerCliente.Conexao.DBXConnection);
proxy.ListarClientesporCodigo(Edit1.Text);
DSServerCliente.CdsCadCli.Active := True;
end;
Minha duvida é se esse processo esta correto.
Heheheheeh, Andreano, estava procurando EXATAMENTE como converter aquela class do post “DataSnap 2010 – Enviando e Recebendo objetos”, em generic, ou seja me deus mastigado, brigadão.
Andreano minha dúvida é como serializar o Delta ClientDataSet que é um OleVariant. Fiz uma classe TDeltaPack com o Delta e o ProviderName. Tenho que passar uma array disso para o servidor de aplicação consigo passar o array o ProviderName que é string mas o Delta não. Como devo proceder isso é fundamental para as minhas aplicações.
Esse recurso de transferência de objetos com o JSON é extremamente interessante.
Mas, se existe uma forma de retornarmos uma classe qualquer através do Server Methods, sendo ela descendente de TObject, qual seria a real necessidade dessa nova implementação?
Desculpe pela pergunta mas, o JSON se torna um padrão mais correto e ideal para a transferência de objetos ou apenas mais uma possibilidade?
Blog nota 10!
Olá Andreano. Parabéns pelo post. Venho aqui esclarecer a seguinte dúvida: tentei usar o método JSONToObject diretamente através da classe TBaseObject, pois o método é estático. Porém dá um “Internal Error” ao tentar fazer o UnMarshal. Observei esse erro, pois as minhas classes de negócio já herdam de outras, então tive que implementar chamando diretamente TBaseObject.JSONToObject. Você poderia me explicar pq está dando esse erro? Obrigado.
Andreano, acabei de conseguir contornar esse problema. No método JSONToObject, adicionei a constraint “constructor” e funcionou tranquilo. Ficou assim: class function JSONToObject(json: TJSONValue): T;
Agora estou conseguindo transformar as mensagens JSON em objetos de negócio assim: obj := TBaseObject.JSONToObject;
Porém, mudei o nome da classe TBaseObject para TFuncoesJSON. Acredito que assim traduz melhor o real objetivo dessa classe.
Valeu.