Parsear datos JSON en Android con JsonReader y Gson

Publicado el 24 enero 2015 por Jamesrevelo @herprogramacion

¿Quieres saber cómo leer datos JSON alojados en un servidor desde tu aplicación Android? ¿Te gustaría aprender formas rápidas y comprensibles para convertir objetos JSON en objetos Java? ¿Además de todo quieres ubicar tus datos en un ListView?...Si te quedas y sigues leyendo hasta el final, tus preguntas serán respondidas a través de varios ejemplos prácticos.

CONTENIDO

  • ¿Qué es JSON?
  • Usar la clase JsonReader para parsear un arreglo JSON
  • Crear un parser personalizado con la clase JsonReader
    • Paso 1: Leer el formato general Json
    • Paso 2: Leer el array de objetos Json
    • Paso 3: Leer los atributos de cada objeto Json
  • Poblar un ListView desde datos Json
    • Crear diseño de los ítems del ListView
    • Crear Adaptador personalizado para la lista
  • Obtener datos Json con una petición HTTP
  • La librería GSON
    • Convertir datos Java a Json
    • Convertir formato Json a objetos Java
  • Conclusiones

¿Qué es JSON?

JSON (Javascript Object Notation) es un formato de intercambio de datos entre clientes y servidores,  basado en la sintaxis de Javascript para representar estructuras en forma organizada y alto nivel, con el fin de acercar a una definición mucho mas amigable por los desarrolladores.

Su notación de datos se asimila a la creación de objetos en Javascript, pero esto no significa que su funcionamiento dependa de este. JSON es un formato en texto plano independiente de todo lenguaje de programación, es más, soporta el intercambio de datos en gran variedad de lenguajes de programación como PHP, Python, C++, C#, Java y Ruby.

JSON es una herramienta potente en el desarrollo de aplicaciones web, ya que facilita el desarrollo y comprensión de intercambio  de datos. Si recuerdas, habíamos visto que XML también puede usarse para el intercambio, pero debido a que su definición genera un DOM, el parseo se vuelve extenso y pesado.

Lee También Tutorial básico del lenguaje XML

Además de ello XML debe usar XPath para especificar rutas de elementos y atributos, por lo que demora la reconstrucción de la petición. En cambio JSON no requiere restricciones adicionales, simplemente se obtiene el texto plano y el engine de Javascript en los navegadores hace el trabajo de parsing sin ninguna complicación.

Tipos de datos en JSON

Similar a la estructuración de datos primitivos y complejos en los lenguajes de programación, JSON establece varios tipos de datos: cadenas, números, booleanos, arrays, objetos y valores null.

El propósito es crear objetos que contengan varios atributos compuestos como pares clave-valor. Donde la clave es un nombre que identifique el uso del valor que lo acompaña.

Veamos un ejemplo:

{
"Id": 101
"Nombre": "Carlos",
"EstaActivo": true,
"Notas": [ 2.3, 4.3, 5.0]
}

La anterior estructura es un objeto JSON compuesto por los datos de un estudiante. Los objetos JSON contienen sus atributos entre llaves "{}", al igual que un bloque de código en Javascript, donde cada atributo debe ir separado por coma ',' para diferenciar cada par.

La sintaxis de los pares debe contener dos puntos ':' para dividir la clave del valor. El nombre del par debe tratarse como cadena y añadirle comillas dobles.

Si notas, este ejemplo trae un ejemplo de cada tipo de dato:

  • El Id es de tipo entero, ya que contiene un número que representa el código del estudiante.
  • El Nombre es un string. Usa comillas dobles para identificarlas.
  • EstaActivo es un tipo booleano que representa si el estudiante se encuentra en la institución educativa o no. Usa las palabras reservadas true y false para declarar el valor.
  • Notas es un arreglo de números reales. El conjunto de sus elementos debes incluirlos dentro de  corchetes "[ ]" y separarlos por coma.

Usar la clase JsonReader para parsear un arreglo JSON

En la sección anterior se discutió una pequeña introducción a la sintaxis de JSON como preámbulo para expresar los propósitos de este capítulo.

Debido a la integración que ha tenido la información en la nube, las aplicaciones ya no tienen límites. Si estas creando un servicio web en el cual tu usuario puede tener acceso a sus datos y características desde cualquier dispositivo, entonces el intercambio de datos con tu servidor será de gran importancia en la versión de tu aplicación Android.

La idea es crear un mecanismo que permita recibir la información que contiene la base de datos externa en formato JSON hacia la aplicación. Con ello se parseará cada elemento y será interpretado en forma de objeto Java para integrar correctamente el aspecto en la interfaz de usuario.

La clase JsonReader de Java es ideal para interpretar datos con formato JSON. Provee un sistema poderoso para el parseo de arreglos y objetos  embebidos en las respuestas de los servidores. No obstante, no se puede considerar como un parser en si mismo, si no como una clase auxiliar para crear tus propios parsers.

Veamos una ilustración que muestra el proceso de parseo que será estudiado:

Como puedes observar el origen de los datos es un servidor externo o hosting que hayas contratado como proveedor para tus servicios web. Ignoraremos la aplicación web que realiza la gestión de encriptación de los datos a formato JSON (puede ser PHP, JavaScript, ASP.NET, etc.).

Tu aplicación Android a través de un cliente realiza una petición GET a la dirección URL del recurso con el fin obtener los datos. Ese flujo entrante debe interpretarse con ayuda de un parser personalizado que implementa la clase JsonReader.

El resultado final es un conjunto de datos adaptable a el API de Android. Dependiendo de tus necesidades, puedes convertirlos en una lista de objetos estructurados que alimenten un adaptador que pueble un ListView o simplemente actualizar la base de datos local de tu aplicación en SQLite.

Para codificar el proceso de parsin mostrado en el diseño de la ilustración, se usará un prototipo de aplicación llamada ZooWak. Esta aplicación muestra datos característicos de una lista de animales salvajes que se encuentra almacenada en la base de datos de un servidor externo.

Los registros de los animales son enviados serializados en formato JSON desde el servidor. A continuación se muestra la lista de ejemplo que se usará (Se truncó el atributo descripcion por comodidad):

[  
{
"especie":"Le\u00f3n",
"descripcion":"El le\u00f3n (Panthera leo) es un mam\u00edfero carn\u00edvoro ...",
"imagen":"leon"
},
{
"especie":"Elefante",
"descripcion":"Los elefantes o elef\u00e1ntidos (Elephantidae) son ...",
"imagen":"elefante"
},
{
"especie":"Cocodrilo",
"descripcion":"Los crocod\u00edlidos (Crocodylidae) son una familia de saur\u00f3psidos ...",
"imagen":"cocodrilo"
},
{
"especie":"Cebra",
"descripcion":"Se conocen como cebra (o zebra, graf\u00eda en desuso ) a tres ...",
"imagen":"cebra"
},
{
"especie":"\u00c1guila",
"descripcion":"El \u00e1guila calva (Haliaeetus leucocephalus), tambi\u00e9n ...",
"imagen":"aguila"
}
]

Zoowak solo tiene una actividad en la que se encuentra un ListView, por lo que se deduce que la salida del proceso de parsing debe ser de tipo estructurado, en este caso es una pieza de código personalizada llamada Animal. Dicho elemento debe converger en el diseño establecido para cada ítem de la lista, así que el adaptador debe inflar elementos en base a dicha configuración.

Crear un parser personalizado con la clase JsonReader

Vamos a crear una clase llamada JsonAnimalParser la cual interpretará un flujo de datos con formato JSON y retornará en una lista de  objeto de tipo Animal.

La definición de la clase Animal es solo un reflejo en Java de los atributos de los objetos JSON que vienen desde el servidor. Veamos:

public class Animal {
private String especie;
private String descripcion;
private String imagen;
public Animal(String especie, String descripcion, String imagen) {
this.especie = especie;
this.descripcion = descripcion;
this.imagen = imagen;
}
public String getEspecie() {
return especie;
}
public void setEspecie(String especie) {
this.especie = especie;
}
public String getDescripcion() {
return descripcion;
}
public void setDescripcion(String descripcion) {
this.descripcion = descripcion;
}
public String getImagen() {
return imagen;
}
public void setImagen(String imagen) {
this.imagen = imagen;
}
}

El formato Json contiene los campos especie, descripción e imagen de la base de datos que está en el servidor externo. Sabiendo sus claves, es posible interpretar cada objeto del arreglo. Cabe destacar que el código Json viene formateado con el estándar UTF-8, por eso se ven tantos códigos en donde van las tildes. Esto nos permite conservar el acento de nuestros datos de texto sin ninguna dificultad.

Ahora observa los pasos para crear nuestro parser:

Paso 1: Leer el formato general Json

Lo primero que se debe implementar es un método que coordine el retorno de los objetos de tipo animal y que además cree una instancia del JsonReader. Veamos:

 public List<Animal> readJsonStream(InputStream in) throws IOException {
// Nueva instancia JsonReader
JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8"));
try {
// Leer Array
return leerArrayAnimales(reader);
} finally {
reader.close();
}
}

El tipo de retorno de la función es una lista de animales, ya que es justo el tipo de estructura que recibirá nuestro adaptador. Además de ello, se crea el lector Json asociando el flujo de entrada y el tipo de encriptado que usarás, en este caso UTF-8. Con ello los acentos serán interpretados automáticamente.

Si todo finalizó con éxito, entonces el lector se cierra con close() para limpiar espacios de memoria referenciada.

Paso 2: Leer el array de objetos Json

Se creó la función leerArrayAnimales() para recorrer todo el array enviado desde el servidor. Recibe como parámetro el lector Json para continuar la labor de lectura. Luego se apunta al primer elemento del array con el método beginArray(), lo que nos permite leer con un bucle while el siguiente elemento con el método hasNext().

public List leerArrayAnimales(JsonReader reader) throws IOException {
// Lista temporal
ArrayList animales = new ArrayList();
reader.beginArray();
while (reader.hasNext()) {
// Leer objeto
animales.add(leerAnimal(reader));
}
reader.endArray();
return animales;
}

hasNext() pregunta al array si existen más objetos aún, por lo que si la respuesta es positiva se añaden los elementos a una lista temporal llamada animales a través del método leerAnimal().

Cuando se acaben los objetos, entonces se debe liberar la memoria, cerrando el array con el método endArray(). Finalmente se retorna la lista temporal con todos los animales.

Paso 3: Leer los atributos de cada objeto Json

El método leerAnimal() accede a todos pares clave-valor que convirtió el lector JsonReader en datos interpretables. Así que aprovechando esa estructura definida, es posible recorrer cada objeto particular en busca de los valores que se refieran a una clave.

public Animal leerAnimal(JsonReader reader) throws IOException {
String especie = null;
String descripcion = null;
String imagen = null;
reader.beginObject();
while (reader.hasNext()) {
String name = reader.nextName();
switch (name) {
case "especie":
especie = reader.nextString();
break;
case "descripcion":
descripcion = reader.nextString();
break;
case "imagen":
imagen = reader.nextString();
break;
default:
reader.skipValue();
break;
}
}
reader.endObject();
return new Animal(especie, descripcion, imagen);
}

Similar a la situación del array, debemos iniciar el objeto con beginObject() para apuntar al primer par. Luego comenzamos un bucle para la lectura de todos los pares con el método análogo hasNext(). Donde se obtiene la clave del par con el método nextName() y se asigna a una variable local.

Ya con ese valor es posible abrir una estructura switch que relacione todos los valores de las claves posibles que tiene tu objeto. En cada comparación debes usar los métodos next() para obtener el valor dependiendo del tipo y asignarlo a una variable temporal. Si tu valor es String, entonces usarás nextString(), si es de tipo int, entonces usas nextInt(), y así sucesivamente.

En el caso default puedes usar el método skipValue(), el cual permite saltar valores que no te interesan. Si de pronto hubiésemos consultado el ID del animal, entonces se usaría este método para evitar su lectura, ya que no interesa en el proceso actual.

Por ultimo retorna en un nuevo objeto Animal que alimente su constructor de todas las variables temporales obtenidas.

Poblar un ListView desde datos Json

El proceso anterior ya es el 90% de la aplicación ZooWak, ahora solo queda crear el adaptador y relacionar los objetos.

El adaptador que vamos a crear tiene ítems con un diseño particular, ya que se incorporará una imagen representativa por cada animal al layout. Las imágenes que usaremos están dentro de la carpeta drawable del proyecto. Cada una tiene asignado el nombre exacto que existe en la base de datos del servidor externo.

Lee También Listas y Adaptadores en Android

Crear diseño de los ítems del ListView

Antes de definir el layout para los ítems que poblarán el ListView echemosle un vistazo al siguiente boceto:

Se organizarán los atributos del objeto Json con un formato enriquecido que resuma las características del animal. Primero usaremos la especie del animal como título general de cada ítem, en este caso puedes apreciar que es "León". Directamente en su límite bottom-top va la imagen que se obtiene de los recursos. Y en último lugar se ubica la descripción del animal justo en el límite bottom-top de la imagen.

¿Sencillo cierto?

Ahora fíjate la implementación del layout en XML:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<TextView
android:id="@+id/especieAnimal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerInParent="true"
android:background="?android:selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:textSize="24sp" />
<ImageView
android:id="@+id/imagenAnimal"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_below="@+id/especieAnimal"
android:scaleType="centerCrop" />
<TextView
android:id="@+id/descAnimal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/imagenAnimal"
android:layout_centerInParent="true"
android:background="?android:selectableItemBackground"
android:clickable="true"
android:focusable="true" />
</RelativeLayout>

La definición es solo un RelativeLayout más textviews y un imageview con centerCrop para ajustar el tamaño del archivo .jpg, algo que tu ya conoces muy bien.

Lee también Configurar Layouts y Views en Android Studio

Crear Adaptador personalizado para la lista

Extenderemos una clase llamada AdaptadorDeAnimales de la clase ArrayAdapter para sobrescribir el método getView() con el fin de empotrar el archivo item_lista.xml y obtener el drawable correspondiente a través del nombre de la imagen.

public class AdaptadorDeAnimales extends ArrayAdapter {
public AdaptadorDeAnimales(Context context, List objects) {
super(context, 0, objects);
}
@Override
public View getView(int position, View convertView, ViewGroup parent){
//Obteniendo una instancia del inflater
LayoutInflater inflater = (LayoutInflater)getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//Salvando la referencia del View de la fila
View v = convertView;
//Comprobando si el View no existe
if (null == convertView) {
//Si no existe, entonces inflarlo
v = inflater.inflate(
R.layout.item_lista,
parent,
false);
}
//Obteniendo instancias de los elementos
TextView especieAnimal = (TextView)v.findViewById(R.id.especieAnimal);
TextView descAnimal = (TextView)v.findViewById(R.id.descAnimal);
ImageView imagenAnimal = (ImageView)v.findViewById(R.id.imagenAnimal);
//Obteniendo instancia de la Tarea en la posición actual
Animal item = getItem(position);
especieAnimal.setText(item.getEspecie());
descAnimal.setText(item.getDescripcion());
imagenAnimal.setImageResource(convertirRutaEnId(item.getImagen()));
//Devolver al ListView la fila creada
return v;
}
private int convertirRutaEnId(String nombre){
Context context = getContext();
return context.getResources()
.getIdentifier(nombre, "drawable", context.getPackageName());
}
}

Lo único fuera de lo común en este código es la obtención del identificador de la imagen a través de su nombre con el método getIdentifier(). Este método recibe como parámetro el nombre del recurso, que en este caso lo obtuvimos desde el objeto Json. También recibe el nombre del tipo de recurso y el paquete donde se encuentra. Siendo este último accedido desde el método getPackageName() de la clase Context.

Estas instrucciones las ubicamos dentro del método convertirRutaEnId(), el cual recibe el nombre de la imagen y retorna el entero representativo del identificador del recurso.

Obtener datos Json con una petición HTTP

Ya teniendo toda la infraestructura necesaria para dirigir el flujo de datos con formato Json, es hora de realizar la petición GET correspondiente. Para ello se recurre al el cliente HttpURLConnection como se vió en el post anterior.

Lee también Operaciones HTTP en Android con el cliente HttpURLConnection

La petición la realizaremos en el método doInBackground() de una tarea asíncrona  personalizada llamada JsonTask. Luego crearemos el adaptador y lo relacionaremos con la lista en onPostExecute().

Lee también Uso de Hilos y tareas asíncronas(AsyncTask) en Android

Veamos:

public class JsonTask extends AsyncTask<URL, Void, List<Animal>>{
@Override
protected List doInBackground(URL... urls) {
List animales = null;
try {
// Establecer la conexión
con = (HttpURLConnection)urls[0].openConnection();
con.setConnectTimeout(15000);
con.setReadTimeout(10000);
// Obtener el estado del recurso
int statusCode = con.getResponseCode();
if(statusCode!=200) {
animales = new ArrayList
();
animales.add(new Animal("Error",null,null));
}
else{

// Parsear el flujo con formato JSON
InputStream in = new BufferedInputStream(con.getInputStream());
// JsonAnimalParser parser = new JsonAnimalParser();
GsonAnimalParser parser = new GsonAnimalParser();
animales = parser.readJsonStream(in);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
con.disconnect();
}
return animales;
}
@Override
protected void onPostExecute(List<Animal> animales) {
/*
Asignar los objetos de Json parseados al adaptador
*/
if(animales!=null) {
adaptador = new AdaptadorDeAnimales(getBaseContext(), animales);
lista.setAdapter(adaptador);
}else{
Toast.makeText(
getBaseContext(),
"Ocurrió un error de Parsing Json",
Toast.LENGTH_SHORT)
.show();
}
}
}

Finalmente el resultado visual de la aplicación es el siguiente:

Puedes descargar el proyecto completo desde aquí:

Descargar Código

La librería GSON para parsear objetos JSON

La librería Gson es otra forma de parsear un formato Json a objetos Java. Gson es de código abierto y se encuentra alojada en http://code.google.com/p/google-gson.

Para usar el repositorio en nuestros proyecto de Android Studio debemos añadir la siguiente dependencia a el archivo build.gradle:


compile 'com.google.code.gson:gson:2.3'

Esta herramienta provee al desarrollador un conjunto de métodos para convertir objetos Json a objetos Java  y viceversa con un minimo esfuerzo. Además permite relacionar objetos serializados Java a una estructura genérica en Json.

Convertir datos Java a Json

La clase principal de la librería es Gson. Para implementar sus funcionalidades debemos instanciar un nuevo objeto y llamar el método deseado.

Para convertir datos atómicos en formato JSON debes usar el método toJson(). Veamos algunos ejemplos:

Gson gson = new Gson();
int entero= 2;
Log.d("Formato Json entero", gson.toJson(entero));
String cadena ="Gson";
Log.d("Formato Json cadena", gson.toJson(cadena));
int arreglo []= {1, 2, 3, 4};
Log.d("Formato Json arreglo", gson.toJson(arreglo));

toJson() retorna en un string que representa la estructura Json dependiendo del tipo que se usó como parámetro. Las impresiones anteriores serían:

Formato Json entero: 2
Formato Json cadena: "Gson"
Formato Json arreglo: [1, 2, 3, 4]

También es posible convertir un objeto completo a formato Json:

Log.d("Objeto Animal con formato Json", gson.toJson(
new Animal (
"Lobo",
"Los lobos son animales carnívoros que andan en manadas…",
"lobo")));

Donde la impresión sería (se muestra con espacios por comodidad):

{  
"especie":"Lobo",
"descripcion":"Los lobos son animales carnívoros que andan en manadas…",
"imagen":"lobo"
}

Convertir formato Json a objetos Java

Es posible realizar el proceso inverso con el método fromJson(), el cual recibe como parámetro el valor y la clase de dato:

int entero = gson.fromJson("1", int.class);
String cadena = gson.fromJson("\"abc\"", String.class);
Boolean booleano = gson.fromJson("true",Boolean.class);
char[] arreglo = gson.fromJson("[m,n,o,p,q]",char[].class);

En nuestro caso específico necesitamos reconstruir los objetos que nos arroja la clase JsonReader a través de la lectura del flujo externo, para crear la lista de animales. Esto solo funcionará si la cantidad y tipo de los atributos del objeto Json coinciden con la definición de la clase Java.

Para ello crearemos una nueva clase llamada GsonAnimalParser, en la cual crearemos el mismo método leerFlujoJson() de JsonAnimalParser e incluiremos una combinación de lectura entre JsonReader y Gson.

public class GsonAnimalParser {
public List leerFlujoJson(InputStream in) throws IOException {
Gson gson = new Gson();
JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8"));
List animales = new ArrayList();
reader.beginArray();
while (reader.hasNext()) {
Animal animal = gson.fromJson(reader, Animal.class);
animales.add(animal);
}
reader.endArray();
reader.close();
return animales;
}
}

Con la ayuda de la librería Gson nos ahorramos la lectura selectiva de los pares clave-valor del objeto Json, ya que reemplazamos la lectura del objeto por el método fromJson().

Conclusiones

JSON es un formato de intercambio de datos supremamente flexible y amigable a los ojos de cualquier programador. Estas características hacen que sea indispensable para la comunicación entre dispositivos que complementan sus datos en un servicio en la nube.

JsonReader es una herramienta efectiva para traducir flujos de datos sin pulir en formato Json. Aunque trae consigo métodos para la conversión a objetos Java, requiere crear manualmente la implementación de la lectura.

Precisamente el trabajo de Gson es la conversión rápida de objetos Java a Json y viceversa, lo que hace que se complemente exitosamente con la clase JsonReader. Aunque vimos varios ejemplos del uso de Gson, no significa que ese sea el máximo de su potencial.

Gson trae consigo características más complejas que pueden serte de utilidad en futuros proyectos con distintas necesidades. Visita su página principal para averiguar hasta el fondo.

Notas: Imágenes autoría de FreePik.

James Revelo Urrea - Desarrollador independiente http://www.hermosaprogramacion.com