16
1 Java API - Manipulando objetos JSON com a JSR 353 Fique por dentro Este artigo irá apresentar a JSR 353, Java API for JSON Processing, que viabiliza e padroniza a criação e leitura de objetos JSON em Java. Veremos no artigo os dois modelos de leitura e geração de conteúdo JSON, conhecidos como modelo de objetos e modelo de streaming. Ao final do artigo criaremos uma aplicação standalone utilizando a JSR como exemplo prático. Com esta nova API o desenvolvedor poderá, rapidamente, produzir e consumir dados em JSON. Sistemas de computadores geralmente não funcionam sozinhos, mas sim se integrando a outros sistemas, ora provendo, ora consumindo dados. Deste modo, os recursos envolvidos para viabilizar essa comunicação, como protocolo, APIs, formato de dados, etc. devem ser analisados quando projetamos qualquer aplicação. Diante de tudo isso, é válido lembrar que, dependendo da solução, podemos aumentar ou reduzir a complexidade dessa interação. Na plataforma Java EE é muito comum o uso do RMI, que é a base do EJB, para interoperabilidade entre componentes de uma mesma aplicação. Mas esse tipo de protocolo binário acaba sendo ineficiente como camada de integração entre softwares distintos, pois cria um acoplamento muito forte entre eles (devido à necessidade de exportar interfaces de serviços e DTOs ver BOX 1 para a aplicação cliente), além de permitir a integração apenas entre softwares que usam esta tecnologia. BOX 1. Data Transfer Object DTO, sigla para Data Transfer Object, representa os objetos utilizados para transferir informações entre camadas da aplicação, ou entre aplicações distintas. Já os protocolos baseados em texto, como o HTTP, permitem que criemos aplicações em qualquer tecnologia/linguagem para servir ou consumir serviços, além de deixar a comunicação mais compreensível para humanos, tornando mais fácil a depuração da troca de mensagens. Com este cenário, os serviços web, apoiados no HTTP, vêm se consolidando como opção para integração de aplicações. Até pouco tempo, o formato XML, aliado ao protocolo SOAP, era predominante no desenvolvimento de web services. O SOAP é uma excelente solução de integração, sendo um protocolo muito bem definido e consolidado. Porém, muitas vezes o excesso de padronização é visto como um obstáculo, pois faz com que a implementação seja mais trabalhosa. É muito comum nos depararmos com problemas simples, e para solucioná-los também é comum estarmos dispostos a abrir mão de tanta padronização em benefício da agilidade. É neste ponto que serviços REST brilham, principalmente usando mensagens no formato JSON. Nos últimos anos, a notação JSON vem se consolidando na preferência dos desenvolvedores como o formato ideal para se trabalhar com web services. Até a versão 6 da plataforma Java EE, no entanto, o Java não contava com uma API padrão para o processamento de JSON.

Java API - Manipulando Objetos JSON Com a JSR 353

Embed Size (px)

Citation preview

Page 1: Java API - Manipulando Objetos JSON Com a JSR 353

1

Java API - Manipulando objetos JSON com a JSR

353

Fique por dentro

Este artigo irá apresentar a JSR 353, Java API for JSON Processing, que viabiliza e padroniza a criação e

leitura de objetos JSON em Java. Veremos no artigo os dois modelos de leitura e geração de conteúdo

JSON, conhecidos como modelo de objetos e modelo de streaming.

Ao final do artigo criaremos uma aplicação standalone utilizando a JSR como exemplo prático. Com esta

nova API o desenvolvedor poderá, rapidamente, produzir e consumir dados em JSON.

Sistemas de computadores geralmente não funcionam sozinhos, mas sim se integrando a outros sistemas, ora

provendo, ora consumindo dados. Deste modo, os recursos envolvidos para viabilizar essa comunicação,

como protocolo, APIs, formato de dados, etc. devem ser analisados quando projetamos qualquer aplicação.

Diante de tudo isso, é válido lembrar que, dependendo da solução, podemos aumentar ou reduzir a

complexidade dessa interação.

Na plataforma Java EE é muito comum o uso do RMI, que é a base do EJB, para interoperabilidade entre

componentes de uma mesma aplicação. Mas esse tipo de protocolo binário acaba sendo ineficiente como

camada de integração entre softwares distintos, pois cria um acoplamento muito forte entre eles (devido à

necessidade de exportar interfaces de serviços e DTOs – ver BOX 1 – para a aplicação cliente), além de

permitir a integração apenas entre softwares que usam esta tecnologia.

BOX 1. Data Transfer Object

DTO, sigla para Data Transfer Object, representa os objetos utilizados para transferir informações entre

camadas da aplicação, ou entre aplicações distintas.

Já os protocolos baseados em texto, como o HTTP, permitem que criemos aplicações em qualquer

tecnologia/linguagem para servir ou consumir serviços, além de deixar a comunicação mais compreensível

para humanos, tornando mais fácil a depuração da troca de mensagens.

Com este cenário, os serviços web, apoiados no HTTP, vêm se consolidando como opção para integração de

aplicações.

Até pouco tempo, o formato XML, aliado ao protocolo SOAP, era predominante no desenvolvimento de

web services. O SOAP é uma excelente solução de integração, sendo um protocolo muito bem definido e

consolidado. Porém, muitas vezes o excesso de padronização é visto como um obstáculo, pois faz com que a

implementação seja mais trabalhosa.

É muito comum nos depararmos com problemas simples, e para solucioná-los também é comum estarmos

dispostos a abrir mão de tanta padronização em benefício da agilidade. É neste ponto que serviços REST

brilham, principalmente usando mensagens no formato JSON.

Nos últimos anos, a notação JSON vem se consolidando na preferência dos desenvolvedores como o

formato ideal para se trabalhar com web services. Até a versão 6 da plataforma Java EE, no entanto, o Java

não contava com uma API padrão para o processamento de JSON.

Page 2: Java API - Manipulando Objetos JSON Com a JSR 353

2

Deste modo, cada projeto utilizava uma implementação própria ou algumas conhecidas no mercado, como é

o caso do Jackson. A JSR 353 vem para preencher esta lacuna, embarcando nos containers Java EE uma

implementação padronizada para criação e leitura de conteúdo JSON.

Este artigo irá apresentar ao leitor a JSR 353 e como usá-la para ler e gerar conteúdos JSON através de duas

técnicas conhecidas como modelo de objetos e modelo de streaming. Depois de apresentar a API, iremos

construir um projeto de consulta de informações climáticas usando um serviço web público que retorna

JSON, como um caso de uso prático da API.

JavaScript Object Notation – JSON

JSON vem sendo largamente adotado principalmente no desenvolvimento de serviços web. Grande parte

deste crescimento se dá pela flexibilidade do formato e também pela facilidade de leitura por humanos. Ao

contrário do XML, JSON é muito menos verboso, pois não possui o sistema de tags, é mais limpo e mais

intuitivo.

Uma informação no formato JSON pode ser apresentada de duas maneiras: como um objeto simples ou

como uma lista (array). Um objeto é uma sequência de pares chave/valor, semelhante a um mapa, sempre

envolvida por chaves ({}). Na estrutura do par chave/valor, a chave é sempre uma String e o valor pode ser

de seis tipos: uma String, um número (inteiro ou decimal), outro objeto, um array, true, false ou null.

Cada chave é separada do valor por dois pontos e os conjuntos chave/valor são separados entre si por

vírgulas. Como o valor pode ser outro objeto, é possível formar uma estrutura hierárquica usando JSON.

Diferentemente do objeto, o array é envolvido por colchetes ([]) e seus elementos são separados por vírgula.

Tais elementos não precisam ter a mesma estrutura ou ser do mesmo tipo.

Em consequência disso, podemos ter um array cujo primeiro elemento é um objeto JSON e o segundo

elemento pode ser outro array. A Listagem 1 mostra um objeto JSON válido que contempla as

possibilidades descritas.

Listagem 1. Exemplo de objeto JSON válido.

{

"editora" : "DevMedia",

"ano" : 2021,

"revista" : "Java Magazine",

"edicao" : 200,

"artigos" : [

{

"titulo" : "JEE21 – Criando um EJB com uma linha de código",

"caracteres" : 23000,

"revisado" : true,

"data" : "21/08/2021"

},

{

"titulo" : "JBrain Programming – Java com comando cerebral",

"caracteres" : 25000,

"revisado" : false,

"data" : null

}

],

"tiragem" : 120000

}

JSR 353

Page 3: Java API - Manipulando Objetos JSON Com a JSR 353

3

De acordo com a especificação, a Java EE 7 provê “uma API para analisar, transformar e fazer consultas a

dados JSON usando um modelo de objetos ou um modelo de streaming”. Na prática isso significa que a API

nos oferece dois modelos para trabalhar com JSON, a saber:

· O modelo de objeto: Neste modelo, tanto na leitura quanto na criação de um objeto JSON a API cria um

objeto em memória, permitindo a leitura de seus dados e também a manipulação, como a alteração de

valores ou a inserção de novos valores na estrutura.

Este modelo consome mais memória que o modelo de streaming, pois exige que o objeto seja carregado de

uma vez para a memória;

· O modelo de streaming: Neste modelo a API disponibiliza um parser baseado em eventos para a leitura

de objetos JSON. Durante o processamento do objeto, eventos do tipo “iniciou um array”, “terminou um

array”, “inicio um objeto”, entre outros, permitem que desenvolvedores tratem apenas os blocos de dados

que lhes interessem, descartando o restante. Devido a esta característica, é um modelo mais econômico para

a memória, sendo indicado para grandes volumes de dados (ou para máquinas com pouca memória).

A especificação define um conjunto de interfaces e classes nos pacotes javax.json e javax.json.stream que

o desenvolvedor precisa utilizar para construir ou ler objetos no formato JSON. Veremos agora esses

principais elementos com uma breve explicação de sua utilização. Mais adiante veremos essas

classes/interfaces em ação, lendo e gerando dados JSON.

· Json: Esta classe é uma fábrica para criar objetos para processar JSON. Através dela obtemos objetos para

criação e leitura de documentos JSON, tanto no modelo de objetos quanto no modelo de streaming;

· JsonArrayBuilder: Esta interface representa um builder de arrays JSON;

· JsonObjectBuilder: Esta interface representa um builder de objetos JSON;

· JsonStructure: Esta é a superinterface dos tipos JsonArray e JsonObject;

· JsonArray: Esta interface representa um array imutável de JSON. O item “artigos” da Listagem 1, por

exemplo, quando lido pela API, é transformado em um JsonArray;

· JsonObject: Esta interface representa um objeto JSON. Cada item da lista de artigos da Listagem 1, por

exemplo, quando lido pela API, é transformado em um JsonObject;

· JsonReader: Esta interface possibilita a leitura de um objeto ou array JSON a partir de uma determinada

fonte;

· JsonGenerator: Esta interface possibilita a geração de objetos JSON para um stream de saída;

· JsonParser: Esta interface provê um mecanismo de leitura de JSON a partir de um stream.

Trabalhando com o Modelo de Objeto

Para criar um objeto JSON usando o modelo de objeto, devemos instanciar um objeto do tipo

javax.json.JsonObjectBuilder, ou do tipo javax.json.JsonArrayBuilder para criar um array.

De acordo com a especificação, estes objetos devem ser criados por objetos builders (Builder Pattern) que

são definidos pela API. Para obter os builders, usamos os métodos estáticos createObjectBuilder() ou

createArrayBuilder() da classe Json.

Page 4: Java API - Manipulando Objetos JSON Com a JSR 353

4

Com o builder em mãos podemos utilizar o método add() sucessivamente para adicionar elementos ao

objeto ou ao array e finalizar a construção usando o método build(), que retorna um JsonObject ou um

JsonArray (ambos são subclasses de JsonStructure).

Os exemplos de código desta seção são para demonstrar o funcionamento da API. Todo o código está

disponível no site da revista e foi desenvolvido usando o Maven para controlar as dependências do projeto e

realizar o build.

Mais adiante, como já informado, iremos expor a criação de um projeto usando a API e como adicionar as

dependências para conseguir executar uma aplicação standalone com tal recurso.

A Listagem 2 mostra a criação de um objeto JSON usando o modelo de objeto.

Listagem 2. Criando um objeto JSON usando o modelo de objeto.

JsonObjectBuilder builder = Json.createObjectBuilder();

builder.add("titulo", "Curtindo a Vida Adoidado");

builder.add("ano", 1986);

builder.add("diretor", "John Hughes");

builder.add("genero", "Comédia");

builder.add("oscar", false);

JsonObject json = builder.build();

System.out.println(json);

Quando executado, um programa com o código da Listagem 2 apresenta o seguinte resultado:

{"titulo":"Curtindo a Vida Adoidado", "ano":1986, "diretor":"John Hughes", "genero":"Comedia",

"oscar":false}

Como explicado anteriormente, o builder JsonObjectBuilder constrói objetos e o builder

JsonArrayBuilder constrói arrays. A Listagem 3 mostra a criação de um array JSON usando o modelo de

objeto.

Listagem 3. Criando um array JSON usando o modelo de objeto.

JsonArrayBuilder builder = Json.createArrayBuilder();

builder.add("Java");

builder.add(true);

builder.add(8176);

builder.addNull();

builder.add(12.33);

JsonArray json = builder.build();

System.out.println(json);

O resultado da execução desse código é:

["Java",true,8176,null,12.33]

Como o leitor deve ter reparado, existem vários métodos add() sobrecarregados, tanto na classe

JsonObjectBuilder quanto na classe JsonArrayBuilder. A invocação de cada um depende dos parâmetros

informados. A Listagem 4 exibe a lista de métodos add() com seus respectivos parâmetros e retornos.

Page 5: Java API - Manipulando Objetos JSON Com a JSR 353

5

Listagem 4. Métodos add() de JsonObjectBuilder e JsonArrayBuilder.

JsonObjectBuilder add(String name, BigDecimal value)

JsonObjectBuilder add(String name, BigInteger value)

JsonObjectBuilder add(String name, boolean value)

JsonObjectBuilder add(String name, double value)

JsonObjectBuilder add(String name, int value)

JsonObjectBuilder add(String name, JsonArrayBuilder builder)

JsonObjectBuilder add(String name, JsonObjectBuilder builder)

JsonObjectBuilder add(String name, JsonValue value)

JsonObjectBuilder add(String name, long value)

JsonObjectBuilder add(String name, String value)

JsonObjectBuilder addNull(String name)

JsonArrayBuilder add(BigDecimal value)

JsonArrayBuilder add(BigInteger value)

JsonArrayBuilder add(boolean value)

JsonArrayBuilder add(double value)

JsonArrayBuilder add(int value)

JsonArrayBuilder add(JsonArrayBuilder builder)

JsonArrayBuilder add(JsonObjectBuilder builder)

JsonArrayBuilder add(JsonValue value)

JsonArrayBuilder add(long value)

JsonArrayBuilder add(String value)

JsonArrayBuilder addNull()

Na construção de um objeto ou de um array JSON, devemos chamar sucessivamente o método add() do

objeto builder para adicionar novos elementos. Toda chamada de add() retorna o próprio builder utilizado na

invocação do método.

Esta característica é chamada de interface fluente, cujo uso reduz sensivelmente a quantidade de código e

facilita a leitura. A Listagem 5 mostra como fica o exemplo da Listagem 3 usando interface fluente.

Listagem 5. Criando um objeto JSON usando o modelo de objeto e interface fluente.

JsonObject json = Json.createObjectBuilder()

.add("titulo", "Curtindo a Vida Adoidado")

.add("ano", 1986)

.add("diretor", "John Hughes")

.add("genero", "Comedia")

.add("oscar", false)

.build();

System.out.println(json);

Podemos também combinar builders de objetos e de arrays para criar objetos com valores do tipo array ou

arrays com elementos do tipo objeto, pois a API permite o aninhamento de objetos e arrays. Este tipo de

situação pode ser útil em casos como o exposto na Listagem 1, no qual temos um objeto na raiz com

informações de uma revista, sendo que uma das chaves, “artigos”, remete a um array e cada item deste array

ainda é outro objeto. A Listagem 6 mostra como fazer o aninhamento.

Listagem 6. Aninhando objetos e arrays em uma estrutura JSON.

JsonObject json = Json.createObjectBuilder()

.add("titulo", "Curtindo a Vida Adoidado")

.add("ano", 1986)

.add("diretor", Json.createObjectBuilder().add("nome", "John hughes").add("pais",

"EUA"))

.add("genero", Json.createArrayBuilder().add("comedia").add("aventura"))

.add("oscar", false)

Page 6: Java API - Manipulando Objetos JSON Com a JSR 353

6

.build();

System.out.println(json);

O resultado da execução do código da Listagem 6 é:

{"titulo":"Curtindo a Vida Adoidado", "ano":1986, "diretor":{"nome":"John hughes", "pais":"EUA"},

"genero":["comedia", "aventura"], "oscar":false}

Usando o Modelo de Objeto para leitura de dados

Anteriormente vimos como criar objetos ou arrays JSON usando a especificação através dos objetos

builders. Além de criar objetos, a API também provê formas de leitura de dados no formato JSON. Para isso,

dispomos do objeto JsonReader.

Para obter um JsonReader, usamos o método estático createReader() da classe Json. Este método recebe

como parâmetro qualquer implementação de reader (da API java.io.Reader), como FileReader,

BufferedReader, etc., e retorna um JsonReader.

A Listagem 7 mostra como realizar a leitura de um JSON usando a API e considerando que o arquivo

json.txt tenha o conteúdo da Listagem 1.

Listagem 7. Leitura do conteúdo JSON de um arquivo usando o modelo de objeto.

JsonReader reader = Json.createReader(new FileReader("/tmp/json.txt"));

JsonObject json = reader.readObject();

String revista = json.getString("revista");

int ano = json.getInt("ano");

JsonArray artigos = json.getJsonArray("artigos");

JsonObject primeiroArtigo = (JsonObject) artigos.get(0);

String titulo = primeiroArtigo.getString("titulo");

System.out.println("Revista: " + revista);

System.out.println("Ano: " + ano);

System.out.println("Primeiro artigo: " + titulo);

É importante complementar que a interface JsonReader disponibiliza três métodos: 1) readObject() –

usado no exemplo – assume que a raiz do conteúdo a ser lido é um objeto e retorna um JsonObject; 2)

readArray() assume que a raiz é um array e retorna um JsonArray; e, 3) read(), que é genérico e retorna

um JsonStructure (superclasse de JsonObject e JsonArray) para situações em que não sabemos o tipo do

conteúdo e devemos tratá-lo no código.

Com o objeto do tipo JsonObject em mãos podemos fazer as leituras como se estivéssemos com um Mapa

de objetos (e estamos, pois JsonObject implementa Map), com a diferença que devemos invocar o método

getter de acordo com o tipo esperado do valor a ser retornado. Por exemplo, se usarmos getString(“nome”)

é porque sabemos que a chave nome irá retornar um valor do tipo String (conteúdo do nome), mas se

usarmos getInt(“idade”) é porque sabemos que a chave idade irá retornar um valor do tipo inteiro.

Outro ponto que podemos verificar na Listagem 7 é que na leitura do atributo artigos invocamos

getJsonArray(), que retorna um JsonArray. Diferente de JsonObject, trabalhar com JsonArray é

equivalente a trabalhar com uma collection, visto que JsonArray implementa a interface Collection. Sendo

assim, podemos usar o método get(index) para buscar um elemento do JsonArray.

Page 7: Java API - Manipulando Objetos JSON Com a JSR 353

7

Usando o Modelo de Streaming para leitura de dados

Como explicado no início do artigo, o modelo de streaming é indicado quando temos grandes volumes de

dados de entrada, mas só nos interessa algumas partes desses dados. Ao contrário do modelo de objeto que

carrega toda a estrutura do JSON lido em memória para que naveguemos pela árvore, o modelo de streaming

oferece uma interface de eventos para que possamos filtrar os conteúdos que nos interessam.

Para usarmos o modelo de streaming invocamos o método estático createParser() da classe Json. Este

método recebe como parâmetro qualquer implementação de InputStream e retorna um objeto do tipo

JsonParser que se comporta como um iterator. Com o parser, invocamos o método hasNext() como

condição de um laço e o next() para ler o próximo evento. A Listagem 8 implementa esta leitura e imprime

o objeto event em cada iteração.

Listagem 8. Leitura do conteúdo JSON de um arquivo usando o modelo de streaming

JsonParser parser = Json.createParser(new FileInputStream("/tmp/json.txt"));

while (parser.hasNext()) {

JsonParser.Event event = parser.next();

System.out.println(event);

}

Considerando que o arquivo /tmp/json.txt tenha o conteúdo do exemplo da Listagem 1, a saída do código

apresentado deve ser idêntica à exibida na Listagem 9. À direita de cada evento foi adicionada uma

explicação do que está sendo iterado.

Listagem 9. Resultado da leitura do JSON usando o modelo de streaming.

START_OBJECT Encontrou o { no início do arquivo, que caracteriza o início de um

objeto

KEY_NAME Encontrou a chave “editora”

VALUE_STRING Encontrou o valor “Devmedia” do tipo STRING

KEY_NAME Encontrou a chave “ano”

VALUE_NUMBER Encontrou o valor 2021 do tipo NUMBER

KEY_NAME Encontrou a chave “revista”

VALUE_STRING Encontrou o valor “Java Magazine” do tipo STRING

KEY_NAME Encontrou a chave “edicao”

VALUE_NUMBER Encontrou o valor 200 do tipo NUMBER

KEY_NAME Encontrou a chave “artigos”

START_ARRAY Encontrou o início do array de artigos

START_OBJECT Encontrou o início do objeto artigo

KEY_NAME Encontrou a chave “titulo”

VALUE_STRING Encontrou o valor “JEE21 – Criando...” do tipo STRING

KEY_NAME Encontrou a chave “caracteres”

VALUE_NUMBER Encontrou o valor 23000 do tipo NUMBER

KEY_NAME Encontrou a chave “revisado”

VALUE_TRUE Encontrou o valor do tipo TRUE

KEY_NAME Encontrou a chave “data”

VALUE_STRING Encontrou o valor “21/08/2021” do tipo STRING

END_OBJECT Encontrou o fim do objeto artigo

.

.

.

END_ARRAY Encontrou o fim do array de artigos

KEY_NAME Encontrou a chave “tiragem”

VALUE_NUMBER Encontrou o valor 12000 do tipo NUMBER

END_OBJECT Encontrou o fim do objeto }

Page 8: Java API - Manipulando Objetos JSON Com a JSR 353

8

A execução do código da listagem deve exibir apenas os tipos de evento. Por exemplo, KEY_NAME

quando encontrou uma chave, ou VALUE_STRING quando encontrou um valor do tipo String. Nesse

momento vale lembrar que um dado em JSON não é nada mais do que uma sucessão de chaves e valores.

Com esse tipo de informação o desenvolvedor consegue filtrar apenas os valores que lhe interessa. Para

obter o valor, devemos usar os métodos get do próprio objeto JsonParser. Estes métodos são responsáveis

por recuperar o valor do objeto encontrado, seja ele uma chave (que sempre será do tipo String) ou o valor

de uma chave, que tem um getter para cada tipo.

A Listagem 10 mostra um exemplo de leitura de um arquivo JSON usando o modelo de streaming que

carrega e exibe os nomes de todas as chaves (primeiro if), valores do tipo String (também no primeiro if) e

valores do tipo Number (segundo if). A condicional para capturar nomes de chaves e valores do tipo String

está agrupada porque ambos usam o mesmo getter para recuperar o valor (getString()). Não faria sentido em

uma aplicação real este tipo de abordagem, mas aqui estamos usando-a apenas como exemplo da sintaxe.

Listagem 10. Lendo valores usando o modelo de streaming

JsonParser parser = Json.createParser(new FileInputStream("/tmp/json.txt"));

while (parser.hasNext()) {

JsonParser.Event event = parser.next();

if (event == Event.KEY_NAME || event == Event.VALUE_STRING) {

System.out.println(parser.getString());

}

if (event == Event.VALUE_NUMBER) {

System.out.println(parser.getLong());

}

}

Repare que invocamos o getString() do parser para chaves e valores String, e o getLong() para valores

numéricos.

Em uma situação mais real, consideremos que o sistema deva ler apenas os nomes dos artigos, desprezando

todo o resto. Para isto, devemos tratar apenas a leitura de valores para chaves de nome “titulo”. A Listagem

11 mostra como implementar este tipo de solução.

Listagem 11. Filtrando o conteúdo usando o modelo de streaming.

JsonParser parser = Json.createParser(new FileInputStream("/tmp/json.txt"));

while (parser.hasNext()) {

JsonParser.Event event = parser.next();

if (event == Event.KEY_NAME && parser.getString().equals("titulo")) {

parser.next();

System.out.println(parser.getString());

}

}

Neste exemplo nos preocupamos apenas com os eventos que nos interessa para recuperar apenas uma parte

da informação; no caso, o título de cada artigo. Este tipo de solução faz sentido em situações nas quais

possuímos um grande volume de dados a ser lido, mas somente uma parte da informação é necessária.

Por exemplo, se nossa aplicação estiver rodando em uma PaaS (Plataforma como Serviço), pode ser crucial

usar o modelo de streaming para reduzir o custo do serviço, visto que este, muitas vezes, cobra por dados

trafegados.

Produzindo JSON com o Modelo de Streaming

Page 9: Java API - Manipulando Objetos JSON Com a JSR 353

9

Assim como no modelo de objetos, também é possível gerar uma estrutura JSON usando o modelo de

streaming. Para isso, devemos obter um objeto da classe JsonGenerator através do método estático

createGenerator() da classe Json. Após criar o objeto devemos usar os métodos write() para a escrita no

stream.

No modelo de streaming, sempre iniciamos a geração do JSON com o método writeStartArray() (para um

JSON com um array na raiz) ou writeStartObject() (para um JSON com um objeto na raiz). Para adicionar

elementos à estrutura usamos o método write() com os parâmetros chave e valor ou write() com apenas o

parâmetro valor para itens de um array. A Listagem 12 mostra como escrever um JSON usando o modelo

de streaming.

Listagem 12. Escrevendo um JSON usando o modelo de streaming.

JsonGenerator gen = Json.createGenerator(new FileOutputStream("/tmp/json_out.txt"));

gen.writeStartObject()

.write("nome", "Joao")

.write("idade", 20)

.write("profissao", "Programador")

.writeStartArray("linguagens")

.write("Java")

.write("Scala")

.write("Javascript")

.write("C++")

.write("Ruby")

.writeEnd()

.writeEnd()

.close();

O código foi propositalmente identado para facilitar a leitura e a compreensão do objeto criado. Se

verificarmos o conteúdo do arquivo gerado pelo código anterior, devemos encontrar o conteúdo da

Listagem 13.

Listagem 13. Saída do programa da Listagem 12.

{

"nome":"Joao",

"idade":20,

"profissao":"Programador",

"linguagens":[

"Java",

"Scala",

"Javascript",

"C++",

"Ruby"

]

}

Consulta do Clima com a API de JSON-P

Agora que o leitor já se familiarizou com a API para JSON da JSR 353, vamos criar uma aplicação que

utiliza serviços web baseados em JSON para consultar uma base de dados pública de informações climáticas

e apresentar ao usuário a temperatura, condições do céu e umidade de uma cidade escolhida através de uma

interface gráfica.

É importante ressaltar que todo container terá uma implementação da JSR embarcada, pois ela faz parte da

Java EE 7. No entanto, em uma aplicação standalone é necessário importar a implementação de referência

para o classpath, como veremos adiante.

Page 10: Java API - Manipulando Objetos JSON Com a JSR 353

10

Além da implementação de referência da JSR 353, usaremos Swing para construir a interface gráfica.

Para iniciar o desenvolvimento da aplicação, o leitor deve criar um diretório para hospedar o código. Como

o projeto usará o Maven para gestão de dependências e build, devemos criar um arquivo nomeado pom.xml

na raiz do diretório criado. O conteúdo do pom.xml é apresentado na Listagem 14.

Listagem 14. Arquivo pom da aplicação de clima.

<project xmlns="http://maven.apache.org/POM/4.0.0"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0

http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<groupId>com.devmedia.jsr353</groupId>

<artifactId>clima</artifactId>

<version>1.0.0</version>

<build>

<pluginManagement>

<plugins>

<plugin>

<artifactId>maven-compiler-plugin</artifactId>

<configuration>

<source>1.7</source>

<target>1.7</target>

</configuration>

</plugin>

<plugin>

<groupId>org.codehaus.mojo</groupId>

<artifactId>exec-maven-plugin</artifactId>

<configuration>

<mainClass>com.devmedia.jsr353.clima.main.ClimaFrame</mainClass>

</configuration>

</plugin>

</plugins>

</pluginManagement>

</build>

<dependencies>

<dependency>

<groupId>org.glassfish</groupId>

<artifactId>javax.json</artifactId>

<version>1.0.2</version>

</dependency>

<dependency>

<groupId>javax.ws.rs</groupId>

<artifactId>javax.ws.rs-api</artifactId>

<version>2.0</version>

</dependency>

<dependency>

<groupId>org.jboss.resteasy</groupId>

<artifactId>resteasy-client</artifactId>

<version>3.0.4.Final</version>

</dependency>

</dependencies>

</project>

Observando atentamente esse arquivo, notamos que existem dois plugins informados na tag

pluginManagement. O primeiro, maven-compiler-plugin, é para configurar o compilador do Maven para

usar o Java 7.

Page 11: Java API - Manipulando Objetos JSON Com a JSR 353

11

O segundo, exec-maven-plugin, nos permite executar a aplicação através de um comando Maven. Para isto,

temos que configurar esse plugin informando a classe com.devmedia.jsr353.clima.main.ClimaFrame

como ponto de início, pois além desta ser responsável por renderizar uma janela, por questão de

simplicidade também a usaremos para iniciar a aplicação, por isso ela irá conter o método estático main().

A vantagem de executar a aplicação usando o Maven é não ter que informar manualmente as dependências

no classpath, como faríamos no caso de executar a aplicação diretamente pelo comando java. Além disso, o

Maven também resolve as dependências transitivas, ou seja, as dependências das dependências.

Logo abaixo no XML estão as dependências. A primeira, javax.json, é a implementação de referência da

JSR 353. Depois temos a javax.ws.rs-api e a resteasy-client, que são, respectivamente, a API e a

implementação de outra JSR para facilitar a criação de clientes de serviços REST.

Feitas as configurações iniciais do projeto, devemos começar a desenvolver o código que irá consumir o

serviço público de informações climáticas. Para atender este requisito, criaremos uma classe chamada

ClimaService. Nesta classe teremos um método chamado getClima() que receberá como parâmetro a

cidade a ser consultada.

O método fará uma chamada REST a um serviço público e retornará um JSON com informações climáticas.

Com a API de JSON-P iremos ler os dados retornados pelo serviço e construir um objeto ClimaInfo com

informações de temperatura, umidade, nome da cidade encontrada e condições do céu.

Para apresentar os dados ao usuário utilizaremos um JFrame chamado ClimaFrame com componentes

como o JTextField, para ser o ponto de entrada dos dados, e JLabels, para exibir os resultados.

Na Listagem 15 é possível analisar a implementação da classe ClimaService. Como é um projeto Maven,

crie o arquivo ClimaService.java dentro da pasta src/main/java a partir da raiz do projeto.

Listagem 15. Código da classe ClimaService.

public class ClimaService {

public ClimaInfo getClima(String cidade) {

Client client = ClientBuilder.newBuilder().build();

WebTarget target = client.target

("http://www.previsaodotempo.org/api.php").queryParam("city", cidade);

Response response = target.request().get();

String clima = response.readEntity(String.class);

System.out.println(clima);

JsonReader reader = Json.createReader(new StringReader(clima));

JsonObject obj = reader.readObject();

JsonObject data = obj.getJsonObject("data");

if (data.containsKey("error") == false) {

String location = data.getString("location");

String temp = data.getString("temperature");

String humidity = data.getString("humidity");

String skytext = data.getString("skytext");

ClimaInfo info = new ClimaInfo();

info.cidade = location;

info.temperatura = String.format("%.1f", ((Long.valueOf(temp) -

32) / 1.8));

info.umidade = humidity;

info.nuvem = skytext.toUpperCase().contains("CLOUD");

return info;

} else {

Page 12: Java API - Manipulando Objetos JSON Com a JSR 353

12

return null;

}

}

}

Como pode ser verificado, no início do método getClima() estamos fazendo uma chamada para um serviço

web público. Para isso usamos classes como Client, WebTarget e Response, que fazem parte da nova API

para clientes REST.

Não está no escopo deste artigo detalhar o uso desta API; basta o leitor entender que este código nos abstrai

de vários detalhes de conexões e requisições com o protocolo HTTP e nos retorna um JSON na variável

clima. Para mais informações sobre REST 2.0, leia o artigo publicado na Java Magazine 122.

Apenas para depuração, o método imprime na saída padrão o valor bruto do JSON e inicia o processo de

conversão utilizando um StringReader. Como esse serviço retorna um objeto na raiz, o primeiro objeto

extraído é um JsonObject. A partir da raiz extraímos o objeto data, que também é um JsonObject.

Na sequência verificamos se o objeto data contém o atributo error, utilizando o método containsKey() da

API. Caso identifique a presença deste atributo, o serviço retorna null.

Caso contrário, inicia a leitura dos atributos do objeto, como cidade, temperatura, umidade e condições do

céu e os converte para um objeto do tipo ClimaInfo, descrito na Listagem 16.

Note que na conversão estamos transformando a temperatura de Fahrenheit para Celsius e convertendo as

condições de céu em um booleano apenas para informar se existem nuvens ou não (poderíamos ser mais

detalhistas neste ponto).

Listagem 16. Implementação da classe ClimaInfo.

public class ClimaInfo {

public String cidade;

public String temperatura;

public String umidade;

public boolean nuvem;

}

Para construir a tela que receberá a entrada dos dados e apresentará os resultados, vamos utilizar um

JFrame. A classe ClimaFrame está descrita na Listagem 17.

Listagem 17. Implementação de ClimaFrame.

public class ClimaFrame extends JFrame {

public ClimaFrame() {

super("JClima");

setSize(400, 400);

setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

setResizable(false);

getContentPane().setBackground(Color.white);

getContentPane().setLayout(null);

final JTextField textCity = new JTextField();

textCity.setLocation(30, 30);

textCity.setSize(340, 50);

textCity.setFont(new Font("Arial", Font.PLAIN, 24));

Page 13: Java API - Manipulando Objetos JSON Com a JSR 353

13

getContentPane().add(textCity);

final JLabel labelTemp = new JLabel();

labelTemp.setLocation(30, 60);

labelTemp.setSize(340, 250);

labelTemp.setFont(new Font("Arial", Font.PLAIN, 100));

getContentPane().add(labelTemp);

/* Apenas parte do código de construção do ClimaFrame é exibido nesta

listagem.

O código completo da aplicação, com os detalhes de construção do frame e de

seus

componentes, está disponível no site da revista. */

final ClimaService climaService = new ClimaService();

textCity.addKeyListener(new KeyAdapter() {

public void keyTyped(KeyEvent e) {

if (e.getKeyChar() == KeyEvent.VK_ENTER) {

String city = ((JTextField)

e.getSource()).getText();

ClimaInfo info = climaService.getClima(city);

if (info == null) {

labelTemp.setText("");

labelCity.setText("Cidade não encontrada.");

labelHumidity.setText("");

labelSun.setVisible(false);

labelCloud.setVisible(false);

}

else {

labelTemp.setText(info.temperatura + "

\u00b0C");

labelCity.setText(info.cidade);

labelHumidity.setText("Umidade: " +

info.umidade + "%");

labelSun.setVisible(info.nuvem == false);

labelCloud.setVisible(info.nuvem);

}

}

}

});

setVisible(true);

}

public static void main(String[] args) {

new ClimaFrame();

}

}

Como o leitor deve ter reparado, o código da classe que implementa a janela da aplicação utiliza dois

arquivos de imagem, sol.png e nuvem.png. Estas imagens serão exibidas dentro de JLabels dependendo do

retorno do serviço. A estrutura de diretórios do nosso projeto exemplo deve ficar semelhante à Figura 1.

Page 14: Java API - Manipulando Objetos JSON Com a JSR 353

14

Figura 1. Estrutura do projeto Clima.

Os arquivos com as imagens, assim como os códigos, podem ser baixados no site da revista. Depois de criar

as classes e inserir os arquivos com as imagens no projeto, podemos compilá-lo e executá-lo usando os

comandos do Maven. A Listagem 18 mostra como fazer isso.

Listagem 18. Compilando e executando a aplicação.

$ mvn clean install

.

.

.

$ mvn exec:java

Após a execução do comando mvn clean install o Maven irá exibir no console uma série de informações

referentes ao download das dependências e também detalhes do processo de build. Estes logs não foram

apresentados na listagem por questões de espaço.

Concluído o build com sucesso, o comando mvn exec:java utiliza um plugin do Maven para executar a

aplicação, iniciando pela classe configurada no pom.xm,l conforme descrito anteriormente.

A janela construída com a classe ClimaFrame será exibida na tela contendo uma caixa de texto que

permitirá ao usuário informar o nome de uma cidade e pressionar Enter para realizar a busca dos dados.

Feito isso, a consulta ao serviço será realizada, os dados serão lidos usando a API de JSON-P e os resultados

serão apresentados, conforme a Figura 2.

Page 15: Java API - Manipulando Objetos JSON Com a JSR 353

15

Figura 2. Tela da aplicação sendo executada.

Além de apresentar os dados climáticos na tela, a aplicação também imprimiu no console o valor bruto do

JSON recebido pelo serviço. Este resultado é exposto na Listagem 19.

Listagem 19. Retorno dos dados em JSON do serviço de consulta do clima.

{"apiVersion":"1.0", "data":{ "location":"Rio de Janeiro, BRA",

"temperature":"82", "skytext":"Clear",

"humidity":"55", "wind":"10",

"date":"2014-03-15", "day":"Monday" }

}

Com o crescimento do uso de web services REST que produzem e consomem dados no formato JSON, a

JSR 353 preencheu uma importante lacuna da Java EE referente ao processamento desse tipo de dado.

Essa especificação foi elaborada pensando em duas formas distintas de uso, o modelo de objetos e o modelo

de streaming. O modelo de objetos é útil quando temos um volume pequeno de dados para ser lido ou

gerado, pois tanto na leitura quando na geração os objetos são inteiramente carregados na memória.

O modelo de streaming, por sua vez, atende grandes volumes de dados evitando grandes alocações de

memória, porém torna o desenvolvimento um pouco menos simples do que o modelo de objetos.

Agora que o processamento de dados do tipo JSON está previsto em especificação, passa a ser um item a

menos que o desenvolvedor deve se preocupar caso opte por alterar o container em que a sua aplicação será

executada.

Além disso, vale ressaltar que o ganho não está somente na portabilidade, pois agora o desenvolvedor

também não precisa mais implementar um parser ou importar um parser de terceiros, reduzindo assim a

complexidade da própria aplicação.

Page 16: Java API - Manipulando Objetos JSON Com a JSR 353

16

Links

Página do IETF que define o padrão JSON

http://tools.ietf.org/html/rfc4627

Página da documentação da API

http://docs.oracle.com/javaee/7/api/javax/json/package-summary.html

Página da JSR 353: Java API for JSON Processing.

http://jcp.org/en/jsr/detail?id=353

API pública para obter informações sobre a previsão do tempo. http://www.previsaodotempo.org