Lección 18 · 18 de 20
JSON y expresiones regulares · Python
Lea y escriba JSON con el módulo json de Python y extraiga datos de un registro con expresiones regulares: grupos con nombre, findall y sub.

JSON es el formato de texto que usan casi todas las API web. `json.loads()` convierte ese texto en objetos de Python: los objetos JSON pasan a diccionarios, las listas a listas y false a False. A partir de ahí, order["items"] se recorre como cualquier lista para calcular el total.
`json.dumps()` hace el camino contrario. Con sort_keys=True las claves salen ordenadas, lo que ayuda a comparar resultados; con indent=2 saldría con sangría, más fácil de leer. Para archivos están json.load() y json.dump(), sin la s.
Las expresiones regulares describen patrones de texto. re.compile() prepara el patrón una vez para usarlo muchas. \d es un dígito, [A-Z]+ una o más mayúsculas y .+ el resto de la línea. `(?P<time>...)` es un grupo con nombre: lo que encaja se recupera con match["time"]. pattern.match(line) comprueba el patrón desde el principio de la línea.
`re.findall()` devuelve todo lo que encaja en el texto. Con el patrón de correo, [\w.]+ son letras, dígitos, guiones bajos o puntos; si el patrón tiene un grupo entre paréntesis, como order (\d+), devuelve solo lo del grupo, y el programa lo pasa a int.
`re.sub()` sustituye lo que encaja: aquí oculta la parte del correo anterior a la arroba. Las cadenas de los patrones empiezan por r (cadenas crudas) para que la barra invertida llegue tal cual a re.
Código · main.py
import jsonimport re# An order as it arrives from a web APIpayload = """{"id": 1042, "customer": "ana@shop.es", "paid": false, "items": [{"sku": "KB-01", "qty": 2, "price": 25.0}, {"sku": "MS-07", "qty": 1, "price": 12.5}]}"""order = json.loads(payload) # JSON text -> dicts and liststotal = sum(item["qty"] * item["price"] for item in order["items"])print("Order", order["id"], "| paid:", order["paid"], "| total:", total)skus = [item["sku"] for item in order["items"]]summary = {"order": order["id"], "total": total, "skus": skus}print(json.dumps(summary, sort_keys=True)) # Python -> JSON textlog = """10:05:12 ERROR payment failed for order 1042 (ana@shop.es)10:06:40 INFO order 1043 shipped to luis.p@mail.com10:09:03 WARN slow response: 2350 ms"""# Named groups: (?P<name>...) captures a piece we can ask for by namepattern = re.compile(r"(?P<time>\d\d:\d\d):\d\d (?P<level>[A-Z]+) (?P<msg>.+)")for line in log.splitlines(): match = pattern.match(line) print(f"{match['level']:<5} at {match['time']}: {match['msg']}")print("Emails:", re.findall(r"[\w.]+@[\w.]+\.\w+", log))print("Orders:", [int(n) for n in re.findall(r"order (\d+)", log)])print("Masked:", re.sub(r"[\w.]+@", "***@", log.splitlines()[1]))Salida · python3 main.py
Order 1042 | paid: False | total: 62.5
{"order": 1042, "skus": ["KB-01", "MS-07"], "total": 62.5}
ERROR at 10:05: payment failed for order 1042 (ana@shop.es)
INFO at 10:06: order 1043 shipped to luis.p@mail.com
WARN at 10:09: slow response: 2350 ms
Emails: ['ana@shop.es', 'luis.p@mail.com']
Orders: [1042, 1043]
Masked: 10:06:40 INFO order 1043 shipped to ***@mail.comPara recordar
json.loadsconvierte texto en objetos;json.dumps, al revés.- Escriba los patrones como cadenas crudas:
r"\d+". (?P<nombre>...)captura un trozo que se pide por nombre.findalldevuelve todas las coincidencias;sublas reemplaza.re.compileprepara un patrón que se usa muchas veces.
Ejercicio
Extraiga del registro los milisegundos de la línea WARN con un grupo con nombre y escriba un aviso si pasan de 2000.