Come ottenere oggetti stringa invece di Unicode da JSON?


276

Sto usando Python 2 per analizzare JSON da file di testo con codifica ASCII .

Quando si caricano questi file con uno jsono simplejson, tutti i miei valori di stringa vengono trasmessi agli oggetti Unicode anziché agli oggetti stringa. Il problema è che devo usare i dati con alcune librerie che accettano solo oggetti stringa. Non posso cambiare le librerie né aggiornarle.

È possibile ottenere oggetti stringa anziché quelli Unicode?

Esempio

>>> import json
>>> original_list = ['a', 'b']
>>> json_list = json.dumps(original_list)
>>> json_list
'["a", "b"]'
>>> new_list = json.loads(json_list)
>>> new_list
[u'a', u'b']  # I want these to be of type `str`, not `unicode`

Aggiornare

Questa domanda è stata posta molto tempo fa , quando ero bloccato con Python 2 . Una soluzione semplice e pulita per oggi è utilizzare una versione recente di Python, ovvero Python 3 e versioni successive .


1
Python3 non presenta alcun problema, il tipo di elementi in new_list èstr
GoingMyWay,

1
Python 3k non è una "versione recente di Python", è solo un ramo alternativo.
user2589273

11
È strano vedere questo commento nel dicembre 2017 - Python 2 è deprecato e non ci sarà manutenzione dopo il 1 ° gennaio 2020, che è meno di 2 anni: pythonclock.org
Zaar Hai

1
@ZaarHai MOLTE persone sono bloccate in Python 2 contro la loro volontà. Ci sono molte applicazioni che incorporano la propria versione di Python per l'automazione e lo scripting, quindi le persone devono usarla fino a quando il fornitore non si aggiorna (ti guardo Maya, Houdini, Nuke ..)
Geordie,

1
@Geordie sicuramente lo so e lo capisco. Il mio commento riguardava la terminologia: Python non è un "ramo alternativo", ma una sfortunata mancanza di alternativa (gioco di parole inteso) per coloro che ne sono bloccati.
Zaar Hai,

Risposte:


101

Una soluzione con object_hook

import json

def json_load_byteified(file_handle):
    return _byteify(
        json.load(file_handle, object_hook=_byteify),
        ignore_dicts=True
    )

def json_loads_byteified(json_text):
    return _byteify(
        json.loads(json_text, object_hook=_byteify),
        ignore_dicts=True
    )

def _byteify(data, ignore_dicts = False):
    # if this is a unicode string, return its string representation
    if isinstance(data, unicode):
        return data.encode('utf-8')
    # if this is a list of values, return list of byteified values
    if isinstance(data, list):
        return [ _byteify(item, ignore_dicts=True) for item in data ]
    # if this is a dictionary, return dictionary of byteified keys and values
    # but only if we haven't already byteified it
    if isinstance(data, dict) and not ignore_dicts:
        return {
            _byteify(key, ignore_dicts=True): _byteify(value, ignore_dicts=True)
            for key, value in data.iteritems()
        }
    # if it's anything else, return it in its original form
    return data

Esempio di utilizzo:

>>> json_loads_byteified('{"Hello": "World"}')
{'Hello': 'World'}
>>> json_loads_byteified('"I am a top-level string"')
'I am a top-level string'
>>> json_loads_byteified('7')
7
>>> json_loads_byteified('["I am inside a list"]')
['I am inside a list']
>>> json_loads_byteified('[[[[[[[["I am inside a big nest of lists"]]]]]]]]')
[[[[[[[['I am inside a big nest of lists']]]]]]]]
>>> json_loads_byteified('{"foo": "bar", "things": [7, {"qux": "baz", "moo": {"cow": ["milk"]}}]}')
{'things': [7, {'qux': 'baz', 'moo': {'cow': ['milk']}}], 'foo': 'bar'}
>>> json_load_byteified(open('somefile.json'))
{'more json': 'from a file'}

Come funziona e perché dovrei usarlo?

La funzione di Mark Amery è più breve e chiara di queste, quindi qual è il punto? Perché vorresti usarli?

Puramente per prestazioni . La risposta di Mark decodifica completamente il testo JSON prima con stringhe unicode, quindi ricicla l'intero valore decodificato per convertire tutte le stringhe in stringhe di byte. Questo ha un paio di effetti indesiderati:

  • Una copia dell'intera struttura decodificata viene creata in memoria
  • Se il tuo oggetto JSON è davvero profondamente annidato (almeno 500 livelli), raggiungerai la massima profondità di ricorsione di Python

Questa risposta mitiga entrambi questi problemi di prestazioni utilizzando il object_hookparametro di json.loade json.loads. Dai documenti :

object_hookè una funzione opzionale che verrà chiamata con il risultato di qualsiasi oggetto letteralmente decodificato (a dict). Verrà utilizzato il valore restituito di object_hook invece di dict. Questa funzione può essere utilizzata per implementare decodificatori personalizzati

Dato che i dizionari hanno annidato molti livelli profondi in altri dizionari a object_hook cui sono passati mentre vengono decodificati , in quel punto possiamo byteizzare eventuali stringhe o liste al loro interno ed evitare la necessità di ricorsioni profonde in seguito.

La risposta di Mark non è adatta per essere utilizzata object_hookcosì com'è, perché ricorre in dizionari nidificati. Evitiamo tale ricorsione in questa risposta con il ignore_dictsparametro a _byteify, che gli viene passato in ogni momento tranne quando gli object_hookpassa un nuovo dictda byteificare. Il ignore_dictsflag dice _byteifydi ignorare dicts poiché sono già stati byteificati.

Infine, le nostre implementazioni json_load_byteifiede json_loads_byteifiedchiamate _byteify(con ignore_dicts=True) sul risultato restituito da json.loado json.loadsper gestire il caso in cui il testo JSON in fase di decodifica non ha un dictlivello superiore.


1
+1 per l'approccio qui; Non l'ho capito davvero quando l'ho letto per la prima volta, ma alla fine ho capito quando l'ho riletto alla luce della risposta di Travis Jensen. Ho apportato una modifica piuttosto aggressiva nella speranza di chiarire come funziona e quali sono i suoi vantaggi rispetto alla mia risposta. L'idea principale del codice rimane intatta, ma ho modificato praticamente tutto il resto. Sentiti libero di ripristinare la mia modifica se ti opponi a questa: è la tua risposta!
Mark Amery,

Nessun problema Mark, molte grazie. Mi piace la tua modifica, è molto più esplicativa della mia originale. Forse, un giorno, imparerò a dare risposte più concise.
Mirec Miskuf,

2
Questa è un'ottima soluzione; efficiente ed elegante. Tuttavia, se sei bloccato nel regno di Python <2.7, come lo sono io, dovrai sostituire la linea: return { byteify(key, ignore_dicts=True): _byteify(value, ignore_dicts=True) for key, value in data.iteritems() }con return dict((_byteify(key, ignore_dicts=True), _byteify(value, ignore_dicts=True)) for key, value in data.iteritems())perché funzioni.
Richard Dunn,

Penso che ti sbagli sul problema della profondità di ricorsione. Con il vostro, posso andare fino a 990: json_loads_byteified('[' * 990 + ']' * 990). Con 991 si arresta in modo anomalo. Mark è ancora lavora con 991: byteify(json.loads('[' * 991 + ']' * 991)). Si schianta a 992. Quindi, almeno in questo test, quello di Mark può andare più in profondità, contrariamente a quanto hai detto.
Stefan Pochmann,

@MarkAmery Cosa ne pensi del mio commento sopra? (Ho appena visto nella cronologia delle modifiche che in realtà sei stato tu ad aggiungere quell'affermazione).
Stefan Pochmann,

180

Mentre ci sono alcune buone risposte qui, ho finito per usare PyYAML per analizzare i miei file JSON, dal momento che fornisce le chiavi e i valori come strstringhe di unicodetipo anziché tipo. Poiché JSON è un sottoinsieme di YAML funziona bene:

>>> import json
>>> import yaml
>>> list_org = ['a', 'b']
>>> list_dump = json.dumps(list_org)
>>> list_dump
'["a", "b"]'
>>> json.loads(list_dump)
[u'a', u'b']
>>> yaml.safe_load(list_dump)
['a', 'b']

Appunti

Alcune cose da notare però:

  • Ottengo oggetti stringa perché tutte le mie voci sono codificate ASCII . Se usassi le voci codificate in Unicode, le recupererei come oggetti Unicode - non c'è conversione!

  • Dovresti (probabilmente sempre) usare la safe_loadfunzione di PyYAML ; se lo usi per caricare file JSON, non hai comunque bisogno della "potenza aggiuntiva" della loadfunzione.

  • Se vuoi un parser YAML che abbia più supporto per la versione 1.2 delle specifiche (e analizza correttamente numeri molto bassi ) prova Ruamel YAML : pip install ruamel.yamled import ruamel.yaml as yamlera tutto ciò di cui avevo bisogno nei miei test.

Conversione

Come detto, non c'è conversione! Se non si è sicuri di gestire solo i valori ASCII (e non si può essere sicuri la maggior parte delle volte), utilizzare meglio una funzione di conversione :

Ho usato quello di Mark Amery un paio di volte, funziona benissimo ed è molto facile da usare. Puoi anche usare una funzione simile come object_hookinvece, in quanto potrebbe ottenere un aumento delle prestazioni su file di grandi dimensioni. Vedi la risposta leggermente più coinvolta di Mirec Miskuf per questo.


8
Abbi cura di te se decidi di usare questa risposta. Funziona perfettamente per il caso di Bruto, ma solo perché sa che i suoi dati contengono solo caratteri codificabili ASCII. Se non hai questa garanzia, questa risposta non funzionerà. Ad esempio, prova a eseguire yaml.load(json.dumps([u'a', u'£', u'É']))sulla shell Python e osserva che torni ['a', u'\xa3', u'\xc9'](che contiene unicodestringhe). Se non puoi essere sicuro che i tuoi dati contengano solo caratteri del set di caratteri ASCII, dovresti invece utilizzare un approccio diverso (consiglio la mia risposta).
Mark Amery,

1
YAML fa anche [u'a', u'b']attenzione.
Carlos Calla,

1
Questo è carino, ma non funziona con numeri bassi .. guarda qui: stackoverflow.com/questions/30458977/…
Oren,

@Oren: questo non è un errore nelle specifiche YAML ma nel parser PyYAML. Il parser YAML di Ruamel funziona.
Bruto

Voglio avere un'uscita come ["a", "b"] non come ['a', 'b'] @Brutus
user60679

141

Non esiste un'opzione integrata per fare in modo che le funzioni del modulo json restituiscano stringhe di byte anziché stringhe unicode. Tuttavia, questa semplice e breve funzione ricorsiva convertirà qualsiasi oggetto JSON decodificato dall'uso di stringhe unicode in stringhe di byte con codifica UTF-8:

def byteify(input):
    if isinstance(input, dict):
        return {byteify(key): byteify(value)
                for key, value in input.iteritems()}
    elif isinstance(input, list):
        return [byteify(element) for element in input]
    elif isinstance(input, unicode):
        return input.encode('utf-8')
    else:
        return input

Basta chiamare questo sull'output che si ottiene da una json.loado json.loadschiamata.

Un paio di note:

  • Per supportare Python 2.6 o precedente, sostituiscilo return {byteify(key): byteify(value) for key, value in input.iteritems()}con return dict([(byteify(key), byteify(value)) for key, value in input.iteritems()]), poiché la comprensione del dizionario non era supportata fino a Python 2.7.
  • Poiché questa risposta si ripercuote sull'intero oggetto decodificato, presenta un paio di caratteristiche di prestazione indesiderabili che possono essere evitate con un uso molto attento dei parametri object_hooko object_pairs_hook. La risposta di Mirec Miskuf è finora l'unica che riesce a risolverlo correttamente, sebbene di conseguenza sia significativamente più complicata del mio approccio.

1
Mi piace questo - non è un'ignoranza - sta riconoscendo che quando le persone dicono "stringhe" e "ascii", per lo più ingenuamente intendevano volere byte, non caratteri teorici unicode. (e non ascii perché vogliono ancora i segni della sterlina all'altra estremità)
Danny Staple

Mi piace questo, funziona quasi nello stesso modo in cui funziona la mia bella stampante, poiché so che JSON non fa la tupla, dovresti aggiungere anche l'eccezione per la tupla.
y.petremann,

Questo è orribilmente inefficiente, che richiede di attraversare ricorsivamente nodi che potrebbero non essere necessari. Il modulo json ti dà i ganci per farlo in modo molto più efficiente. La risposta che segue object_hookè in realtà molto peggio di questa, però, ma, usando object_pairs_hook, puoi trovare un metodo ragionevolmente efficiente che non richiede ricorsione o rivisitazione di nodi che non contengono stringhe.
Travis Jensen,

1
@TravisJensen Interessante. Il object_pairs_hookmetodo è forse leggermente più difficile da capire rispetto a questo (è necessario capire come funziona il parametro e perché elenchi e dicts richiedono una gestione diversa) e il vantaggio in termini di prestazioni non conta per la maggior parte delle persone ... ma mi aspetto esiste, specialmente per chiunque abbia a che fare con un oggetto JSON insolitamente profondamente annidato.
Mark Amery,

plus1 Questa è la risposta più concisa; inoltre PyYAML è un problema da installare. L'unica cosa migliore sarebbe in qualche modo micro-stream la conversione in modo che non usi la memoria 4X.
personal_cloud

74

È possibile utilizzare il object_hookparametro per json.loadspassare un convertitore. Non devi fare la conversione dopo il fatto. Il jsonmodulo passerà sempre object_hooksolo i dadi e passerà in modo ricorsivo in dadi nidificati, quindi non dovrai ricorrere in dadi nidificati da solo. Non penso che vorrei convertire le stringhe unicode in numeri come gli spettacoli Wells. Se è una stringa unicode, è stata citata come stringa nel file JSON, quindi dovrebbe essere una stringa (o il file è errato).

Inoltre, proverei a evitare di fare qualcosa di simile str(val)a un unicodeoggetto. Dovresti usarlo value.encode(encoding)con una codifica valida, a seconda di cosa si aspetta la tua lib esterna.

Quindi, per esempio:

def _decode_list(data):
    rv = []
    for item in data:
        if isinstance(item, unicode):
            item = item.encode('utf-8')
        elif isinstance(item, list):
            item = _decode_list(item)
        elif isinstance(item, dict):
            item = _decode_dict(item)
        rv.append(item)
    return rv

def _decode_dict(data):
    rv = {}
    for key, value in data.iteritems():
        if isinstance(key, unicode):
            key = key.encode('utf-8')
        if isinstance(value, unicode):
            value = value.encode('utf-8')
        elif isinstance(value, list):
            value = _decode_list(value)
        elif isinstance(value, dict):
            value = _decode_dict(value)
        rv[key] = value
    return rv

obj = json.loads(s, object_hook=_decode_dict)

3
Questo va bene se l'oggetto in sè un JSON Object(una raccolta non ordinata di chiave: valore si accoppia con il carattere ':' che separa la chiave e il valore, separati da virgola e racchiusi tra parentesi graffe), ma non se è, diciamo, un JSON Array. Quindi, se dato un JSON Arraysimile ["a", "b"], il risultato sarà comunque [u'a', u'b']. Nessuno degli altri parametri personalizzabili attualmente disponibili per il tipo di hook json.loads()può fare il lavoro.
martineau,

2
Dato che, come hai detto, il jsonmodulo passerà in modo ricorsivo nei messaggi nidificati dict, non è necessario verificarli nelle due funzioni, quindi le due elifclausole che li controllano dovrebbero essere rimosse.
martineau,

1
Si noti che l'avvio di nomi di funzioni con un trattino basso ha un significato speciale per le istruzioni di importazione. Se si inseriscono queste funzioni in un file chiamato Utility.py e in un altro file si fa from Utility import *, le funzioni non verranno visualizzate a causa di quel carattere di sottolineatura.
M Katz,

1
Questa è una pessima idea. object_hookviene chiamato per ogni oggetto json analizzato, quindi se ricerchi in ciò che ti viene dato, stai "ricodificando" le cose che hai già "byteizzato". Le prestazioni cresceranno geometricamente con le dimensioni dell'oggetto. Ho incluso una risposta qui che utilizza object_pairs_hooke non soffre di quel problema.
Travis Jensen,

38

Questo perché json non ha alcuna differenza tra oggetti stringa e oggetti unicode. Sono tutte stringhe in JavaScript.

Penso che JSON abbia ragione a restituire oggetti unicode . In realtà, non accetterei nulla di meno, poiché le stringhe javascript sono in realtà unicodeoggetti (ovvero le stringhe JSON (javascript) possono memorizzare qualsiasi tipo di carattere unicode), quindi ha senso creare unicodeoggetti quando si traducono stringhe da JSON. Le stringhe semplici non andrebbero bene poiché la libreria dovrebbe indovinare la codifica desiderata.

È meglio usare unicodeoggetti stringa ovunque. Quindi la tua migliore opzione è aggiornare le tue librerie in modo che possano gestire oggetti unicode.

Ma se vuoi davvero dei bytestring, codifica solo i risultati nella codifica che preferisci:

>>> nl = json.loads(js)
>>> nl
[u'a', u'b']
>>> nl = [s.encode('utf-8') for s in nl]
>>> nl
['a', 'b']

Grazie nosklo, è quello che ho fatto per primo. Ma come ho già detto, i dati reali che ho usato sono piuttosto nidificati e tutti, quindi questo ha introdotto un po 'di sovraccarico. Sto ancora cercando una soluzione automatica ... Esiste almeno una segnalazione di bug là fuori in cui le persone si lamentano del fatto che simplejson restituisca oggetti stringa anziché unicode.
Bruto

1
@Brutus: penso che Json abbia ragione nel restituire oggetti unicode. In effetti, non accetterei nulla di meno, poiché le stringhe javascript sono in realtà oggetti unicode. Quello che voglio dire è che le stringhe json (javascript) possono memorizzare qualsiasi tipo di carattere unicode, quindi ha senso creare oggetti unicode durante la traduzione da json. Invece dovresti davvero riparare le tue librerie.
nosklo,

16

Esiste una soluzione semplice.

TL; DR: utilizzare ast.literal_eval()invece di json.loads(). Entrambi aste jsonsono nella libreria standard.

Sebbene non sia una risposta "perfetta", se la tua intenzione è quella di ignorare del tutto Unicode, la si ottiene abbastanza lontano. In Python 2.7

import json, ast
d = { 'field' : 'value' }
print "JSON Fail: ", json.loads(json.dumps(d))
print "AST Win:", ast.literal_eval(json.dumps(d))

dà:

JSON Fail:  {u'field': u'value'}
AST Win: {'field': 'value'}

Questo diventa più peloso quando alcuni oggetti sono in realtà stringhe Unicode. La risposta completa diventa rapidamente pelosa.


11
Meglio essere sicuri che il tuo json non contenga alcun valore null, trueo false, perché non sono validi in Python e potrebbero literal_eval()non riuscire.
ʇsәɹoɈ

3
@ ʇsәɹoɈ Spero anche meglio che il tuo JSON non contenga un solidus con escape ( \/) all'interno di una stringa o una sequenza di escape unicode (come "\u0061", che è un altro modo di scrivere "a"). La sintassi letterale di Python è incompatibile con JSON in diversi modi e non mi fiderei di questa risposta per qualsiasi script che non avrei intenzione di buttare via.
Mark Amery,

La gente ha ragione nel dire che se la stringa è davvero unicode allora questa risposta fallisce, ma se così fosse non saremmo in grado di trasmettere comunque una stringa. +1 per una risposta che funziona solo quando funziona e genera un'eccezione altrimenti
Stefan Sullivan,

se possibile non usare jsonper scaricare i dati, basta usare printse si esegue python. Quindi ast.literal_evallavora
Jean-François Fabre

11

La risposta di Mike Brennan è vicina, ma non c'è motivo di attraversare l'intera struttura. Se si utilizza il object_hook_pairsparametro (Python 2.7+):

object_pairs_hookè una funzione opzionale che verrà chiamata con il risultato di qualsiasi oggetto letteralmente decodificato con un elenco ordinato di coppie. object_pairs_hookVerrà utilizzato il valore restituito di anziché il dict. Questa funzione può essere utilizzata per implementare decodificatori personalizzati che si basano sull'ordine in cui vengono decodificate le coppie chiave e valore (ad esempio, collections.OrderedDictricorderà l'ordine di inserimento). Se object_hookè anche definito, ha la object_pairs_hookpriorità.

Con esso, ricevi ogni oggetto JSON che ti viene consegnato, in modo da poter eseguire la decodifica senza necessità di ricorsione:

def deunicodify_hook(pairs):
    new_pairs = []
    for key, value in pairs:
        if isinstance(value, unicode):
            value = value.encode('utf-8')
        if isinstance(key, unicode):
            key = key.encode('utf-8')
        new_pairs.append((key, value))
    return dict(new_pairs)

In [52]: open('test.json').read()
Out[52]: '{"1": "hello", "abc": [1, 2, 3], "def": {"hi": "mom"}, "boo": [1, "hi", "moo", {"5": "some"}]}'                                        

In [53]: json.load(open('test.json'))
Out[53]: 
{u'1': u'hello',
 u'abc': [1, 2, 3],
 u'boo': [1, u'hi', u'moo', {u'5': u'some'}],
 u'def': {u'hi': u'mom'}}

In [54]: json.load(open('test.json'), object_pairs_hook=deunicodify_hook)
Out[54]: 
{'1': 'hello',
 'abc': [1, 2, 3],
 'boo': [1, 'hi', 'moo', {'5': 'some'}],
 'def': {'hi': 'mom'}}

Nota che non dovrò mai chiamare l'hook ricorsivamente poiché ogni oggetto verrà consegnato all'hook quando usi il object_pairs_hook. Devi preoccuparti degli elenchi, ma come puoi vedere, un oggetto all'interno di un elenco verrà convertito correttamente e non dovrai ricorrere per farlo accadere.

EDIT: Un collega ha sottolineato che Python2.6 non ha object_hook_pairs. Puoi ancora usare questo Python2.6 facendo una piccola modifica. Nel gancio sopra, cambia:

for key, value in pairs:

per

for key, value in pairs.iteritems():

Quindi utilizzare object_hookinvece di object_pairs_hook:

In [66]: json.load(open('test.json'), object_hook=deunicodify_hook)
Out[66]: 
{'1': 'hello',
 'abc': [1, 2, 3],
 'boo': [1, 'hi', 'moo', {'5': 'some'}],
 'def': {'hi': 'mom'}}

L'uso dei object_pairs_hookrisultati in un dizionario in meno viene istanziato per ogni oggetto nell'oggetto JSON, che, se si analizzava un documento enorme, potrebbe valere la pena.


1
Questo è pulito e sembra molto vicino a meritare il segno di spunta verde (che Bruto ha, mirabilmente, già passato in giro liberalmente poiché sono arrivate risposte migliori). Ma ... perché non gestire effettivamente gli elenchi correttamente in deunicodify_hookquello che esponi in questa risposta? Al momento, hai un'implementazione deunicodify_hookche non scorre le liste e deunicodifica le stringhe e le liste al loro interno, e quindi l'output che stai esibendo non corrisponde all'output che il tuo hook effettivamente produrrà. Risolvilo e questa risposta sarà superiore alla mia.
Mark Amery,

Frivolo: suggerirei anche di dimostrare la funzione con il normale interprete CPython piuttosto che quello che stai usando qui (che penso sia IronPython)? L'interprete CPython è più familiare alla maggior parte degli utenti Python ed è, a mio avviso, più bello.
Mark Amery,

Questo non funziona per me, ma sono sicuro che sia un po 'strano quello che sto facendo ... Sto memorizzando un elenco da un documento JSON più grande in un file. Sia che lo carichi con o senza questo object_pairs_hook, ogni articolo viene fuori Unicode. Darn.
visto il

1
@rsaw buon punto! Poiché object_pairs_hookviene chiamato solo per gli oggetti , se il testo JSON ha un elenco di stringhe al livello superiore, questa soluzione non riuscirà. Non c'è modo di risolvere questo problema senza chiamare una funzione sulla cosa da cui è stata restituita json.load; nessuno dei json.loadganci può garantire che sarai in grado di gestire ogni stringa. Penso che questo sia un difetto abbastanza grande per me continuare a raccomandare la mia soluzione piuttosto che usare i ganci.
Mark Amery,

-1 perché mi sono appena reso conto che Mirec Miskuf ha già pubblicato una risposta hook-hook che non presenta né gli svantaggi dell'approccio di Mike Brennan (ricodifica più volte gli stessi dizionari) né di questo (non riesce a byteizzare elenchi nidificati o elenchi di livello superiore o stringhe). Non sono sicuro del motivo per cui la sua risposta è languita quasi senza attenzione mentre questa - che è inferiore - ha rapidamente guadagnato voti.
Mark Amery,

9

Temo che non ci sia modo di farlo automaticamente all'interno della libreria simplejson.

Lo scanner e il decodificatore in simplejson sono progettati per produrre testo unicode. Per fare ciò, la libreria utilizza una funzione chiamata c_scanstring(se è disponibile, per velocità) o py_scanstringse la versione C non è disponibile. La scanstringfunzione viene chiamata più volte da quasi tutte le routine di Simplejson per la decodifica di una struttura che potrebbe contenere testo. Dovresti eseguire il monkeypatch del scanstringvalore in simplejson.decoder o una sottoclasse JSONDecodere fornire praticamente la tua intera implementazione di tutto ciò che potrebbe contenere testo.

La ragione per cui simplejson genera unicode, tuttavia, è che la specifica json menziona specificamente che "Una stringa è una raccolta di zero o più caratteri Unicode" ... il supporto per Unicode è assunto come parte del formato stesso. L' scanstringimplementazione di Simplejson arriva fino al punto di scansionare e interpretare gli escape unicode (anche il controllo degli errori per le rappresentazioni di set di caratteri multi-byte non validi), quindi l'unico modo in cui può restituire il valore in modo affidabile è come Unicode.

Se hai una libreria obsoleta che ha bisogno di una str, ti consiglio di cercare faticosamente la struttura dei dati nidificati dopo l'analisi (che riconosco è ciò che hai esplicitamente detto che volevi evitare ... scusa), o forse avvolgere le tue librerie in una sorta di facciata in cui è possibile massaggiare i parametri di input a un livello più granulare. Il secondo approccio potrebbe essere più gestibile del primo se le strutture dei dati sono effettivamente nidificate in profondità.


4

Come Mark (Amery) osserva correttamente: L' uso del deserializzatore di PyYaml su una discarica JSON funziona solo se si dispone solo di ASCII. Almeno fuori dalla scatola.

Due rapidi commenti sull'approccio PyYaml:

  1. MAI utilizzare yaml.load sui dati dal campo. È una funzione (!) Di yaml per eseguire codice arbitrario nascosto all'interno della struttura.

  2. È possibile farlo funzionare anche per i non ASCII tramite questo:

    def to_utf8(loader, node):
        return loader.construct_scalar(node).encode('utf-8')
    yaml.add_constructor(u'tag:yaml.org,2002:str', to_utf8)

Ma la performance non è paragonabile alla risposta di Mark Amery:

Lanciando alcuni dadi di esempio profondamente annidati sui due metodi, ottengo questo (con dt [j] = delta temporale di json.loads (json.dumps (m))):

     dt[yaml.safe_load(json.dumps(m))] =~ 100 * dt[j]
     dt[byteify recursion(Mark Amery)] =~   5 * dt[j]

Quindi deserializzazione che include la completa camminata sull'albero e la codifica, ben all'interno dell'ordine di grandezza dell'implementazione basata su C di JSON. Lo trovo straordinariamente veloce ed è anche più robusto del carico di yaml in strutture profondamente annidate. E meno soggetto a errori di sicurezza, guardando yaml.load.

=> Mentre apprezzerei un puntatore a un convertitore basato solo su C, la funzione byteify dovrebbe essere la risposta predefinita.

Ciò è particolarmente vero se la tua struttura json proviene dal campo, contenente l'input dell'utente. Perché allora probabilmente avrai bisogno di camminare comunque sulla tua struttura, indipendentemente dalle tue strutture dati interne desiderate ('sandwich unicode' o solo stringhe di byte).

Perché?

Normalizzazione Unicode . Per chi non lo sapesse: prendi un antidolorifico e leggi questo .

Quindi usando la ricorsione byteify uccidi due uccelli con una fava:

  1. prendi le tue distrazioni da discariche json nidificate
  2. ottenere valori di input dell'utente normalizzati, in modo da trovare gli elementi nella memoria.

Nei miei test si è scoperto che la sostituzione di input.encode ('utf-8') con un unicodedata.normalize ('NFC', input) .encode ('utf-8') era persino più veloce di senza NFC - ma dipende fortemente dai dati del campione, credo.


3

Il gotcha è quello simplejsone jsonsono due moduli diversi, almeno nel modo in cui gestiscono l'unicode. Hai jsonin py 2.6+ e questo ti dà valori unicode, mentre simplejsonrestituisce oggetti stringa. Basta provare easy_install-ing simplejson nel proprio ambiente e vedere se funziona. Lo ha fatto per me.


2

Basta usare pickle invece di json per il dump e il caricamento, in questo modo:

    import json
    import pickle

    d = { 'field1': 'value1', 'field2': 2, }

    json.dump(d,open("testjson.txt","w"))

    print json.load(open("testjson.txt","r"))

    pickle.dump(d,open("testpickle.txt","w"))

    print pickle.load(open("testpickle.txt","r"))

L'output che produce è (stringhe e numeri interi sono gestiti correttamente):

    {u'field2': 2, u'field1': u'value1'}
    {'field2': 2, 'field1': 'value1'}

1
+1 per una soluzione che non richiede pacchetti aggiuntivi (come yaml ). Ma a volte - come nel mio caso originale - ho bisogno di avere i dati in JSON, quindi pickle non è sempre l'opzione migliore. Inoltre, hai safe_loadin YAML, non so se esiste qualcosa di simile per il sottaceto .
Bruto

1

Quindi, ho riscontrato lo stesso problema. Indovina qual è stato il primo risultato di Google.

Poiché ho bisogno di passare tutti i dati a PyGTK, anche le stringhe unicode non sono molto utili per me. Quindi ho un altro metodo di conversione ricorsivo. In realtà è anche necessario per la conversione JSON typesafe - json.dump () verrebbe salvato su qualsiasi non letterale, come gli oggetti Python. Tuttavia, non converte gli indici dict.

# removes any objects, turns unicode back into str
def filter_data(obj):
        if type(obj) in (int, float, str, bool):
                return obj
        elif type(obj) == unicode:
                return str(obj)
        elif type(obj) in (list, tuple, set):
                obj = list(obj)
                for i,v in enumerate(obj):
                        obj[i] = filter_data(v)
        elif type(obj) == dict:
                for i,v in obj.iteritems():
                        obj[i] = filter_data(v)
        else:
                print "invalid object in data, converting to string"
                obj = str(obj) 
        return obj

L'unico problema che potrebbe sorgere qui è se hai bisogno delle chiavi in ​​un dizionario convertite da Unicode. Sebbene questa implementazione converta i valori, mantiene le chiavi unicode. Se crei un 'newobj', usa newobj [str (i)] = ... e, al termine, assegna obj = newobj, anche le chiavi verranno convertite.
Neal Stublen,

Questo potrebbe essere più bello con comprensione o meglio convertendo le chiavi. È anche unidiomatico; entrambi muta gli oggetti sul posto (nel caso dei dizionari) e restituisce il nuovo valore, che è incompatibile con i metodi di raccolta incorporati di Python che mutano l'oggetto corrente o ne restituiscono uno nuovo, ma non entrambi.
Mark Amery,

1

Avevo un dict JSON come stringa. Le chiavi e i valori erano oggetti unicode come nell'esempio seguente:

myStringDict = "{u'key':u'value'}"

Potrei usare la byteifyfunzione suggerita sopra convertendo la stringa in un dictoggetto usando ast.literal_eval(myStringDict).


L'esempio che hai fornito non è un esempio di JSON. {u'key':u'value'}non è JSON.
Mark Amery,

2
So perfettamente che non è JSON. È così che è stato analizzato da una fonte esterna nel mio script Python. Se fosse JSON direttamente come nell'esempio seguente, non avrei bisogno della funzione byteify contrassegnata come soluzione: {"firstName": "John", "lastName": "Doe"}. Sarebbe fantastico se prima di votare leggessi le risposte. Grazie.
narko,

1

Supporta Python2 e 3 tramite hook (da https://stackoverflow.com/a/33571117/558397 )

import requests
import six
from six import iteritems

requests.packages.urllib3.disable_warnings()  # @UndefinedVariable
r = requests.get("http://echo.jsontest.com/key/value/one/two/three", verify=False)

def _byteify(data):
    # if this is a unicode string, return its string representation
    if isinstance(data, six.string_types):
        return str(data.encode('utf-8').decode())

    # if this is a list of values, return list of byteified values
    if isinstance(data, list):
        return [ _byteify(item) for item in data ]

    # if this is a dictionary, return dictionary of byteified keys and values
    # but only if we haven't already byteified it
    if isinstance(data, dict):
        return {
            _byteify(key): _byteify(value) for key, value in iteritems(data)
        }
    # if it's anything else, return it in its original form
    return data

w = r.json(object_hook=_byteify)
print(w)

Ritorna:

 {'three': '', 'key': 'value', 'one': 'two'}

0

Questo è in ritardo per il gioco, ma ho costruito questo lanciatore ricorsivo. Funziona per le mie esigenze e penso che sia relativamente completo. Potrebbe esserti d'aiuto.

def _parseJSON(self, obj):
    newobj = {}

    for key, value in obj.iteritems():
        key = str(key)

        if isinstance(value, dict):
            newobj[key] = self._parseJSON(value)
        elif isinstance(value, list):
            if key not in newobj:
                newobj[key] = []
                for i in value:
                    newobj[key].append(self._parseJSON(i))
        elif isinstance(value, unicode):
            val = str(value)
            if val.isdigit():
                val = int(val)
            else:
                try:
                    val = float(val)
                except ValueError:
                    val = str(val)
            newobj[key] = val

    return newobj

Basta passare un oggetto JSON in questo modo:

obj = json.loads(content, parse_float=float, parse_int=int)
obj = _parseJSON(obj)

L'ho come membro privato di una classe, ma puoi riutilizzare il metodo come ritieni opportuno.


Ho riscontrato un problema in cui sto cercando di analizzare JSON e passare il mapping risultante a una funzione come ** kwargs. Sembra che i nomi dei parametri delle funzioni non possano essere Unicode, quindi la tua funzione _parseJSON è fantastica. Se c'è un modo più semplice, qualcuno può farmelo sapere.
Neal Stublen,

1
Questo codice ha un problema: si effettua una chiamata ricorsiva nel pezzo Elenco, che fallirà se gli elementi dell'elenco non sono essi stessi dizionari.
I82

Oltre al bug descritto da @ I82Much, anche questo ha un nome errato (in realtà non analizza il JSON; json.loadsprima è necessaria una chiamata), tenta arbitrariamente di convertire stringhe in ints per nessun motivo spiegato, e non è copia-e- pronto per la pasta.
Mark Amery,

0

Ho riscritto il _parse_json () di Wells per gestire i casi in cui l'oggetto json stesso è un array (il mio caso d'uso).

def _parseJSON(self, obj):
    if isinstance(obj, dict):
        newobj = {}
        for key, value in obj.iteritems():
            key = str(key)
            newobj[key] = self._parseJSON(value)
    elif isinstance(obj, list):
        newobj = []
        for value in obj:
            newobj.append(self._parseJSON(value))
    elif isinstance(obj, unicode):
        newobj = str(obj)
    else:
        newobj = obj
    return newobj

0

ecco un codificatore ricorsivo scritto in C: https://github.com/axiros/nested_encode

Sovraccarico prestazionale per strutture "medie" intorno al 10% rispetto a json.loads.

python speed.py                                                                                            
  json loads            [0.16sec]: {u'a': [{u'b': [[1, 2, [u'\xd6ster..
  json loads + encoding [0.18sec]: {'a': [{'b': [[1, 2, ['\xc3\x96ster.
  time overhead in percent: 9%

usando questa struttura di test:

import json, nested_encode, time

s = """
{
  "firstName": "Jos\\u0301",
  "lastName": "Smith",
  "isAlive": true,
  "age": 25,
  "address": {
    "streetAddress": "21 2nd Street",
    "city": "\\u00d6sterreich",
    "state": "NY",
    "postalCode": "10021-3100"
  },
  "phoneNumbers": [
    {
      "type": "home",
      "number": "212 555-1234"
    },
    {
      "type": "office",
      "number": "646 555-4567"
    }
  ],
  "children": [],
  "spouse": null,
  "a": [{"b": [[1, 2, ["\\u00d6sterreich"]]]}]
}
"""


t1 = time.time()
for i in xrange(10000):
    u = json.loads(s)
dt_json = time.time() - t1

t1 = time.time()
for i in xrange(10000):
    b = nested_encode.encode_nested(json.loads(s))
dt_json_enc = time.time() - t1

print "json loads            [%.2fsec]: %s..." % (dt_json, str(u)[:20])
print "json loads + encoding [%.2fsec]: %s..." % (dt_json_enc, str(b)[:20])

print "time overhead in percent: %i%%"  % (100 * (dt_json_enc - dt_json)/dt_json)

0

Con Python 3.6, a volte mi imbatto ancora in questo problema. Ad esempio, quando si ottiene la risposta da un'API REST e si carica il testo della risposta in JSON, ottengo ancora le stringhe unicode. Ho trovato una soluzione semplice usando json.dumps ().

response_message = json.loads(json.dumps(response.text))
print(response_message)

-1

Mi sono imbattuto anche in questo problema e, avendo a che fare con JSON, ho trovato un piccolo ciclo che converte le chiavi unicode in stringhe. ( simplejsonsu GAE non restituisce le chiavi di stringa.)

obj è l'oggetto decodificato da JSON:

if NAME_CLASS_MAP.has_key(cls):
    kwargs = {}
    for i in obj.keys():
        kwargs[str(i)] = obj[i]
    o = NAME_CLASS_MAP[cls](**kwargs)
    o.save()

kwargsè ciò che passo al costruttore dell'applicazione GAE (a cui non piacciono le unicodechiavi **kwargs)

Non robusto come la soluzione di Wells, ma molto più piccolo.


-1

Ho adattato il codice dalla risposta di Mark Amery , in particolare per sbarazzarmi dei isinstanceprofessionisti della dattilografia.

La codifica viene eseguita manualmente ed ensure_asciiè disabilitata. I documenti di Python per lo json.dumpdicono

Se sure_ascii è True (impostazione predefinita), tutti i caratteri non ASCII nell'output vengono salvati con sequenze \ uXXXX

Disclaimer: nel doctest ho usato la lingua ungherese. Alcune importanti codifiche di caratteri ungheresi sono: cp852la codifica IBM / OEM utilizzata, ad es. in DOS (a volte indicato come ascii , penso erroneamente, dipende dall'impostazione della tabella codici ), cp1250usata ad es. in Windows (a volte indicato come ansi , a seconda delle impostazioni locali) e iso-8859-2, a volte utilizzato su server http. Il testo del test Tüskéshátú kígyóbűvölőè attribuito a Koltai László (modulo per il nome personale nativo) ed è tratto da Wikipedia .

# coding: utf-8
"""
This file should be encoded correctly with utf-8.
"""
import json

def encode_items(input, encoding='utf-8'):
    u"""original from: https://stackoverflow.com/a/13101776/611007
    adapted by SO/u/611007 (20150623)
    >>> 
    >>> ## run this with `python -m doctest <this file>.py` from command line
    >>> 
    >>> txt = u"Tüskéshátú kígyóbűvölő"
    >>> txt2 = u"T\\u00fcsk\\u00e9sh\\u00e1t\\u00fa k\\u00edgy\\u00f3b\\u0171v\\u00f6l\\u0151"
    >>> txt3 = u"uúuutifu"
    >>> txt4 = b'u\\xfauutifu'
    >>> # txt4 shouldn't be 'u\\xc3\\xbauutifu', string content needs double backslash for doctest:
    >>> assert u'\\u0102' not in b'u\\xfauutifu'.decode('cp1250')
    >>> txt4u = txt4.decode('cp1250')
    >>> assert txt4u == u'u\\xfauutifu', repr(txt4u)
    >>> txt5 = b"u\\xc3\\xbauutifu"
    >>> txt5u = txt5.decode('utf-8')
    >>> txt6 = u"u\\u251c\\u2551uutifu"
    >>> there_and_back_again = lambda t: encode_items(t, encoding='utf-8').decode('utf-8')
    >>> assert txt == there_and_back_again(txt)
    >>> assert txt == there_and_back_again(txt2)
    >>> assert txt3 == there_and_back_again(txt3)
    >>> assert txt3.encode('cp852') == there_and_back_again(txt4u).encode('cp852')
    >>> assert txt3 == txt4u,(txt3,txt4u)
    >>> assert txt3 == there_and_back_again(txt5)
    >>> assert txt3 == there_and_back_again(txt5u)
    >>> assert txt3 == there_and_back_again(txt4u)
    >>> assert txt3.encode('cp1250') == encode_items(txt4, encoding='utf-8')
    >>> assert txt3.encode('utf-8') == encode_items(txt5, encoding='utf-8')
    >>> assert txt2.encode('utf-8') == encode_items(txt, encoding='utf-8')
    >>> assert {'a':txt2.encode('utf-8')} == encode_items({'a':txt}, encoding='utf-8')
    >>> assert [txt2.encode('utf-8')] == encode_items([txt], encoding='utf-8')
    >>> assert [[txt2.encode('utf-8')]] == encode_items([[txt]], encoding='utf-8')
    >>> assert [{'a':txt2.encode('utf-8')}] == encode_items([{'a':txt}], encoding='utf-8')
    >>> assert {'b':{'a':txt2.encode('utf-8')}} == encode_items({'b':{'a':txt}}, encoding='utf-8')
    """
    try:
        input.iteritems
        return {encode_items(k): encode_items(v) for (k,v) in input.iteritems()}
    except AttributeError:
        if isinstance(input, unicode):
            return input.encode(encoding)
        elif isinstance(input, str):
            return input
        try:
            iter(input)
            return [encode_items(e) for e in input]
        except TypeError:
            return input

def alt_dumps(obj, **kwargs):
    """
    >>> alt_dumps({'a': u"T\\u00fcsk\\u00e9sh\\u00e1t\\u00fa k\\u00edgy\\u00f3b\\u0171v\\u00f6l\\u0151"})
    '{"a": "T\\xc3\\xbcsk\\xc3\\xa9sh\\xc3\\xa1t\\xc3\\xba k\\xc3\\xadgy\\xc3\\xb3b\\xc5\\xb1v\\xc3\\xb6l\\xc5\\x91"}'
    """
    if 'ensure_ascii' in kwargs:
        del kwargs['ensure_ascii']
    return json.dumps(encode_items(obj), ensure_ascii=False, **kwargs)

Vorrei anche evidenziare la risposta di Jarret Hardie che fa riferimento alle specifiche JSON , citando:

Una stringa è una raccolta di zero o più caratteri Unicode

Nel mio caso d'uso avevo dei file con JSON. Sono utf-8file codificati. ensure_asciisi traduce in file json correttamente sfuggiti ma non molto leggibili, ecco perché ho adattato la risposta di Mark Amery alle mie esigenze.

Il doctest non è particolarmente ponderato ma condivido il codice nella speranza che possa essere utile per qualcuno.


Non sono sicuro di vedere i vantaggi dell'utilizzo della duck duck qui? Sappiamo che le raccolte restituite json.loadssaranno elenchi o dicts, non un tipo definito dall'utente o definito dalla libreria che implementa i loro metodi e metodi magici, quindi perché non fare solo un isinstancecontrollo? Non è più facile da capire che verificare l'esistenza di iteritemso se iteraccetterà l'oggetto come argomento?
Mark Amery,

@MarkAmery si tratta di discariche, non di carichi. se si creano dati da scaricare - anziché caricarli - non si può essere sicuri di cosa si tratti. l'idea era di lasciarlo arrivare da qualsiasi parte del codice.
n611x007,

-2

Dai un'occhiata a questa risposta a una domanda simile come questa che lo afferma

Il prefisso u significa solo che hai una stringa Unicode. Quando usi davvero la stringa, questa non apparirà nei tuoi dati. Non essere generato dall'output stampato.

Ad esempio, prova questo:

print mail_accounts[0]["i"]

Non vedrai una u.


Non è vero se ad esempio vuoi formattare qualcosa che contiene una stringa unicode, in Py2. ad esempio '{}'.format({u'x' : u'y'})include ancora le u.
Ponkadoodle,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.