2016-04-24 19 views
7

Ho un grande for loop in cui creo oggetti json e mi piacerebbe essere in grado di scrivere in streaming l'oggetto in ogni iterazione su un file. Mi piacerebbe poter usare il file più tardi in un modo simile (leggi gli oggetti uno alla volta). I miei oggetti JSON contengono newline e non posso semplicemente scaricare ogni oggetto come una linea in un file. Come posso ottenere questo?Python, scrivere oggetti json/dizionario su un file in modo iterativo (uno alla volta)

Per renderlo più concreto, considerare quanto segue:

for _id in collection: 
    dict_obj = build_dict(_id) # build a dictionary object 
    with open('file.json', 'a') as f: 
     stream_dump(dict_obj, f) 

stream_dump è la funzione che voglio.

Nota che non voglio creare un elenco di grandi dimensioni e scaricare l'intero elenco utilizzando qualcosa come json.dump(obj, file). Voglio essere in grado di aggiungere l'oggetto al file in ogni iterazione.

Grazie.

+0

Se non capisco la tua domanda in modo errato, sembra possibile scrivere una linea di separazione che i tuoi dati non hanno "-----" in ogni iterazione dopo aver scritto l'oggetto e durante la lettura crea un nuovo oggetto quando vedi quel separatore . – alpert

+0

Ah, capisco. Questo sicuramente funziona. Ho pensato che potrebbero esserci altre soluzioni di elaborazione del flusso. – CentAu

risposta

3

è necessario lavorare con una sottoclasse di JSONEncoder e poi delega la funzione build_dict

from __future__ import (absolute_import, division, print_function,) 
#      unicode_literals) 

import collections 
import json 


mycollection = [1, 2, 3, 4] 


def build_dict(_id): 
    d = dict() 
    d['my_' + str(_id)] = _id 
    return d 


class SeqProxy(collections.Sequence): 
    def __init__(self, func, coll, *args, **kwargs): 
     super(SeqProxy, *args, **kwargs) 

     self.func = func 
     self.coll = coll 

    def __len__(self): 
     return len(self.coll) 

    def __getitem__(self, key): 
     return self.func(self.coll[key]) 


class JsonEncoderProxy(json.JSONEncoder): 
    def default(self, o): 
     try: 
      iterable = iter(o) 
     except TypeError: 
      pass 
     else: 
      return list(iterable) 
     # Let the base class default method raise the TypeError 
     return json.JSONEncoder.default(self, o) 


jsonencoder = JsonEncoderProxy() 
collproxy = SeqProxy(build_dict, mycollection) 


for chunk in jsonencoder.iterencode(collproxy): 
    print(chunk) 

Ouput:

[ 
{ 
"my_1" 
: 
1 
} 
, 
{ 
"my_2" 
: 
2 
} 
, 
{ 
"my_3" 
: 
3 
} 
, 
{ 
"my_4" 
: 
4 
} 
] 

di leggerlo di nuovo blocco per blocco è necessario utilizzare JSONDecoder e passare un chiamabile come object_hook. Questo hook è chiamato con ogni nuovo oggetto decodificato (ogni dict nella vostra lista) quando si chiama JSONDecoder.decode(json_string)

+0

Perfetto, grazie.Solo una domanda, cosa fa 'SeqProxy'? – CentAu

+1

La tua collezione non restituirà un "dict" per ogni elemento (stai chiamando 'build_dict' su ogni elemento) e' SeqProxy' avvolge la tua collezione e restituisce il risultato di 'build_dict' quando il' JSONEncoder' richiede l'elemento successivo nel lista per serializzarlo. – mementum

+0

Per favore correggimi se ho torto: questo risolve due problemi: (a) i proxy sono necessari per chiamare la funzione personalizzata 'build_dict' su un sottoinsieme specifico della raccolta; (b) il compito di serializzare un chunk di chunk è già fornito dal modulo JSON attraverso la funzione 'iterencode'. - Mi sono concentrato su (b) e non ho capito il codice fino a quando non ho capito che era tutto a (a). – lenz

2

Dal momento che si sta generando i file da soli, si può semplicemente scrivere un oggetto JSON per riga:

for _id in collection: 
    dict_obj = build_dict(_id) # build a dictionary object 
    with open('file.json', 'a') as f: 
     f.write(json.dumps(dict_obj)) 
     f.write('\n') 

e poi leggere dalla iterazione su linee:

with open('file.json', 'r') as f: 
    for line in f: 
     dict_obj = json.loads(line) 

Questa non è una grande soluzione generale, ma si tratta di un semplice, se siete sia il generatore e consumatore.

-1

soluzione più semplice:

rimuovere tutti i caratteri di spazio bianco dal documento JSON:

import string 

def remove_whitespaces(txt): 
    """ We shall remove all whitespaces""" 
    for chr in string.whitespace: 
     txt = txt.replace(chr) 

Ovviamente si potrebbe anche json.dumps(json.loads(json_txt)) (BTW questo anche verificare che il testo è un JSON valido).

Ora è possibile scrivere i documenti su un file di una riga ciascuno.

Seconda soluzione:

creare un flusso [AnyStr] Io, scrivere l'Io un documento valido, (i documenti di essere parte di un oggetto o di un elenco) e poi scrivere il io in un file (o caricarlo al cloud).

+2

E cosa succede se gli spazi bianchi sono parte integrante del contenuto? – mementum

+0

Buona osservazione! In ogni caso, json.dumps (json.loads (json_txt)) sarebbe perfetto in questo caso. –

+0

Perché dovresti rimuovere tutti gli spazi bianchi? Non vedo come questo è collegato all'OP. Se si desidera avere il dump JSON completo su una singola riga, fare 'json.dump (... indent = None)' (in realtà, è già il default). I newline all'interno dei nodi di testo sono comunque sfuggiti. – lenz

Problemi correlati