RISOLTO Parser Python Da .json A .csv

Iperione

Nuovo Utente
24
0
CPU
2,9 GHz Intel Core i5
HDD
Flash 500GB
RAM
8 GB 1867 MHz DDR3
GPU
Intel Iris Graphics 6100 1536 MB
OS
macOS Sierra
Ciao a tutti,
sto cercando di creare un parser in Python che, dato in input un file .json, dia come output un file .csv che scandagli il file .json trovando determinate informazioni.

Sono un principiante in Python, e non ho mai gestito file .json e .csv quindi anche qualche link utile o una buona infarinatura non sarebbe male visto che sono un autodidatta...

In allegato metto il codice che ho scritto finora. Il problema principale è che non riesco a scandagliare il file .json come vorrei.

Codice:
#importo le librerie
import json
import csv

#Chiedo all'utente il percorso del file che devo parsare
x = input("immettere percorso File .json: ")

#apro e parso il file .json
data = open(x).read()
data_parsed = json.loads(data)

#creo e apro un file .csv per scrivere
csvaporeon = open('File_Parsered.csv', 'w')
#creo il writer
csvwriter = csv.writer(csvaporeon)

#ciclo per tradurre il file .json in quello .csv
count = 0

for i in data_parsed:
    if count == 0:
        header = i.keys()
        csvwriter.writerow(header)
        count += 1

    csvwriter.writerow(i.values())

#salvo e chiudo il file .csv
csvaporeon.close()

#capire come funziona BENE il ciclo for e le chiamate al metodo .csv

Grazie a tutti per l'attenzione
 
Ultima modifica:

1nd33d

Utente Attivo
653
279
CPU
Intel i5 3570K @ 4,5Ghz
Dissipatore
Scythe Mugen 2
Scheda Madre
Gigabyte Z77X-UD3H
HDD
Samsung 840 PRO 256GB + Sandisk Ultra 250GB + Sandisk Plus 960GB
RAM
2x8GB Crucial Ballistix Tactical @2000Mhz CL9
GPU
XFX RX480 GTR Black Edition
Audio
Auzentech X-Fi Forte
Monitor
AOC i2369VW
PSU
Seasonic P660
Case
eh?
Periferiche
Razer Naga HEX v2
OS
Windows 10 64bit - Linux Mint 18
La domanda è un po' generica... dovresti specificare cosa intendi con "come vorrei".
CSV è un formato di memorizzazione in cui vi sono un certo numero di righe descritte da un fissato numero di colonne, praticamente hai una matrice.
Json invece lavora molto sulla strutturazione gerarchica dei dati: un oggetto può contenerne altri, in numero variabile. Per cui non è detto che tu possa tradurre un Json in CSV così facilmente, o quantomeno non in un singolo CSV.

ps: usa il tag code per il codice, così almeno lo si può copiare/modificare/testare...
 
  • Mi piace
Reazioni: Iperione

Iperione

Nuovo Utente
24
0
CPU
2,9 GHz Intel Core i5
HDD
Flash 500GB
RAM
8 GB 1867 MHz DDR3
GPU
Intel Iris Graphics 6100 1536 MB
OS
macOS Sierra
@1nd33d

Fondamentalmente non riesco a capire bene la struttura del file .json che devo dare in input. E' un file molto grande con tantissime ramificazioni e faccio fatica a capirne la struttura.
Oltretutto non riesco a capire bene cosa faccia di preciso json.loads(). Mi sono studiato brevemente cosa sono i dizionari e ho visto che nel file json ci sono ci sono sia liste che dizionari...
Hai ragione la mia è una domanda un po' generica ma è perché sto facendo qualcosa di nuovo e non so bene da dove cominciare...
Un buon inizio sarebbe sapere la procedura con il quale in generale si gestisce un file .json dopo averci applicato sopra json.loads().

Ti ringrazio per l'attenzione intanto :thanks:
 

1nd33d

Utente Attivo
653
279
CPU
Intel i5 3570K @ 4,5Ghz
Dissipatore
Scythe Mugen 2
Scheda Madre
Gigabyte Z77X-UD3H
HDD
Samsung 840 PRO 256GB + Sandisk Ultra 250GB + Sandisk Plus 960GB
RAM
2x8GB Crucial Ballistix Tactical @2000Mhz CL9
GPU
XFX RX480 GTR Black Edition
Audio
Auzentech X-Fi Forte
Monitor
AOC i2369VW
PSU
Seasonic P660
Case
eh?
Periferiche
Razer Naga HEX v2
OS
Windows 10 64bit - Linux Mint 18
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.
 
Ultima modifica:
  • Mi piace
Reazioni: Iperione e Mursey

Iperione

Nuovo Utente
24
0
CPU
2,9 GHz Intel Core i5
HDD
Flash 500GB
RAM
8 GB 1867 MHz DDR3
GPU
Intel Iris Graphics 6100 1536 MB
OS
macOS Sierra
Innanzi tutto grazie mille per la chiarezza :thanks:

Ho capito cosa intendi, quindi dovrei innanzi tutto capire bene come è strutturato il file .json per poi poterlo gestire in Python giusto? Ma se dovessi avere in output solo determinate colonne, ad esempio nel primo file che hai inviato (il .json non annidato con semplicemente nome, cognome, età e sesso) volessi creare un .csv che ha semplicemente due colonne (nome ed età) con i rispettivi valori che tipo di controllo potrei applicare? io avevo pensato ad un banale

Codice:
if i.keys() == 'nome' or i.keys() == 'eta'

ma non funziona...
 

Iperione

Nuovo Utente
24
0
CPU
2,9 GHz Intel Core i5
HDD
Flash 500GB
RAM
8 GB 1867 MHz DDR3
GPU
Intel Iris Graphics 6100 1536 MB
OS
macOS Sierra
ok, dopo ripetute testate sul computer sono riuscito a fare un programmino in Python che dato in input questo file .json:

JavaScript:
[
   {
   "nome" : "Gianni",
   "cognome" : "Rossi",
   "eta" : "25",
   "sesso" : "m"
   },
   {
   "nome" : "Marco",
   "cognome" : "Bianchi",
   "eta" : "20",
   "sesso" : "m"
   },
   {
   "nome" : "Anna",
   "cognome" : "Rossi",
   "eta" : "24",
   "sesso" : "f"
   }
]

Restituisce questo:

Codice:
nome,eta
Gianni,25
Marco,20
Anna,24

Il codice è il seguente:

Python:
#importo le librerie
import json
import csv

#Chiedo all'utente il percorso del file che devo parsare
x = input("immettere percorso File .json: ")

#apro e parso il file .json
data = open(x).read()
data_parsed = json.loads(data)
print(data_parsed)

#creo e apro un file .csv per scrivere
csvaporeon = open('File_Parsered.csv', 'w')
#creo il writer
csvwriter = csv.writer(csvaporeon)

#ciclo per tradurre il file .json in quello .csv
count = 0
header_fin = []

for persone in data_parsed:
    valori = []
    if count == 0:
        header = persone.keys()
        print(header)

        for test in header:
            if test == 'nome' or test == 'eta':
                header_fin.append(test)

        print(header_fin)
        csvwriter.writerow(header_fin)
        count += 1

#    Se esiste una key nome o età scrivo il valore
    for test in persone:
        if test == 'nome':
            valori.append(persone['nome'])
        if test == 'eta':
            valori.append(persone['eta'])

    csvwriter.writerow(valori)

#salvo e chiudo il file .csv
csvaporeon.close()

Ora, premettendo che il file in entrata .json che dovrò andare a parsare sarà enormemente più complesso, vorrei sapere se secondo te è un buon codice o ha degli smells. Come ripeto non sono un programmatore esperto e vorrei evitare di trascinarmi dietro del codice malconcio visto che la situazione andrà via via a complicarsi sempre di più...

Come sempre ti ringrazio in anticipo per l'infinita pazienza :thanks:
 
Ultima modifica da un moderatore:

1nd33d

Utente Attivo
653
279
CPU
Intel i5 3570K @ 4,5Ghz
Dissipatore
Scythe Mugen 2
Scheda Madre
Gigabyte Z77X-UD3H
HDD
Samsung 840 PRO 256GB + Sandisk Ultra 250GB + Sandisk Plus 960GB
RAM
2x8GB Crucial Ballistix Tactical @2000Mhz CL9
GPU
XFX RX480 GTR Black Edition
Audio
Auzentech X-Fi Forte
Monitor
AOC i2369VW
PSU
Seasonic P660
Case
eh?
Periferiche
Razer Naga HEX v2
OS
Windows 10 64bit - Linux Mint 18
Come già detto, CSV non è equivalente a Json. Se il Json è molto complesso, altrettanto complesso sarà lo script python.
Innanzi tutto una annotazione sulla scelta del nome delle variabili: sii chiaro. 'persone' non è un buon nome, perchè la variabile 'persone' non contiene tante persone, ne contiene una. Chiamala dunque 'persona' (con la 'a' finale). Sembra un dettaglio inutile, ma ti assicuro che quando hai a che fare con liste, torna utile ed è molto più chiaro. Per esempio se hai un ciclo for del tipo 'for persona in persone' è molto più chiaro.

Passando al codice, secondo me non ha molto senso questo:
Codice:
for test in header:
   if test == 'nome' or test == 'eta':
      header_fin.append(test)
Se sai già che header avrai, allora tanto vale scrivere direttamente:
Codice:
header_fin = ['nome', 'eta']
A questo punto, al posto di
Codice:
valori = []
for test in persone:
        if test == 'nome':
            valori.append(persone['nome'])
        if test == 'eta':
            valori.append(persone['eta'])
scrivi
Codice:
valori = [persona['nome'], persona['eta']]
Questo prevede comunque che tu espliciti il nome del campo (nome e eta). Ma puoi sfruttare il fatto di avere già lo header che contiene tali nomi, quindi puoi anche scrivere:
Codice:
valori = []
for h in header_fin:
   valori.append(persona[h])
Ma per andare sul "pitonico", io scriverei tutto in una riga:
Codice:
valori = [persona[h] for h in header_fin]

In definitiva la seconda parte del tuo codice potrebbe essere riscritta come segue:
Codice:
#ciclo per tradurre il file .json in quello .csv
header = ['nome', 'eta']
csvwriter.writerow(header)

for persona in data_parsed:
    csvwriter.writerow([persona[h] for h in header])

#salvo e chiudo il file .csv
csvaporeon.close()
che è molto più compatta e leggibile.
 
  • Mi piace
Reazioni: Iperione

Iperione

Nuovo Utente
24
0
CPU
2,9 GHz Intel Core i5
HDD
Flash 500GB
RAM
8 GB 1867 MHz DDR3
GPU
Intel Iris Graphics 6100 1536 MB
OS
macOS Sierra
Sono riuscito a fare il parser! L'ho fatto seguendo le tue indicazioni ed è un codice molto compatto! Grazie per tutto




Sent from my iPhone using Toms Hardware Italia Forum
 

Entra

oppure Accedi utilizzando
Discord Ufficiale Entra ora!