(Java EE 7 – JSR353) Trabalhando com objetos JSON em aplicações Java

Olá pessoal, neste post vamos falar um pouco a respeito da API padrão para processamento de objetos JSON (Javascript Object Notation), a “Java API for JSON Processing” ou JSON-P, disponibilizada através da JSR353 como parte da especificação do Java EE 7.

O que são as JSRs

Para quem não conhece as JSRs (Java Specification Requests, ou Requisição de especificação Java) são documentos construídos com intuito de formalizar alguma funcionalidade ou nova tecnologia que fará parte da plataforma Java, em um sentido geral. Estas especificações são definidas através de um processo, denominado “Java Community Process” ou “JCP”.

Por meio do JCP a especificação é disponibilizada para toda a comunidade, e os desenvolvedores e demais interessados que desejarem podem participar de perto deste processo, e acompanhar toda a evolução de uma determinada JSR por meio do site do JCP, pois toda a evolução de uma JSR é dsponibilizada em forma de revisões públicas, onde os membros da comunidade Java podem acompanhar a evolução a respeito da nova tecnologia ou funcionalidade foco da JSR.

Assim que uma JSR chega ao seu estado final, ela passa a ser considerada a implementação de referência de uma determinada tecnologia, assim quando falamos que algumas JSRs foram disponibilizadas em uma versão específica do Java Enterprise Edition, em termos gerais estamos dizendo que novas funcionalidades e melhorias estão disponíveis agora nesta versão da plataforma, e podem ser utilizadas no desenvolvimento de novas aplicações.

Sobre as demais “JSRs” que compõem o Java EE 7, há um link no final da página com maiores detalhes a respeito de cada uma delas, para que o leitor que desejar possa conferir.

Falando a respeito da manipulação de informações no formato JSON, até a versão 6 do JAVA EE, não havia um padrão definido pela especificação Java com este fim, assim as aplicações desenvolvidas que tinham a necessidade de processar informações neste formato, utilizavam ferramentas próprias ou mesmo desenvolvidas por terceiros, que normalmente não seguem um padrão em comum de se trabalhar entre elas, tendo cada uma delas a sua própria forma de trabalhar com as informações, a respeito destas ferramentas podemos citar alguns exemplos como “Jackson”, ou Google G-SON.

Com a definição de um padrão para atender esta necesssidade, as aplicações passam a contar com uma maior portabilidade entre diferentes servidores de aplicação existentes, e passam a necessitar menos de ferramentas próprias ou de terceiros, o que contribui para diminuir a complexidade do código gerado, pois a nova especificação já traz ao desenvolvedor um conjunto de funcionalidades implementadas para este fim.

O formato JSON

Com o crescente aumento das tecnologias móveis, e utilização de serviços WEB, o formato JSON tem sido cada dia mais utilizado para troca de informações entre diferentes aplicações.

O formato JSON é bastante limpo, claro e fácil de manipular, sendo o formato mais comumente utilizado por serviços do tipo “RESTful”.

Outros exemplos da utilidade do formato JSON, incluem:

  •  criação de arquivos de configuração, devido sua fácil leitura e manutenção
  • utilizado em requisições do tipo “ajax” para troca de informações
  • utilizado em bancos de dados conhecidos como “NoSQL”

No formato JSON podemos representar as informações de duas formas, podendo ser:

  1. Objetos:
    São representadas por sequências não ordenadas de pares de “chave” e “valor” separados por “:”, contidas pelo uso do “{” e “}”, sendo que cada par “chave/valor” é separado por “,”, também utilizado para separação de objetos. Cada chave é representada por uma “string”, e possui um “valor” associado a ela (descrição abaixo).
  2. Arrays:
    São representados por uma sequência ordenada de “valores”, separados entre si por “,”, contidos pelo uso do “[” e “]”.

Quanto aos valores utilizados em um Objeto ou Array JSON, podem ser dos tipos “string”, “numero”, “objeto”, “array”, “true”, “false” ou “null”.

Vale ressaltar que o formato JSON possibilita o uso de “valores” como sendo outros Objetos JSON, possibilitando a construção de hierarquias dentre os diferentes objetos associados.

Como exemplo, vamos imaginar que estejamos desenvolvendo uma aplicação, que recebe dados de uma loja de livros, e através do formato JSON as informações dos livros a venda são disponibilizadas a nossa aplicação, a seguir temos um exemplo de como poderia ser informado um determinado “livro”, disponibilizado para nós no formato JSON:

{ "id":98746,
  "nome":"Artificial Intelligence",
  "subtitulo":"A Modern Approach",
  "preco":56.84,
  "isbn":{"tipo":13,
          "valor":"978-0136042594"},
  "editora":"Prentice Hall",
  "edicao":3.0,
  "ano":2009.0,
  "autor":["Stuart Russell",
           "Peter Norvig"]
}

Apenas como exemplo, veja como poderia ser a mesma informação disponibilizada em formato XML:

<livro>
    <ano>2009.0</ano>
    <autor>Stuart Russell</autor>
    <autor>Peter Norvig</autor>
    <edicao>3.0</edicao>
    <editora>Prentice Hall</editora>
    <id>98746</id>
    <isbn>
        <tipo>13</tipo>
        <valor>978-0136042594</valor>
    </isbn>
    <nome>Artificial Intelligence</nome>
    <preco>56.84</preco>
    <subtitulo>A Modern Approach</subtitulo>
</livro>

Falando sobre a JSON-P

Na prática a API, oferece ferramentas e mecanismos para trabalhar com arquivos e informações no formato JSON, como o descrito acima, possibilitando a leitura e escrita de uma forma padronizada. Embora a API seja conhecida como JSON-P, é importante saber que a mesma não tem relação com o conhecido “JSON with padding” ou  JSONP.

Esta API fornece duas forma diferentes de se trabalhar, através do “Modelo de Objeto” e “Modelo de Streaming”.

O Modelo de Objeto: Carrega toda a informação de um Objeto JSON na memória da aplicação, para que seja possível a sua manipulação, consome mais quantidade de memória que o modelo de streaming. Este modelo funciona de maneira semelhante a API DOM (Document Object Model) utilizada para manipulação de arquivos XMLs.

O Modelo de Streaming: Através de eventos específicos na leitura de um objeto JSON, é possível tratar os trechos de informação desejados, descartando as demais informações, e por este aspecto consome menor quantidade de memória que o modelo de objeto, pois seu processamento é realizado a medida em que a leitura ou escrita ocorre, sem a necessidade de colocar todo objeto JSON na memória. É o modelo mais indicado para o processamento de um grande número de informações.
Este modelo funciona de maneira semelhante a API SAX (Simple Api for XML) utilizada para manipulação de arquivos XMLs.

Por ser uma API da plataforma Enterprise Edition, pode ser utilizada em aplicações que executam em servidores de aplicação que atendem a especificação Java EE 7, sem qualquer importação de arquivos adicionais, porém caso deseje, é possível utilizá-la em aplicações que não necessitam dos servidores de aplicação, assim como em outras APIs do Java EE 7, para isto basta realizar a importação do arquivo “.jar” contendo a implementação da API, adicionando-o ao “classpath” da sua aplicação, também é possível fazer uso da mesma através do controle de dependências fornecido pela ferramenta “Maven”.

Vamos conhecer algumas das principais classes desta API, através dos exemplos abaixo:

Modelo de Objetos

Para construir um Objeto no formato JSON, através do modelo de Objetos, é preciso basicamente:

  • Criar um “builder” ou construtor  específico, seja ele para um Objeto ou Array, sendo representados pelas interfaces “JsonObjectBuilder” (javax.json.JsonObjectBuilder) ou “JsonArrayBuilder” (javax.json.JsonArrayBuilder).
  • para criação deste “builder”, pode ser utilizado os métodos de fabricação estáticos presentes na classe “Json” (javax.json.Json), sendo “Json.createObjectBuilder()” ou “Json.createArrayBuilder()” de acordo com o tipo JSON que se deseja criar.
  • a partir do “builder” obtido, adicionar toda informação desejada, por meio dos métodos de adição disponíveis (métodos “add”), lembrando que todo objeto JSON, as chaves são representadas por Strings, então as chamadas basicamente esperam receber o nome de uma chave, e o seu valor em seguida.
  • ao final, utilizar o método “build()“, para retornar o objeto construído, sendo uma implementação das interfaces “JsonObject” ou “JsonArray”

Vamos construir através da API o Objeto JSON citado previamente, para representar o nosso exemplo de “Livro”, conforme:

// Criando um Objeto JSON com Modelo de Objetos:

// Contruindo o ISBN do Livro
JsonObjectBuilder isbnBuilder = Json.createObjectBuilder();
isbnBuilder.add("tipo", 13);
isbnBuilder.add("valor", "978-0136042594");
JsonObject isbn = isbnBuilder.build();

// Construindo o Array de Autores do Livro
JsonArrayBuilder autoresBuilder = Json.createArrayBuilder();
autoresBuilder.add("Stuart Russell");
autoresBuilder.add("Peter Norvig");
JsonArray autores = autoresBuilder.build();

// Construindo o Livro.
// Utilizando Interfaces Fluentes através das chamadas do método "add" em sequência
JsonObject livro = Json.createObjectBuilder()
                       .add("id", 98746)
                       .add("nome", "Artificial Intelligence")
                       .add("subtitulo", "A Modern Approach")
                       .add("preco", 56.84)
                       .add("editora", "Prentice Hall")
                       .add("edicao", 3.0)
                       .add("isbn", isbn)
                       .add("autor", autores)
                       .add("ano", 2009.0)
                       .add("edicao",3.0)
                       .build();

System.out.println(livro);

A saída para esta chamada deve ser objeto JSON conforme mostrado no primeiro exemplo do artigo.

Observe que para as chamadas “.add(“isbn”, isbn)” e “.add(“autor”, autores);” o valor informado corresponde a um “JsonObject” e um “JsonArray“, e que a chamada aos métodos “add” para construção do “livro” utilizam um mecanismo denominado interface fluente, onde cada chamada ao método “add” retorna uma referência a mesmo objeto, neste caso o mesmo “builder”. Caso deseje alterar um determinado valor, basta chamar novamente o método “add” informando a “chave” e o novo valor desejado.

Para ler um Objeto JSON a partir do modelo de dados é preciso basicamente:

  • criar um leitor, representado pela interface “JsonReader” (javax.json.JsonReader)
  • para a criação deste leitor, é possível utilizar o método de fabricação estático “Json.createReader(…)“, podendo receber como parâmetro uma instância de qualquer classe que implemente “InputStream” (java.io.InputStream) ou “Reader” (java.io.Reader),
  • a partir do nosso “leitor”, chamar o método “readObject()” para obter um “JsonObject” (Objeto JSON), ou “readArray()” para obter um “JsonArray” (Array JSON),
  • caso possua um “JsonObject”, é possível ler as informações por métodos “get” passando o nome da “chave” desejada.
  • caso possua um “JsonArray”, é possível ler as informações utilizando o “índice” do valor desejado.

Exemplo da leitura para o JSON que representa o nosso livro:

// Cria nosso stream a ser utilizado
		 
URL urlDoWebService = new URL("http://localhost:8080/biblioteca-rest/loja/livro");
InputStream streamParaLeitura = urlDoWebService.openStream();

// Cria o nosso leitor
// O Stream informado se refere a uma URL, mas poderíamos estar lendo de um arquivo, por ex.
JsonReader leitorDeObjeto = Json.createReader(streamParaLeitura);
		
//Faz a leitura e retorna o nosso objeto
JsonObject livroJson = leitorDeObjeto.readObject();
		
//Construindo um Objeto da nossa aplicação com as informações de ISBN
Isbn isbn = new Isbn();
		
//Ajusta os valores, lendo a informação do JSON.
isbn.setTipo(livroJson.getJsonObject("isbn").getInt("tipo"));
isbn.setValor(livroJson.getJsonObject("isbn").getString("valor"));
		
System.out.println("Json Obtido: " + livroJson);
System.out.println("Nome do Livro: " + livroJson.getString("nome"));
System.out.println("Objeto da nossa aplicação: " + isbn);
	    
JsonArray autorJsonArray = livroJson.getJsonArray("autor");
System.out.println("Autores: " + livroJson.getJsonArray("autor"));
System.out.println("Primeiro nome de autor: " + autorJsonArray.getString(0));
System.out.println("Valor do Array que não existe, assumindo o default: " + autorJsonArray.getString(10, "Valor default definido, índice inválido"));

Observe que a linha “new URL(“http://localhost:8080/biblioteca-rest/loja/livro”)” e em seguida a chamada do “openStream()” faz a criação de um “InputStream” que neste exemplo corresponde a URL de um serviço RESTful, criado apenas para os nossos exemplos, poderia ser utilizado um caminho para um arquivo desejado, por exemplo.

A saída padrão para este código será:

Json Obtido: {"id":98746,"nome":"Artificial Intelligence","subtitulo":"A Modern Approach","preco":56.84,"isbn":{"tipo":13,"valor":"978-0136042594"},"editora":"Prentice Hall","edicao":3.0,"ano":2009.0,"autor":["Stuart Russell","Peter Norvig"]}
Nome do Livro: Artificial Intelligence
Objeto da nossa aplicação: Isbn [tipo=13, valor=978-0136042594]
Autores: ["Stuart Russell","Peter Norvig"]
Primeiro nome de autor: Stuart Russell
Valor do Array que não existe, assumindo o default: Valor default definido, índice inválido

Os métodos “get” são chamados de acordo com o tipo de valor que se deseja obter, com pré conhecimento para o tipo presente no JSON, para isto deve-se informar o nome da “chave”, semelhante as chamadas realizadas para um Map<Key/Value>, porém para cada tipo de valor, existe um método “get” específico. A construção para um objeto da aplicação deve ser feita criando-se uma instância da classe desejada, e a partir da leitura do JSON, asjustar no objeto os seus valores, como por exemplo feito no trecho a seguir :

//Construindo um Objeto da nossa aplicação com as informações de ISBN
Isbn isbn = new Isbn();		
//Ajusta os valores, lendo a informação do JSON.
isbn.setTipo(livroJson.getJsonObject("isbn").getInt("tipo"));
isbn.setValor(livroJson.getJsonObject("isbn").getString("valor"));

Os métodos “get” acionados para o objeto “JsonArray” tem por base a utilização de um determinado índice, caso o índice seja inválido uma exceção é lançada, como por exemplo “java.lang.IndexOutOfBoundsException“, ou se desejar pode se utilizar o método “get” informando um valor default a ser utilizado caso o índice seja inválido.

Modelo de Streaming

Para construir um Objeto no formato JSON, através do Modelo Streaming, é preciso basicamente:

  • Criar uma “fábrica” de “geradores de json”, representada pela interface “JsonGeneratorFactory” (javax.json.stream.JsonGeneratorFactory)
  • podemos obter nossa “fábrica”, através da chamada de “Json.createGeneratorFactory(Map<String, ?>)”, podemos informar algumas configurações nesta chamada, em forma de um “Map”
  • com a nossa “fábrica”, obtemos um gerador de JSON representado pela classe “JsonGenerator” (javax.json.stream.JsonGenerator)
  • como o nosso gerador, podemos escrever a informação JSON, começando pela “raiz”, através dos métodos que iniciam Arrays (.writeStartArray()) ou iniciam Objetos (.writeStartObject()).
  • seguindo o fluxo de escrita do JSON, podemos utilizar os métodos “write” para adicionar o valor desejado, e ao final de cada “bloco de escrita” os métodos “.writeEnd()“.
  • ao final finalizamos a escrita com o método “.close()

Exemplo da escrita do nosso exemplo de objeto “livro”, utilizando o Modelo de Stream:

//Criando JSON com Streaming API

//Configuração para escrita com indentação
// (espaços e quebras de linha adicionadas a informação)
Map<String, Object> config = new HashMap<String, Object>(1);
config.put(JsonGenerator.PRETTY_PRINTING, true);

//Criando nossa fábrica de Gerador de JSON
JsonGeneratorFactory fabricaDoGerador = Json.createGeneratorFactory(config);

//Criando nosso gerador de JSON, que terá a escrita na saída padrão 
JsonGenerator geradorDeJson = fabricaDoGerador.createGenerator(System.out);

//Escrevendo todo nosso Objeto JSON
geradorDeJson.writeStartObject()
             .write("id", 98746)
             .write("nome","Artificial Intelligence")
             .write("subtitulo", "A Modern Approach")
             .write("preco", 56.84)
             .writeStartObject("isbn").write("tipo", 13)
                                      .write("valor", "978-0136042594")
                                      .writeEnd() 
             .write("editora", "Prentice Hall")
             .write("edicao", 3.0)
             .write("ano", 2009.0)
             .writeStartArray("autor").write("Stuart Russell")
                                      .write("Peter Norvig")
                                      .writeEnd()
             .writeEnd()
             .close();

Observe que ao criarmos Objetos ou Arrays JSON, internamente a outro objetos, utilizamos os métodos “.writeStartObject(<nome da chave>)”, e “.writeStartArray(<nome da chave>)”, para que o mesmo seja escrito, tendo já atribúido a ele o nome de uma “chave” específica.

Neste exemplo, o método “close()” é acionado apenas ao final da escrita da informação, desta forma, neste momento toda a informação é direcionada para a saída configurada (configuração feita no trecho “fabricaDoGerador.createGenerator(System.out)”), se desejar realizar escritas parciais, e não apenas aguardar que o “close()” a realizse, é possível utilizar para isto o método “.flush()” do nosso gerador, contanto que o mesmo ainda não tenha sido fechado.

O resultado do código acima, será a exibição do nosso objeto JSON, como mostrado no primeiro exemplo, contendo toda indentação.

Para realizar a leitura de um Objeto no formato JSON, através do Modelo Streaming, é preciso basicamente:

  • obter uma “fábrica” para o objeto que irá ler a informação, conhecido como “parser”
  • podemos obter nossa “fábrica” que é representada pela interface “JsonParserFactory” (javax.json.stream.JsonParserFactory), através da chamada do método “Json.createParserFactory(Map<String, ?>)“, podendo informar algumas configurações nesta chamada em forma de um “Map”
  • com a nossa fábrica, podemos criar um parser, representado pela interface “JsonParser” (javax.json.stream.JsonParser) através dos diferentes métodos “.createParser(…)” disponíveis
  • durante a criação do parse, devemos informar qual será a a origem da leitura, através dos diferentes argumentos utilizados no método “.createParser(…)”
  • obtido o “parser” a informação já pode ser lida, de forma semelhante ao comportamento de um “Iterator”, retornando sempre algum “evento” específico na leitura do conteúdo, representado pelo Enum “Event” (javax.json.stream.JsonParser.Event)

Segue abaixo, um exemplo da leitura do nosso objeto JSON “Livro”, obtido através da chamada a URL de um Serviço RESTful:

// Lendo JSON com o Modelo de Streaming

// Cria a nossa URL do serviço, apenas criada para exemplos.
// obtém o "stream" para sua chamada.
URL urlDoWebService = new URL("http://localhost:8080/biblioteca-rest/loja/livro");
InputStream streamParaLeitura = urlDoWebService.openStream();

// Fábrica do nosso parser, sem informar qualquer configuração específica
JsonParserFactory fabricaDeParser = Json.createParserFactory(null);
//Obtém um parser, que irá ler as informações retornadas pelo nosso serviço
JsonParser parser = fabricaDeParser.createParser(streamParaLeitura);

// A medida em que lê o conteúdo, os eventos podem ser obtidos a partir do "parser"
while (parser.hasNext()) {
    //Um evento corresponde aos diferentes tipos da informação lida no JSON
    Event evento = parser.next();

    System.out.println("Evento obtido é do tipo: " + evento);

    // Podemos tomar uma decisão e escolher o que queremos ler ou não,
    // com base no tipo do Enum
    switch (evento) {
        case KEY_NAME: {
            System.out.println(" Encontramos uma chave, o seu nome é: " + parser.getString());
            break;
        }
        case START_OBJECT: {
            System.out.println(" Encontramos o inicio de um Objeto.");
            break;
        }
        case START_ARRAY: {
            System.out.println(" Encontramos o inicio de um Array.");
            break;
        }
    }
}

Observe que através da chamada “parser.hasNext()”, realizamos a leitura da informação enquanto houver o que ser lido, neste caso é possível saber que há o que ser lido através dos diferentes “eventos” encontrados no JSON que representa o nosso objeto “livro”.

Podemos ver Um exemplo de decisão a ser tomada, através do trecho:

...
case KEY_NAME: {
System.out.println(" Encontramos uma chave, o seu nome é: " + parser.getString());
break;
}
...

Onde todas as chaves do Arquivo tem seu “nome” exibido, um possível uso ser obter o nome de todas as chaves existentes, sem a necessidade de carregar toda informação obtida com o JSON na memória da aplicação, o quê ocorre de forma diferente ao modelo de objetos.

Podemos ver que a JSON-P tem como intuito atender a uma necessidade de padronizar a forma de trabalhar com informações no formato JSON na plataforma Java, e oferecer uma ferramenta já integrada as soluções Enterprise, que até a sua publicação não existia, inicialmente introduzida na versão 7 do Java EE.

Vimos que toda leitura e escrita entre os Objetos Java, é feita de forma manual, efetuando a leitura e escrita de cada “chave” ou trecho da informação desejada, através dos dois modelos distintos de trabalho.

Vale adiantar que a nova versão da JSON-P (1.1) está prevista para ser disponibilizada na versão 8 do Java EE, prevendo melhorias na API e um mecanismo de “binding” entre o texto JSON e os objetos Java, o quê hoje não existe, obrigando que toda leitura seja realizada de forma direta entre Objetos Java (POJOs, entidades da aplicação) X Informação JSON, e vice-versa, mais detalhes sobre a próxima versão nos links ao final do post.

Links e Referências externas

https://www.jcp.org/en/home/index – JCP Site.

https://jsonp.java.net/ – Site do Projeto Open Source, de implementação da referência da JSON-P.

https://json-processing-spec.java.net/nonav/releases/1.0/fcs/javadocs/index.html – Java DOC da versão 1.0 da API JSON-P.

http://docs.oracle.com/javaee/7/tutorial/ – Detalhes e tutorial do Java EE 7.

http://jcp.org/en/jsr/detail?id=342 – Especificação do Java EE 7 (atual versão enterprise).

http://jcp.org/en/jsr/detail?id=366 – Mais informações sobre o Java EE 8 (próxima versão enterprise).

http://pt.wikipedia.org/wiki/NoSQL – Banco de dados NoSQL

Por MARIO EDUARDO GIOLO

Postado em: 14 de janeiro de 2015

Confira outros artigos do nosso blog

REST não é JSON

21 de agosto de 2017

Bruno Sofiato

[Webinar] Profile de aplicações Java com Oracle Mission Control e Flight Recorder

24 de julho de 2017

Danival Calegari

Criando Mocks de serviços REST com SoapUI

27 de junho de 2017

Monise Costa

JavaScript 6: diferença entre var, let e const

09 de maio de 2017

Otávio Felipe do Prado

Deixe seu comentário