CSV va deixar Papyrus parlant l'idioma de les taules, però amb un sostre a la vista: les taules són planes. Els socis de la botiga no ho són — en Lluís (LLUIS-001) té un nom, un codi i una llista de compres, cadascuna amb la seva data i la seva quantia. Encabir una llista dins d'una cel·la de CSV és forçar el format; per a estructures niades existeix JSON (JavaScript Object Notation), el format de text en què avui s'escriuen les configuracions de les aplicacions i les respostes de gairebé totes les APIs web. En aquesta lliçó aprendràs les quatre funcions del mòdul json, la taula d'equivalències entre tipus Python i JSON (i què en queda fora), i portaràs el fil de Papyrus dos passos més enllà: el catàleg de dataclasses viatjarà a JSON amb asdict() i tornarà amb Llibre(**dades), i la botiga estrenarà el seu config.json.
Contingut
- Què és JSON i on viu
- Les quatre funcions del mòdul
json:dump,load,dumps,loads - Tipus Python ↔ JSON: la taula d'equivalències
indentiensure_ascii=False: JSON llegible i amb accents- El catàleg en JSON:
asdict()a l'anada,Llibre(**dades)a la tornada - Niament: els socis de Papyrus amb les seves compres
- JSON vs CSV: taula de criteri
config.json: les constants de Papyrus surten del codi- Una menció a
pickle - Errors comuns i consells
- Exercicis amb solucions
Què és JSON i on viu
JSON és un format de text pla per representar dades estructurades. La seva sintaxi et resultarà sospitosament familiar:
{
"titol": "L'Odissea",
"preu": 12.5,
"estoc": 4,
"disponible": true,
"etiquetes": ["classics", "poesia epica"]
}Sembla un diccionari de Python amb una llista a dins — i gairebé ho és (les diferències, a la taula d'equivalències). Va néixer al món JavaScript, però avui és neutral i omnipresent. El trobaràs en tres hàbitats:
- APIs web: quan al mòdul 10 el teu programa demani dades a un servidor (o les serveixi amb Flask), les respostes seran JSON.
- Fitxers de configuració: preferències d'aplicacions, ajustos d'editors (el
settings.jsonde VS Code, sense anar més lluny). - Intercanvi i emmagatzematge de dades estructurades: just el que Papyrus necessita per al catàleg i els socis.
A diferència de CSV, JSON nia sense límit (objectes dins de llistes dins d'objectes) i conserva els tipus bàsics: un 12.5 torna com a número, no com la cadena "12.5". El peatge de conversió que vam pagar a 06-01 i 06-02 gairebé desapareix.
Les quatre funcions del mòdul json: dump, load, dumps, loads
Tota la lliçó gira sobre quatre funcions de la biblioteca estàndard. El truc mnemotècnic: la s final significa string — les versions amb s treballen amb cadenes en memòria; les versions sense s, amb fitxers oberts.
| Funció | Direcció | Treballa amb | Ús típic |
|---|---|---|---|
json.dump(dades, f) |
Python → JSON | Fitxer obert per escriure | Desar el catàleg a cataleg.json |
json.load(f) |
JSON → Python | Fitxer obert per llegir | Carregar config.json en arrencar |
json.dumps(dades) |
Python → JSON | Retorna una cadena | Enviar dades a una API, imprimir per depurar |
json.loads(cadena) |
JSON → Python | Rep una cadena | Interpretar la resposta d'una API |
Les quatre en acció:
import json
llibre = {"titol": "Hamlet", "preu": 9.95, "estoc": 6}
# dumps/loads: cadena en memoria (fixa-t'hi: sense tocar el disc)
text = json.dumps(llibre)
print(text) # {"titol": "Hamlet", "preu": 9.95, "estoc": 6}
print(type(text)) # <class 'str'>
copia = json.loads(text)
print(copia["preu"] + 1) # 10.95 — torna com a float, no com a "9.95"!
# dump/load: directe a fitxer — les regles de 06-01 segueixen vigents (with + utf-8)
with open("llibre.json", "w", encoding="utf-8") as f:
json.dump(llibre, f)
with open("llibre.json", "r", encoding="utf-8") as f:
recuperat = json.load(f)
print(recuperat == llibre) # True — round-trip perfecte, tipus inclososAquest copia["preu"] + 1 marca la diferència amb tot l'anterior: en text pla i CSV, els números tornaven com a cadenes i convertíes a mà; JSON recorda que 9.95 era un número. (El newline="" de 06-02 era una peculiaritat del mòdul csv; amb json no es fa servir.)
Tipus Python ↔ JSON: la taula d'equivalències
La traducció entre tots dos mons està definida tipus a tipus:
| Python (en escriure) | JSON | Python (en llegir de tornada) |
|---|---|---|
dict |
objecte {...} |
dict |
list, tuple |
array [...] |
list (la tupla no torna com a tupla!) |
str |
string "..." |
str |
int |
número | int |
float |
número | float |
True / False |
true / false |
True / False |
None |
null |
None |
I la lletra petita del contracte, que convé llegir:
- Les claus d'un objecte JSON són sempre cadenes. Un dict de Python amb claus enteres (
{1: "a"}) s'escriu com{"1": "a"}i en tornar les claus sónstr. Si fas servir claus no textuals, el round-trip no és fidel. - Les tuples es degraden a llistes:
("a", "b")→["a", "b"]→["a", "b"]. JSON no té tuples. - El que NO se serialitza:
set,datetime/date,bytesi — atenció — els teus objectes:json.dumps(Llibre("Hamlet", 9.95, 6))llançaTypeError: Object of type Llibre is not JSON serializable. L'estratègia estàndard és convertir abans a tipus bàsics (unset→list, undate→"2026-07-13"ambstr()oisoformat(), i una dataclass →dict, com veuràs d'aquí a dues seccions).
Aquesta darrera limitació no és un defecte: JSON és deliberadament petit perquè qualsevol llenguatge pugui llegir-lo. Els teus objectes són de Python; les seves dades, universals.
indent i ensure_ascii=False: JSON llegible i amb accents
Per defecte, dump/dumps ho escriuen tot en una línia i escapen els caràcters no ASCII. El resultat funciona, però és il·legible per a humans:
config = {"nom_botiga": "Papyrus", "especialitat": "clàssics"}
print(json.dumps(config))
# {"nom_botiga": "Papyrus", "especialitat": "cl\u00e0ssics"} ← i els meus accents?Dos arguments ho arreglen:
indent=4: formata amb salts de línia i sagnat de 4 espais — imprescindible en fitxers que una persona hagi de llegir o editar (configuracions).ensure_ascii=False: escriuàen lloc de\u00e0. L'escapament és una protecció arcaica per a sistemes que no entenen UTF-8; com que nosaltres obrim sempre ambencoding="utf-8"(06-01), podem desactivar-la i tenir accents de veritat al fitxer.
Regla pràctica del curs: en fitxers JSON destinats a humans, indent=4, ensure_ascii=False sempre. En JSON que viatja entre programes (APIs), la versió compacta estalvia bytes i ningú no la llegeix.
El catàleg en JSON: asdict() a l'anada, Llibre(**dades) a la tornada
L'obstacle — "els teus objectes no se serialitzen" — té una solució elegant per a dataclasses, i ve a la mateixa biblioteca: dataclasses.asdict() converteix una dataclass (recursivament, camps niats inclosos) en un diccionari de tipus bàsics, que JSON sí que entén:
from dataclasses import asdict
odissea = Llibre("L'Odissea", 12.50, 4) # la dataclass canonica de 05-06
print(asdict(odissea)) # {'titol': "L'Odissea", 'preu': 12.5, 'estoc': 4}I per al viatge de tornada, el desempaquetament de diccionaris amb ** (03-02): Llibre(**{"titol": "L'Odissea", "preu": 12.5, "estoc": 4}) equival a Llibre(titol="L'Odissea", preu=12.5, estoc=4). Com que els camps de la dataclass es diuen igual que les claus del dict, encaixen sols. El round-trip complet del catàleg queda així:
import json
from pathlib import Path
from dataclasses import asdict
def desar_cataleg_json(cataleg, ruta="cataleg.json"):
"""dict[str, Llibre] → llista de dicts → JSON."""
dades = [asdict(llibre) for llibre in cataleg.values()]
with open(ruta, "w", encoding="utf-8") as f:
json.dump(dades, f, indent=4, ensure_ascii=False)
def carregar_cataleg_json(ruta="cataleg.json"):
"""JSON → llista de dicts → dict[str, Llibre]. Sense conversions str→float: JSON recorda."""
if not Path(ruta).exists(): # el pedac de sempre; el modul 7 arriba al rescat aviat
return {}
with open(ruta, "r", encoding="utf-8") as f:
dades = json.load(f)
return {normalitzar_titol(d["titol"]): Llibre(**d) for d in dades}
desar_cataleg_json(cataleg)
recarregat = carregar_cataleg_json()
print(recarregat == cataleg) # True
print(recarregat["hamlet"].preu_final(soci=True)) # 9.83 — la xifra de sempreCompara carregar_cataleg_json amb el carregar_cataleg de 06-02: han desaparegut float(fila["preu"]) i int(fila["estoc"]). JSON va retornar cada tipus intacte i Llibre(**d) va reconstruir l'objecte en una expressió — amb __post_init__ validant de regal, com sempre. El cataleg.json resultant és una llista d'objectes:
[
{
"titol": "L'Odissea",
"preu": 12.5,
"estoc": 4
},
{
"titol": "Hamlet",
"preu": 9.95,
"estoc": 6
}
](Nota al marge: desem cataleg.values() com a llista i reconstruïm les claus normalitzades en carregar, igual que a 06-02 — la clau és derivable, no cal emmagatzemar-la.)
Niament: els socis de Papyrus amb les seves compres
Aquí JSON juga en una lliga on CSV no arriba. Cada soci és un objecte amb una llista de compres a dins, i cada compra és al seu torn un objecte — l'estructura calca les LiniaVenda de 05-06:
socis = [
{
"nom": "Lluis", "codi": "LLUIS-001",
"compres": [
{"data": "2026-07-13", "titol": "Hamlet", "quantia": 9.83},
{"data": "2026-07-11", "titol": "L'Odissea", "quantia": 12.35},
],
},
{
"nom": "Marta", "codi": "MARTA-002",
"compres": [
{"data": "2026-07-12", "titol": "El Quixot", "quantia": 15.71},
],
},
{"nom": "Pau", "codi": "PAU-003", "compres": []},
]
with open("socis.json", "w", encoding="utf-8") as f:
json.dump(socis, f, indent=4, ensure_ascii=False)I en carregar, navegues per l'estructura amb la sintaxi de sempre — índexs i claus encadenats (04-01/04-03), o millor, bucles i comprensions:
with open("socis.json", "r", encoding="utf-8") as f:
socis = json.load(f)
print(socis[0]["compres"][1]["titol"]) # L'Odissea
despesa_per_soci = {s["codi"]: round(sum(c["quantia"] for c in s["compres"]), 2)
for s in socis}
print(despesa_per_soci) # {'LLUIS-001': 22.18, 'MARTA-002': 15.71, 'PAU-003': 0}Intenta imaginar aquesta dada en CSV: o una fila per compra repetint les dades del soci, o llistes encabides en una cel·la. JSON ho expressa tal com ho penses.
JSON vs CSV: taula de criteri
Dues lliçons, dos formats de text. El criteri per triar:
| CSV (06-02) | JSON (aquesta lliçó) | |
|---|---|---|
| Forma de les dades | Taula plana: files × columnes | Arbre: niament lliure |
| Tipus | Tot torna com a str |
Conserva número/booleà/null |
| L'obre Excel/Sheets | Sí, directament | No (és per a programes) |
| Mida amb moltes files | Compacte (capçalera una vegada) | Verbós (claus repetides per objecte) |
| Hàbitat natural | Exportacions, fulls de càlcul, dades massives per files | APIs, configuracions, estructures niades |
| A Papyrus | vendes.csv (històric tabular que l'Anna obre a Excel) |
config.json, socis.json (estructura i tipus) |
Regla ràpida: les dades són una taula que un humà voldrà obrir en un full de càlcul? CSV. Tenen estructura, niament o tipus que importen? JSON. El catàleg de Papyrus, com que és tabular, viu còmode en tots dos — l'hem desat en els dos formats precisament perquè comparis el codi.
config.json: les constants de Papyrus surten del codi
Des del mòdul 1, NOM_BOTIGA, IVA_LLIBRES i DESCOMPTE_SOCI viuen escrites al codi. Funciona, però canviar el descompte de soci obliga a editar un .py. La manera professional és externalitzar la configuració a un fitxer que un no-programador pugui tocar — i JSON amb indent és ideal:
import json
def carregar_config(ruta="config.json"):
with open(ruta, "r", encoding="utf-8") as f:
return json.load(f)
config = carregar_config()
NOM_BOTIGA = config["nom_botiga"] # "Papyrus"
IVA_LLIBRES = config["iva_llibres"] # 0.04 — float de debo, a punt per multiplicar
DESCOMPTE_SOCI = config["descompte_soci"] # 0.05Si demà el gremi pacta un altre descompte, l'Anna edita config.json amb el Bloc de notes i cap .py no canvia. Observa que els tipus arriben correctes sense conversió: 0.04 és float perquè JSON ho recorda. (I si config.json no existeix o algú el trenca editant-lo? json.load llança JSONDecodeError — un altre client esperant a la porta del mòdul 7.)
Una menció a pickle
La biblioteca estàndard inclou una altra via de persistència: pickle, que serialitza objectes Python tal qual — sets, dates, les teves classes, gairebé qualsevol cosa — a un format binari (fitxers en mode "wb"/"rb", els que 06-01 va deixar apuntats):
import pickle
with open("cataleg.pkl", "wb") as f: # binari: sense encoding
pickle.dump(cataleg, f) # el dict[str, Llibre] sencer, objectes inclosos
with open("cataleg.pkl", "rb") as f:
recarregat = pickle.load(f) # tornen objectes Llibre, sense asdict ni **Temptador, però té tres lletres petites que el releguen a usos puntuals: és només-Python (cap altre llenguatge ni Excel no el llegirà), és il·legible (no pots obrir-lo en un editor per inspeccionar-lo, el gran avantatge del text que venim explotant) i és insegur amb dades alienes — despicklejar un fitxer d'origen no fiable pot executar codi arbitrari; la mateixa documentació oficial ho adverteix. Criteri del curs: per desar estat propi i temporal entre execucions teves, val; per intercanviar, configurar o arxivar, text (CSV/JSON) sempre. Papyrus es queda amb JSON.
Errors Comuns i Consells
- Confondre les quatre funcions:
json.dump(dades, "cataleg.json")falla —dumpvol un fitxer obert, no una ruta. Ijson.load(text)també — per a cadenes ésloads. Mnemotècnia: lasés de string. - Intentar serialitzar objectes directament:
json.dump(cataleg, f)amb valorsLlibrellançaTypeError: ... not JSON serializable. Primerasdict()(o construeix dicts/llistes a mà). - Oblidar que les tuples i els sets no sobreviuen: la tupla torna com a llista i el set ni tan sols s'escriu (
TypeError). Converteix el set alistabans i reconverteix-lo en carregar si t'importa la deduplicació. - Claus no textuals:
{2026: "any"}es desa com{"2026": "any"}i en tornar la clau ésstr. Si la clau és un número que importa, desa'l com a valor. - Editar JSON a mà i trencar-lo: una coma final després de l'últim element (
"estoc": 4,}) o cometes simples són JSON invàlid →JSONDecodeErroren carregar. Els editors moderns ho marquen; fins al mòdul 7, revisa abans de desar. - Escriure sense
ensure_ascii=Falseen fitxers que llegiran persones: els accents es converteixen en seqüències\u00e0i el fitxer sembla corromput sense estar-ho. - Consell: per inspeccionar un JSON compacte rebut de fora,
print(json.dumps(dades, indent=4, ensure_ascii=False))l'"embelleix" a l'instant. És el pretty-print de depuració estàndard.
Exercicis
Exercici 1: l'estat complet de la botiga
Escriu desar_estat(cataleg, ruta="estat.json") que desi en un únic JSON un objecte amb tres claus: "botiga" (el nom, des de la constant), "desat" (la data d'avui com a cadena str(date.today()) — recorda que date no se serialitza) i "cataleg" (la llista d'asdict()). Escriu també carregar_estat(ruta) que retorni la tupla (data, dict[str, Llibre]). Comprova el round-trip amb els quatre llibres canònics.
Exercici 2: la compra queda a l'historial
Escriu registrar_compra_soci(codi, compra, ruta="socis.json") que carregui el JSON de socis, localitzi el soci pel seu camp "codi", afegeixi el diccionari compra a la seva llista "compres" i reescrigui el fitxer complet (patró carregar → modificar → desar). Registra a en Pau (PAU-003) la compra {"data": "2026-07-13", "titol": "Faust", "quantia": 20.75} — per fi van arribar exemplars — i comprova rellegint el fitxer que la seva llista ja no és buida. Si el codi no existeix, imprimeix un avís i no toquis el fitxer.
Exercici 3: criteri de format
Per a cada dada de Papyrus, decideix CSV o JSON i justifica-ho en un comentari d'una línia: (a) l'històric de deu anys de vendes que l'Anna filtra a Excel cada trimestre; (b) les preferències de l'aplicació de caixa (nom, IVA, descompte, imprimir tiquet?); (c) els socis amb les seves llistes de compres; (d) la llista de novetats que la distribuïdora importa al seu sistema, "en el que sigui, però que ho obri tothom". Després escriu la fitxa del soci Lluís (amb dues compres) en tots dos formats i observa quin força l'estructura.
Solucions
Solució 1:
import json
from datetime import date
from dataclasses import asdict
from pathlib import Path
def desar_estat(cataleg, ruta="estat.json"):
estat = {
"botiga": NOM_BOTIGA,
"desat": str(date.today()), # date → str: el peatge del no serialitzable
"cataleg": [asdict(l) for l in cataleg.values()],
}
with open(ruta, "w", encoding="utf-8") as f:
json.dump(estat, f, indent=4, ensure_ascii=False)
def carregar_estat(ruta="estat.json"):
if not Path(ruta).exists():
return None, {}
with open(ruta, "r", encoding="utf-8") as f:
estat = json.load(f)
cataleg = {normalitzar_titol(d["titol"]): Llibre(**d) for d in estat["cataleg"]}
return estat["desat"], cataleg
desar_estat(cataleg)
data, recarregat = carregar_estat()
print(data, recarregat == cataleg) # 2026-07-13 TrueUn sol fitxer amb metadades + dades: l'embrió del "desar partida" de qualsevol aplicació.
Solució 2:
import json
def registrar_compra_soci(codi, compra, ruta="socis.json"):
with open(ruta, "r", encoding="utf-8") as f:
socis = json.load(f)
for soci in socis:
if soci["codi"] == codi:
soci["compres"].append(compra) # modificar en memoria...
break
else: # el for/else de 02-02: no s'ha trobat
print(f"AVIS: no existeix el soci {codi}")
return
with open(ruta, "w", encoding="utf-8") as f: # ...i reescriure sencer
json.dump(socis, f, indent=4, ensure_ascii=False)
registrar_compra_soci("PAU-003", {"data": "2026-07-13", "titol": "Faust", "quantia": 20.75})JSON no es pot "apendre" com el CSV de vendes: el fitxer és una estructura completa, així que el patró és sempre carregar → modificar → abocar. (20.75 és la xifra canònica: el preu de soci de Faust.)
Solució 3:
# (a) CSV — tabular, massiu i desti Excel: el seu habitat exacte
# (b) JSON — tipus barrejats (str, float, bool) i estructura de configuracio
# (c) JSON — llistes niades dins de cada soci; en CSV no hi caben be
# (d) CSV — "que ho obri tothom" es la definicio de lingua francaEn escriure la fitxa d'en Lluís en tots dos: en JSON les compres nien amb naturalitat; en CSV acabes repetint Lluis,LLUIS-001 a cada fila de compra o inventant un subformat dins d'una cel·la — el senyal inequívoc que el format no encaixa.
Conclusió
JSON completa l'arsenal de persistència de Papyrus: dump/load per a fitxers i dumps/loads per a cadenes, tipus que sobreviuen al viatge (adeu al peatge str → float), indent=4, ensure_ascii=False per a fitxers llegibles per humans, asdict() + Llibre(**dades) com a pont idiomàtic entre dataclasses i disc, niament per als socis amb les seves compres i un config.json que treu les constants del codi. CSV per a taules amb destí Excel, JSON per a estructura i configuració, pickle només per a estat propi i amb cautela. Però fixa't en el que se'ns està acumulant: registre_altes.txt, cataleg.csv, vendes.csv, cataleg.json, socis.json, config.json… mitja dotzena de fitxers escampats a la mateixa carpeta que el codi. Una botiga endreçada no amuntega les caixes al costat de la caixa registradora: calen carpetes (dades/, copies/, informes/), rutes que funcionin en qualsevol màquina i còpies de seguretat nocturnes amb data. D'organitzar tot això — amb pathlib com a protagonista i el mòdul os que el mòdul 3 va deixar promès — se n'ocupa l'última lliçó del mòdul.
Curs de Programació en Python
Mòdul 1: Introducció a Python
- Introducció a Python
- Configuració de l'Entorn de Desenvolupament
- Sintaxi de Python i Tipus de Dades Bàsics
- Variables i Constants
- Entrada i Sortida Bàsica
- Entorns Virtuals i Gestió de Paquets
Mòdul 2: Estructures de Control
Mòdul 3: Funcions i Mòduls
- Definició de Funcions
- Arguments de Funció
- Funcions Lambda
- Mòduls i Paquets
- Visió General de la Biblioteca Estàndard
Mòdul 4: Estructures de Dades
Mòdul 5: Programació Orientada a Objectes
Mòdul 6: Gestió de Fitxers
- Lectura i Escriptura de Fitxers
- Treball amb Fitxers CSV
- Gestió de Dades JSON
- Operacions amb Fitxers i Directoris
Mòdul 7: Gestió d'Errors i Excepcions
- Introducció a les Excepcions
- Gestió d'Excepcions
- Llançament d'Excepcions
- Excepcions Personalitzades
- Bones Pràctiques i Registre d'Errors amb logging
Mòdul 8: Temes Avançats
- Anotacions de Tipus (type hints)
- Decoradors
- Generadors
- Gestors de Context
- Concurrència: Fils i Processos
- Asyncio per a la Programació Asíncrona
Mòdul 9: Proves i Depuració
- Introducció a les Proves
- Proves Unitàries amb unittest
- Proves amb pytest
- Desenvolupament Guiat per Proves
- Tècniques de Depuració
- Ús de pdb per a la Depuració
Mòdul 10: Desenvolupament Web amb Python
- Introducció al Desenvolupament Web
- Fonaments del Framework Flask
- Construcció d'APIs REST amb Flask
- Introducció a Django
- Construcció d'Aplicacions Web amb Django
Mòdul 11: Ciència de Dades amb Python
- Introducció a la Ciència de Dades
- NumPy per a la Computació Numèrica
- Pandas per a la Manipulació de Dades
- Matplotlib per a la Visualització de Dades
- Introducció a l'Aprenentatge Automàtic amb scikit-learn
