Trabalhando com JSON no iOS
Postado em 10. mai, 2012 por Diego Chohfi em Inovação, Mobile
Com o crescimento do mercado mobile, a integração de sistemas com os pequenos aparelhos se tornou essencial. O formato JSON se popularizou com a mesma velocidade que o desenvolvimento mobile. Sua simplicidade e facilidade de desenvolver parsers o tornou uma das principais alternativas na integração de sistemas web, em especial para ser consumido via AJAX.
No iOS não foi diferente. A necessidade dos desenvolvedores levou o surgimento de diversas bibliotecas que faziam o trabalho de ler e gerar JSON com extrema facilidade, como o JSON Framework e o TouchJSON. Porém a Apple não quis ficar de fora e incorporou a classe NSJSONSerialization, capaz de gerenciar o formato tanto no iOS como no desenvolvimento para OSX, com a mesma facilidade que as bibliotecas opensource trouxeram.
Recetemente desenvolvemos um projeto para iOS para auxiliar os alunos do nosso curso de iOS, o IP-67, enriquecendo a experiência e trazendo um exemplo bem palpável. A aplicação consiste em buscar os pontos de ônibus próximos a localização do usuário. Para integrar a nossa aplicação com um backend em Ruby utilizamos JSON como o formato e a classe NSJSONSerialization se mostrou extremamente simples e fácil de se trabalhar. Vamos entender como fizemos, do lado do dispositivo, a leitura e manipulação dos dados.
Primeiramente precisamos obter as informações no formato JSON para então parsea-los. Utilizando o método + (id)dataWithContentsOfURL:(NSURL *)url; da classe NSData para fazer uma request e obter as informações de forma síncrona em um endereço da WEB:
NSString *url = [NSString stringWithFormat:@"http://ondeestaoalbi.herokuapp.com/onibusesNosPontosProximos.json?lat=%f&long=%f", -23.588453, -46.632103]; NSData *jsonData = [NSData dataWithContentsOfURL: [NSURL URLWithString:url]];
O nosso serviço retornará um JSON com a seguinte estrutura:
{
"nome": "920016054",
"descricao": "R. Dr. Neto De Araujo, 311",
"coordenada": {
"latitude": -23.5876,
"longitude": -46.6321
}
}
Após receber as informações, vamos utilizar a classe NSJSONSerialization para transformar o JSON em um NSDictionary, contendo as chaves e valores das informações sobre o ponto.
NSError* error;
NSDictionary *resultados = [NSJSONSerialization JSONObjectWithData:jsonData
options:NSJSONReadingMutableContainers error:&error];
Caso o JSON seja válido e a conversão seja feita com sucesso podemos buscar os valores no NSDictionary a partir de uma chave, utilizando o método - (id)objectForKey:(id)aKey;:
NSError *error;
NSDictionary *resultados = [NSJSONSerialization JSONObjectWithData:jsonData
options:NSJSONReadingMutableContainers error:&error];
if(!error) {
NSString *nome = [resultados objectForKey:@"nome"];
NSString *descricao = [resultados objectForKey:@"descricao"];
NSLog(@"Nome: %@, descrição: %@", nome, descricao);
}
Poderíamos continuar utilizando o NSDictionary para armazenar as informações dos pontos encontrados, mas imagine que por algum motivo precisemos mostrar a descrição do nosso Ponto sempre com letra minúscula e removendo os espaços do começo e do fim da nossa NSString, o código ficaria da seguinte forma:
NSString *nome = [resultados objectForKey:@"nome"];
NSString *descricao = [[resultados objectForKey:@"descricao"] lowercaseString];
descricao = [descricao stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSLog(@"Nome: %@, descrição: %@", nome, descricao);
Perceba que o código não é mais tão simples quanto anteriormente, além disso, precisaríamos espalhá-lo por todo o projeto. A manutenção também seria difícil, pois precisaremos copiar e colar o mesmo código toda vez que a descrição for exibida e caso a regra de exibição seja alterada o código que copiamos e colamos terá que ser alterado.
Temos esse problema pois estamos utilizando a classe NSDictionary para guardar estado do nosso Ponto e não podemos definir nenhum comportamento adicional. Para manter os dados organizados e de fácil manutenção devemos sempre criar classes para representar tipos e não mantê-los na representação de chave e valor, desta forma, além de guardar o estado do objeto, podemos também definir novos comportamentos, facilitando a manutenção e a reutilização de código.
Vamos criar as classes com os referentes atributos e também definir um método de classe para criar uma instância do Ponto, esse método será responsável por buscar suas informações em um NSDictionary passado como argumento. Essa regra fica isolada e fácil de dar manutenção caso alguma das chaves mude:
#import <Foundation/Foundation.h>
#import "Coordenada.h"
@interface Ponto : NSObject
@property(nonatomic, strong) NSString *nome;
@property(nonatomic, strong) NSString *descricao;
@property(nonatomic, strong) Coordenada *coordenada;
+ (Ponto *) comDicionario: (NSDictionary *) dicionario;
@end
#import "Ponto.h"
@implementation Ponto
@synthesize nome, descricao, coordenada;
+ (Ponto *) comDicionario: (NSDictionary *) dicionario {
Ponto * ponto = [[Ponto alloc] init];
[ponto setNome: [dicionario objectForKey:@"nome"]];
[ponto setDescricao: [dicionario objectForKey:@"descricao"]];
Coordenada *coordenada = [Coordenada comDicionario: [dicionario objectForKey: @"coordenada"]];
[ponto setCoordenada: coordenada];
return ponto;
}
@end
Se fosse preciso definir uma regra para buscar a descrição do Ponto da forma que descrevemos anteriormente, podemos alterar o método que busca o atributo na classe, isolando o código e facilitando futuras manutenções.
#import "Ponto.h"
@implementation Ponto
@synthesize nome, descricao, coordenada;
- (NSString *) descricao {
NSString *descricaoEmMinusculo = [descricao lowercaseString];
return [descricaoEmMinusculo stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
@end
Para obter um Ponto a partir de um JSON chegamos ao seguinte código:
NSString *url = [NSString stringWithFormat:@"http://ondeestaoalbi.herokuapp.com/onibusesNosPontosProximos.json?lat=%f&long=%f", -23.588453, -46.632103];
NSData *jsonData = [NSData dataWithContentsOfURL: [NSURL URLWithString:url]];
NSError *error;
NSDictionary *resultados = [NSJSONSerialization JSONObjectWithData:jsonData
options:NSJSONReadingMutableContainers error:&error];
if(!error){
Ponto *ponto = [Ponto comDicionario: resultados];
NSLog(@"Ponto: %@", ponto.descricao);
}
Percebendo a dificuldade e a duplicação de código que temos que escrever ao buscar os valores no NSDictionary, desenvolvemos uma pequena biblioteca para facilitar essa tarefa. O KeyValueObjectMapping surgiu da necessidade de transformar grandes estruturas de dados automaticamente em objetos, seguindo padrões e quando necessário adicionando configurações para alterar as convenções. Poderíamos remover a implementação do método + (Ponto *) comDicionario: (NSDictionary *) dicionario; e com o KeyValueObjectMapping teríamos:
DCKeyValueObjectMapping *parser = [DCKeyValueObjectMapping mapperForClass:[Ponto class]]; Ponto *ponto = [parser parseDictionary:resultados];
Lembrando que o KeyValueObjectMapping serve para transformar qualquer tipo de dado que se comporte como um NSDictionary, chave e valor, podendo ser um JSON, um PLIST, entre outros. Você está convidado a contribuir nos projetos!

ASSINE NOSSO RSS
Felipe Conde
10. mai, 2012
Olá Diego.
Muito boa a solução!
Ultimamente tenho usado o framework RESTKit. Já ouviu falar no projeto? http://restkit.org/
Mão na roda também!
abraço
Diego Chohfi
10. mai, 2012
Opa Felipe, tudo bom? O RESTkit é muito legal mesmo, diversas facilidades inclusive as mapear um objeto direto para uma representação em JSON.
Witaro
10. mai, 2012
Pessoal, desculpa a pergunta “retrô”, mas o que vcs recomendam para XML no iOS?
Felipe Benevides
10. mai, 2012
Excelente post, Diego! Na minha app usei o JSON Framework (faz uns 3 meses) e a performance dele era muito melhor que a do TouchJSON.
E ah, utilizamos a mesma abordagem de criar um método de classe para receber o NSDictionary e até agora têm sido muito fácil de dar manutenção.
Diego Chohfi
10. mai, 2012
Opa Witaro, tudo bom? Retrô mas muita gente precisa, né? A Apple tem uma classe para parsear xml: NSXMLParser mas a galera diz que é um pouco chato e ela se compara ao StAX. Porém tem algumas opensource que fazem o trabalho bem mais simples, de uma olhada nessa: https://github.com/nfarina/xmldocument. Abraços.
Witaro
11. mai, 2012
Valeu, Diego! Vamos comparar ele com o TBXML, que foi bem avaliado aqui:
http://www.raywenderlich.com/553/how-to-chose-the-best-xml-parser-for-your-iphone-project
Cleverson Sacramento
14. mai, 2012
Diego, escrevi um post também sobre este assunto. Falei sobre requisições GET, PUT e DELETE usando os recursos nativos do Foundation Framework também.
Espero que ajude outras pessoas:
http://cleversonsacramento.com/2012/05/13/objective-c-e-restful-web-services/
Diego Chohfi
15. mai, 2012
Opa Cleverson, tudo bom? Muito bom seu post cara, sempre bom ajudar a galera
Estagiário
16. mai, 2012
Muito bom preciso aprender mais sobre desenvolvimento em iOS.
Francisco Martins
30. jul, 2012
Primeira vez que vi o post, parecia aramaico: impossível de entender. Depois de uma aula, consegui acompanhar quase tudo.
Mas a idéia é: O curso de iOS da Caelum é muito bom e esse post me será muito útil.
Diego Chohfi
31. jul, 2012
Opa Francisco, que bom que gostou do post! Fico feliz que as aulas te ajudaram a entender melhor.
Thiago
04. ago, 2012
Diego, primeiramente seu post é um dos melhores sobre o assunto, pesquisei muito na net e realmente vc esta d parabéns.
Criei uma página em Asp q me retorna um Json, esta página no micro-01 por ex. Criei minha aplicação no Micro-02 e troquei o localhost do seu código pelo IP do micro-01.
Nao recebo nada na minha aplicação, você saberia me dizer se eu teria que habilitar algo no Xcode.
Já desativei o firewall no Micro-01 e consigo acessar a página ASP gerada por ele usando um navegador no Micro-02
Diego Chohfi
06. ago, 2012
Opa Thiago, tudo bom? Obrigado pelos elogios, fico feliz que tenha gostado. Referente ao problema, inicialmente tente isolar os problemas de rede: suba o projeto e faça a request na sua máquina, localhost mesmo, e veja se da certo.
Raul
07. ago, 2012
Olá, fiquei com dúvida em relação a url, ela é fake ou disponibilizada pela sptrans?
Diego Chohfi
07. ago, 2012
Olá Raul, a URL não é fake porém não é disponibilizada pela sptrans. Utilizamos ela no aplicativo do Busao, comentado no post.
Mauro
12. dez, 2012
Fala ai, parabéns pelo Post, gostaria de saber se consigo fazer o inverso, enviar uma requisição a uma URL com dados JSon para o Servidor recebe-lo. Um POST com JSon.
Obrigado!
welton
06. fev, 2013
Amigo estou com um problema:
no codigo :
NSString *nome = [resultados objectForKey:@"nome"];
NSString *descricao = [resultados bjectForKey:@"descricao"];
retorna um erro de NSArray_ como se o NSDictionary resultados não estivesse identificando o os Key tais como “nome”, “descricao” etc.
segui com o seguinte URL http://ondeestaoalbi.herokuapp.com/onibusesNosPontosProximos.json?lat=-23.588453&long=-46.632103
se puder me ajudar ficarei muito agradecido
Lucas Rafael
03. mai, 2013
Diego, parabéns!
Ficou muito bem explicado e o mais interessante de tudo é a sua iniciativa de compartilhar gratuitamente o seu conhecimento.
Obrigado!