Atif Aziz, Scott Mitchell
Fevereiro de 2007
Aplica-se a:
JSON
Ajax
Resumo: Este artigo discute o JavaScript Object Notation (ou JSON), um formato de troca de dados baseado em texto que, por ser padronizado, constitui um formato mais adequado para aplicativos da Web de estilo Ajax. (22 páginas impressas).
Conteúdo
Introdução
Noções básicas sobre notação literal em JavaScript
Comparação entre JSON e XML
Criando e analisando mensagens JSON com JavaScript
Trabalhando com JSON no .NET Framework
Conclusão
Referências
Baixe o código-fonte deste artigo.
Introdução
Ao criar um aplicativo que irá se comunicar com um computador remoto, deve-se escolher um protocolo de formato e troca de dados. Há uma variedade de opções abertas e padronizadas, e a opção ideal depende dos requisitos de aplicativos e da funcionalidade já existente. Por exemplo, serviços da Web baseados em SOAP formatam os dados em carga útil XML encapsulada em envelope SOAP.
Embora XML funcione bem em muitos cenários de aplicativo, ele apresenta algumas desvantagens que o tornam menos preferível que outros. Um dos casos em que XML é, com freqüência, menos preferível são os aplicativos da Web de estilo Ajax. Ajax é uma técnica usada para criar aplicativos da Web interativos que propiciam uma experiência de usuário mais fluida por meio de chamadas leves e fora de banda ao servidor Web, em lugar de postbacks de página inteira. Essas chamadas assíncronas são iniciadas no cliente, por meio de JavaScript, e envolvem formatar dados, enviá-los a um servidor Web e analisar e trabalhar com os dados retornados. Embora a maioria dos navegadores consiga construir, enviar e analisar XML, o JavaScript Object Notation (ou JSON), oferece um formato de troca de dados padronizado mais adequado para aplicativos da Web de estilo Ajax.
JSON é um formato de troca de dados aberto e baseado em texto (consulte o RFC 4627). Assim como o XML, é humanamente legível, independente de plataforma e goza de uma ampla disponibilidade de implementações. Os dados formatados segundo o padrão JSON são leves e podem ser analisados por implementações de JavaScript com uma facilidade surpreendente, o que faz dele um formato de troca de dados ideal para aplicativos da Web de estilo Ajax. Uma vez que, em essência, é um formato de dados, o JSON não se limita apenas a aplicativos da Web em Ajax, podendo ser usado em praticamente qualquer cenário em que aplicativos necessitem trocar ou armazenar informações estruturadas na forma de texto.
Este artigo examina o padrão JSON, sua relação com o JavaScript e o compara ao XML. O Jayrock, uma implementação de JSON de código aberto para .NET, é discutido e são dados exemplos de criação e análise de mensagens JSON em JavaScript e C#.
Noções básicas sobre notação literal em JavaScript
Os literais são usados nas linguagens de programação para expressar literalmente valores fixos, como o valor inteiro constante de 4 ou a cadeia de caracteres “Hello, World” (Olá, mundo). Os literais podem ser usados na maioria das linguagens sempre que se permitir uma expressão, como uma parte de uma condição em uma instrução de controle, um parâmetro de entrada quando uma função é chamada, em atribuição de variável, e assim por diante. Por exemplo, o código em C# e Visual Basic a seguir inicializa a variável x com o valor inteiro constante de 42.
int x = 42; // C#
Dim x As Integer = 42 ' Visual Basic
Linguagens de programação diferentes permitem literais de diferentes tipos. A maioria das linguagens de programação aceita, no mínimo, literais para tipos escalares, como inteiros, números de ponto flutuante, cadeias de caracteres e booleanos. O interessante do JavaScript é que, além dos tipos escalares, há suporte também a literais para tipos estruturados, como matrizes e objetos. Esse recurso permite uma sintaxe concisa para a criação e a inicialização sob demanda de matrizes e objetos.
Literais de matrizes em JavaScript são compostas de zero ou mais expressões, sendo que cada expressão representa um elemento da matriz. Os elementos de matriz encontram-se entre colchetes ([]) e delimitados por vírgulas. O exemplo a seguir define uma matriz literalmente com sete elementos de cadeia de caracteres contendo os nomes dos sete continentes:
var continents = ["Europe", "Asia", "Australia", "Antarctica", "North
America", "South America", "Africa"];
alert(continents[0] + " is one of the " + continents.length + "
continents.");
Agora, compare o código acima ao que seria necessário para criar e inicializar uma matriz em JavaScript sem a notação literal:
var continents = new Array();
continents[0] = "Europe";
continents[1] = "Asia";
continents[2] = "Australia";
continents[3] = "Antarctica";
continents[4] = "North America";
continents[5] = "South America";
continents[6] = "Africa";
Um literal de objeto define os membros de um objeto e seus valores. A lista de membros do objeto e seus valores encontram-se entre chaves ({}), com cada membro delimitado por uma vírgula. Dentro de cada membro, o nome e o valor são delimitados por dois pontos (:). O exemplo a seguir cria um objeto e o inicializa com três membros denominados Address (Endereço), City (Cidade) e PostalCode (Código postal), com os respectivos valores de "123 Anywhere St.", "Springfield" e "99999".
var mailingAddress = {
"Address" : "123 Anywhere St.",
"City" : "Springfield",
"PostalCode" : 99999
};
alert("The package will be shipped to postal code " +
mailingAddress.PostalCode);
Os exemplos apresentados até aqui ilustram o uso de literais numéricos e de cadeias de caracteres dentro de literais de matrizes e de objetos. Também é possível expressar um gráfico inteiro utilizando recursivamente a notação, de modo que os elementos de matriz e os valores dos membros de objetos possam, por sua vez, usar literais de objeto e de matriz. Por exemplo, o código a seguir ilustra um objeto que possui uma matriz como membro (PhoneNumbers), a qual é composta de uma lista de objetos.
var contact = {
"Name": "John Doe",
"PermissionToCall": true,
"PhoneNumbers": [
{
"Location": "Home",
"Number": "555-555-1234"
},
{
"Location": "Work",
"Number": "555-555-9999 Ext. 123"
}
]
};
if (contact.PermissionToCall)
{
alert("Call " + contact.Name + " at " + contact.PhoneNumbers[0].Number);
}
Observação Uma abordagem mais minuciosa do suporte a literais para JavaScript pode ser encontrada no Guia básico de JavaScript 1.5, na seção Literais.
Dos literais de JavaScript ao JSON
O JSON é um formato de troca de dados criado a partir de um subconjunto da notação literal de objetos em JavaScript. Embora a sintaxe aceita pelo JavaScript para valores literais seja bastante flexível, é importante notar que o JSON tem regras muito mais estritas. De acordo com o padrão JSON, por exemplo, o nome de um membro de objeto deve ser uma cadeia de caracteres JSON válida. Uma cadeia de caracteres em JSON deve estar entre aspas duplas. O JavaScript, por outro lado, permite que os nomes de membros de objeto sejam delimitados por aspas duplas ou simples ou até que o sinal seja omitido, desde que o nome do membro não entre em conflito com uma palavra-chave de JavaScript reservada. Igualmente, os elementos de matriz ou os valores de membro de objeto em JSON se restringem a um conjunto bastante limitado. Em JavaScript, contudo, os elementos de matriz e os valores de membro de objeto podem se referir a praticamente qualquer expressão JavaScript válida, inclusive chamadas de função e definições!
O charme do JSON está na sua simplicidade. Uma mensagem formatada segundo o padrão JSON compõe-se de um único objeto ou matriz de nível alto. Os elementos de matriz e os valores de objeto podem ser objetos, matrizes, cadeias de caracteres, números, valores booleanos (verdadeira ou falso) ou nulos. Isso, em poucas palavras, é o padrão JSON! É mesmo simples assim. Consulte www.json.org ou o RFC 4627 para obter uma descrição mais formal do padrão.
Um dos pontos fracos do JSON é a falta de literal para data/hora. Muitas pessoas ficam surpresas e desapontadas ao descobrir isto em seu primeiro encontro com o JSON. A explicação pura e simples (consoladora ou não) para a ausência de literal para data/hora é que o JavaScript também jamais teve um: o suporte a valores de data e hora em JavaScript é inteiramente oferecido pelo objeto Date. A maioria dos aplicativos que usam JSON como formato de dados, portanto, tendem geralmente a usar uma cadeia de caracteres ou um número para expressar valores de data e hora. Se for usada uma cadeia de caracteres, normalmente se espera que ela esteja em formato ISO 8601. Se um número for usado, porém, o valor normalmente representa o número de milissegundos no UTC (Tempo Universal Coordenado) desde a época, onde época é definida como a meia-noite de 1° de janeiro de 1970 (UTC). Trata-se, mais uma vez, de mera convenção, e não de parte do padrão JSON. Se trocar dados com outro aplicativo, você terá que verificar sua documentação para saber como ele codifica valores de data e hora dentro de um literal de JSON. Por exemplo, o ASP.NET AJAX da Microsoft não usa nenhuma das convenções descritas. Em vez disso, ele codifica valores DateTime de .NET como cadeia de caracteres de JSON, cujo conteúdo é \/Date(ticks)\/ e onde ticks representa os milissegundos desde a época (UTC). Assim, 29 de novembro de 1989, 4:55:30 AM em UTC é codificado como "\/Date(628318530718)\/".
ASP.NET AJAX: dentro de cadeia de caracteres JSON para data e hora
O serializador AJAX JSON em ASP.NEXT codifica uma instância de DateTime como cadeia de caracteres JSON. Em seus ciclos de pré-lançamento, o ASP.NET AJAX usava o formato "@ticks@", onde ticks representa o número de milissegundos desde 1° de janeiro de 1970 no UTC. Uma data e hora em UTC, como 29 de novembro de 1989, 4:55:30 AM, seria escrito como "@62831853071@". Embora seja simples e direto, este formato não consegue diferenciar entre um valor de data e hora serializado e uma cadeia de caracteres que parece uma data serializada, mas que não deve ser desserializada como tal. Por conseqüência, a equipe do ASP.NET AJAX fez uma alteração na versão final para cuidar desse problema, adotando o formato "\/Date(ticks)\/".
O novo formato se baseia em um pequeno truque para reduzir as chances de má interpretação. Em JSON, um caractere de barra (/) em uma cadeia de caracteres pode ser ignorado mediante uma barra invertida (\), ainda que não seja estritamente necessário. Aproveitando-se disso, a equipe do ASP.NET AJAX modificou o serializador JavaScriptSerializer de modo a escrever uma instância DateTime na forma da cadeia de caracteres "\/Date(ticks)\/". A evasão das duas barras normais é superficial, mas significativa para o JavaScriptSerializer. Pelas regras do JSON, "\/Date(ticks)\/" é tecnicamente equivalente a "/Date(ticks)/", mas o JavaScriptSerializer desserializará o primeiro como DateTime e o segundo como String. As chances de ambigüidade são, portanto, consideravelmente menores quando comparadas ao formato "@ticks@" mais simples das versões de pré-lançamento.
Comparação entre JSON e XML
Tanto JSON quanto XML podem ser usados para representar objetos nativos e na memória em um formato de troca de dados baseado em texto e humanamente legível. Além disso, os dois formatos de troca de dados são isomórficos: dado um texto em um dos formatos, um equivalente é concebível no outro. Por exemplo, ao chamar um dos Web services publicamente acessíveis do Yahoo!, é possível indicar, via parâmetro “querystring”, se a resposta deve ser formatada como XML ou JSON. Portanto, a decisão sobre um formato de troca de dados não é uma simples questão de escolher este ou aquele como uma solução genial, mas, na verdade, saber qual formato possui as características que o tornam mais apropriados para um aplicativo em particular. Por exemplo, o XML tem suas raízes na marcação de texto de documento e tende a brilhar nesse espaço (como fica evidente com o XHTML). O JSON, por outro lado, tem suas raízes em tipos e estruturas de linguagem de programação e, por isso, propicia um mapeamento mais natural e prontamente disponível para a troca de dados estruturados. Além desses dois pontos de partida, a tabela a seguir o ajudará a compreender e a comparar as principais características do XML e do JSON.
Principais diferenças de características entre XML e JSON
| Característica | XML | JSON |
|---|
| Tipos de dados | Não oferece nenhuma noção de tipos de dados. A adição de informações de tipo depende de Esquema XML. | Oferece tipos de dados escalares e a capacidade de expressar dados estruturados através de matrizes e objetos. |
| Suporte a matrizes | As matrizes devem ser expressas por convenções; por exemplo, pelo uso de um elemento externo de espaço reservado que modele o conteúdo das matrizes como elementos internos. Normalmente, o elemento externo usa a forma plural do nome utilizado para elementos internos. | Suporte nativo a matrizes. |
| Suporte a objetos | Os objetos devem ser expressos por convenções, muitas vezes, por um uso combinado de atributos e elementos. | Suporte nativo a objetos. |
| Suporte a valores nulos | Requer o uso de xsi:nil nos elementos em um documento de instância XML, mais a importação do namespace correspondente. | Reconhece nativamente o valor null (nulo). |
| Comentários | Suporte nativo e geralmente disponíveis através de APIs. | Sem suporte. |
| Namespaces | Tem suporte a namespaces, o que elimina o risco de conflitos de nome ao combinar documentos. Os namespaces também permitem a extensão segura dos padrões baseados em XML existentes. | Não possui o conceito de namespace. Os conflitos de nomenclatura são evitados, normalmente, pelo aninhamento de objetos ou pelo uso de um prefixo em um nome de membro do objeto (na prática, é preferível o primeiro). |
| Decisões de formatação | Complexo. Requer maior esforço para se decidir como mapear tipos de aplicativos para elementos e atributos XML. Pode criar debates exaltados para discutir se é melhor uma abordagem centrada em elementos ou em atributos. | Simples. Oferece um mapeamento muito mais direto de dados de aplicativo. A única exceção pode ser a ausência de literal para data/hora. |
| Tamanho | Os documentos tendem a serem maiores, especialmente quando usada uma abordagem de formatação centrada em elementos. | A sintaxe é bastante concisa e produz texto formatado em que a maior parte do espaço é consumido (corretamente, portanto) pelos dados representados. |
| Análise em JavaScript | Requer uma implementação XML DOM e código de aplicativo adicional para mapear texto de volta para objetos JavaScript. | Não requer código de aplicativo adicional para analisar texto; pode usar a função eval do JavaScript. |
| Curva de aprendizado | Geralmente, tende a exigir o uso de várias tecnologias conjugadas: XPath, Esquema XML, XSLT, Namespaces XML, DOM e assim por diante. | Pilha de tecnologia muito simples já familiar aos desenvolvedores com experiência em JavaScript ou outras linguagens de programação dinâmicas. |
O JSON é um formato de troca de dados relativamente novo e não tem os anos de aceitação ou suporte do fornecedor de que goza, hoje, o XML (embora o JSON esteja indo rapidamente nessa direção). A tabela a seguir destaca o estado atual na área do XML e do JSON.
Diferenças de suporte entre XML e JSON
| Suporte | XML | JSON |
|---|
| Ferramentas | Possui um conjunto maduro de ferramentas amplamente disponibilizadas por vários fornecedores do mercado. | Suporte escasso a ferramentas sofisticadas, como editores e formatadores. |
| Microsoft .NET Framework | Suporte muito bom e maduro desde a versão 1.0 do .NET Framework. O suporte a XML está disponível como parte da BCL (Base Class Library). Para ambientes não gerenciados, há o MSXML. | Nada por enquanto, exceto uma implementação inicial como parte do ASP.NET AJAX. |
| Plataforma e idioma | Analisadores e formatadores estão amplamente disponíveis em muitas plataformas e idiomas (implementações comerciais e de código aberto). | Analisadores e formatadores já estão amplamente disponíveis em muitas plataformas e em vários idiomas. Consulte json.org para obter um bom conjunto de referências. A maioria das implementações até agora tende a ser projetos de código aberto. |
| Idioma integrado | Os fornecedores do mercado, atualmente, estão fazendo experiências com suporte literal nos idiomas. Consulte o Projeto LINQ da Microsoft para obter mais informações. | Tem suporte nativo apenas em JavaScript/ECMAScript. |
Observação Nenhuma destas tabelas pretende ser uma lista abrangente de pontos comparativos. Há outros aspectos sob os quais ambos os formatos de dados podem ser comparados, mas nós achamos que estes pontos principais devem ser suficientes para produzir uma impressão inicial.
Criando e analisando mensagens JSON com JavaScript
Quando se usa o JSON como formato de troca de dados, duas tarefas comuns transformam uma representação nativa e na memória em sua representação textual JSON, e vice-versa. Infelizmente, no momento da escrita, o JavaScript não oferece funções internas para a criação de texto JSON a partir de um dado objeto ou matriz. Espera-se que esses métodos sejam incluídos na quarta edição do padrão ECMAScript, em 2007. Enquanto essas funções de formatação JSON não se encontram formalmente adicionadas ao JavaScript e amplamente disponíveis em implementações populares, use o script de implementação de referência, disponíveis para download em http://www.json.org/json.js.
Em sua iteração mais recente, na época em que este artigo foi escrito, o script json.js em www.json.org adiciona funções toJSONString() a matrizes, cadeias de caracteres, booleanos, objetos e outros tipos de JavaScript. As funções toJSONString() para tipos escalares (como números e booleanos) são bastante simples, já que precisam retornar apenas uma representação em forma de cadeia de caracteres do valor da instância. A função toJSONString() para o tipo Boolean (booleano), por exemplo, retorna a cadeia de caracteres "true", se o valor for verdadeiro, e "false", se for falso. As funções toJSONString()para os tipos Array (matriz) e Object (objeto) são mais interessantes. Para instâncias de matriz, a função toJSONString() de cada elemento contido é chamada em seqüência, e os resultados são concatenados com vírgulas a delimitá-los. O resultado final encontra-se entre colchetes. Da mesma forma, para instâncias de objeto, cada membro é enumerado e sua função toJSONString(), invocada. O nome do membro e a representação JSON de seu valor são concatenados com dois pontos (:) no meio; cada par de nome de membro e valor é delimitado por uma vírgula e o resultado total é posto entre chaves.
O saldo das funções toJSONString() é que qualquer tipo pode ser convertido em seu formato JSON com uma única chamada de função. O JavaScript a seguir cria um objeto Array (matriz) e adiciona, deliberadamente, sete elementos String (cadeia de caracteres) , utilizando o método detalhado e não literal, a título de ilustração. Em seguida, ele segue adiante para exibir a representação JSON da matriz:
// josn.js must be included prior to this point
var continents = new Array();
continents.push("Europe");
continents.push("Asia");
continents.push("Australia");
continents.push("Antarctica");
continents.push("North America");
continents.push("South America");
continents.push("Africa");
alert("The JSON representation of the continents array is: " +
continents.toJSONString());
.gif)
Figura 1. A função toJSONString() emite a matriz formatada segundo o padrão JSON.
Analisar texto JSON é ainda mais simples. Como o JSON é meramente um subconjunto de literais JavaScript, ele pode ser analisado em uma representação na memória por meio da função eval(expr), tratando o texto JSON de origem como código-fonte JavaScript. A função eval aceita como entrada uma cadeia de caracteres de código JavaScript válido e avalia a expressão. Por conseqüência, esta única linha de código é o que basta para transformar texto JSON em uma representação nativa:
var value = eval( "(" + jsonText + ")" );
Observação Os parênteses extras são usados para fazer com que eval trate a entrada de origem como uma expressão, incondicionalmente. Isso é importante, especialmente, para objetos. Se você tentar chamar eval com uma cadeia de caracteres contendo texto JSON que define um objeto, como a cadeia de caracteres "{}" (representando um objeto vazio), ele simplesmente retornará indefinido como resultado analisado. Os parênteses obrigam o analisador de JavaScript a enxergar as chaves de alto nível como notação literal de uma instância de Object, em vez de, digamos, chaves definindo um bloco de instruções. Conseqüentemente, o mesmo problema não ocorre se o item de alto nível for uma matriz, como em eval("[1,2,3]"). Por questão de uniformidade, contudo, o texto JSON deve sempre estar entre parênteses antes de eval ser chamado, para que não haja ambigüidade na interpretação do código.
Quando se avalia notação literal, uma instância correspondente à sintaxe literal é retornada e atribuída a value (valor). Considere o exemplo a seguir, que usa a função eval para analisar a notação literal de uma matriz e atribuir a matriz resultante à variável continents (continentes).
var arrayAsJSONText = '["Europe", "Asia", "Australia", "Antarctica",
"North America", "South America", "Africa"]';
var continents = eval( arrayAsJSONText );
alert(continents[0] + " is one of the " + continents.length + "
continents.");
Evidentemente, na prática, o texto JSON avaliado virá de alguma origem externa, em vez de estar embutido em código, como no caso acima.
A função eval avalia indistintamente qualquer que seja a expressão que lhe é transmitida. Uma origem pouco confiável poderia, portanto, incluir JavaScript potencialmente perigoso misturado ou ao longo da notação literal que constitui os dados JSON. Em cenários nos quais se desconfia da origem, é altamente recomendável analisar o texto JSON por meio da função parseJSON() (encontrada em json.js):
// Requires json.js
var continents = arrayAsJSONText.parseJSON();
A função parseJSON() também usa eval, mas apenas se a cadeia de caracteres contida em arrayAsJSONText estiver em conformidade com o padrão de texto JSON. Ela faz isso por meio de um inteligente teste de expressão regular.
Trabalhando com JSON no .NET Framework
O texto JSON pode ser facilmente criado e analisado a partir de código JavaScript, o que é parte de seu encanto. Entretanto, quando JSON é usado em um aplicativo da Web ASP.NET, apenas o navegador dispõe de suporte a JavaScript, já que o código do servidor deve estar escrito, muito provavelmente, em Visual Basic ou C#.
A maioria das bibliotecas Ajax criadas para ASP.NET oferecem suporte para criação e análise programáticas de texto JSON. Logo, para trabalhar com JSON em um aplicativo .NET, considere usar uma dessas bibliotecas. Há uma grande variedade de opções de código aberto e de terceiros, e a Microsoft também tem sua própria biblioteca Ajax, denominada ASP.NET AJAX.
Neste artigo, examinaremos exemplos que usam Jayrock, uma implementação de código aberto do JSON para Microsoft .NET Framework, criada por Atif Aziz, co-autor deste texto. Preferimos usar Jayrock em lugar de ASP.NET AJAX por três motivos:
- O Jayrock tem código aberto, o que possibilita estendê-lo ou personalizá-lo conforme a necessidade.
- O Jayrock pode ser usado em aplicativos ASP.NET 1.x, 2.0 e Mono, enquanto que o ASP.NET AJAX serve apenas para ASP.NET versão 2.0.
- O escopo do Jayrock se limita a JSON e JSON-RPC, sendo que o primeiro é o foco principal deste artigo. Embora o ASP.NET AJAX contenha algum suporte à criação e à análise de texto JSON, sua principal finalidade é oferecer uma plataforma sofisticada para a criação de aplicativos da Web de estilo Ajax ponta a ponta em ASP.NET. Os adornos e enfeites podem causar distração quando o foco central é o JSON.
Trabalhar com JSON em .NET usando o Jayrock é semelhante a trabalhar com XML através das classes XmlWriter, XmlReader e XmlSerializer no .NET Framework. As classes JsonWriter, JsonReader, JsonTextWriter e JsonTextReader encontradas no Jayrock imitam a semântica das classes .NET Framework XmlWriter, XmlReader, XmlTextWriter e XmlTextReader. Essas classes são úteis para fazer interface com JSON em um nível baixo e orientado por fluxo. Usando essas classes, é possível criar ou analisar texto JSON gradativamente, através de uma série de chamadas de método. Por exemplo, usar o método WriteNumber(number) da classe JsonWriter faz com que seja escrita a representação em cadeia de caracteres apropriada de number (número), de acordo com o padrão JSON. A classe JsonConvert oferece métodos de exportação e importação (Export e Import) para converter entre tipos .NET e JSON. Esses métodos proporcionam uma funcionalidade similar àquela encontrada nos métodos Serialize e Deserialize, respectivamente, da classe XmlSerializer.
Criando texto JSON
O código a seguir ilustra o uso da classe JsonTextWriter para criar o texto JSON de uma matriz de continentes em cadeia de caracteres. Esse texto JSON é enviado a uma instância de TextWriter transmitida ao construtor, que, por acaso, é o fluxo de saída do console neste exemplo (em ASP.NET, você pode usar Response.Output em seu lugar):
using (JsonTextWriter writer = JsonTextWriter(Console.Out))
{
writer.WriteStartArray();
writer.WriteString("Europe");
writer.WriteString("Asia");
writer.WriteString("Australia");
writer.WriteString("Antarctica");
writer.WriteString("North America");
writer.WriteString("South America");
writer.WriteString("Africa");
writer.WriteEndArray();
}
Além dos métodos WriteStartArray, WriteString e WriteEndArray, a classe JsonWriter dispõem de métodos para escrever outros tipos de valor JSON, como WriteNumber, WriteBoolean, WriteNull e assim por diante. Os métodos WriteStartObject, WriteEndObject e WriteMember criam texto JSON para um objeto. O exemplo a seguir ilustra a criação de texto JSON para o objeto de contato examinado na seção "Noções básicas sobre notação literal em JavaScript":
private static void WriteContact()
{
using (JsonWriter writer = new JsonTextWriter(Console.Out))
{
writer.WriteStartObject(); // {
writer.WriteMember("Name"); // "Name" :
writer.WriteString("John Doe"); // "John Doe",
writer.WriteMember("PermissionToCall"); // "PermissionToCall"
:
writer.WriteBoolean(true); // true,
writer.WriteMember("PhoneNumbers"); // "PhoneNumbers" :
writer.WriteStartArray(); // [
WritePhoneNumber(writer, // {
"Location": "Home",
"Home", "555-555-1234"); // "Number":
"555-555-1234" },
WritePhoneNumber(writer, // {
"Location": "Work",
"Work", "555-555-9999 Ext. 123"); // "Number":
"555-555-9999 Ext. 123" }
writer.WriteEndArray(); // ]
writer.WriteEndObject(); // }
}
}
private static void WritePhoneNumber(JsonWriter writer, string location,
string number)
{
writer.WriteStartObject(); // {
writer.WriteMember("Location"); // "Location" :
writer.WriteString(location); // "...",
writer.WriteMember("Number"); // "Number" :
writer.WriteString(number); // "..."
writer.WriteEndObject(); // }
}
Os métodos Export e ExportToString, na classe JsonConvert, podem ser usados para serializar em texto JSON um tipo .NET especifico. Por exemplo, em vez de criar manualmente o texto JSON da matriz de sete continentes utilizando a classe JsonTextWriter, esta chamada a JsonConvert.ExportToString produz os mesmos resultados:
string[] continents = {
"Europe", "Asia", "Australia", "Antarctica", "North America", "South
America", "Africa"
};
string jsonText = JsonConvert.ExportToString(continents);
Analisando texto JSON
A classe JsonTextReader oferece uma variedade de métodos para analisar os tokens de texto JSON, sendo que o método básico é Read. Toda vez que o método Read é chamado, o analisador consome o token seguinte, que pode ser um valor de cadeia de caracteres ou de número, um nome de membro de objeto, o início de uma matriz, e assim por diante. Quando aplicável, o texto analisado do token atual pode ser acessado via propriedade Text. Por exemplo, se o leitor decidir se basear em dados booleanos, a propriedade Text irá retornar "true" (verdadeiro) ou "false" (falso) dependendo do valor de análise real.
O código a seguir usa a classe JsonTextReader para analisar a representação de texto JSON de uma matriz de cadeia de caracteres que contém os nomes dos sete continentes. Cada continente que começa com a letra "A" é enviado ao console:
string jsonText = @"[""Europe"", ""Asia"", ""Australia"", ""Antarctica"",
""North America"", ""South America"", ""Africa""]";
using (JsonTextReader reader = new JsonTextReader(new
StringReader(jsonText)))
{
while (reader.Read())
{
if (reader.TokenClass == JsonTokenClass.String &&
reader.Text.StartsWith("A"))
{
Console.WriteLine(reader.Text);
}
}
}
Observação A classe JsonTextReader em Jayrock é um analisador de texto JSON bastante liberal. Ela, de fato, permite muito mais sintaxe do que a considerada texto JSON válido segundo as regras dispostas no RFC 4627. Por exemplo, a classe JsonTextReader permite que comentários de uma única ou várias linhas apareçam no texto JSON, como se poderia esperar em JavaScript. Comentários de linha única começam com barra-barra (//) e comentários de várias linhas começam com barra-asterisco (/*) e terminam com asterisco-barra (*/). Comentários de linha única também podem começar com o sinal de jogo-da-velha (#), comum em arquivos de configuração de estilo Unix. Em todos os casos, os comentários são inteiramente ignorados pelo analisador e nunca são expostos pela API. Além disso, como em JavaScript, a classe JsonTextReader permite que uma cadeia de caracteres JSON seja delimitada por um apóstrofo ('). O analisador pode tolerar até mesmo uma vírgula extra após o último membro de um objeto ou elemento de uma matriz.
Apesar de todos esses acréscimos, JsonTextReader é um analisador em conformidade! JsonTextWriter, por outro lado, produz apenas texto JSON estritamente em conformidade com o padrão. Tudo isto segue o que, com freqüência, se cunha como a essência da robustez, que recomenda: "Seja conservador no que você faz; seja liberal no que aceita dos outros".
Para converter texto JSON diretamente em um objeto .NET, use o método de importação da classe JsonConvert, especificando o tipo de saída e o texto JSON. O exemplo a seguir mostra a conversão de uma matriz de cadeias de caracteres JSON em uma matriz de cadeias de caracteres .NET:
string jsonText = @"[""Europe"", ""Asia"", ""Australia"", ""Antarctica"",
""North America"", ""South America"", ""Africa""]";
string[] continents = (string[]) JsonConvert.Import(typeof(string[]),
jsonText);
Veja abaixo um exemplo mais interessante de conversão, que apanha um feed RSS XML, desserializa-o em um tipo .NET utilizando XmlSerializer e, em seguida, converte o objeto em texto JSON por meio de JsonConvert (convertendo, efetivamente, o RSS em XML em texto JSON):
XmlSerializer serializer = new XmlSerializer(typeof(RichSiteSummary));
RichSiteSummary news;
// Get the MSDN RSS feed and deserialize it...
using (XmlReader reader = XmlReader.Create("http://msdn.microsoft.com/rss.xml"))
{
news = (RichSiteSummary) serializer.Deserialize(reader);
}
// Export the RichSiteSummary object as JSON text, emitting the output to
Console.Out
using (JsonTextWriter writer = new JsonTextWriter(Console.Out))
{
JsonConvert.Export(news, writer);
}
Observação A definição de RichSiteSummary e seus tipos relacionados pode ser encontrada nos exemplos que acompanham este artigo.
Usando JSON em ASP.NET
Depois de ter examinado maneiras de trabalhar com JSON em JavaScript e a partir do .NET Framework usando Jayrock, é hora de passarmos a um exemplo prático de onde e como todo este conhecimento pode ser aplicado. Considere o recurso de retorno de chamada de script do cliente em ASP.NET 2.0, que simplifica o processo de efetuar chamadas fora de banda do navegador para a página ASP.NET (ou para um controle em particular na página). Durante um típico cenário de retorno de chamada, o script do cliente no navegador empacota e envia dados de volta ao servidor Web para a execução de algum processamento por um método do servidor. Após receber os dados de resposta do servidor, o cliente utiliza-os para atualizar a exibição do navegador.
Observação Mais informações podem ser encontradas no artigo Retornos de chamada de scripts no ASP.NET 2.0 da MSDN Magazine.
O desafio em um cenário de retorno de chamada do cliente é que o cliente e o servidor podem remeter a cadeia de caracteres apenas para frente e para trás. Portanto, as informações a serem trocadas devem ser convertidas, de sua representação nativa e na memória para uma cadeia de caracteres, para que possam ser enviadas e, quando recebidas, analisadas e reconvertidas para sua forma inicial. O recurso de retorno de chamada de script do cliente do ASP.NET 2.0 não requer um formato de cadeia de caracteres em particular para os dados trocados, nem oferece nenhuma funcionalidade interna para a conversão entre as representações nativa/na memória e cadeia de caracteres; cabe ao desenvolvedor implementar a lógica de conversão com base em algum formato de troca de dados de sua preferência.
O exemplo a seguir ilustra como usar JSON como formato de troca de dados em um cenário de retorno de chamada de script do cliente. Em particular, o exemplo consiste em uma página ASP.NET que usa dados do banco de dados Northwind para fornecer uma listagem das categorias em uma lista suspensa; os produtos na categoria selecionada são exibidos em uma lista com marcadores (consulte a Figura 3). Sempre que a lista suspensa é alterada no cliente, é feito um retorno de chamada que transmite uma matriz cujo único elemento é o CategoryID selecionado.
Observação Estamos transmitindo uma matriz que contém o CategoryID selecionado como seu único elemento (em vez de transmitir, simplesmente, o CategoryID) porque o padrão JSON exige que todo texto JSON tenha um objeto ou matriz como raiz. Obviamente, não é necessário que o cliente transmita o texto JSON ao servidor — nós poderíamos ter dado como exemplo a transmissão somente do CategoryID selecionado como cadeia de caracteres. Nós, contudo, queríamos demonstrar o envio de texto JSON nas mensagens tanto de solicitação, como de resposta do retorno de chamada.
O código a seguir no manipulador de eventos Page_Load configura o controle Web Categories DropDownList de modo que, quando alterado, a função GetProductsForCategory seja chamada e transmita o valor das listas suspensas selecionadas. Essa função inicia o retorno de chamada de script do cliente, caso o valor de lista suspensa transmitido seja maior que zero:
// Add client-side onchange event to drop-down list
Categories.Attributes["onchange"] = "Categories_onchange(this);";
// Generate the callback script
string callbackScript = ClientScript.GetCallbackEventReference(
/* control */ this,
/* argument */ "'[' + categoryID + ']'",
/* clientCallback */ "showProducts",
/* context */ "null");
// Add the Categories_onchange function
ClientScript.RegisterClientScriptBlock(GetType(),
"Categories_onchange", @"
function Categories_onchange(sender)
{
clearResults();
var categoryID = sender.value;
if (categoryID > 0)
{
" + callbackScript + @"
}
}", true);
O método GetCallBackEventReference na classe ClientScriptManager, utilizado para gerar o código JavaScript que invoca o retorno de chamada, tem a seguinte assinatura:
public string GetCallbackEventReference (
Control control,
string argument,
string clientCallback,
string context,
)
O parâmetro argument especifica quais dados são enviados do cliente para o servidor Web durante o retorno de chamada, e o parâmetro clientCallback especifica o nome da função do cliente a ser invocada ao término do retorno de chamada (showProducts). O método GetCallBackEventReference gera o seguinte código JavaScript e o adiciona à marcação processada:
WebForm_DoCallback('__Page','[' + categoryID +
']',showProducts,null,null,false)
'[' + categoryID + ']'é o valor transmitido ao servidor durante o retorno de chamada (uma matriz com um único elemento, categoryID) e showProducts é a função JavaScript executada quando volta o retorno de chamada.
No servidor, o método executado em resposta ao retorno de chamada usa a classe JsonConvert do Jayrock para analisar o texto JSON de entrada e formatar o texto JSON de saída. Em particular, os nomes dos produtos associados à categoria selecionada são recuperados e retornados na forma de matriz de cadeia de caracteres.
// Deserialize the JSON text into an array of integers
int[] args = (int[]) JsonConvert.Import(typeof(int[]), eventArgument);
// Read the selected CategoryID from the array
int categoryID = args[0];
// Get products based on categoryID
NorthwindDataSet.ProductsRow[] rows = Northwind.Categories.FindByCategoryID(categoryID).GetProductsRows();
// Load the names into a string array
string[] productNames = new string[rows.Length];
for (int i = 0; i < rows.Length; i++)
{
productNames[i] = rows[i].ProductName;
}// Serialize the string array as JSON text and return it to the clientreturn JsonConvert.ExportToString(productNames);
Observação A classe JsonConvert é usada duas vezes: primeiro, para converter o texto JSON de eventArgument em uma matriz de inteiros e, depois, para converter a matriz de cadeia de caracteres productNames em texto JSON a ser retornado ao cliente. Alternativamente, poderíamos ter usado aqui as classes JsonReader e JsonWriter, mas JsonConvert faz o mesmo trabalho muito bem quando os dados envolvidos são relativamente pequenos e facilmente mapeáveis para tipos existentes.
Quando os dados são retornados do servidor, a função JavaScript especificada pelo método GetCallBackEventReference é chamada e transmite o valor de retorno. Esse método JavaScript, showProducts, começa fazendo referência ao elemento <div>ProductOutput. Em seguida, ele analisa a resposta JSON e adiciona dinamicamente uma lista não ordenada, com um item na lista para cada elemento de matriz. Se nenhum produto for retornado para a categoria selecionada, uma mensagem correspondente é exibida.
function showProducts(arg, context)
{
// Dump the JSON text response from the server.
document.forms[0].JSONResponse.value = arg;
// Parse JSON text returned from callback.
var categoryProducts = eval("(" + arg + ")");
// Get a reference to the <div> ProductOutput.
var output = document.getElementById("ProductOutput");
// If no products for category, show message.
if (categoryProducts.length == 0)
{
output.appendChild(document.createTextNode("There are no products
for this category..."));
}
else
{
// There are products, display them in an unordered list.
var ul = document.createElement("ul");
for (var i = 0; i < categoryProducts.length; i++)
{
var product = categoryProducts[i];
var li = document.createElement("li");
li.appendChild(document.createTextNode(product));
ul.appendChild(li);
}
output.appendChild(ul);
}
}
A Figura 2 ilustra a seqüência de eventos, enquanto que a Figura 3 mostra este exemplo em ação; o código completo está incluído no download deste artigo.
.gif)
Figura 2: O cliente envia o CategoryID selecionado como elemento único em uma matriz e o servidor retorna uma matriz de nomes de produtos associados.
.gif)
Figura 3: Os produtos são exibidos em uma lista com marcadores, dentro da categoria selecionada.
Conclusão
O JSON é um formato de troca de dados leve e baseado em texto, criado a partir de um subconjunto da notação literal da linguagem de programação JavaScript. Ele oferece codificação sucinta para estrutura de dados de aplicativos e é usado normalmente em cenários nos quais há implementação JavaScript disponível para um ou ambos os aplicativos que trocam dados, como os aplicativos da Web de estilo Ajax. O encanto do JSON está em sua simplicidade de compreensão, adoção e implementação. O JSON na prática não apresenta curva de aprendizado para desenvolvedores já familiarizados com JavaScript ou outras linguagens de programação com suporte similar a notação literal sofisticada (como Python e Ruby). A análise de texto JSON em código JavaScript pode ser realizada através de uma simples chamada à função eval, e criar texto JSON é moleza com o script json.js disponibilizado em http://www.json.org/json.js.
Há um número crescente de bibliotecas para se trabalhar com JSON em todas as principais plataformas e estruturas. Neste artigo, nós examinamos o Jayrock, uma biblioteca de código aberto para a criação e a análise de texto JSON em aplicativos .NET. O Jayrock pode ser usado em aplicativos ASP.NET 1.x, 2.0 e Mono. O ASP.NET AJAX oferece funcionalidade JSON similar, mas apenas para aplicativos ASP.NET 2.0.
Boa programação!
Ajax ou AJAX?
O termo Ajax foi cunhado, inicialmente, por Jesse James Garrett para descrever o estilo de aplicativos da Web e o conjunto de tecnologias envolvidas na criação de aplicativos da Web altamente interativos. Historicamente, o termo Ajax se disseminou pela Web como o acrônimo AJAX, que significa JavaScript e XML assíncronos (AJAX, Asynchronous JavaScript And XML). Com o tempo, no entanto, as pessoas imaginaram que o "X" do AJAX não era muito representativo do formato de dados subjacente utilizado para a comunicação com o servidor Web em segundo plano, já que a maioria das implementações estavam mudando para JSON, por ser uma alternativa mais simples e eficaz. Assim, em vez de mudar o acrônimo para AJAJ, que trava um pouco a língua, o acrônimo AJAX vem sendo geralmente aposentado para dar lugar ao termo Ajax.
No momento da redação deste artigo, podemos esperar um uso misto e amplo de "AJAX" e "Ajax" significando a mesmíssima coisa. Aqui, adotamos o "termo Ajax". Produtos comerciais que fornecem estruturas para habilitar aplicativos de estilo Ajax, entretanto, tendem a usar a forma de sigla para distingui-los de um produto de limpeza de mesmo nome e evitar quaisquer problemas legais ou de marca comercial.
Referências
Agradecimentos especiais
Antes de este artigo ser enviado à MSDN, nós contamos com uma série de voluntários que ajudaram a revisar as provas e forneceram comentários sobre conteúdo, gramática e objetividade. Os principais colaboradores do processo de revisão foram Douglas Crockford, Eric Schönholzer e Milan Negovan.
Sobre os autores
Atif Aziz é consultor-chefe da Skybow AG, onde seu foco principal é ajudar os clientes a compreenderem e a criarem soluções na plataforma de desenvolvimento .NET. Atif colabora regularmente com a comunidade de desenvolvedores da Microsoft, dando palestras em conferências e escrevendo artigos para publicações técnicas. Ele é palestrante da INETA e presidente do maior Grupo de usuários .NET da Suíça. Ele pode ser contatado através do seguinte web site: http://www.raboof.com.
Scott Mitchell , autor de seis livros sobre ASP/ASP.NET e fundador da 4GuysFromRolla.com, trabalha com tecnologias Web Microsoft desde 1998. Scott é consultor autônomo, treinador e escritor. Ele pode ser contatado por meio de seu blog: http://scottonwriting.net/.