json — Notación de objetos JavaScript¶
Propósito: | Codifica objetos de Python como cadenas JSON y decodifica cadenas JSON en objetos de Python. |
---|
El módulo json
proporciona una interfaz de programación similar a
pickle
para convertir objetos Python en memoria a una representación
serializada conocida como notación de objetos JavaScript (JSON). A diferencia de
pickle, JSON tiene el beneficio de tener implementaciones en muchos idiomas
(especialmente JavaScript). Es el más utilizado para la comunicación entre el
servidor web y cliente en una interfaz de programación REST, pero también es
útil para otras necesidades de comunicación entre aplicaciones.
Codificar y decodificar tipos de datos simples¶
El codificador entiende los tipos nativos de Python por defecto (str
,
int
, float
, list
, tuple
, y dict
).
import json
data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
print('DATA:', repr(data))
data_string = json.dumps(data)
print('JSON:', data_string)
Los valores se codifican de una manera superficialmente similar a la salida de
repr()
de Python.
$ python3 json_simple_types.py
DATA: [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
JSON: [{"a": "A", "b": [2, 4], "c": 3.0}]
La codificación, luego la decodificación puede no dar exactamente el mismo tipo de objeto.
import json
data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
print('DATA :', data)
data_string = json.dumps(data)
print('ENCODED:', data_string)
decoded = json.loads(data_string)
print('DECODED:', decoded)
print('ORIGINAL:', type(data[0]['b']))
print('DECODED :', type(decoded[0]['b']))
En particular, las tuplas se convierten en listas.
$ python3 json_simple_types_decode.py
DATA : [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
ENCODED: [{"a": "A", "b": [2, 4], "c": 3.0}]
DECODED: [{'a': 'A', 'b': [2, 4], 'c': 3.0}]
ORIGINAL: <class 'tuple'>
DECODED : <class 'list'>
Salida de consumo humano vs salida compacta¶
Otro beneficio de JSON sobre pickle
es que los resultados son legible
por humanos. La función dumps()
acepta varios argumentos para hacer la
salida aún mejor. Por ejemplo, la bandera sort_keys
le dice al codificador
que envíe las claves de un diccionario ordenadas, en lugar de ordenadas al
azar.
import json
data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
print('DATA:', repr(data))
unsorted = json.dumps(data)
print('JSON:', json.dumps(data))
print('SORT:', json.dumps(data, sort_keys=True))
first = json.dumps(data, sort_keys=True)
second = json.dumps(data, sort_keys=True)
print('UNSORTED MATCH:', unsorted == first)
print('SORTED MATCH :', first == second)
La clasificación hace que sea más fácil escanear los resultados a ojo, y también hace posible comparar la salida JSON en las pruebas.
$ python3 json_sort_keys.py
DATA: [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
JSON: [{"a": "A", "b": [2, 4], "c": 3.0}]
SORT: [{"a": "A", "b": [2, 4], "c": 3.0}]
UNSORTED MATCH: True
SORTED MATCH : True
Para estructuras de datos altamente anidadas, especifica un valor para
indent
para que la salida está mejor formateada.
import json
data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
print('DATA:', repr(data))
print('NORMAL:', json.dumps(data, sort_keys=True))
print('INDENT:', json.dumps(data, sort_keys=True, indent=2))
Cuando la sangría es un entero no negativo, la salida es más parecida a la de
pprint
, con espacios iniciales para cada nivel de la estructura de datos
que coinciden con el nivel de sangría.
$ python3 json_indent.py
DATA: [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
NORMAL: [{"a": "A", "b": [2, 4], "c": 3.0}]
INDENT: [
{
"a": "A",
"b": [
2,
4
],
"c": 3.0
}
]
La salida detallada como ésta aumenta el número de bytes necesarios para transmitir la misma cantidad de datos, sin embargo, no está destinada a ser utilizada en un entorno de producción. De hecho, es posible ajustar la configuración para separar los datos en la salida codificada para que sea aún más compacta que la configuración predeterminada.
import json
data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
print('DATA:', repr(data))
print('repr(data) :', len(repr(data)))
plain_dump = json.dumps(data)
print('dumps(data) :', len(plain_dump))
small_indent = json.dumps(data, indent=2)
print('dumps(data, indent=2) :', len(small_indent))
with_separators = json.dumps(data, separators=(',', ':'))
print('dumps(data, separators):', len(with_separators))
El argumento separators
de dumps()
debe ser una tupla que contiene las
cadenas para separar elementos en una lista y claves de valores en un
diccionario. El valor predeterminado es (',', ':')
. Al eliminar el
espacio en blanco, se produce una salida más compacta.
$ python3 json_compact_encoding.py
DATA: [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
repr(data) : 35
dumps(data) : 35
dumps(data, indent=2) : 73
dumps(data, separators): 29
Codificar diccionarios¶
El formato JSON espera que las llaves de un diccionario sean cadenas. Al
tratar de codificar un diccionario con tipos que no son de cadena, las llaves
producen un TypeError
. Una forma de evitar esa limitación es decir al
codificador que omita las claves que no sean de cadena usando el argumento
skipkeys
:
import json
data = [{'a': 'A', 'b': (2, 4), 'c': 3.0, ('d',): 'D tuple'}]
print('First attempt')
try:
print(json.dumps(data))
except TypeError as err:
print('ERROR:', err)
print()
print('Second attempt')
print(json.dumps(data, skipkeys=True))
En lugar de elevar una excepción, la clave que no es de cadena se ignora.
$ python3 json_skipkeys.py
First attempt
ERROR: keys must be a string
Second attempt
[{"a": "A", "b": [2, 4], "c": 3.0}]
Trabajar con tipos personalizados¶
Todos los ejemplos hasta ahora han usado tipos incorporados de Python porque
son soportados por json
de forma nativa. Es común necesitar codificar
también clases personalizadas, y hay dos formas de hacerlo.
Dada esta clase para codificar:
class MyObj:
def __init__(self, s):
self.s = s
def __repr__(self):
return '<MyObj({})>'.format(self.s)
La forma simple de codificar una instancia MyObj
es definir una función
para convertir un tipo desconocido a un tipo conocido. No necesita hacer la
codificación, por lo que simplemente debe convertir un objeto a otro.
import json
import json_myobj
obj = json_myobj.MyObj('instance value goes here')
print('First attempt')
try:
print(json.dumps(obj))
except TypeError as err:
print('ERROR:', err)
def convert_to_builtin_type(obj):
print('default(', repr(obj), ')')
# Convert objects to a dictionary of their representation
d = {
'__class__': obj.__class__.__name__,
'__module__': obj.__module__,
}
d.update(obj.__dict__)
return d
print()
print('With default')
print(json.dumps(obj, default=convert_to_builtin_type))
En convert_to_builtin_type()
, instancias de clases no reconocidas por
json
se convierten en diccionarios con suficiente información para volver a
crear el objeto si un programa tiene acceso a los módulos de Python necesarios.
$ python3 json_dump_default.py
First attempt
ERROR: Object of type 'MyObj' is not JSON serializable
With default
default( <MyObj(instance value goes here)> )
{"__class__": "MyObj", "__module__": "json_myobj", "s": "instance
value goes here"}
Para decodificar los resultados y crear una instancia MyObj()
, usa el
argumento object_hook
de loads()
para vincular al decodificador para
que la clase pueda importarse desde el módulo y usarse para crear la instancia.
El object_hook
se llama para cada diccionario decodificado del flujo de
datos entrante, brindando la oportunidad de convertir el diccionario a otro
tipo de objeto. La función de gancho debe devolver el objeto que la aplicación
que llama debe recibir en lugar del diccionario.
import json
def dict_to_object(d):
if '__class__' in d:
class_name = d.pop('__class__')
module_name = d.pop('__module__')
module = __import__(module_name)
print('MODULE:', module.__name__)
class_ = getattr(module, class_name)
print('CLASS:', class_)
args = {
key: value
for key, value in d.items()
}
print('INSTANCE ARGS:', args)
inst = class_(**args)
else:
inst = d
return inst
encoded_object = '''
[{"s": "instance value goes here",
"__module__": "json_myobj", "__class__": "MyObj"}]
'''
myobj_instance = json.loads(
encoded_object,
object_hook=dict_to_object,
)
print(myobj_instance)
Dado que json
convierte los valores de cadena a objetos Unicode, estos
necesitan volver a codificarse como cadenas ASCII antes de que puedan usarse
como argumentos palabra clave del constructor de la clase.
$ python3 json_load_object_hook.py
MODULE: json_myobj
CLASS: <class 'json_myobj.MyObj'>
INSTANCE ARGS: {'s': 'instance value goes here'}
[<MyObj(instance value goes here)>]
Hay ganchos similares disponibles para los tipos integrados enteros
(parse_int
), números de coma flotante (parse_float
), y constantes
(parse_constant
).
Clases de codificador y decodificador¶
Además de las funciones de conveniencia ya cubiertas, el módulo json
proporciona clases para la codificación y decodificación. Usando las clases
directamente da acceso a interfaces de programación adicionales para
personalizar su comportamiento.
El JSONEncoder
utiliza una interfaz iterable para producir «fragmentos» de
datos codificados, lo que facilita la escritura en archivos o sockets de red
sin tener que representar una estructura completa de datos en memoria.
import json
encoder = json.JSONEncoder()
data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
for part in encoder.iterencode(data):
print('PART:', part)
La salida se genera en unidades lógicas, en lugar de estar basada en cualquier valor de tamaño.
$ python3 json_encoder_iterable.py
PART: [
PART: {
PART: "a"
PART: :
PART: "A"
PART: ,
PART: "b"
PART: :
PART: [2
PART: , 4
PART: ]
PART: ,
PART: "c"
PART: :
PART: 3.0
PART: }
PART: ]
El método encode()
es básicamente equivalente a
''.join(encoder.iterencode())
, con una comprobación de error adicional por
adelantado.
Para codificar objetos arbitrarios, anula el método default()
con una
implementación similar a la utilizada en convert_to_builtin_type()
.
import json
import json_myobj
class MyEncoder(json.JSONEncoder):
def default(self, obj):
print('default(', repr(obj), ')')
# Convert objects to a dictionary of their representation
d = {
'__class__': obj.__class__.__name__,
'__module__': obj.__module__,
}
d.update(obj.__dict__)
return d
obj = json_myobj.MyObj('internal data')
print(obj)
print(MyEncoder().encode(obj))
La salida es la misma que la implementación anterior.
$ python3 json_encoder_default.py
<MyObj(internal data)>
default( <MyObj(internal data)> )
{"__class__": "MyObj", "__module__": "json_myobj", "s": "internal
data"}
Decodificar texto, luego convertir el diccionario en un objeto toma una un poco más de trabajo para configurar que la implementación anterior, pero no mucho.
import json
class MyDecoder(json.JSONDecoder):
def __init__(self):
json.JSONDecoder.__init__(
self,
object_hook=self.dict_to_object,
)
def dict_to_object(self, d):
if '__class__' in d:
class_name = d.pop('__class__')
module_name = d.pop('__module__')
module = __import__(module_name)
print('MODULE:', module.__name__)
class_ = getattr(module, class_name)
print('CLASS:', class_)
args = {
key: value
for key, value in d.items()
}
print('INSTANCE ARGS:', args)
inst = class_(**args)
else:
inst = d
return inst
encoded_object = '''
[{"s": "instance value goes here",
"__module__": "json_myobj", "__class__": "MyObj"}]
'''
myobj_instance = MyDecoder().decode(encoded_object)
print(myobj_instance)
Y la salida es la misma que en el ejemplo anterior.
$ python3 json_decoder_object_hook.py
MODULE: json_myobj
CLASS: <class 'json_myobj.MyObj'>
INSTANCE ARGS: {'s': 'instance value goes here'}
[<MyObj(instance value goes here)>]
Trabajar con flujos y archivos¶
Todos los ejemplos hasta ahora han asumido que la versión codificada de la
estructura de datos completa podría mantenerse en la memoria al mismo tiempo.
Con grandes estructuras de datos, puede ser preferible escribir la codificación
directamente a un objeto similar a un archivo. Las funciones de conveniencia
load()
y dump()
aceptan referencias a un objeto similar a un archivo
utilizar para leer o escribir.
import io
import json
data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
f = io.StringIO()
json.dump(data, f)
print(f.getvalue())
Un socket o un identificador de archivo normal funcionaría de la misma manera
que el búfer StringIO
utilizado en este ejemplo.
$ python3 json_dump_file.py
[{"a": "A", "b": [2, 4], "c": 3.0}]
Aunque no está optimizada para leer solo parte de los datos a la vez, la
función load()
ofrece la ventaja de encapsular la lógica de generar objetos
del flujo de entrada.
import io
import json
f = io.StringIO('[{"a": "A", "c": 3.0, "b": [2, 4]}]')
print(json.load(f))
Al igual que para dump()
, cualquier objeto similar a un archivo se puede
pasar a load()
.
$ python3 json_load_file.py
[{'a': 'A', 'c': 3.0, 'b': [2, 4]}]
Flujos de datos mixtos¶
JSONDecoder
incluye raw_decode()
, un método para decodificar una
estructura de datos seguida de más datos, como los datos JSON con texto al
final. El valor de retorno es el objeto creado por la decodificación de los
datos de entrada, y un índice en esos datos que indica dónde se detuvo la
decodificación.
import json
decoder = json.JSONDecoder()
def get_decoded_and_remainder(input_data):
obj, end = decoder.raw_decode(input_data)
remaining = input_data[end:]
return (obj, end, remaining)
encoded_object = '[{"a": "A", "c": 3.0, "b": [2, 4]}]'
extra_text = 'This text is not JSON.'
print('JSON first:')
data = ' '.join([encoded_object, extra_text])
obj, end, remaining = get_decoded_and_remainder(data)
print('Object :', obj)
print('End of parsed input :', end)
print('Remaining text :', repr(remaining))
print()
print('JSON embedded:')
try:
data = ' '.join([extra_text, encoded_object, extra_text])
obj, end, remaining = get_decoded_and_remainder(data)
except ValueError as err:
print('ERROR:', err)
Desafortunadamente, esto solo funciona si el objeto aparece al principio de la entrada.
$ python3 json_mixed_data.py
JSON first:
Object : [{'a': 'A', 'c': 3.0, 'b': [2, 4]}]
End of parsed input : 35
Remaining text : ' This text is not JSON.'
JSON embedded:
ERROR: Expecting value: line 1 column 1 (char 0)
JSON en la línea de comando¶
El módulo json.tool
implementa un programa de línea de comandos para re
formatear datos JSON para que sean más fácil de leer.
[{"a": "A", "c": 3.0, "b": [2, 4]}]
El archivo de entrada example.json
contiene un mapeo con las llaves fuera
de orden alfabético. El primer ejemplo a continuación muestra los datos re
formateando en orden, y el segundo ejemplo usa --sort-keys
para ordenar las
llaves de mapeo antes de imprimir la salida.
$ python3 -m json.tool example.json
[
{
"a": "A",
"c": 3.0,
"b": [
2,
4
]
}
]
$ python3 -m json.tool --sort-keys example.json
[
{
"a": "A",
"b": [
2,
4
],
"c": 3.0
}
]
Ver también
- Documentación de la biblioteca estándar para json
- Notas para portar Python 2 a 3 para json
- JavaScript Object Notation – Página de incio JSON, con documentación e implementaciones en otros idiomas.
- jsonpickle –
jsonpickle
permite que cualquier objeto Python sea serializado en JSON.