2013-05-09 9 views
5

Sto memorizzando nella cache alcuni dati JSON e nella memoria è rappresentato come una stringa di codifica JSON. Nessun lavoro viene eseguito sul JSON dal server prima di inviarlo al cliente, oltre collazione di più oggetti memorizzati nella cache, in questo modo:In Python, json non sfugge a una stringa

def get_cached_items(): 
    item1 = cache.get(1) 
    item2 = cache.get(2) 
    return json.dumps(item1=item1, item2=item2, msg="123") 

Ci possono essere altri elementi inclusi con il valore di ritorno, in questo caso rappresentato di msg="123".

Il problema è che gli elementi memorizzati nella cache sono a doppio escape. Dovrebbe essere la libreria a consentire un pass-through della stringa senza sfuggirgli.

Ho esaminato la documentazione per json.dumps default argument, poiché sembra essere il luogo in cui uno verrebbe indirizzato e cercato su google/SO ma non ha trovato risultati utili.

Sarebbe un peccato, dal punto di vista delle prestazioni, se dovessi decodificare il JSON di ogni elemento memorizzato nella cache per inviarlo al browser. Sarebbe sfortunato dal punto di vista della complessità non essere in grado di utilizzare json.dumps.

La mia inclinazione è scrivere una classe che memorizza la stringa memorizzata nella cache e quando il gestore default incontra un'istanza di questa classe, utilizza la stringa senza eseguire l'escape. Devo ancora capire come raggiungere questo obiettivo, e sarei grato per i pensieri e l'assistenza.

EDIT Per chiarezza, ecco un esempio della proposta default tecnica:

class RawJSON(object): 
    def __init__(self, str): 
     self.str = str 

class JSONEncoderWithRaw(json.JSONEncoder): 
    def default(self, o): 
     if isinstance(o, RawJSON): 
      return o.str # but avoid call to `encode_basestring` (or ASCII equiv.) 
     return super(JSONEncoderWithRaw, self).default(o) 

Ecco un esempio degenerata di quanto sopra:

>>> class M(): 
     str = '' 
>>> m = M() 
>>> m.str = json.dumps(dict(x=123)) 
>>> json.dumps(dict(a=m), default=lambda (o): o.str) 
'{"a": "{\\"x\\": 123}"}' 

L'uscita desiderato includerebbe l'escape stringa m.str, essendo:

'{"a": {"x": 123}}' 

Sarebbe bene se il modulo json non codificasse/fuggisse il ritorno del parametro default, o se lo stesso potrebbe essere evitato. In assenza di un metodo tramite il parametro default, potrebbe essere necessario raggiungere l'obiettivo in questo punto sovraccaricando il metodo encode e iterencode di JSONEncoder, che presenta sfide in termini di complessità, interoperabilità e prestazioni.

risposta

5

Un modo rapido-n-sporco è di patch json.encoder.encode_basestring*() funzioni:

import json 

class RawJson(unicode): 
    pass 

# patch json.encoder module 
for name in ['encode_basestring', 'encode_basestring_ascii']: 
    def encode(o, _encode=getattr(json.encoder, name)): 
     return o if isinstance(o, RawJson) else _encode(o) 
    setattr(json.encoder, name, encode) 


print(json.dumps([1, RawJson(u'["abc", 2]'), u'["def", 3]'])) 
# -> [1, ["abc", 2], "[\"def\", 3]"] 
+0

Molto intelligente - lo adoro! –

3

Se si memorizzano nella cache le stringhe JSON, è necessario prima decodificare in strutture python; non v'è alcun modo per json.dumps() di distinguere tra stringhe normali e le stringhe che sono strutture veramente JSON-codificati:

return json.dumps({'item1': json.loads(item1), 'item2': json.loads(item2), 'msg': "123"}) 

Purtroppo, non v'è alcuna opzione per includere i dati già convertiti JSON in questo; la funzione default deve restituire i valori Python. Si estrae i dati da qualsiasi oggetto passato e si restituisce un valore che può essere convertito in JSON, non un valore che è già JSON stesso.

L'unico altro approccio che posso vedere è quello di inserire i valori "template", quindi utilizzare tecniche di sostituzione di stringa per manipolare l'output JSON per sostituire i modelli con i dati in cache attuale:

json_data = json.dumps({'item1': '==item1==', 'item2': '==item2==', 'msg': "123"}) 
return json_data.replace('"==item1=="', item1).replace('"==item2=="', item2) 

Una terza opzione è per memorizzare nella cache item1 e item2 in formato non serializzato, come una struttura Python invece di una stringa JSON.

+0

Grazie Martijn. Prendo in considerazione che qualsiasi tipo di ritorno dalla funzione dei parametri 'default' deve essere sfuggito? La risposta non era ovvia dalla mia implementazione (Python 2.7 di homebrew su Mac OS X), ma ti credo se lo dici tu. ;) –

+0

@ BrianM.Hunt: il gestore 'default' viene utilizzato solo per i tipi di valore che' json.dumps' non sa come gestire. 'str' è facile, il modulo sa * esattamente * come gestirlo, quindi il parametro' default' non viene consultato per quelli. –

+0

E 'fuggire' non è il termine corretto qui; 'json.dumps()' prende le strutture Python come input e produce output JSON. Dagli una stringa di Python e fuori viene una stringa * JSON *, indipendentemente da cosa ci sia all'interno della stringa di Python. Non dovresti confondere i due tipi, anche se JSON è un dato di byte e * contenuto * in una stringa di Python. –

1

È possibile utilizzare il meglio mantenuto simplejson invece di json che fornisce questa funzionalità.

import simplejson as json 
from simplejson.encoder import RawJSON 

print(json.dumps([1, RawJSON(u'["abc", 2]'), u'["def", 3]'])) 
# -> [1, ["abc", 2], "[\"def\", 3]"] 

Si ottiene semplicità del codice, oltre a tutte le ottimizzazioni C di simplejson.

Problemi correlati