json.loads() non fa altro che prendere il file json così come ottenuto dall'operazione open().read() (che è fondamentalmente una stringa), e lo trasforma in un oggetto che tipicamente sarà composto da dizionari e liste.
Un dizionario (classe dict) è una mappa tra chiavi e valori. Una lista è semplicemente una sequenza (ordinata) di valori.
Il fatto che il tuo codice funzioni o meno dipende da come il json è strutturato. Se il json è semplice e non ha annidamenti particolari va bene, altrimenti male.
Esempio in cui va bene:
Codice:
#file .json in input
[
{
"nome" : "Gianni",
"cognome" : "Rossi",
"eta" : "25",
"sesso" : "m"
},
{
"nome" : "Marco",
"cognome" : "Bianchi",
"eta" : "20",
"sesso" : "m"
},
{
"nome" : "Anna",
"cognome" : "Rossi",
"eta" : "24",
"sesso" : "f"
}
]
Il valore ritornato da json.reads() sarà, in questo caso, una lista di dizionari che hanno come chiavi i valori "nome", "cognome", "eta" e "sesso"
Codice:
[{u'sesso': u'm', u'cognome': u'Rossi', u'eta': u'25', u'nome': u'Gianni'},
{u'sesso': u'm', u'cognome': u'Bianchi', u'eta': u'20', u'nome': u'Marco'},
{u'sesso': u'f', u'cognome': u'Rossi', u'eta': u'24', u'nome': u'Anna'}]
In linea generale, un gruppo di valori racchiusi tra parentesi quadre andrà a comporre una lista, mentre un gruppo di coppie chiave : valore andrà a comporre un dizionario.
Codice:
for i in data_parsed:
...
dentro al ciclo for "i" è ogni volta uno di quei tre dizionari (la scelta del nome "i" per quella variabile è infelice, usa qualcosa di più comprensibile, nel mio esempio potrebbe essere "for persona in data_parsed").
Quando invochi i.keys() stai chiedendo il valore delle chiavi, che sono quelle che ho detto sopra. Questo lo fai una volta sola perchè l'intestazione nel csv la scrivi solo nella prima riga. Quando invece invochi i.values() stai chiedendo i valori associati alle chiavi, e questo lo fai 3 volte.
L'output csv sarà questo:
Codice:
sesso,cognome,eta,nome
m,Rossi,25,Gianni
m,Bianchi,20,Marco
f,Rossi,24,Anna
ed è giusto (anche se l'ordine è sballato)
Questo caso però è basilare... funziona bene solo se le singole persone sono descritte da un dizionario che non nidifica. Inoltre stai dando per scontato che le chiavi siano uguali per tutte le persone, ma non è detto che sia così per qualsiasi file json...
Se per esempio volessimo essere cavalieri e omettere l'età di Anna, potremmo cancellare la riga "eta : 24". Il risultato sarebbe questo:
Codice:
sesso,cognome,eta,nome
m,Rossi,25,Gianni
m,Bianchi,20,Marco
f,Rossi,Anna
Il problema qui è che "Anna" risulta il terzo valore, ma nelle prime due righe il terzo valore è occupato dall'età, mentre il nome è al 4° posto. Questo è un problema di "stile", ma python lo ignora allegramente perchè è lecito.
Il problema vero è però quando hai nidificazioni, vogliamo per esempio inserire gli animali domestici di ognuno.
Codice:
#file json
[
{
"nome" : "Gianni",
"cognome" : "Rossi",
"eta" : "25",
"sesso" : "m",
"animali" : [
{"tipo" : "coniglio", "nome" : "Bunny"}
]
},
{
"nome" : "Marco",
"cognome" : "Bianchi",
"eta" : "20",
"sesso" : "m"
},
{
"nome" : "Anna",
"cognome" : "Rossi",
"eta" : "20",
"sesso" : "f",
"animali" : [
{"tipo" : "gatto", "nome" : "Ciro"},
{"tipo" : "cane", "nome" : "Lucky"}
]
}
]
Il risultato sarà:
Codice:
sesso,cognome,eta,animali,nome
m,Rossi,25,"[{u'tipo': u'coniglio', u'nome': u'Bunny'}]",Gianni
m,Bianchi,20,Marco
f,Rossi,20,"[{u'tipo': u'gatto', u'nome': u'Ciro'}, {u'tipo': u'cane', u'nome': u'Lucky'}]",Anna
Che ha evidentemente dei problemi, perchè gli oggetti annidati sono mandati in output allo stesso modo in cui li vede python: una lista di dizionari.
Il codice che hai scritto di fatto gestisce solo il primo livello del json, i livelli inferiori vanno gestiti in modo diverso.
Ma nota da subito una cosa: ogni persona può avere un numero arbitrario di animali domestici, e quindi ogni riga nel CSV potrà avere una lunghezza diversa. Questo è evidentemente un problema, la principale limitazione del CSV, ovvero la sua rigidità. Se hai un po' di esperienza con i database, potremmo paragonare la differenza tra CSV e Json alla differenza che c'è tra un database relazionale (es. SQL) e uno a oggetti (es. MongoDB).
Nei database relazionali un problema del genere si risolve separando gli animali dalle persone e aggiungendo una chiave esterna, in altre parole dovresti prevedere un CSV per le persone e uno per gli animali.
Ammettiamo di conoscere la struttura del json (il nome di tutte le chiavi, anche annidate), allora un codice che funziona è il seguente:
Codice:
#importo le librerie
import json
import csv
#apro e parso il file .json
with open("in.json") as injson:
data_parsed = json.loads(injson.read())
#creo e apro un file .csv per scrivere le persone
with open('persone.csv', 'w') as csvpers:
#creo il writer persone
csvpers = csv.writer(csvpers)
#creo e apro un file .csv per scrivere gli animali
with open('animali.csv', 'w') as csvanim:
#creo il writer
csvanim = csv.writer(csvanim)
count_pers = 0
count_anim = 0
for persona in data_parsed:
if count_pers == 0:
#aggiungi "id" davanti alle chiavi, e tolgo "animali"
csvpers.writerow(['id'] + [k for k in persona.keys() if k != 'animali'])
count_pers += 1
#ha animali?
#controllo la lunghezza della lista associata alla chiave "animali"
#per evitare errori, controllo prima se la chiave "animali" effettivamente esiste
if 'animali' in persona.keys():
if len(persona['animali']) > 0:
for animale in persona['animali']:
if count_anim == 0:
#aggiungi "proprietario" davanti alle chiavi
csvanim.writerow(['proprietario'] + animale.keys())
count_anim += 1
#scrivi l'animale con l'id del padrone davanti
csvanim.writerow([count_pers] + animale.values())
#rimuovi gli animali prima di scrivere la pesona
del persona['animali']
#scrivi dati persona aggiungendo il suo id davanti
csvpers.writerow([count_pers] + persona.values())
L'output saranno due file, uno per le persone e uno per gli animali:
Codice:
id,sesso,cognome,eta,nome
1,m,Rossi,25,Gianni
2,m,Bianchi,20,Marco
3,f,Rossi,20,Anna
Codice:
proprietario,tipo,nome
1,coniglio,Bunny
3,gatto,Ciro
3,cane,Lucky
ed è giusto.
Tra le persone non ho più il campo "animali" (potrei anche tenerlo e indicare si/no se ha almeno un animale) ma ho il campo "id" che identifica la persona, in un db relazionale sarebbe la chiave primaria della tabella "persone".
Tra gli animali, oltre al tipo e nome, ho "proprietario" che corrisponde all'"id" della persona proprietaria. In un db relazionale sarebbe una chiave esterna.
L'esempio che ti ho fatto funziona nel caso in cui conosci perfettamente la struttura del json, per un approccio più generale, dovresti ricorsivamente verificare quali valori sono liste (nell'esempio sappiamo che solo "animali" è una lista) e trattarle di conseguenza.