Onde mesmo esta instalado o servidor Datasnap…. gostaria de descobrir a configuração do servidor:  local (ip) onde (porta) como (path)…

Olhando como o Indy-10 trabalha – não é tão intuitivo em se tratando de broadcast – então é preciso trabalhar um pouco.

Utilizar   TIdUDPServer – No Indy-10 o TIdUDPClient não obtive sucesso em pegar o retorno com broadcast… isto mudou o rumo da implementação – passei a pensar em montar DOIS servidores diferentes, um para o Servidor – outro para o Cliente.

Ver Código da Classe:  TIdZeroConfServer     e    TIdZeroConfClient

Projetos exemplos para Servidor e Cliente

Implementação

  1. No servidor datasnap preparar para receber o pedido do cliente solicitando os dados de configuração do servidor.

    [code lang=”pascal”]

    procedure TForm1.FormCreate(Sender: TObject);
    begin
    FServer := TIdHTTPWebBrokerBridge.Create(Self);
    // criar o servidor zeroConf
    // ———————————————————————-
    FZeroConf := TIdZeroConfServer.create(self);
    end;

    procedure TForm1.ButtonStopClick(Sender: TObject);
    begin
    TerminateThreads;
    FServer.Active := False;
    FServer.Bindings.Clear;
    // parar o servidor broadcast
    // ———————————————————————-
    FZeroConf.active := false;
    end;

    procedure TForm1.StartServer;
    begin
    if not FServer.Active then
    begin
    FServer.Bindings.Clear;
    FServer.DefaultPort := StrToInt(EditPort.Text);
    FServer.Active := True;
    // configura o ZeroConf
    // ——————————————————————–
    FZeroConf.active := false;
    FZeroConf.AppDefaultPort := FServer.DefaultPort; // Porta do servidor Datasnap
    FZeroConf.AppDefaultHost := FZeroConf.LocalHost; // IP de onde se encontra o Servidor da Aplicação Datasnap
    FZeroConf.AppDefaultPath :=’/’; // path base do servidor
    FZeroConf.active := true; // ativar o servidors
    end;
    end;

    [/code]

  2. Implementar no cliente Datasnap:

    [code lang=”pascal”]
    procedure TForm4.FormCreate(Sender: TObject);
    begin

    // incia o cliente
    // ————————————————-
    FZeroConfClient := TIdZeroConfClient.create(self);
    FZeroConfClient.OnResponseEvent := DoReceberDados;

    end;

    procedure TForm4.Button1Click(Sender: TObject);
    begin
    if not FZeroConfClient.active then
    FZeroConfClient.active := true;
    Memo1.Lines.Add(‘Envia comando de procurar servidor (‘+FormatdateTime(‘hh:mm:ss’,now)+’)’);
    FZeroConfClient.BroadcastIP := ”;//’192.168.56.1′;
    FZeroConfClient.Send;
    end;

    procedure TForm4.DoReceberDados(Sender: TObject; AMessage: String);
    begin
    // AMessage – retorna o JSON com os dados do servidor
    Memo1.Lines.Add(‘Resposta(‘+FormatdateTime(‘hh:mm:ss’,now)+’):’+AMessage);
    Memo1.Lines.Add(”);
    FZeroConfClient.Active := false; // desliga
    end;

    [/code]

 

Como funciona a mecânica

Quando iniciar o servidor  TIdZeroConfServer, ele criar um servidor UDP que fica esperando um broadcast na porta 53330 (configurável) ao ativar o ZeroConf passar os parametros do servidor Datasnap que será utilizado para responder as solitações dos clientes;

Do lado do cliente, ao ativar o TidZeroConfClient, ele criar uma escuta na porta 53331 e envia (send) comando solicitando configuração do servidor… recebe a resposta no evento – DoReceberDados(…);

Formato da Resposta

A reposta é um JSON:   {“service”:”ZeroConf”,”command”:”response”,”payload”:”yyyy-dd-mm hh:mm:ss”,”source”:”ip do servidor”,”host”;”ip onde o datasnap esta respondendo”,”port”:”porta do datasnap”,”path”:”caminho http”}

 

Cuidados/Limitações

Alguns firewall tendem a bloquear mensagem de broadcast, já que não é visto com bons olhos pelos gerenciadores de rede.

Usando broadcast por UDP, o pacote circula somente na rede local – não saindo para outras redes.

Multiplos aplicativos tentando utilizar a mesma porta… alterar para utilizar portas diferentes para aplicativos diferentes – provavelmente será necessário tratar as exceções para os casos de tentativa de abrir portas que  estão em uso.

 

 

 

 

Por algum tempo não dei muita atenção para a RTTI. Tudo era muito trabalhoso. Quando cheguei na família XE notei que as coisa tinham mudado bastante, então passei a fazer uso de umas coisas aqui.. outras ali… quando nem tinha me dado conta as coisas estavam ficando sérias.

RTTI é uma ferramenta poderosa, mas dá trabalho. Gostaria de simplificar um pouco as coisa para poder usar com mais frequência e com mais segurança.

Depois de várias tentativas concluí que o caminha mais rápido seria usar Class Helper para entregar ao TObject suporte mais facilitado para as chamadas RTTI.

 

[code lang=”pascal”]
TObjectHelper = class helper for TObject
….
// RTTI
property Properties[AName: string]: TValue read GetProperties
write SetProperties;
property Fields[AName: string]: TValue read GetFields write SetFields;
property Methods[AName: String]: TRttiMethod read GetMethods;
function HasAttribute(aMethod: TRttiMethod;
attribClass: TCustomAttributeClass): Boolean;
function InvokeAttribute(attribClass: TCustomAttributeClass;
params: array of TValue): Boolean;
function InvokeMethod(AName: string; params: array of TValue): Boolean;

end;

[/code]

Ver classe completa: RTTI Class Helper
* alguns métodos foram alterados para resolver conflitos.
 

Exemplo:

 

[code]
{$R *.dfm}
uses System.Classes.helper, System.TypInfo;

procedure TForm3.Button1Click(Sender: TObject);
begin
Button1.GetPropertiesList( ListBox1.Items ); // pega uma lista de properiedades do Button1
edit2.Text := Button1.Properties[‘Caption’].AsString; // pega o valor da propriedade caption
end;

procedure TForm3.Button2Click(Sender: TObject);
begin
button1.Properties[ ‘Caption’ ] := edit2.Text; // altera a proprieda do Caption
end;

procedure TForm3.Button3Click(Sender: TObject);
begin
button1.GetFieldsList( ListBox2.Items, [mvPrivate,mvPublic] );
end;

[/code]

Ver Exemplos

 

.

Quando se trabalha com processos paralelos o isolamento de memória com acesso comum a processos diferentes é fundamental.

Uma opção é implementar utilizando  TThreadList que mantem o controle da lista bloqueando ou liberando quando precisa acessar a área de armazenamento sem especialidades do TStringList .

De outro lado o TStringList não é threadsafe o que motiva a reescrever as funcionalidades para compartilhamento da lista entre os processos…   Ver Classes TThreadSafe….

[code lang=”pascal”]
TThreadSafeStringList
// public
procedure Clear;
function Count: integer;
function IndexOf(AText: string): integer;
function IndexOfName(AText: string): integer;
procedure Add(AText: string; ADupl: boolean = true);
procedure Delete(AIndex: integer);
procedure Remove(AText: string);
function LockList: TStringList;
procedure UnlockList; inline;
property Items[AIndex: integer]: string read Getitems write Setitems;
property Delimiter: Char read GetDelimiter write SetDelimiter;
property DelimitedText: string read GetDelimitedText write SetDelimitedText;
function Text: string;
property CommaText: string read GetCommaText write SetCommaText;
property QuoteChar: Char read GetQuoteChar write SetQuoteChar;
procedure Assing(AStrings: TStrings);
procedure AssingTo(AStrings: TStrings);
procedure AddTo(AStrings: TStrings);
property Values[AName: string]: String read GetValues write SetValues;
property Names[AIndex: integer]: String read GetNames write SetNames;
[/code]

Exemplo de como utilizar as lista compartilhando em Threads diferentes mantendo o controle do acesso a lista… [Codigo]

[code lang=”pascal”]
{$R *.fmx}

procedure TForm2.Button1Click(Sender: TObject);
var
x: integer;
begin
strList.Clear;
// Thread 1
tthread.CreateAnonymousThread(
procedure
var
x: integer;
begin
for x := 0 to random(1000) do
begin
strList.Add(‘X’ + intToStr(x));
tthread.Sleep(random(10));
end;
strList.Add(‘X-FIM’);

tthread.Queue(nil,
procedure
begin
strList.AssingTo(Memo1.lines);
end);

end).start;

// Thread 2
tthread.CreateAnonymousThread(
procedure
var
x: integer;
begin
for x := 0 to random(1000) do
begin
strList.Add(‘Z’ + intToStr(x));
tthread.Sleep(random(10));
end;
strList.Add(‘Z-FIM’);

tthread.Queue(nil,
procedure
begin
strList.AssingTo(Memo1.lines);
end);

end).start;

end;

procedure TForm2.FormCreate(Sender: TObject);
begin
strList := TThreadSafeStringList.create;

end;

procedure TForm2.FormDestroy(Sender: TObject);
begin
strList.free;
end;

[/code]

[usa LogEvents]
Tenho uma quantidade de produtos relativamente grande que requer processamento de custos de produção envolvendo custo de matérias primas, mão-de-obra e outros custos vinculado a célula de produção.

A modelagem prevê que uma ficha de produção pode conter outras fichas formando uma lista de dependências dos processos o que gera processamento recursivo de dependências.

Como se pode imaginar, não é um processamento sequenciado tão simples e pode ser demorado em face a profundidade da arvore de dependência que um produto pode exigir.

Então repensando os processos, o desafio passou exigir processamento em paralelo das fichas de tal forma que fosse possível processar uma quantidade de produtos ao mesmo tempo e aproveitando melhor os recursos da máquina;

Neste cenário, saber qual o estágio de processamento de cada ficha e o onde se encontra o cálculo passou a ser requisito de interação com usuário;

Para executar vamos utilizar da biblioteca de processamento em paralelo do Delphi (introduzido no XE7, no exemplo usamos Berlin).

Passos:

  • Isolar as conexões de banco de dados para trata-las individualmente por Task;
  • Criar infraestrutura de comunicação entre o processamento e feedback com usuário;
  • Tratar a sincronização de informações geradas pelas várias TTasks em andamento informando a janela de progresso do usuário;
imagem_janela
Tendo em mente que o controle possa ser utilizado em outras aplicações, o uso de um procedimento ANONIMOUS me parece bastante resistente a diversidade de códigos a que poderá vir a ser utilizado.
Veja como ficou o exemplo de execução:

[code lang=”pascal”]

procedure TForm8.Button1Click(Sender: TObject);
var
LProgr: IProgressEvents;
i: integer;
begin
// inicializa a janela de progresso
LProgr := TProgressEvents.new;
LProgr.max := 100; // opcional: marca o número máximo itens
LProgr.MaxThreads := SpinEdit1.Value ; // indica o número máximo de threads em paralelo
LProgr.CanCancel := true; :// marca se pode cancelar a operação

for i := 1 to 100 do
begin // loop de demonstração – simulando uma lista de processos
LProgr.Text := ‘Produto: ‘ + intToStr(i); // texto livre

// onde as coisas acontecem…..
// adiciona o processo a ser executado e aponta o método anonimous as ser executado pela TTask
LProgr.add(i, ‘Produto: ‘ + intToStr(i), // processo a executar
procedure(x: integer)
var
n: integer;
msg: string;
begin
msg := ‘Produto: ‘ + intToStr(x); // processo em execução
LogEvents.DoProgress(self, 0, etStarting, msg); // notifica que o processo foi iniciado
n := Random(10000);

sleep(n);
LogEvents.DoProgress(self, 0, etWorking, msg); // notifica que esta em execução
// executa o código de calculo … aqui…
n := Random(10000);
if LProgr.Terminated then exit; // checa se o usuario cancelou a operação
sleep(n);
end);
if LProgr.Terminated then
break;
end;
LogEvents.DoProgress(self, 0, etAllFinished, ”); // sinaliza que todas os processo foram completados.
end;

[/code]

Código fonte com o Exemplo e classes que implementam a janela de monitoramento do progresso de cada thread.

Como é de conhecimento da comunidade o FireDac não tem suporte completo ao Firebird3, já que o lançamento do FB3 veio depois do lançamento do Berlin.

Quando se trabalha com Package (novidade no FB3) não é possível escolher na IDE qual o procedimento a executar no componente TFDStoredProc.
Uma forma de fazer isto é escrevendo um editor (delphi way) para auxiliar a propriedade StoredProcName…

[code lang=”pascal”]

unit Data.fireStoredProcEditor;

interface

uses
SysUtils, Classes, DesignIntf, DesignEditors, DB;

type
TFireStoredProcNames = class(TStringProperty)
private
procedure GetValues(Proc: TGetStrProc); override;

public
function GetAttributes: TPropertyAttributes; override;
end;

procedure Register;

implementation

uses FireDAC.Comp.Client, FireDAC.Phys.Intf;

procedure Register;
begin
RegisterPropertyEditor(TypeInfo(string), TFDCustomStoredProc,
‘StoredProcName’, TFireStoredProcNames);
end;

{ TFireStoredProcNames }

function TFireStoredProcNames.GetAttributes: TPropertyAttributes;
begin
result := [paValueList];
end;

procedure TFireStoredProcNames.GetValues(Proc: TGetStrProc);
var
DB: TFDCustomStoredProc;
qry: TFDQuery;
eh3:boolean;
oMetaIntf: IFDPhysConnectionMetadata;
function iff(b:boolean;t,f:string):string;
begin
if b then result := t else result := f;
end;
begin
if (GetComponent(0).InheritsFrom(TFDCustomStoredProc)) then
begin
DB := TFDCustomStoredProc(GetComponent(0));
if assigned(DB.Connection) then
begin
if (DB.Connection.DriverName = ‘FB’) then
begin
oMetaIntf := DB.Connection.ConnectionMetaDataIntf;
eh3 := oMetaIntf.ServerVersion.ToString[1]=’3′;
qry := TFDQuery.create(nil);
try
qry.Connection := DB.Connection;
qry.SQL.Text := ‘select rdb$procedure_name sName from rdb$procedures ‘;
if eh3 then
qry.SQL.Text := qry.SQL.Text+ iff(db.PackageName<>”, ‘ where rdb$package_name = ‘ + QuotedStr(DB.PackageName.ToUpper),’ where rdb$package_name is null ‘);
qry.Open;
with qry do
while eof = false do
begin
Proc(fieldByName(‘sName’).asString);
next;
end;
finally
qry.Free;
end;
end
else
inherited;
end;
end
else
inherited;

end;

end.

[/code]

Exemplo de uma package no FB3: DateUtils Package

Criando um Packege no Delphi para a Integração
Para integrar o novo editor é necessário criar um novo projeto Package no Delphi e incluir o código do editor.
[code]
// exemplo do projeto do Package (mínimo)
package FireEditores;
{$R *.res}
requires
DesignIDE;
contains
Data.fireStoredProcEditor in ‘Data.fireStoredProcEditor.pas’;
end.

[/code]

Estava precisando de informações sobre uma exceção e o log de erro não dizia nada relevante possível de encontrar onde o problema ocorria.

Já vi vários posts sobre o assunto peguntando como fazer isto. Então não tive outra saída… mãos-a-obra.

(uso Delphi 10.1)

A instância  “Application” possui um evento “application.onException” que permite indicar um método para redirecionar a saída de todas as exceções não tratadas pelo aplicativo.

[code lang=”pascal”]
// preparando o evento no formulário principal
procedure TForm1.DoAppException(sender:TObject; E:Exception);
begin
DoAppExceptionEvent(sender,e,true);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
Application.OnException := DoAppException;

end;
[/code]

[code lang=”pascal”]
uses Forms, System.Classes,System.SysUtils,System.RTTI;

// grava o log em disco
procedure ErrorLog(ATexto: string);
var
LArquivo: string;
LTextFile: textfile;
begin
LArquivo := ‘Erros_’ + formatDateTime(‘yyyymmdd’, date) + ‘.log’;
ForceDirectories(ExtractFilePath(LArquivo));

AssignFile(LTextFile, LArquivo);
try
{$I-}
Append(LTextFile);
{$I+}
if IOResult <> 0 then // se o arquivo nao existe, criar um novo;
Rewrite(LTextFile);
WriteLn(LTextFile, ATexto);
finally
CloseFile(LTextFile);
end;
end;

// monta a mensagem do log com base nos atributos do objecto que gerou a exceção
procedure DoAppExceptionEvent(Sender: TObject; E: Exception;
AShow: boolean = True);
var
LMsg: string;
function GetRTTILog(ASender: TObject): string;
var
LNome: string;
LContext: TRttiContext;
LType: TRttiType;
LProp: TRttiProperty;
LVar: TValue;
LTxt: String;
begin
result := ”;
if ASender=nil then exit;
result := ‘ClassName: ‘ + ASender.ClassName + #13#10;
LContext := TRttiContext.Create;
try
LType := LContext.GetType(ASender.ClassType);
for LProp in LType.GetProperties do
begin
try
LVar := LProp.GetValue(ASender);
LTxt := LVar.AsString;
if LTxt <> ” then
result := result + LProp.Name + ‘: ‘ + LTxt + #13#10;
except
end;
end;
finally
LContext.Free;
end;
end;

begin
try
LMsg := ”;
if assigned(Sender) then
begin
LMsg := GetRTTILog(Sender);
end;
LMsg := LMsg + ‘ Message: ‘ + E.Message;
ErrorLog(LMsg);
except
on ee: Exception do
ErrorLog(ee.Message);
end;
if AShow then
begin
E.Message := LMsg;
Application.ShowException(E);
end;
end;

[/code]

[primeira parte]

2. Preparando o Sensor

Escolha o seu sensor entre os vários disponíveis em MySensors – baixe o código e siga as recomendações – pode acreditar… vai funcionar.

Os programas escritos para Arduino seguem o padrão C/C++ (ver mais) e utilizam uma IDE básica com suporte a uma variedade impressionante de processadores da linha ATMEL.

Uma vez instalado a IDE e feito escolha do sensor que irá utilizar o próximo passo será montar a parte eletrônica do sensor ( Arduino + Rádio + Sensor ).sensors

Agora que já tem o código do sensor e a eletrônica montada – é hora de transferir o programa para o arduino – vai encontrar farto material de como fazer isto tanto em MySersors como em  arduino.cc.

Se tudo deu certo… você já tem um sensor funcionando…

 

Até aqui não temos Delphi… na próxima fase vamos trabalhar o Gateway.

 

[continua]

 

Uma solução popular para integração de IoT é fazer uso da plataforma Arduino como base de aquisição de dados de campo.

Para alguém que nasceu enfiado no código, como eu, a eletrônica parece se uma barreira – esquece… isto era antes de Arduino.

“Arduino, palavra por vezes traduzida ao português como Arduíno, é uma plataforma de prototipagem eletrônica de hardware livre e de placa única, projetada com um microcontrolador Atmel AVR com suporte de entrada/saída embutido, uma linguagem de programação padrão, a qual tem origem em Wiring, e é essencialmente C/C++. O objetivo do projeto é criar ferramentas que são acessíveis, com baixo custo, flexíveis e fáceis de se usar por artistas e amadores. Principalmente para aqueles que não teriam alcance aos controladores mais sofisticados e de ferramentas mais complicadas.” (Wikipédia)

Como plataforma de prototipagem permite infinitas possibilidades de soluções e conexões com sensores e interação com objetos nas mais diversas área da tecnologia. Arduino como precursora dos processadores que cabem na palma da mão – praticamente se tornou um padrão de mercado para a indústria, onde já encontramos importantes players se dedicando em lançar produtos/placas com a filosofia disseminada pelo Arduino.

Quando se olha do lado do hardware, encontramos em MySensors uma base de integração bem servida de sensores e atuadores para soluções comuns do dia-a-dia.

Como protocolo MySensors estabelece regras simples de troca de informações por porta SERIAL ou TCP/IP (Ethernet) no PC e Rádio (sem fio) no lado do hardware, que permite receber dados coletados de campos e/ou enviar dados para os sensores de campo utilizando comunicação sem fio (rádio).

Quando um ponto MySensors se comunicam sem si ( entre sensores ), ele utiliza uma estrutura de rede MESH em que estabelece uma relação de recepção dos dados endereçados ao próprio ponto (sensor) e retransmissão do sinal quando
forem informações destinados a outro sensor seu vizinho. De forma colaborativa em uma rede MESH o dado viaja entre os sensores até chegar ao seu mestre principal (gateway-gw) – o que torna cada ponto (sensor) em um repetidor (R) de sinal;

Um sensor envia um dado para o Gateway em formato texto (ex: 12;6;0;0;3;My Light\n) – se o destino/Gateway estiver fora do alcance, o dado é retransmitido pelos vizinhos até chegar ao seu destino final.

Uma variedade de sensores podem ser conectados ao seu Arduino que se comunica com o Gateway usando protocolo MySensors e chega ao PC para gerência, armazenamento e/ou reação comandando novas ordens aos sensores.

[continua…]

Quando se trata de eventos o mais comum é encontrar uma propriedade que recebe um ponteiro para um procedimento.

Um tipo de evento comum é

property OnClick:TNotifyEvent ….

Para atribuir o evento ao objeto é preciso construir um procedimento para a classe e associá-lo ao evento do objeto, se você já tentou atribuir um procedimento que não esta ligado a uma classe, já notou que há limitações.

Porque criar um novo procedimento em uma classe que será utilizado em um objeto específico… Com eventos anônimos não precisa. (fontes)

[code lang=”pascal”]
TMinhaClasse = class(TObjectExt)
end;
…..

var FObjMinhaClasse:TMinhaClass;
begin
// definindo o evento anônimo
FObjMinhaClasse:= TMinhaClasse.create;
FObjMinhaClasse.onFireEvent := procedure (sender:TObject)
begin
……
end;
end;

// usando o evento anônimo
FObjMinhaClasse.FireEvent(sender);

[/code]

 

Recriando TObject

[code lang=”pascal”]

TObject = class(System.TObject)
private
FOnFireEvent: TProc<TObject>;
procedure SetOnFireEvent(const Value: TProc<TObject>);
public
procedure FireEvent;overload;
procedure FireEvent(Sender:TObject);overload;
property OnFireEvent:TProc<TObject> read FOnFireEvent write SetOnFireEvent;
end;

…..
{ TObject }

procedure TObject.FireEvent;
begin
FireEvent(self);
end;

procedure TObject.FireEvent(Sender: TObject);
begin
if assigned(FOnFireEvent) then
FonFireEvent(Sender);
end;

procedure TObject.SetOnFireEvent(const Value: TProc<TObject>);
begin
FOnFireEvent := Value;
end;
[/code]

 

Exemplos

Este post foi inspirado no livro do Nick Hodges “Coding in Delphi”

 

Estava trabalhando para simplificar o TRESTClient do DataSnap  e implementar alguns recursos que gostaria de ter e não encontrei no componente distribuido com o delphi ( Package para TRESTSocialClient ). Mapear as respostas Array para um TFDDataset primordial para facilitar o desenvolvimento (ex: TRESTSocialClientDataset). E quando não é possível mapear para um Dataset… código+código+bug+complexo…

Quando trabalho com um Servidor REST Datasnap escrito para delphi – a integração é simples utilizando o “Wizard” para criar um projeto REST Client disponível. Quando o servidor REST responde para múltiplas plataformas é imperativo que o servidor envie respostas “padronizadas” que sejam compatíveis com a plataforma que o cliente utiliza. O caminho mais rápido é responder JSON padrão evitando respostas específicas de uma plataforma ou outra.

Exemplo: um método não pode responder com um TDataset.Data como é o indicado pelo Delphi… isto obrigaria que o cliente seja igualmente um Delphi.. Então para manter padrão o método deve responder com um TJSONValue ou TJSONObject.

Tomando por base o Servidor Datasnap do post “FireDAC – Datasnap” onde publica o método  GetCliente/{cnpj} (http://localhost:8080/datasnap/rest/TServerMethods1/GetCliente/123456) que retorna:

[code lang=”JSON”]
{"result":[{"cliente":[{"RowId":1,"codigo":1,"nome":"Embarcadero SA","cidade":"Sao Paulo","estado":"SP","endereco":"Rua…xxxx…,10","debitos":100000.12},{"RowId":2,"codigo":2,"nome":"Embarcadero SA2","cidade":"Sao Paulo","estado":"SP","endereco":"Rua…xxxx…,10","debitos":100000.12}],"adicional":[{"codigo":1,"nome":"Exemplo 2"}]}]}
[/code]

Nas andanças pelos BLOGs de MVPs encontrei um projeto “Introducing JsonToDelphiClass” que aponta para o repositório com o código do projeto.

O projeto JsonToDelphiClass permite que a representação JSON de retorno do servidor DataSnap seja colado no espaço para texto indicada e usar o botão de geração da Unit contendo a classe Delphi para a representação JSON.

As classes geradas pelo projeto possuem um método que fornecendo o código de retorno do servidor – popula o objeto delphi – facilitando a conversão da estrutura JSON para Classe.

class function FromJsonString(AJsonString: string): T... (criar a classe e popula)

.

Passando aquele retorno do Servidor, veja como ele construiu as classes para a representação JSON:

[code lang=”pascal”]
unit JsonRestServer;

// *************************************************
// Generated By: JsonToDelphiClass – 0.65
// Project link: https://github.com/PKGeorgiev/Delphi-JsonToDelphiClass
// Generated On: 2016-04-10 15:41:06
// *************************************************
// Created By : Petar Georgiev – 2014
// WebSite : http://pgeorgiev.com
// *************************************************

interface

uses Generics.Collections, Rest.Json;

type

TAdicionalClass = class
private
FCodigo: Extended;
FNome: String;
public
property codigo: Extended read FCodigo write FCodigo;
property nome: String read FNome write FNome;
function ToJsonString: string;
class function FromJsonString(AJsonString: string): TAdicionalClass;
end;

TClienteClass = class
private
FRowId: Extended;
FCidade: String;
FCodigo: Extended;
FDebitos: Extended;
FEndereco: String;
FEstado: String;
FNome: String;
public
property RowId: Extended read FRowId write FRowId;
property cidade: String read FCidade write FCidade;
property codigo: Extended read FCodigo write FCodigo;
property debitos: Extended read FDebitos write FDebitos;
property endereco: String read FEndereco write FEndereco;
property estado: String read FEstado write FEstado;
property nome: String read FNome write FNome;
function ToJsonString: string;
class function FromJsonString(AJsonString: string): TClienteClass;
end;

TResultClass = class
private
FAdicional: TArray<TAdicionalClass>;
FCliente: TArray<TClienteClass>;
public
property adicional: TArray<TAdicionalClass> read FAdicional write FAdicional;
property cliente: TArray<TClienteClass> read FCliente write FCliente;
destructor Destroy; override;
function ToJsonString: string;
class function FromJsonString(AJsonString: string): TResultClass;
end;

TRootClass = class
private
FResult: TArray<TResultClass>;
public
property result: TArray<TResultClass> read FResult write FResult;
destructor Destroy; override;
function ToJsonString: string;
class function FromJsonString(AJsonString: string): TRootClass;
end;

implementation

{TAdicionalClass}

function TAdicionalClass.ToJsonString: string;
begin
result := TJson.ObjectToJsonString(self);
end;

class function TAdicionalClass.FromJsonString(AJsonString: string): TAdicionalClass;
begin
result := TJson.JsonToObject<TAdicionalClass>(AJsonString)
end;

{TClienteClass}

function TClienteClass.ToJsonString: string;
begin
result := TJson.ObjectToJsonString(self);
end;

class function TClienteClass.FromJsonString(AJsonString: string): TClienteClass;
begin
result := TJson.JsonToObject<TClienteClass>(AJsonString)
end;

{TResultClass}

destructor TResultClass.Destroy;
var
LclienteItem: TClienteClass;
LadicionalItem: TAdicionalClass;
begin

for LclienteItem in FCliente do
LclienteItem.free;
for LadicionalItem in FAdicional do
LadicionalItem.free;

inherited;
end;

function TResultClass.ToJsonString: string;
begin
result := TJson.ObjectToJsonString(self);
end;

class function TResultClass.FromJsonString(AJsonString: string): TResultClass;
begin
result := TJson.JsonToObject<TResultClass>(AJsonString)
end;

{TRootClass}

destructor TRootClass.Destroy;
var
LresultItem: TResultClass;
begin

for LresultItem in FResult do
LresultItem.free;

inherited;
end;

function TRootClass.ToJsonString: string;
begin
result := TJson.ObjectToJsonString(self);
end;

class function TRootClass.FromJsonString(AJsonString: string): TRootClass;
begin
result := TJson.JsonToObject<TRootClass>(AJsonString)
end;

end.

[/code]