thxou

Ama la sabiduría. Desea el conocimiento.

Parsear Y Crear Ficheros en Formato JSON en iOS

| Comments

Desde la salida de iOS 5 Apple incluyó en su API la clase NSJSONSerialization, la cual nos permite convertir objetos JSON en objetos de Objective-C (que ya tocaba también) y viceversa de manera sencilla.

Para los que no saben que es JSON (JavaScript Object Notation), es un tipo de sintaxis que nos permite representar porciones grandes o pequeñas de datos para poder almacenarlos y/o intercambiarlos con otros entornos. Mucho más sencillo y pequeño que XML y además más rápido de parsear. Usa exactamente la misma sintaxis que usa JavaScript para crear objetos pero es totalmente independiente del lenguaje. Esta y otras cosas son las cosas que lo hacen atractivo para empresas como Twitter, Yahoo, Google, etc., que ya tienen sus APIs adaptadas a JSON desde hace un tiempo, además que facilita las cosas al programador y ahora veremos hasta que punto.

La sintaxis de JSON

Es muy sencillo aprender la notación de JSON ya que tan solo hay 3 cosas para considerar:
  1. Los datos son pares de nombres y valores separados por comas.
  2. Los objetos están encerrados entre llaves ({ }).
  3. Los arrays están encerrados entre corchetes ([ ]).

Esto se cumple para todo en el fichero, también para los objetos que van anidados dentro de los arrays. No obtante solo se permiten cierto tipo de objetos, y son: NSString,  NSNumber,  NSArray,  NSDictionaryNSNull, de manera que si quieres meter objetos como por ejemplo NSDate, vas a tener que convertirlos a NSStringantes o algún objeto compatible. Un fichero de JSON luce así por dentro:

1
2
3
4
5
6
7
8
{
    "nombre" : "ThXou",
    "web" : "thxou.com"
    "aficiones" : [
        { "titulo" : "Fútbol" },
        { "titulo" : "Snowboard" }
    ]
}

Aquí podemos identificar que todo eso es un objeto (va encerrado entre { }) y luego tiene varios datos, entre ellos un array (aficiones) que contiene a su vez 2 objetos con un dato por objeto (título). Se cumplen los 3 puntos de arriba en todos los casos, esto es algo a considerar siempre. También hay que considerar que el nivel más alto del fichero JSON debe ser o un NSDictionary o un NSArraypara que este sea válido.

Parseando JSON desde una URL

Nuestra información en JSON está alojada en un sitio web, así que tenemos primero que descargar esta información y convertirla en un objeto manipulable, por eso usamos el método dataWithContentsOfURL:options:error: y hacemos la conversión a NSData:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)viewDidLoad
{
    [super viewDidLoad];

    // creamos un botón para generar el JSON
    UIBarButtonItem *json = [[UIBarButtonItem alloc] initWithTitle:@"Generar JSON"
                                                             style:UIBarButtonItemStylePlain
                                                            target:self
                                                            action:@selector(generarJSON:)];
    self.navigationItem.rightBarButtonItem = json;

    // indicamos la url desde donde tomaremos los datos
    NSURL *url = [NSURL URLWithString:@"http://dl.dropbox.com/u/270074/iOSTutorials/JSON/test.json"];
    NSData *urlData = [NSData dataWithContentsOfURL:url];

    [self empezarAParsear:urlData];
}

Simplemente usamos el método viewDidLoad: para descargar esta información al inicio de la carga del controlador. Hay que saber que la descarga de información en el hilo principal (Main thread) podría bloquear la interfaz gráfica, a nosotros no nos sucede debido a que descargamos muy poca información. Si vas a descargar grandes cantidades de datos es necesario hacerlo en segundo plano.

Antes de iniciar la descarga creamos un botón que más adelante nos va a permitir generar datos en formato JSON a partir de lo que ya tenemos parseado. Luego de la descarga, toda la información va a estar contenida en un objeto NSData. La clase NSJSONSerialization tiene el método JSONObjectWithData:options:error:, el cual nos permite parsear el contenido de este objeto.

Como hemos dicho antes el nivel más alto del ficher0 en JSON debe ser un diccionario o un array, en nuestro caso es un diccionario con una única key llamada comunidades; esta a su vez es un array con otros objetos dentro. Por lo tanto vamos a parsear la información directamente a un objeto NSDictionary:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)empezarAParsear:(NSData *)urlData
{
    NSError *error = nil;
    // parseamos los datos de la URL
    NSDictionary *jsonDic = [NSJSONSerialization JSONObjectWithData:urlData
                                                            options:kNilOptions
                                                              error:&error];
    // si hubo algún error en el parseo lo mostramos
    if (error != nil)
    {
        NSLog(@"ERROR: %@", [error localizedDescription]);
    }
    else {
        self.comunidades = jsonDic[@"comunidades"];
    }
}

La propiedad comunidades almacena el array contenido en el fichero bajo la key comunidades. El paso que queda ahora es mostralo. Como puede observar, uno de los argumentos del método JSONObjectWithData: es options:. Este argumento puede tener 3 valores diferentes según la configuración que se quiera tener en cuenta al momento de parsear el fichero. Yo he puesto kNilOptionsque equivale a 0, o a no elegir ninguna opción más concretamente, pero tu podría elegir entre estos 3:

  1. NSJSONReadingMutableContainers: Los arrays y diccionarios son creados como objetos mutables, es decir que pueden ser cambiados en cualquier momento, incluso antes de comenzar el parseo.
  2. NSJSONReadingMutableLeaves: Todos los strings contenidos serán creados como mutables.
  3. NSJSONReadingAllowFragments: Permite el parseo de objetos en el nivel más alto del fichero pero no sean arrays o diccionarios.

Mostrando el contenido

Para que se vea más claro he usado un TableView para mostrar los datos parseados. Primero obtenemos la información de cada comunidad en el diccionario comunidad y segundo la vamos mostrando formateada a medida que se van recorriendo las filas de la TableView:

1
2
3
4
5
6
NSDictionary *comunidad = self.comunidades[indexPath.row];
cell.textLabel.text = comunidad[@"nombre"];
NSString *subtitle = [NSString stringWithFormat:@"Superficie: %@ - %@%%",
                      comunidad[@"superficie"],
                      comunidad[@"porcentaje"]];
cell.detailTextLabel.text = subtitle;

Todo esto lo hacemos en el método tableView:cellForRowAtIndexPath: el cual va ser el encargado de llenar los campos del TableView con la información correspondiente.

Generando datos en formato JSON

Ahora haremos el proceso inverso. Ahora convertiremos datos de un array a datos en formato JSON. Para hacer esto hay un botón en la barra de navegación del TableView que creamos en el viewDidLoad:, y que al ser presionado ejecuta el método generarJSON::

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (void)generarJSON:(id)sender
{
    NSError *error = nil;
    // generamos los datos en JSON
    NSData *json = [NSJSONSerialization dataWithJSONObject:self.comunidades
                                                   options:NSJSONWritingPrettyPrinted
                                                     error:&error];

    // convertimos los datos a un string para poder mostrarlos
    NSString *jsonString = [[NSString alloc] initWithData:json encoding:NSUTF8StringEncoding];

    // los mostramos en un alertView
    UIAlertView *alerta = [[UIAlertView alloc] initWithTitle:nil
                                                     message:jsonString
                                                    delegate:self
                                           cancelButtonTitle:@"Ok"
                                           otherButtonTitles:nil];
    [alerta show];
}

Para no hacer mucho más largo este tutorial simplemente hemos convertido toda la información que ya estaba parseada nuevamente a formato JSON, esto lo hemos hecho con el método dataWithJSONObject:options:error. Este método también tiene un argumento options:, pero en este caso solo tiene una opción:

  1. NSJSONWritingPrettyPrinted: Con esta opción se hace la conversión pero la devuelve en un formato más legible y ordenado (tal y como verás al presionar el botón). Si por el contrario deseas compactar más el string devuelto (sin espacios ni tabuladores), puedes pasarle la opción kNilOptions.

Luego de hacer la conversión mostramos los datos en una ventana de alerta.

Hay otro método que se puede usar para comprobar si es que la información que deseamos convertir a JSON se puede convertir o no. Este método es isValidJSONObject: y devuelve YES si es posible hacer la conversión y NO de lo contrario. Su uso es muy sencillo:

1
2
3
4
5
6
7
if ([NSJSONSerialization isValidJSONObject:self.comunidades])
{
    // realizamos la conversión
}
else {
    // mostramos un error
}

Conclusión

Es muy sencillo implementar esta funcionalidad como han podido ver. Tan solo hay un par de métodos que realmente usamos de esta clase, el resto son arreglos que nos permiten mostrar el contenido de acuerdo a nuestros gustos.

Recordar que no todos los formatos se pueden convertir a JSON, para los que no, hay que hacer otro tipo de conversiones. Un ejemplo es si vamos a pasar un objeto NSDate, podemos usar el método stringFromDate: de la clase NSDateFormatter para convertirlo a un string, y luego volver a recuperarlo con el método dateFromString:.

Esto es todo. Cualquier duda no dudéis en usar los comentarios.

Comments