Leer y escribir ficheros json en python
Veamos como convertir un objeto python a json, tanto en memoria, como escribiendo o leyendo de un fichero. Para ellos necesitamos el módulo json
que viene por defecto en la instalación de python. Tienes el código de ejemplo de este tutorial en python-json
Importar librería json[editar]
Lo primero, como siempre, importar la librería que nos hace falta
import json
El módulo json viene por defecto con la instalación de python. Hay otras librerías adicionales que podríamos instalarnos, pero lo dejamos para otros tutoriales.
Convertir entre dict y JSON[editar]
Un JSON no es más que una cadena de texto, bien leída de un fichero, bien contenida en una variable string. Para manejarla en nuestro programa y acceder o rellenar sus datos, es mejor hacerlo como dict
. Afortunadamente, con la librería json la conversión de uno a otro es bastante rápida.
Convertir dict a JSON : dumps()[editar]
El módulo tiene la función dumps()
a la que se le pasa un diccionario y devuelve un string json.
# Un dict
data = {"nombre": "juan", "apellido": "Perez", "fecha_nacimiento": {"año": 2002, "mes": 12, "dia": 1}}
# Convertir dict a json string
json_data = json.dumps(data)
print(type(json_data))
print(json_data)
# json_data es de tipo string. La salida de este código es
# <class 'str'>
# {"nombre": "juan", "apellido": "Perez", "fecha_nacimiento": {"a\u00f1o": 2002, "mes": 12, "dia": 1}}
Hemos llamado a json.dumps()
pasando como parámetro un dict (diccionario) de python. Obtenemos de resultado un objeto de tipo cadena cuyo contenido es el dict convertido a json. Fíjate que caracteres extraños como la ñ de año los convierte a formato unicode, puesto que json no los acepta.
Convertir JSON a dict : loads()[editar]
La función loads()
permite leer la cadena y volver a convertirla en dict.
# Utilizamos la cadena generada antes
dict_data = json.loads(json_data)
print(type(dict_data))
print(dict_data)
# dict_data es un dict. La salida de este código es
# <class 'dict'>
# {'nombre': 'juan', 'apellido': 'Perez', 'fecha_nacimiento': {'año': 2002, 'mes': 12, 'dia': 1}}
Leer y escribir en un fichero JSON[editar]
Sabiendo convertir de JSON a dict y de dict a JSON, sería fácil leer y escribir de un fichero JSON. Bastaría leerlo o escribirlo como string y luego hacer la conversión a o desde dict. Pero la librería json nos ofrece funciones para hacerlo directamente.
Las funciones dump()
y load()
funcionan igual que dumps()
y loads()
, pero en vez de sobre un string, lo hacen sobre un fichero. Para ello debemos pasarles un descriptor de fichero abierto.
Escribir fichero json con python: dump()[editar]
El siguiente ejemplo usa dump()
para escribir un fichero JSON a partir de un dict
# un dict con los datos
data = {"nombre": "juan", "apellido": "Perez", "fecha_nacimiento": {"año": 2002, "mes": 12, "dia": 1}}
# Apertura y escritura del fichero JSON
with open("json-data.txt", "w") as file:
json.dump(data, file)
Hemos abierto el fichero con la función open()
como fichero de escritura modo texto y obtenemos su descriptor como file
. Nos basta llamar a dump()
para salvar el dict python en formato json en un fichero. El contenido del fichero sería
{"nombre": "juan", "apellido": "Perez", "fecha_nacimiento": {"a\u00f1o": 2002, "mes": 12, "dia": 1}}
Leer fichero json con python: load()[editar]
Para leer el fichero, basta con abrilo para lectura y usar la función load()
# Abrimos de lectura el fichero generado antes
with open("json-data.txt", "r") as file:
data_object = json.load(file)
# Sacamos por pantalla lo leído, un dict.
print(type(data_object))
print(data_object)
# La salida de este programa es
# <class 'dict'>
# {'nombre': 'juan', 'apellido': 'Perez', 'fecha_nacimiento': {'año': 2002, 'mes': 12, 'dia': 1}}
Parámetros avanzados de dump() y dumps()[editar]
Hemos usado dump()
y dumps()
da la forma más sencilla, dejando por defecto los parámetros opcionales de dichas funciones. Veamos cuales son y su significado. Se pone el valor que tiene por defecto.
skipkeys=False
. Si hay algún valor en el dict que no es un tipo serializable a json, da un error. Por ejemplo, python admite números complejos, pero json no los admite. Si uno de los valores del dict en un número complejo, estas funciones darán una excepción. Poniendo aTrue
este parámetro no saltará la excepción, simplemente se saltará el valor que no irá al json.ensure_ascii=True
. Si hay caracteres extraños en el dict, como la ñ que pusimos antes, se convierte a unicode para asegurar que es compatible con json. Si ponemos esta opción aFalse
, no se hará esta conversión y en json se escribirá la ñ tal cual.check_circular=True
. Si dict contiene una referencia circular salta un error. Una referencia circular es que el objeto dict, dentro de una de sus claves, tenga directamente o indirecamente una referencia a sí mismo. En nuestros ejemplos, si justo después de definirdata
como un dict, hacemosdata["circular"] = data
estamos incluyendo en la clave "circular" una referencia a sí mismo. Daría error de recursión. Si ponemos aFalse
este parámetro, no se hace esta verificación, por lo que el json crecerá indefinidamente hasta comerse toda la memoria.allow_nan=True
. json no admite valoresnan
,inf
o-inf
. Con la opción por defecto se generará el json con los textosNaN
,Infinity
y-Infinity
, aunque no son estrictamente válidos en json. Si ponemos esta opción aFalse
, si dict tiene alguno de estos valores, saltará un error.cls=None
. Usa el encoder a json por defecto. En este parámetro podríamos pasar un encoder propio, por ejemplo, que sea capaz de convertir a json de una forma válida los números complejos.indent=None
. Con el valor por defecto crea el json de la forma más compacta posible, es decir, una sola línea. Si ponemos un valor positivo, hará un "pretty print" del json indentando el número de espacios que le indiquemos como valor positivo. Si ponemos un string, indentará usando ese string.separators=None
. Los separadores por defecto para json son ", " y ": ", con espacio detrás. Podemos poner aquí los separadores que queramos como una tupla, por ejemplo, podemos quitar los espacios(",",":")
default=None
. Como hemos comentado antes, si hay un valor en el dict que no se puede convertir a json salta un error. Por ejemplo un número complejo. Si pasamos como parámetrodefault
una función que admita un parámetro y devuelva su representación en json, se usará esta función en vez de dar error. El json contendrá como valor lo que nuestra funcióndefault()
haya decidido.sort_keys=False
. Si se pone aTrue
el json tendrá ordenada alfabéticamente las claves.
json pretty print con python[editar]
Si queremos una cadena de texto json correctamente indentada, se hace con el parámetro indent
que acabamos de ver. Ponemos aquí un ejemplo puesto que suele ser una consulta muy habitual por la gente que empieza.
data = {"nombre": "juan", "apellido": "Perez", "fecha_nacimiento": {"año": 2002, "mes": 12, "dia": 1}}
string = json.dumps(data, indent=3)
print(string)
Simplemente hemos añadido a dumps
el parámetro indent=3
para obtener el json indentado de tres en tres espacios. La salida de este programa sería
{
"nombre": "juan",
"apellido": "Perez",
"fecha_nacimiento": {
"a\u00f1o": 2002,
"mes": 12,
"dia": 1
}
}
Parámetros avanzados de load() y loads()[editar]
Para leer un json y convertirlo en dict hemos usado la forma fácil, las funciones load()
y loads()
sin parámetros opcionales. Pero hay parámetros opcionales que podemos cambiar. Los ponemos a continuación, junto con su valor por defecto.
cls=None
Usa el decoder json por defecto. Aquí podemos pasar un decoder propio que hagamos.object_hook=None
Aquí podemos pasar una funciónmi_funcion()
propia a la que la funciónloads()
llamará pasando como parámetro cada dict que vaya parseando del json. Nuestra función puede modificar ese dict y devolverlo modificado. La funciónloads()
usará el valor devuelto por nuestra función en vez de el dict que ella ha obtenido del json. Esto nos permite hacer un decoder propio sin necesidad de parsear el json desde cero.object_pairs_hook=None
Similar aobject_hook
pero nuestra función recibirá una lista de pares clave:valor. Podemos modificar y devolver lo modificado. La funciónloads()
usará lo que le devolvamos en vez de lo que ella ha obtenido.parse_float=None
. Podemos pasar una función propia para parsear los números flotantes. Por defecto se usa la función propia de pythonfloat()
, pero podemos hacer una función con otro tipo de conversión de str a float si queremos.parse_int=None
. Idem aparse_float
pero para enteros.parse_constant=None
. Idem a las anteriores, pero se le llamará cuando de json se lean los valoresNaN
,Infinity
y-Infinity
Encoder y Decoder de json: JSONEncoder y JSONDecoder[editar]
Las clases JSONEncoder
y JSONDecoder
son las que usan por defecto las funciones mencionadas hasta ahora. Sin embargo, estas clases están accesibles y podemos hacer dos cosas con ellas:
- Instanciarlas y usarlas. No tiene demasiado sentido teniendo las funciones fáciles
load()
,loads()
,dump()
ydumps()
. Tienes no obstante un ejemplo en la funcióncon_encoders()
de json-string.py - Heredar de ellas para hacer nuestro propio coder o encoder con características particulares en las que tengamos interés. De alguna manera podemos hacer lo mismo con los parámetros avanzados de las funciones
load()
,loads()
,dump()
ydumps()
, pero aquí sería cuestión de sobreescribir métodos deJSONEncoder
yJSONDecoder
Un ejemplo para números complejos de y a json[editar]
Aunque el estándar json no permite números complejos, vamos a ver cómo se haría el decoder y el encoder para que esté soportado. Vamos primero con el encoder
class MyEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, complex):
return {"complex": '%d+%dj' % (o.real, o.imag)}
return json.JSONEncoder.default(self, o)
Hemos heredado de json.JSONEncoder
. Sobreescribimos el método default()
. Se llamará a este método uno de los tipos del dict de python no tenga conversión estándar a json, por ejemplo, un número complejo. En el métoco comprobamos si el objeto o
que nos pasan es un complejo y en caso afirmativo devolvemos un dict {"complex": "2+3j"}
. El valor será el que sea, no el que he puesto aquí y va como string, de forma que luego podamos hacer fácilmente la conversión contraria. Si el valor no es de tipo complejo, llamamos al método de la clase padre y devolvemos lo que esta devuelva. Es de interés notar que como a este método sólo nos llaman si el tipo no tiene conversión estándar a json, la llamada a la clase padre dará un error.
La salida en json para un número complejo sería un texto {"complex": "2+3j"}
Vamos ahora con el decoder.
class MyDecoder(json.JSONDecoder):
def __init__(self):
json.JSONDecoder.__init__(self, object_hook=self.object_hook)
def object_hook(self, dct):
if "complex" in dct:
value = complex(dct["complex"])
return value
return dct
Heredamos de json.JSONDecoder
. Ponemos un constructor llamando al de la clase padre para que el parámetro object_hook
llame a nuestro método object_hook()
. Podríamos ponerle cualquier otro nombre. La clase padre llamará al método object_hook()
cada vez que haya decodificado un dict. En el método comprobamos que la clave "complex" está en el dict. En caso afirmativo, convertimos dct["complex"]
que será el string "2+3j"
en un número complejo. Para ello usamos la función complex()
que admite como parámetro el string y devuelve el número complejo. Nuesta función object_hook()
devuelve entonces el número complejo como tipo complex
de python. Si el dict no contiene la clave "complex", lo devolvemos tal cual.
En json-custom-encoders.py tienes el ejemplo completo con un main de prueba.