19 de julho de 2013

Lendo dados JSON em aplicações Delphi

O JavaScript Object Notation - ou simplesmente JSON - é um formato padrão para troca de informações muito usado na construção de sites devido à sua estrutura baseada no JavaScript. Essa característica dá aos scripts em uma página HTML a flexibilidade para recuperar informações complexas e aplicá-las ao HTML conforme a necessidade, incrementando a usabilidade da página.

Assim como o XML, JSON é um formato texto aberto, isto é, que pode ser extendido para acrescentar novos valores quando for preciso; ambos também são fáceis de serem lidos por humanos. As informações nesse formato são construídas com pares nome/valor, sendo suportados 3 tipos básicos de dados: texto (string), números e booleanos. Os pares de valores, no entanto, podem ser rearranjados para construir objetos complexos e listas de objetos (arrays). O quadro a seguir mostra o exemplo de um objeto JSON complexo:
/* Objeto JSON complexo, incluindo um array com 3 outros objetos */
{
"empresa":"ABC71",
"ativa": true,
"fundacao": 1971,
"empregados": [
{"nome": "Jose", "sobrenome": "Silva" },
{"nome": "Beto", "sobrenome": "Marinho" },
{"nome": "Jim" , "sobrenome": "Jones" }
]
};

Há diversos serviços disponíveis na internet - alguns gratuitos - com os quais podemos recuperar diferentes tipos de informação (lista de países, previsão do tempo, cotações de moedas, etc). Além disso, é possível construirmos nossos próprios serviços para prover qualquer tipo de dado que necessitemos. Neste post, eu mostro como aplicações Delphi podem se beneficiar de serviços como esses, usando como exemplo um dos serviços publicados no site GitHub.

O retorno do serviço proposto é uma série de objetos descrevendo cada pais do mundo, incluindo seu nome, um id numérico e a sigla de 3 letras usada para o país. O quadro abaixo traz como exemplo o objeto para o Brasil:
{
"title":"Brazil",
"population":174468575,
"currency_code": "BRL",
"currency": "Brazilian Real ",
"nationality_plural": "Brazilians",
"nationality_singular": "Brazilian",
"map_reference": "South America ",
"_id": 33,
"country": "Brazil",
"fips104": "BR",
"iso2": "BR",
"iso3": "BRA",
"ison": 76,
"internet": "BR",
"capital": "Brasilia "
}

Como se trata de um serviço oferecido na internet, o primeiro passo é acessar o endereço do serviço para obter o conteúdo disponível. Isso pode ser conseguido com o componente TIdHTTP do Indy, conforme descrito no post Trabalhando com HTTP em Delphi. Para nosso cenário, isso se resume ao código do quadro abaixo:
var JsonStream: TStringStream;
begin
JsonStream:= TStringStream.Create('');

try
{ JsonStream conterá os dados JSON requisitados :}
idHttp1.Get('https://raw.github.com/nosql/data-refine/master/data/json/countries.json', JsonStream);
TrataJsonStream(JsonStream);
finally
JsonStream.Free();
end;

Após a chamada da função Get, a variável JsonStream contém a estrutura JSON completa com os dados dos paises. Essas informações estão em formato texto mas o Delphi possui um mecanismo de parse para carregá-los num objeto próprio, o TJSONObject:
var jso : TJSONObject;
jsop: TJSONPair;
begin
jso := TJsonObject.Create;
jso.Parse (JsonStream.Bytes, 0);

for jsop in jso do begin
if jsop.JsonString.Value = '_id' then
pais.id := ((jsop.JsonValue) As TJSONNumber).AsInt
else
if jsop.JsonString.Value = 'iso3' then
pais.ISO3 := jsop.JsonValue.Value
else
if jsop.JsonString.Value = 'country' then
pais.Pais := jsop.JsonValue.Value;
end;
jso.Free;
end;

Um TJSONObject armazena a lista dos pares nome/valor que constituem um objeto JSON. Como vemos no quadro anterior, a lista é um iterator para valores do tipo TJSONPair, com o qual podemos extrair tanto o nome (JasonString) quanto o valor (JasonValue) de cada propriedade do objeto JSON.

No entanto, há um problema com o código acima. Mencionei antes que o serviço retorna uma série de objetos mas o código mostrado trata apenas o primeiro objeto recuperado no stream. Para tratar os demais, temos que conhecer melhor a função Parse do objeto JSON.

O primeiro parâmetro da função Parse é o stream obtido junto ao serviço na web. O segundo parâmetro indica a partir de qual caractere do stream o Parse deve começar seu trabalho. A função retorna a posição do stream onde a leitura do objeto corrente foi concluída. Isso tudo é necessário porque não sabemos de antemão o tamanho de cada objeto. Mas, com essas informações que temos, podemos montar um laço capaz de ler todos os objetos incluídos no stream, como faz o código a seguir:
var i : Int64;
jso : TJSONObject;
jsop: TJSONPair;
begin
i := 0;
repeat
jso := TJsonObject.Create;
i := abs (jso.Parse (JsonStream.Bytes, i));

for jsop in jso do begin
{ Trata aqui cada JSON Pair }
end;
jso.Free;
until (i >= JsonStream.Size);
end;

O objeto JSON usado no exemplo é relativamente simples, não possuindo outros objetos aninhados ou mesmo um array de valores. Para esses casos, o laço que trata os pares de valores deve testar o tipo do valor e tratá-lo apropriadamente, reconstituindo o objeto complexo. Isso pode ser conseguido usando o ClassName do valor ou RTTI, como abaixo:
if jsop.JsonValue Is TJSONObject then
TrataObjeto (jsop.JsonValue As TJSONObject)
else
if> jsop.JsonValue Is TJSONArray then
TrataArray (jsop.JsonValue As TJSONArray)
else begin
{ Trata valores simples }
end;

A forma como transportei o conteúdo de um objeto JSON para uma estrutura equivalente do Delphi aqui neste post é o método mais simples. O Delphi disponibiliza mecanismos de serialização de informações usando JSON que são bem mais elaborados e flexíveis. Mostro tais mecanismos em outro ocasião.

Um comentário :

Carlos Daniel disse...

Interessante artigo e quem trabalha com versões mais antigas do Delphi, como o Delphi 2007 por exemplo, recomendo usar a unit uJSON de Fabio Almeida, aqui estou usando muito bem.

Postar um comentário

OBS: Os comentários enviados a este Blog são submetidos a moderação. Por isso, eles serão publicados somente após aprovação.

Observação: somente um membro deste blog pode postar um comentário.