O JSON (JavaScript Object Notation) a partir de agora está definido como uma API Java na JSR 353 e integrado à API padrão Java EE 7. JSON é um formato leve para troca de dados. O formato é bastante legível tanto para ler quanto para escrever seja por humanos ou para as máquinas.

O JSON é um formato de dados independente de linguagem, nesse artigo, por exemplo, vemos como ler e escrever arquivos JSON em Java. Uma estrutura JSON pode ser construída como uma coleção de pares nome/valor. O exemplo abaixo mostra um exemplo da representação em JSON de um objeto que descreve um filme:


 {
     "nome": "Transformers",
      "atores": [
        "Shia LaBeouf",
        "Megan Fox",
        "Josh Duhamel"
      ],
      "ano": 2007
}

O objeto acima possui três pares nome/valor. O primeiro elemento é um nome que equivale a “nome” com um valor String para o nome do filme “Transformers”. O segundo elemento são os “atores” com um array de valores para os atores do filme que são “Shia LaBeouf”, "Megan Fox" e "Josh Duhamel". Por fim, o terceiro elemento é o “ano” com um valor numérico para o ano que o filme foi lançado, neste caso “2007”.

Para aqueles que desenvolvem Web Services o JSON rapidamente tem se tornado a primeira escolha dos desenvolvedores para consumir e criar Web Services. Atualmente, as aplicações Java utilizam diferentes bibliotecas de implementação para produzir ou consumir JSON. Estas bibliotecas são empacotadas, juntamente com a aplicação, aumentando assim o tamanho geral do arquivo implantado no servidor. Dessa forma, agora a API Java fornece uma API padrão para analisar e gerar JSON para que os aplicativos que usam a API do Java sejam menores e portáteis.

Entre os objetivos da API destacam-se: a possibilidade de produzir e consumir JSON de forma distribuída (similar a API para XML StAX), e a construção de um modelo de objetos Java para o JSON (similar à API DOM para XML).

No restante do artigo veremos mais características do JSON e como podemos manipular a mais nova API integrante do Java.

API Streaming

A API Streaming fornece uma forma de analisar e gerar JSON. A API fornece um interpretador baseado em eventos e permite que o desenvolvedor solicite pelo próximo evento, em vez de manipular o evento em um método de callback. Isto dá ao desenvolvedor um controle mais procedural sobre o processamento do JSON. Dessa forma, eventos do analisador podem ser processados ou descartados, ou o próximo evento pode ser gerado.

O modelo de streaming é adequado para o processamento local, onde apenas partes específicas da estrutura do JSON precisam ser acessadas, e o acesso aleatório para outras partes dos dados não é necessária.

A API Streaming é uma API de baixo nível projetada para processar grandes quantidades de dados JSON de forma eficiente.

A API Streaming é similar a StAX API para XML e consiste de interfaces JsonParser para consumir JSON e da interface JsonGenerator para produzir JSON. Nas próximas seções veremos como consumir e produzir JSON utilizando a API Streaming.

Como Consumir JSON Utilizando a API Streaming

A interface JsonParser contém um método para analisar dados JSON usando um modelo de fluxo. JSONParser fornece acesso somente a leitura aos dados JSON usando o modelo de programação onde o código da aplicação controla a thread e chama métodos na interface do analisador para mover o analisador para frente ou para obter dados JSON a partir do estado atual do analisador. Este modelo de programação é chamado de pull-parsing.

Um JsonParser pode ser criado por um InputStream, conforme exemplifica a Listagem 1.

Listagem 1. Exemplo de criação de um JsonParser através de um FileInputStream.


  File f = new File("C:\json.txt");  
  FileInputStream fi = new FileInputStream(f);
  JsonParser parser = Json.createParser(fi);
  

O código acima mostra como criar um JsonParser através de um InputStream obtido por um novo FileInputStream.

JsonParser também pode ser criado a partir de um Reader, conforme mostrado no código da Listagem 2.

Listagem 2. Exemplo de criação de um JsonParser através de um StringReader.


  JsonParser parser = Json.createParser(new StringReader(…));

O código acima mostra como criar um JsonParser através de um StringReader.

Também podemos criar múltiplas instâncias de JsonParser usando JsonParserFactory. Segue um exemplo no código da Listagem 3.

Listagem 3. Exemplo de como criar múltiplas instâncias de JsonParser.


  JsonParserFactory factory = Json.createParserFactory(null);
  JsonParser parser1 = factory.createParser(...);
  JsonParser parser2 = factory.createParser(...);

A fábrica também pode ser configurada com o mapa especificado nas propriedades de configuração específicas do provedor (servidor). Todas as propriedades de configuração sem suporte especificados no mapa são ignorados.

No caso do exemplo acima, propriedades nulas foram passadas durante a criação da fábrica do JsonParser.

O modelo “pull-parsing” é usado para interpretar JSON. O método next(), conforme veremos posteriormente, retorna o evento para o próximo estado de análise, que pode ser qualquer um dos seguintes tipos:

  • START_ARRAY
  • END_ARRAY
  • START_OBJECT
  • END_OBJECT
  • KEY_NAME
  • VALUE_STRING
  • VALUE_NUMBER
  • VALUE_TRUE
  • VALUE_FALSE
  • VALUE_NULL

O interpretador gera os eventos START_OBJECT e END_OBJECT para um objeto JSON vazio "{ }".

Para um objeto com os seguintes pares nome/valor:


  {
           "nome":"Transformers",
           "protagonista":"Shia LaBeouf"
  }

Os eventos gerados são os em negrito abaixo (START_OBJECT, KEY_NAME, VALUE_STRING):


  {START_OBJECT
  "nome"KEY_NAME:"Transformers"VALUE_STRING,
  "protagonista"KEY_NAME:"Shia LaBeouf"VALUE_STRING
  }

Os eventos gerados para um array com dois objetos JSON são mostrados em negrito abaixo:


[START_ARRAY
  {START_OBJECT "nome"KEY_NAME:"Transformers"VALUE_STRING }END_OBJECT,
     {START_OBJECT "protagonista"KEY_NAME:"Shia LaBeouf"VALUE_STRING    }END_OBJECT
]END_ARRAY

Os eventos gerados para uma estrutura aninhada são mostrados em negrito abaixo:


  {START_OBJECT
           "titulo"KEY_NAME:"Transformers"VALUE_STRING,
           "ano"KEY_NAME:2007VALUE_NUMBER,
           "atores"KEY_NAME:[START_ARRAY
                              "Shia LaBeouf"VALUE_STRING,
                              "Megan Fox"VALUE_STRING,
                              "Josh Duhamel"VALUE_STRING
                     ]END_ARRAY
  }END_OBJECT

Dado o texto abaixo, copie e cole o texto num arquivo chamado json.txt. Salve este arquivo em C:\teste.


  {
      "nome": "Transformers",
      "atores": [
        "Shia LaBeouf",
        "Megan Fox",
        "Josh Duhamel"
      ],
           
      "ano": 2007
  }

Com o Eclipse aberto digite o código da Listagem 4.

Listagem 4. Exemplo de código para ler JSON com JsonParser.


  package com.exemplo;
   
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.FileNotFoundException;
   
  import javax.json.Json;
  import javax.json.stream.JsonParser;
  import javax.json.stream.JsonParser.Event;
   
  public class TesteJSON {
   
         public static void main(String args []) throws FileNotFoundException {
               File f = new File("C:\\teste\\json.txt");  
               FileInputStream fi = new FileInputStream(f);
               JsonParser parser = Json.createParser(fi);
               
               while (parser.hasNext()) {
                      Event event = parser.next();
   
                      switch (event) {
                             case KEY_NAME: {
                                    System.out.print(parser.getString() + "="); break;
                             }
                             case VALUE_STRING: {
                                    System.out.println(parser.getString()); break;
                             }
                             case VALUE_NUMBER: {
                                    System.out.println(parser.getString()); break;
                             }
   
                      }
                      
               }
         }
  }

Podemos verificar no código acima alguns conceitos já explicados anteriormente como os eventos e a forma de analisar um arquivo json com JsonParser. Se rodarmos este código podemos verificar a saída abaixo:


  nome=Transformers
  atores=Shia LaBeouf
  Megan Fox
  Josh Duhamel
  ano=2007

Podemos constatar o quanto é simples e rápido fazer uma leitura com JSON utilizando a API padrão do Java.

Como Produzir JSON Usando a API Streaming

A API Streaming fornece também uma maneira de gerar JSON para um fluxo de saída escrevendo um evento de cada vez. JsonGenerator contém métodos “writeXXX” para escrever pares de nome/valor em objetos JSON e valores em arrays JSON. Segue na Listagem 5 um exemplo de como utilizar JsonGenerator e configurá-lo para uma saída

Listagem 5. Exemplo de utilização do JsonGenerator.

 
  JsonGeneratorFactory factory = Json.createGeneratorFactory(null);
  JsonGenerator gen = factory.createGenerator(System.out);
  gen.writeStartObject().writeEnd();

Neste código um JsonGenerator é obtido através de um JsonGeneratorFactory e configurado para gravar a saída em System.out. A fábrica pode ser configurada com o mapa especificado nas propriedades de configuração específicas do provedor. Todas as propriedades de configuração sem suporte especificados no mapa são ignoradas. No caso do código utilizado acima, estamos passando uma propriedade nula durante a criação do gerador de fábrica. Além disso, neste código criamos um objeto vazio, sem pares nome/valor, que é criado e gravado para o fluxo de saída configurado. Um objeto é iniciado quando o método writeStartObject é chamado, e finalizado com o método writeEnd. JsonGenerator pode ser configurado para gravar um Writer também.

A estrutura do JSON gerada no exemplo acima é: { }

Um objeto com dois pares nome/valor pode ser gerado da mesma forma que está na Listagem 6.

Listagem 6. Exemplo de como gerar um objeto com dois pares nome/valor.


  gen.writeStartObject()
  .write("nome", "Transformer")
  .write("ano", "2007")
  .writeEnd();

Um par nome/valor é escrito através do método “write” que recebe um nome como o primeiro parâmetro e um valor como o segundo parâmetro. O valor pode ser do tipo BigDecimal, BigInteger, boolean, double, int, long, String, ou JsonValue.

A estrutura JSON gerada para o exemplo anterior é a seguinte:

array13

Um array com dois objetos com cada objeto contendo um par nome/valor pode ser gerado da mesma forma presente na Listagem 7.

Listagem 7. Exemplo de como gerar um array com dois objetos.


  gen.writeStartArray()
  .writeStartObject()
  .write("nome", "Transformers")
  .writeEnd()
   
  .writeStartObject()
  .write("ano", "2007")
  .writeEnd()
  .writeEnd();
  

Um novo array é iniciado quando o método writeStartArray é chamado e finalizado quando o método writeEnd é chamado. Um objeto dentro de um array é escrito via métodos writeStartObject e writeEnd. A estrutura gerada pelo JSON acima é mostrada abaixo:


  [
           { "nome":"Transformers" },
           { "ano":"2007" }
  ] 

Uma estrutura aninhada com dois pares nome/valor e um array podem ser gerados conforme a Listagem 8.

Listagem 8. Exemplo de como gerar uma estrutura aninhada com um array e dois pares com nome/valor.


  gen.writeStartObject()
  .write("titulo", "Transformers")
  .write("ano", 2007)
  .writeStartArray("atores")
  .write("Shia LaBeouf")
  .write("Megan Fox")
  .write("Josh Duhamel")
  .writeEnd()
  .writeEnd();

Um array é iniciado através de um writeStartArray. Cada elemento do array é escrito com o método write, que pode receber valores do tipo BigDecimal, BigInteger, boolean, double, int, long, String, ou JsonValue.

A estrutura JSON gerada pelo exemplo acima é mostrada abaixo:


  {
           "titulo":"Transformers",
           "ano":2007,
           "cast":[
                     "Shia LaBeouf",
                     "Megan Fox",
                     "Josh Duhamel"
           ]
  }

API Object Model

A API Object Model é uma API de alto nível que fornece modelos de objetos imutáveis para objetos JSON e estruturas de array. Essas estruturas JSON são representadas como modelos de objetos através dos tipos JsonObject e JsonArray.

O JsonObject fornece uma visão de Mapa para acessar a coleção não-ordenada de zero ou mais pares nome/valor do modelo. Da mesma forma, JsonArray fornece uma visão de lista para acessar a sequência ordenada de zero ou mais valores do modelo.

Este modelo de programação é mais flexível e ativa o processamento que requer acesso aleatório ao conteúdo completo da árvore. No entanto, muitas vezes não é tão eficiente quanto o modelo da API Streaming e requer mais memória.

A API Object Model é similar a API DOM para XML e usa o padrão Builder para criar esses modelos de objetos. Esta API consiste das interfaces JsonReader para consumir JSON, JsonObjectBuilder e JsonArrayBuilder para produzir JSON.

Consumindo JSON com a API Object Model

Para consumir JSON usando JsonReader da API Object Model podemos utilizar métodos para ler informações JSON usando o modelo de objeto a partir de uma fonte de entrada. JsonReader pode ser criado de um InputStream. Segue um exemplo na Listagem 9 de como isso poderia ser feito.

Listagem 9. Exemplo utilizando JsonReader para consumir um modelo de objeto.


  JsonReader reader = Json.createReader(new FileInputStream(...));

Este código mostra como criar um novo JsonReader a partir de um InputStream obtido de um novo FileInputStream.

JsonReader também pode ser criado a partir de um Reader. Segue o exemplo da Listagem 10.

Listagem 10. Exemplo para criar um JsonParser a partir de um StringReader.


  JsonParser parser = Json.createParser(new StringReader(...));

Este código mostra como criar um JsonParser a partir de um StringReader.

Podemos criar múltiplas instâncias de JsonReader usando JsonReaderFactory. Segue na Listagem 11 um exemplo de como podemos fazer isto.

Listagem 11. Exemplo de como criar múltiplas instâncias de JsonReader usando JsonReaderFactory.


  JsonReaderFactory factory = Json.createReaderFactory(null);
  JsonReader parser1 = factory.createReader(...);
  JsonReader parser2 = factory.createReader(...);

A “factory” pode ser configurada com um mapa especificado com as propriedades de configuração específicas do provedor. Todas as propriedades de configuração sem suporte especificados no mapa são ignorados. No exemplo acima foram passadas propriedades nulas durante a criação da fábrica do reader. Um objeto vazio do JSON pode ser lido como na Listagem 12.

Listagem 12. Exemplo de como ler um objeto vazio do JSON.


  JsonReader jsonReader = Json.createReader(new StringReader("{}"));
  JsonObject json = jsonReader.readObject();

Neste código, um JsonReader é inicializado através de StringReader, na qual lê um objeto JSON vazio. Ao chamar o método readObject temos como retorno uma instância de JSONObject.

Um objeto com dois pares nome/valor pode ser lido conforme o exemplo da Listagem 13.

Listagem 13. Exemplo de como ler um objeto dois pares nome/valor.


  package com.exemplo;
   
  import java.io.StringReader;
   
  import javax.json.Json;
  import javax.json.JsonObject;
  import javax.json.JsonReader;
   
  public class TesteJSONGravacao {
   
         public static void main(String args[]) {
               JsonReader jsonReader = Json.createReader(new StringReader("{"
                             + " \"nome\":\"Transformers\","
                             + " \"ano\":\"2007\""
                             + "}"));
   
               JsonObject json = jsonReader.readObject();
               System.out.println(json.getString("nome"));
               System.out.println(json.getString("ano"));
   
         }
         
  }

Neste código, o método getString retorna o valor da string para a chave especificada no objeto. Outros métodos getXXX podem ser usados para acessar o valor com base no tipo de dados.

Um array com dois objetos com cada objeto possuindo um par nome/valor pode ser lido como apresenta a Listagem 14.

Listagem 14. Exemplo de como ler dois objetos com cada um possuindo um par nome/valor.


  jsonReader = Json.createReader(new StringReader("["
                     + " { \"nome\":\"Transformers\" },"
                     + " { \"ano\":\"2007\" }"
                     + "]"));
   
  JsonArray jsonArray = jsonReader.readArray();

Neste código, chamando o método readArray tem-se como retorno uma instância da interface JsonArray. Esta interface possui métodos para obter boolean, integer, e os valores String em um índice específico. Esta interface estende java.util.List, e de modo geral as operações de lista também estão disponíveis.

O código da Listagem 15 mostra um exemplo de como podemos ler uma estrutura aninhada.

Listagem 15. Exemplo de como ler uma estrutura aninhada.


  jsonReader = Json.createReader(new StringReader("{"
           + " \"titulo\":\"Transformers\","
           + " \"ano\":2007,"
           + " \"atores\":["
           + " \"Shia LaBeouf\","
           + " \"Megan Fox\","
           + " \"Josh Duhamel\""
           + " ]"
           + "}"));
   
  json = jsonReader.readObject();

Produzindo JSON com a API Object Model

O objeto JsonObjectBuilder pode ser usado para criar modelos que representam objetos JSON. O modelo resultante é do tipo JsonObject.

Da mesma forma, JsonArrayBuilder pode ser usado para criar modelos que representam arrays JSON onde o modelo resultante é do tipo JsonArray. Segue o exemplo da Listagem 16.

Listagem 16. Exemplo de como criar um JsonObject.


  JsonObject jsonObject = Json.createObjectBuilder().build();

Neste código, um JsonObjectBuilder é usado para criar um objeto vazio. Um objeto vazio, sem quaisquer pares nome/valor é criado. A estrutura JSON gerada é { }.

Múltiplas instância também podem ser criados através do JsonBuilderFactory. Segue na Listagem 17 um exemplo de código.

Listagem 17. Exemplo de como criar múltiplas instancias com JsonBuilderFactory.


  JsonBuilderFactory factory = Json.createBuilderFactory(null);
  JsonArrayBuilder arrayBuilder = factory.createArrayBuilder();
  JsonObjectBuilder objectBuilder = factory.createObjectBuilder();

A “factory” pode ser configurada com um mapa especificado com as propriedades de configuração específicas do provedor. Todas as propriedades de configuração sem suporte especificados no mapa são ignorados.

No código acima as propriedades nulas são passadas durante a criação da fábrica.

O JSONObject gerado pode ser escrito para um fluxo de saída via JsonWriter. Segue na Listagem 18 um exemplo.

Listagem 18. Exemplo de como enviar o JSONObject gerado para um fluxo de saída.


  Json.createWriter(System.out).writeObject(jsonObject);

Neste código, uma nova instância de JsonWriter é criada e configurada para escrever em System.out. O jsonObject anteriormente criado é então escrito quando o método writeObject é chamado.

JsonWriter pode ser configurado para escrever em um Writer também.

Um objeto com dois pares nome/valor pode ser gerado. Segue na Listagem 19 o código.

Listagem 19. Exemplo de como gerar um par nome/valor com o método add.


  Json.createObjectBuilder()
           .add("nome", "Transformers")
           .add("ano", "2007")
           .build();

Um par nome/valor é escrito através do método add, na qual recebe um nome como o primeiro parâmetro e um valor como o segundo parâmetro. O valor pode ser do tipo BigDecimal, BigInteger, boolean, double, int, long, String, JsonValue, JsonObjectBuilder, ou JsonArrayBuilder. Especificando o valor como JsonObjectBuilder e JsonArrayBuilder permite que possamos criar objetos aninhados e arrays.

A estrutura JSON gerada com o exemplo acima é:

array13

Um array com dois objetos com cada objeto com um par nome/valor pode ser gerado da mesma forma que a Listagem 20.

Listagem 20. Exemplo de como gerar um array com dois objetos onde cada objeto tem um par nome/valor.


  JsonArray jsonArray = Json.createArrayBuilder()
           .add(Json.createObjectBuilder().add("nome","Transformers"))
           .add(Json.createObjectBuilder().add("ano","2007"))
           .build();

Iniciamos um novo array criando um JsonArrayBuilder. Escrevemos um objeto dentro de um array chamando o método add e criando um novo objeto usando o método JsonObjectBuilder. A estrutura JSON gerada será:


  [
           { "nome":"Transformers" },
           { "ano":"2007" }
  ]

O método JsonWriter.writeArray é chamado para escrever um array para o fluxo de saída configurado.

Uma estrutura aninhada com dois pares nome/valor e um array pode ser gerado da mesma forma que na Listagem 21.

Listagem 21. Exemplo de como gerar uma estrutura aninhada com dois pares nome/valor e um array.


  jsonArray = Json.createArrayBuilder()
           .add(Json.createObjectBuilder()
           .add("titulo", "Transformers")
           .add("ano", 2007)
           .add("atores", Json.createArrayBuilder()
           .add("Shia LaBeouf")
           .add("Megan Fox")
           .add("Josh Duhamel")))
           .build();

Iniciamos um array nomeado chamando o método add, passando o nome do array, e criando um novo array chamado o método Json.createArrayBuilder. Cada elemento do array é escrito através do método add, que pode receber valores do tipo BigDecimal, BigInteger, boolean, double, int, long, String, JsonValue, JsonObjectBuilder, ou JsonArrayBuilder.

A estrutura JSON gerada pelo código acima é mostrada abaixo:


  {
           "titulo":"Transformers",
           "ano":2007,
           "atores":[
                     "Shia LaBeouf",
                     "Megan Fox",
                     "Josh Duhamel"
           ]
  }

O JSON (Java API for JSON Processing OU JSON-P) está definido na JSR-353. O JSON consiste de uma API Streaming e uma API Object Model. A primeira é semelhante ao StAX enquanto que a segunda é semelhante ao DOM. Além disso, JSON é open-source e está disponível sobre as licenças CDDL v1.1 e GPL v2. Para adquirir o JSON basta visitarmos o site https://java.net/projects/jsonp/. O JSON agora também é parte integrante do Java EE 7. Neste artigo ainda vimos diversos exemplos e explicamos em detalhes como produzir e consumir JSON. A API Streaming é uma API de baixo nível e muito eficiente para analisar e gerar JSON consistindo de duas interfaces primárias: JsonParser e JsonGenerator. A API Object Model por sua vez é uma API de alto nível que fornece modelos de objetos para objetos JSON e estruturas de arrays.

Bibliografia

[1]Josh Juneau. Java EE 7 Recipes: A Problem-Solution Approach. Apress, 2013.

[2]Josh Juneau. Introducing Java EE 7: A Look at What's New. Apress, 2013.

[3]Arun Gupta. Java EE 7 Essentials. O'Reilly, 2013.