You are on page 1of 18

Introducción al Formato JSON

Características principales de JSON:

 JSON es muy popular: es seguramente el formato más utilizado para


intercambiar datos entre aplicaciones web. Entre otros, se supone que ya
ha superado en uso a XML (otro formato de intercambio de datos que
todavía se utiliza en algunas aplicaciones AJAX) como formato de
intercambio de datos en comunicaciones asincrónicas entre cliente y
servidor.

 JSON es legible por personas e interpretable por algoritmos. En otras


palabras, un documento JSON está estructurado de forma que pueda ser
leído fácilmente por un programa de ordenador, mientras que una persona
puede recorrer rápidamente el fichero y entender qué datos contiene.

 JSON está basado en un subconjunto de JavaScript: su diseño se inspira en


los objetos literales de JavaScript (también conocido como ECMAScript),
pero sin embargo es agnóstico al lenguaje (es decir, no tiene
características que lo hagan más fácil de procesar en un lenguaje de
programación o en otro). Puede facilitar el intercambio de datos entre la
mayoría, si no todos, de los lenguajes de programación. De hecho, JSON
usa acuerdos de programación comunes, lo que lo hace familiar a la
mayoría de los programadores, con independencia del lenguaje (o
lenguajes) en el que trabajen.

¿Qué aspecto tiene un documento JSON?


Aquí vemos un ejemplo de documento JSON:

{
"artists" : [
{
"artistname" : "Deep Purple",
"albums" : [
{
"albumname" : "Machine Head",
"year" : "1972",
"genre" : "Rock"
},
{
"albumname" : "Stormbringer",
"year" : "1974",
"genre" : "Rock"
}
]
}
]
}

 Aunque normalmente los documentos tendrán más datos que este


ejemplo, rara vez tendrán una estructura más compleja.
 Un documento JSON contiene texto, llaves ({}), corchetes ([]), dos
puntos (:), comas, comillas dobles (“”) y unos pocos caracteres más.
 Pero posiblemente la característica más relevante de JSON es que los
datos consisten de pares nombre/valor. Estos pares nombre/valor
representan la estructura de los datos. Incluso si no se sabe JSON,
será posible obtener una idea básica de la estructura de datos sólo
mirando la forma en que los datos son almacenados en el ejemplo
anterior.
 La mejor noticia es, sin duda, que el pequeño ejemplo mostrado más
arriba cubre casi toda la sintaxis JSON. No hay mucho más que
conocer, excepto la sintaxis que mostramos en la próxima sección de
este curso.
Sintaxis JSON: Objetos y Arrays
JSON almacena los datos en objetos y arrays. La sintaxis JSON refleja
cómo se definen estos 2 tipos de estructuras.

Objetos JSON
Un objeto JSON es un conjunto desordenado de pares nombre/valor,
encerrado entre llaves ({}).
En el siguiente ejemplo, artistname es el nombre y Deep Purple su valor.

{ "artistname" : "Deep Purple" }

Un objeto puede contener cero o más pares nombre/valor. Múltiples pares


nombre/valor se escriben separados por comas (,). De esta forma, los
siguientes son ejemplos de objetos JSON válidos:

{ }

{ "artistname" : "Pink Floyd" }

{
"artistname" : "Pink Floyd",
"formed" : "1964"
}

{
"artistname" : "Pink Floyd",
"formed" : "1964",
"origin" : "Cambridge, United Kingdom"
}

En JSON, un nombre es una cadena (string). Su valor puede ser un objeto,


un array, número, cadena, true, false o null. En los ejemplos anteriores,
los valores son todos cadenas.

Arrays JSON
Un array JSON es una colección ordenada de valores. Permite construir
listas de valores.

Un array comienza con un corchete izquiero ([) y termina con un corchete


derecho (]). Dentro del array los valores van separados por comas (,).

Aquí hay un ejemplo de un array:


{
"artists" : [
{
"artistname" : "Pink Floyd",
"formed" : "1964"
},
{
"artistname" : "Bruce Springsteen",
"born" : "1949"
},
{
"artistname" : "U2",
"formed" : "1976"
}
]
}
Sintaxis JSON: Legibilidad y Datos
Anidados
Espacios en blanco
Se pueden usar blancos para darle más legibilidad a los documentos, como
en casi todos los lenguajes de programación modernos. En concreto, se
pueden utilizar espacios, saltos de línea y tabuladores para mejor
legibilidad de los ficheros JSON.

Así que se puede hacer, por ejemplo:

{
"artists" : [
{ "artistname" : "Pink Floyd", "formed" : "1964" },
{ "artistname" : "Bruce Springsteen", "born" : "1949" },
{ "artistname" : "U2", "formed" : "1976" }
]
}

O también:

{
"artists" : [
{
"artistname" : "Pink Floyd",
"formed" : "1964"
},
{
"artistname" : "Bruce Springsteen",
"born" : "1949"
},
{
"artistname" : "U2",
"formed" : "1976"
}
]
}

Estos dos ejemplos son perfectamente equivalentes en lo que a los datos


respecta. Poner o quitar blancos no tiene ningún impacto en los datos. Sin
embargo, agregar blancos dentro de las comillas de una cadena sí cambia
los datos.

Datos anidados
La mayoría de los ficheros JSON contienen muchos datos anidados. Esto
depende de la estructura de los datos.

Por ejemplo, podemos tener un array de artistas. El nombre es artists, y el


valor es un array que contiene los datos de varios artistas. Cada artista
tiene su propio array de discos, cada uno de ellos con su correspondiente
par nombre/valor.

{
"artists" : [
{
"artistname" : "Pink Floyd",
"formed" : "1964",
"albums" : [
{
"albumname" : "The Dark Side of the Moon",
"year" : "1973",
"genre" : "Rock"
},
{
"albumname" : "Wish You Were Here",
"year" : "1975",
"genre" : "Rock"
}
]
}
]
}

Para ver mejor el ejemplo, lo extendemos con 3 artistas distintos:

{
"artists" : [
{
"artistname" : "Pink Floyd",
"formed" : "1964",
"albums" : [
{
"albumname" : "The Dark Side of the Moon",
"year" : "1973",
"genre" : "Rock"
},
{
"albumname" : "Wish You Were Here",
"year" : "1975",
"genre" : "Rock"
}
]
},
{
"artistname" : "Bruce Springsteen",
"born" : "1949",
"albums" : [
{
"albumname" : "Born to Run",
"year" : "1975",
"genre" : "Folk Rock"
},
{
"albumname" : "Born in the U.S.A",
"year" : "1984",
"genre" : "Folk Rock"
},
{
"albumname" : "Human Touch",
"year" : "1992",
"genre" : "Rock and Roll"
}
]
},
{
"artistname" : "U2",
"formed" : "1976",
"albums" : [
{
"albumname" : "The Joshua Tree",
"year" : "1987",
"genre" : "Pop rock"
},
{
"albumname" : "Achtung Baby",
"year" : "1991",
"genre" : "Rock"
}
]
}
]
}
Ejemplo de Uso de JSON: Modelo
de Usuario de SocNet
Ahora vamos a ver cómo se puede utilizar JSON para recordar los datos
relevantes de un usuario.

En primer lugar, es importante destacar que la estructura que aquí


proponemos no es, ni mucho menos, la única posible. De hecho, la simpleza
y flexibilidad de JSON hace que usualmente exista más de una forma
posible de representar determinados datos. Tampoco necesariamente una
de esas formas será mejor que la otra, sino que es posible que el
desarrollador utilice algún criterio propio para elegir entre las candidatas.
Por ejemplo, es posible que se elija la estructura que facilite su lectura, ya
sea por parte de los programas o de las propias personas.

La primera decisión que tomamos es utilizar un fichero JSON individual para


cada usuario del sistema (una alternativa hubiera sido un único fichero con
todos los datos de usuarios del sistema). Esta forma de representarlo nos
va a permitir verificar muy fácilmente si existe un usuario determinado:
basta con buscar si existe el fichero para guardar sus datos.

Dentro de ese fichero, guardaremos un objeto JSON con las propiedades


necesarias para cada usuario: user_name, password, email, lista de amigos
(personas a las que sigue) y lista de mensajes publicados.

Los tres primeros valores serán strings. La lista de amigos será un array,
donde cada elemento será el identificador de un amigo (utilizaremos el
correo electrónico como identificación única en el sistema). Finalmente, la
lista de mensajes será otro array donde cada elemento será a su vez un
array (es decir, serán array anidados). Cada array interno contendrá el
instante de publicación del mensaje (timestamp, un número real) y el texto
del mensaje (una cadena).

El siguiente ejemplo muestra la representación del hipotético usuario en el


sistema.

{"user_name": "James", "password": "007", "messages": [[1531864361.4371479, "Me


llamo Bond, James Bond"], [1532646331.975348], "Dry Martini batido, no agitado”],
"email": "james.bond@mi6.uk", "friends": [“M@mi6.uk”, “Q@mi6.uk”,
“Felix.Leiter@cia.gov”]}
Lectura y Escritura de JSON desde
Python
Las estructuras de JSON tienen una traducción directa a estructuras
Python:

 Los objetos JSON son representados como diccionarios en Python

 Los arrays JSON son representados como listas en Python

 true y false en JSON son valores del tipo boolean en Python

 Las cadenas en JSON son cadenas en Python

 Los números en JSON son float o integer, según corresponda, en Python

Por este motivo, las funciones para leer y escribir ficheros JSON son muy
sencillas de utilizar. En ambos casos recurrimos a la biblioteca JSON, así
que lo primero que debemos hacer es:

import json

Lectura
La función para leer un fichero JSON es json.load. Como parámetro un
fichero, así que una forma normal de usarla es:

import json

Después de ejecutar esto, la variable data contendrá un diccionario con


los datos cargados del fichero. Si por ejemplo hubiéramos cargado el
fichero con los datos del usuario James visto en la sección anterior, ahora
data[‘user_name’] sería “James” y data[‘messages’] sería una lista Python
con los mensajes publicados por James.

Escritura
Volcar una estructura Python a un fichero JSON es igual de sencillo.
Supongamos que tenemos la siguiente inicialización de la variable datos:

datos = {
"user_name": "James",
"password": “007”,
"messages": [(1532648502.113984, “mensaje 1”), (1532648642.729385, “mensaje
1”)],
"email": session['email'],
"friends": session['friends']
}
Entonces guardar los datos en el fichero correspondiente sería:

with open(“james.bond@mi6.uk”, 'w') as f:


json.dump(datos, f)

Implementación de Sesiones
Ya hemos dicho en varias oportunidades que el protocolo HTTP no tiene
estado, que no recuerda. ¿Qué significa eso en la práctica? Que
necesitamos información adicional para implementar una “conversación” entre
cliente y servidor. Esta idea de conversación, donde ambas partes
recuerdan lo que han hablado hasta el momento, se llama sesión.

Aunque el concepto o la duración de una sesión puede variar en distintos


entornos, básicamente es el conjunto de interacciones entre cliente y servidor en un lapso de
tiempo razonable.

La primera vez que un cliente realiza una petición, después de un tiempo sin
interactuar, el servidor abre una sesión. Las subsecuentes peticiones
desde ese cliente se consideran dentro de la misma sesión. Si pasa mucho
tiempo sin que el cliente realice una petición, el servidor asume que ya no
está conectado y termina la sesión.

La biblioteca Flask nos ofrece este concepto de sesión. Pero si HTTP no


tiene información específica que permita identificar al usuario o la sesión,
¿cómo sabe Flask a qué usuario corresponde una determinada petición?

Existen al menos 3 formas de que una petición HTTP transporte información


que permita identificar al usuario o la sesión:

 Cookies: las cookies son pequeños ficheros que se adjuntan a una


respuesta HTTP, con información de identificación de usuario. En sucesivas
peticiones HTTP, el navegador incluye ese fichero automáticamente, por lo
que el servidor podrá tener la identificación de ese usuario.

 Campos ocultos: en los formularios que el servidor envía a cliente para que
sean completados, incluyo un campo del tipo input que no se muestra al
usuario; ese campo lleva información que cuando los datos del formulario
se envíen de vuelta al servidor le servirá para identificar al cliente.

 Reescritura de URLs: aunque nos parezcan iguales, el servidor introduce


automáticamente pequeñas modificaciones a las URLs; de esta forma, la
URL específica que solicite permitirá al servidor identificar al cliente.

La buena noticia es que, generalmente, el mecanismo que se use para


transportar la información de sesión no es visible al desarrollador de la
aplicación web. Esto es exactamente lo que ocurre con el soporte de
sesiones que nos ofrece Flask, y que veremos a continuación.
Formas de Gestionar Sesión
Vamos a ver cómo podemos manejar una sesión en Python y Flask para
poder almacenar información entre cada una de las peticiones que el
cliente haga al servidor.

Objeto Flask Session


El objeto que nos guarda la información entre sesiones se llama session. Lo
primero será importarlo dentro de nuestro código Python para luego poder
usarlo:

from flask import Flask, session

Como la información de la sesión viaja del servidor al cliente ida y vuelta,


es importante que nadie en el camino, ni siquiera el propio cliente, puedan
alterar esa información. En particular, se debe evitar que alguien de fuera
“tome control” de la sesión. En seguridad informática, este tipo de ataque
se conoce como CSRF (Cross-site request forgery) o robo de sesión.

Para asegurarse que eso no ocurre, Flask utiliza técnicas de encriptación


para proteger la información. Para eso, necesita una clav de
encriptación/desencriptación. Esta clave, que sólo debe conocer el servidor
y que debe ser distinta para todos los programas servidor, se crea en el
propio código del servidor. Por ejemplo:

app.secret_key = 'esto-es-una-clave-muy-secreta'

De hecho, en el código de nuestra aplicación SocNet habrás visto una línea


similar a esta:

app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'

Ahora ya sabes para qué sirve. Si quieres tener más información de este
problema de seguridad informática y su solución, puedes leer aquí.

Acceso a la sesión Flask


Por ejemplo, imaginemos que tenemos un formulario que le pide datos al
usuario, y en cuyo campo action tenemos el valor procesarNombre. El
código para tratar esos datos podría ser, por ejemplo:

(1) @app.route('/procesarNombre',methods=['POST'])
(2) def procesa_nombre():
(3) nombre = request.form['nombre']
(4) apellido = request.form['apellido']
(5) session['nombre'] = nombre
(6) session['apellido'] = apellido
(7) return 'Hola ' + nombre + ' ' + apellido
En este ejemplo, en las líneas 3 y 4 se toman los datos provenientes del
formulario, y se almacenan en las variables nombre y apellido,
respectivamente. En las líneas 5 y 6 esos datos se guardan en la sesión; es decir,
estarán disponibles en futuras peticiones del mismo usuario.

Siguiendo con el ejemplo, imagina que en otro formulario el usuario nos ha


ingresado su fecha de nacimiento. Al recibir esos datos, se podría escribir
el siguiente código:

(1) @app.route('/saludo',methods=['GET'])
(2) def saludo():
(3) fecha_nacimiento = request.form['nacimiento']
(4) nacim = datetime.strptime(fecha_nacimiento, "%Y-%m-%d")
(5) dias = (datetime.now – f_formato).days
(6) if 'nombre' in session:
(7) nom = session['nombre']
(8) else:
(9) nom = ''
(10)
(11) if 'apellido' in session:
(12) apell = session['apellido']
(13) else:
(14) apell = ''
(15)
(16) return 'Hola: ' + nom + ' ' + apell + ', tienes ' + dias + 'días de vida.'

Ten en cuenta que, para que este código funcione, debes


importar datetime de la siguiente forma:

from datetime import datetime

En las líneas 3 a 5 cogemos el dato de la fecha de nacimiento del formulario


(está en formato string), lo convertidos a una fecha (datetime) y
calculamos la diferencia en días con el día actual (datetime.now()).

Entre las líneas 6 y 9 intentamos acceder al atributo nombre de la sesión.


Como la sesión es un diccionario, la condición de la línea 6 se cumple si el
atributo nombre ha sido guardado en sesión; en ese caso, en 7 se lee ese
valor y se lo almacena en la variable nom. Si el nombre no está en la
sesión, en la variable nom se guarda la cadena vacía ('').

En las líneas 11 a 14 se repite este procedimiento con el apellido, y se


guarda en la variable apell.

Finalmente, en la línea 16 se devuelve un mensaje basado en estos datos.

Lo importante de este ejemplo es como se accede a los


datos nombre y apellido a pesar de que esos datos no están en el
formulario que se envía a la función saludo(), sino que habían sido tratados
antes por la función procesa_nombre().

¡Ya sabemos cómo guardar datos en la sesión de un usuario y recuperarlos


más tarde!
Red Social: Sesiones
Ahora vamos a ver ejemplo de cómo utilizamos las sesiones en nuestra
aplicación favorita.

La primera función, load_user(email, passwd), intenta cargar los datos


desde un fichero.

Importante: aquí almacenamos los datos en un fichero por una cuestión de


simplicidad. En aplicaciones con cientos o miles de usuarios lo normal es
almacenar los datos en una base de datos propiamente dicha.
Si el fichero existe, carga los datos y a continuación comprueba que la clave
suministrada coincida con la que está almacenada.

Nota de seguridad: otra vez, para facilitar las explicaciones y la comprensión de


los conceptos básicos, realizamos algunas simplificaciones que antes de
hacer público un sistema deben ser corregidas. Por ejemplo, nunca se debe
guardar una clave de usuario “en claro”, siempre se debe hacer codificada
con algún algoritmo como por ejemplo SHA-256. Lamentable estas
cuestiones quedan fuera del ámbito de este curso.
Si todas las condiciones son correctas, la función guarda los datos
relevantes del usuario (nombre, mensajes que ha escrito hasta el momento,
la clave, su correo electrónico y la lista de amigos) en la sesión, para que
estén disponibles para las próxima llamadas desde el cliente.

def load_user(email, passwd):


"""
It loads data for the given user (identified by email) from the data directory.
It looks for a file whose name matches the user email
:param email: user id
:param passwd: password to check in order to validate the user
:return: content of the home page (app basic page) if user exists and password
is correct
"""
file_path = os.path.join(SITE_ROOT, "data/", email)
if not os.path.isfile(file_path):
return process_error("User not found / No existe un usuario con ese
nombre", url_for("login"))
with open(file_path, 'r') as f:
data = json.load(f)
if data['password'] != passwd:
return process_error("Incorrect password / la clave no es correcta",
url_for("login"))
session['user_name'] = data['user_name']
session['messages'] = data['messages']
session['password'] = passwd
session['email'] = email
session['friends'] = data['friends']
return redirect(url_for("home"))

De la misma forma, cuando un usuario quiere salir del sistema,


debemos guardar los datos en el fichero (base de datos) para que estén disponibles
la próxima vez que el usuario vuelva a nuestra aplicación. Este es el
objetivo de la función save_current_user(), que vuelca los datos de session
en el fichero correspondiente.

Por otra parte, cuando un usuario se da de alta, se debe crear el


fichero, previa comprobación que no exista un usuario con el mismo
identificador (correo electrónico). Esto es lo que hace la
función create_user_file(name, email, passwd, passwd_confirmation).

def save_current_user():
datos = {
"user_name": session["user_name"],
"password": session['password'],
"messages": session['messages'], # lista de tuplas (time_stamp, mensaje)
"email": session['email'],
"friends": session['friends']
}
file_path = os.path.join(SITE_ROOT, "data/", session['email'])
with open(file_path, 'w') as f:
json.dump(datos, f)

def create_user_file(name, email, passwd, passwd_confirmation):


"""
It creates the file (in the /data directory) for storing user data. The file
name will match the user email.
If the file already exists, it returns an error.
If the password does not match the confirmation, it returns an error.
:param name: Name or nickname of the user
:param email: user email, which will be later used for retrieving data
:param passwd: password for future logins
:param passwd_confirmation: confirmation, must match the password
:return: if no error is found, it sends the user to the home page
"""

directory = os.path.join(SITE_ROOT, "data")


if not os.path.exists(directory):
os.makedirs(directory)
file_path = os.path.join(SITE_ROOT, "data/", email)
if os.path.isfile(file_path):
return process_error("The email is already used, you must select a
different email / Ya existe un usuario con ese nombre", url_for("signup"))
if passwd != passwd_confirmation:
return process_error("Your password and confirmation password do not
match / Las claves no coinciden", url_for("signup"))
datos = {
"user_name": name,
"password": passwd,
"messages": [],
"friends": []
}
with open(file_path, 'w') as f:
json.dump(datos, f)
session['user_name'] = name
session['password'] = passwd
session['messages'] = []
session['friends'] = []
session['email'] = email
return redirect(url_for("home"))
Red Social: Mensajes de mis
Amigos
Ahora vamos a poner en práctica lo que hemos aprendido de sesiones
extendiendo la funcionalidad de nuestra aplicación de red social. Para ello
vamos a implementar la funcionalidad necesaria para mostrar en la pantalla
del usuario la lista de mensajes de las personas a las que sigue(sus “amigos”).

La función, a la que llamaremos get_friends_messages_with_authors(),


buscará los amigos del usuario actual (pista: deberían estar guardados en la
sesión) y para cada uno de ellos cargará el fichero correspondiente donde
se guardan sus mensajes. La función debe retornar una lista de tuplas, cada
una representando un mensaje de la forma (user, time_stamp, message).

Recomendación: para facilitar el desarrollo de un código más fácil de entender


y mantener, te recomiendo que uses 2 funciones: la primera que cargue la
lista de amigos desde la sesión y, para cada uno de ellos, la segunda
función cargue el fichero correspondiente.

You might also like