jueves, 7 de julio de 2011

Parsear JSON utilizando Gson

Voy a explicar cómo parsear objetos JSON utilizando la librería open source Gson (http://code.google.com/p/google-gson/) utilizando como ejemplo los trending topics de Twitter.
La ventaja de utilizar Gson es que la generación de objetos Java es inmediata, no más toString() Basta definir el modelo que representará la respuesta JSON, pasárselo al parseador de Gson y él se encargará de hacer todo el trabajo. Esto también funciona en sentido inverso: serializar objetos nunca fue tan fácil.
La consulta de los trending topics mundiales de Twitter tiene esta pinta:
[
    {
        "created_at":"2011-07-06T15:10:14Z",
        "as_of":"2011-07-06T15:22:20Z",
        "locations":[
            {
                "name":"Worldwide",
                "woeid":1
            }
        ],
        "trends":[
            {
                "url":"http:\/\/search.twitter.com\/search?q=%23nationalkissingday",
                "events":null,
                "name":"#nationalkissingday",
                "promoted_content":null,
                "query":"%23nationalkissingday"
            },
            {
                "url":"http:\/\/search.twitter.com\/search?q=%23goseeksomehelp",
                "events":null,
                "name":"#goseeksomehelp",
                "promoted_content":null,
                "query":"%23goseeksomehelp"
            },
            {
                "url":"http:\/\/search.twitter.com\/search?q=%23elmejorinvento",
                "events":null,
                "name":"#elmejorinvento",
                "promoted_content":null,
                "query":"%23elmejorinvento"
            },
            {
                "url":"http:\/\/search.twitter.com\/search?q=BTWBall",
                "events":null,
                "name":"BTWBall",
                "promoted_content":null,
                "query":"BTWBall"
            },
            {
                "url":"http:\/\/search.twitter.com\/search?q=%22Brasil%203x2%20Cuba%22",
                "events":null,
                "name":"Brasil 3x2 Cuba",
                "promoted_content":null,
                "query":"%22Brasil%203x2%20Cuba%22"
            },
            {
                "url":"http:\/\/search.twitter.com\/search?q=JoeJonasIsPERFECT",
                "events":null,
                "name":"JoeJonasIsPERFECT",
                "promoted_content":null,
                "query":"JoeJonasIsPERFECT"
            },
            {
                "url":"http:\/\/search.twitter.com\/search?q=%22Abbey%20Dawn%22",
                "events":null,
                "name":"Abbey Dawn",
                "promoted_content":null,
                "query":"%22Abbey%20Dawn%22"
            },
            {
                "url":"http:\/\/search.twitter.com\/search?q=%22Charlie%20Adam%22",
                "events":null,
                "name":"Charlie Adam",
                "promoted_content":null,
                "query":"%22Charlie%20Adam%22"
            },
            {
                "url":"http:\/\/search.twitter.com\/search?q=%22Noel%20Gallagher%22",
                "events":null,
                "name":"Noel Gallagher",
                "promoted_content":null,
                "query":"%22Noel%20Gallagher%22"
            },
            {
                "url":"http:\/\/search.twitter.com\/search?q=Boonen",
                "events":null,
                "name":"Boonen",
                "promoted_content":null,
                "query":"Boonen"
            }
        ]
    }
]
Como se puede ver lo que estamos recibiendo es: - Array de un solo elemento con: - Atributos informando de la fecha de generación. - Array de localizaciones con un solo elemento. (no lo utilizaremos) - Array de 'trends' con 10 elementos.
A su vez, cada trend tiene varios atributos. Para este ejemplo nos quedaremos solo con URL y Name.
Lo siguiente es crear el modelo que representará esta estructura de datos. Para ello se necesitan dos clases muy simples. La primera es TwitterTrends que modela un elemento tipo 'trend'
package es.lgvalle.trendsparser;
import com.google.gson.annotations.SerializedName;

public class TwitterTrend {
 @SerializedName("name")
 private String name;

 @SerializedName("url")
 private String url;

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

 public String getUrl() {
  return url;
 }

 public void setUrl(String url) {
  this.url = url;
 }
}
La segunda es TwitterTrends que contiene una lista de TwitterTrend junto con la fecha de creación
package es.lgvalle.trendsparser;
import java.util.List;
import com.google.gson.annotations.SerializedName;

public class TwitterTrends {

 @SerializedName("as_of")
    private String as_of;
 
 @SerializedName("created_at")
    private String created_at;
 
 @SerializedName("trends")
    private List trends;
 
    public String getCreated_at() {
  return created_at;
 }
 public void setCreated_at(String created_at) {
  this.created_at = created_at;
 }
 public String getAs_of() {
        return as_of;
    }
    public void setAs_of(String asOf) {
        as_of = asOf;
    }
    public List getTrends() {
        return trends;
    }
    public void setTrends(List trends) {
        this.trends = trends;
    }
}
En este ejemplo voy a generar un objeto TwitterTrends que mostrará sus 'trends' en un FlipperView. Al hacer click sobre un elemento navegaremos a su url asociada.
El código que hace esto quedaría así:
Type objType = new TypeToken>() {}.getType();
ArrayList trendsList = (ArrayList) JSONClient.getJson(TRENDS_URL, objType);

// El api de Twitter devuelve un array con un único elemento.
TwitterTrends objs = (TwitterTrends) trendsList.get(0);

for (final TwitterTrend tr : objs.getTrends()) {
 TextView trend = (TextView) getLayoutInflater().inflate(R.layout.trend, null);
 trend.setText(tr.getName());
 trend.setOnClickListener(new OnClickListener() {
  @Override
  public void onClick(View v) {
   startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(tr.getUrl())));
   }
 });
 mFlipper.addView(trend);
}
mFlipper.startFlipping();
Como se puede ver en primer lugar hay que crear un TypeToken especificando el tipo de objeto que modela el JSON que vamos a recibir.
Este TypeToken es propio de la librería Gson y sirve para lidiar con la pérdida de tipo al crear objetos genéricos en Java. Básicamente el problema es que en Java, cuando creamos un List y pedimos el tipo obtenemos 'List', la información sobre String se pierde. Para más información: http://download.oracle.com/javase/tutorial/java/generics/erasure.html
El Type Erasure es útil para muchas cosas pero un gran problema para nuestro ejemplo. A Gson necesitamos especificarle exactamente el objeto que vamos a construir y ahí es donde entra TypeToken.
Finalmente la clase JSONClient.java quedaría así:
package es.lgvalle.trendsparser;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.Type;

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import com.google.gson.Gson;

public class JSONClient {
 public static Object getJson(String url, Type objType) {
  Gson gson = new Gson();
  Reader r = new InputStreamReader(getStream(url));
  Object fromJson = gson.fromJson(r, objType);
  return fromJson;
 }

 private static InputStream getStream(String url) {
  HttpGet request = new HttpGet(url);
  InputStream is = null;
  try {
   HttpResponse response = new DefaultHttpClient().execute(request);
   final int statusCode = response.getStatusLine().getStatusCode();
   if (statusCode != HttpStatus.SC_OK) {
    Log.v("Error " + statusCode + " for URL " + url);
   } else {
    is = response.getEntity().getContent();
   }
   
  } catch (IOException e) {
   request.abort();
   Log.v("Error for URL " + url);
  }
  Log.v("Got stream from "+url);
  return is;
 }
}
Done.
El código completo de este ejemplo está disponible aquí.

No hay comentarios:

Publicar un comentario en la entrada