Jak uzyskać obiekty JSON zamiast Unicode?

276

Używam Pythona 2 do analizy JSON z plików tekstowych zakodowanych w ASCII .

Podczas ładowania tych plików za pomocą jsonlub simplejson, wszystkie moje ciągi znaków są rzutowane na obiekty Unicode zamiast na ciągi znaków. Problem polega na tym, że muszę używać danych z niektórymi bibliotekami, które akceptują tylko obiekty łańcuchowe. Nie mogę zmienić bibliotek ani zaktualizować ich.

Czy można uzyskać obiekty łańcuchowe zamiast Unicode?

Przykład

>>> 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`

Aktualizacja

To pytanie zostało zadane dawno temu , kiedy utknąłem w Pythonie 2 . Jednym łatwym i czystym rozwiązaniem na dziś jest użycie najnowszej wersji Pythona - tj. Python 3 i nowszych .

Brutus
źródło
1
W Pythonie 3 nie ma problemu, typ elementów na nowej liście jeststr
GoingMyWay
1
Python 3k nie jest „najnowszą wersją Pythona”, jest tylko alternatywną gałęzią.
user2589273,
11
Dziwnie jest zobaczyć taki komentarz w grudniu 2017 r. - Python 2 jest przestarzały i po 1 stycznia 2020 r. Nie będzie już można wykonywać żadnych czynności konserwacyjnych: mniej niż 2 lata: pythonclock.org
Hai
1
@ZaarHai DUŻO ludzi utknęło w Pythonie 2 wbrew ich woli. Istnieje wiele aplikacji, które osadzają własną wersję Pythona do automatyzacji i skryptów, więc ludzie muszą jej używać do czasu aktualizacji dostawcy (patrzę na ciebie Maya, Houdini, Nuke ..)
Geordie
1
@Geordie Na pewno to wiem i rozumiem. Mój komentarz dotyczył terminologii - Python nie jest „alternatywną gałęzią”, ale raczej niefortunnym brakiem alternatywy (przeznaczona gra słów) dla tych, którzy utknęli z nią.
Zaar Hai,

Odpowiedzi:

101

Rozwiązanie z 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

Przykładowe użycie:

>>> 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'}

Jak to działa i dlaczego miałbym go używać?

Funkcja Marka Amery jest krótsza i bardziej przejrzysta niż te, więc jaki jest ich sens? Dlaczego chcesz z nich korzystać?

Wyłącznie dla wydajności . Odpowiedź Marka dekoduje najpierw tekst JSON za pomocą ciągów Unicode, a następnie przechodzi przez całą zdekodowaną wartość, aby przekonwertować wszystkie ciągi na ciągi bajtowe. Ma to kilka niepożądanych efektów:

  • Kopia całej zdekodowanej struktury zostaje utworzona w pamięci
  • Jeśli obiekt JSON jest naprawdę głęboko zagnieżdżony (500 poziomów lub więcej), osiągniesz maksymalną głębokość rekurencji w Pythonie

Ta odpowiedź łagodzi oba te problemy z wydajnością przy użyciu object_hookparametru json.loadi json.loads. Z dokumentów :

object_hookjest funkcją opcjonalną, która zostanie wywołana z wynikiem dowolnego dekodowania literału obiektu (a dict). Zamiast tego zostanie użyta wartość zwracana przez object_hook dict. Tej funkcji można użyć do zaimplementowania niestandardowych dekoderów

Ponieważ słowniki zagnieżdżone na wielu poziomach głęboko w innych słownikach są przekazywane object_hook podczas dekodowania , możemy w tym miejscu bajtować wszelkie ciągi znaków lub listy i unikać późniejszej potrzeby głębokiej rekurencji.

Odpowiedź Marka nie nadaje się do użycia w object_hookobecnym kształcie, ponieważ powtarza się w zagnieżdżonych słownikach. Zapobiegamy tej rekurencji w tej odpowiedzi z ignore_dictsparametrem do _byteify, który jest do niej przekazywany przez cały czas, z wyjątkiem sytuacji, gdy object_hookprzekazuje ją do nowego dictbajtowania. ignore_dictsFlag opowiada _byteifyignorować dicts ponieważ już byteified.

Wreszcie, nasze implementacje json_load_byteifiedi json_loads_byteifiedwywołanie _byteify(z ignore_dicts=True) wyniku zwróconego z json.loadlub w json.loadscelu obsługi przypadku, w którym dekodowany tekst JSON nie ma dictnajwyższego poziomu.

Mirec Miskuf
źródło
1
+1 za podejście tutaj; Tak naprawdę nie zrozumiałem go, kiedy po raz pierwszy go przeczytałem, ale w końcu zrozumiałem, kiedy ponownie go przeczytałem w świetle odpowiedzi Travisa Jensena. Dokonałem dość agresywnej edycji w nadziei wyjaśnienia, jak to działa i jakie są jego zalety w stosunku do mojej odpowiedzi. Podstawowa idea kodu pozostaje nietknięta, ale zmodyfikowałem prawie wszystko inne. Jeśli sprzeciwisz się temu, wycofaj moją edycję - to twoja odpowiedź!
Mark Amery
Nie ma problemu, Mark, wielkie dzięki. Podoba mi się twoja edycja, jest znacznie bardziej objaśniająca niż moja oryginalna. Może kiedyś nauczę się udzielać bardziej zwięzłych odpowiedzi.
Mirec Miskuf
2
To świetne rozwiązanie; wydajny i elegancki. Jeśli jednak utkniesz w królestwie Pythona <2.7, tak jak ja, musisz zastąpić linię: return { byteify(key, ignore_dicts=True): _byteify(value, ignore_dicts=True) for key, value in data.iteritems() }z return dict((_byteify(key, ignore_dicts=True), _byteify(value, ignore_dicts=True)) for key, value in data.iteritems()), aby działała.
Richard Dunn,
Myślę, że nie masz racji co do problemu głębokości rekurencji. Z Ciebie, mogę iść do 990: json_loads_byteified('[' * 990 + ']' * 990). Z 991 ulega awarii. Mark nadal współpracuje z 991: byteify(json.loads('[' * 991 + ']' * 991)). Rozbija się przy 992. Tak więc przynajmniej w tym teście Mark może pójść głębiej, w przeciwieństwie do tego, co powiedziałeś.
Stefan Pochmann
@MarkAmery Co sądzisz o moim powyższym komentarzu? (Właśnie widziałem w historii edycji, że to ty dodałeś to roszczenie).
Stefan Pochmann
180

Chociaż jest tu kilka dobrych odpowiedzi, ostatecznie użyłem PyYAML do parsowania moich plików JSON, ponieważ daje klucze i wartości jako strciągi znaków zamiast unicodetypu. Ponieważ JSON jest podzbiorem YAML, działa dobrze:

>>> 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']

Notatki

Należy jednak pamiętać o kilku rzeczach:

  • Dostaję obiekty łańcuchowe, ponieważ wszystkie moje wpisy są zakodowane w ASCII . Gdybym używał wpisów kodowanych w Unicode, odzyskałbym je jako obiekty Unicode - nie ma konwersji!

  • Powinieneś (prawdopodobnie zawsze) użyć safe_loadfunkcji PyYAML ; jeśli użyjesz go do ładowania plików JSON, i tak nie potrzebujesz „dodatkowej mocy” loadfunkcji.

  • Jeśli chcesz parser YAML, który ma większe wsparcie dla wersji 1.2 specyfikacji (i poprawnie analizuje bardzo niskie liczby ) wypróbuj Ruamel YAML : pip install ruamel.yamli to import ruamel.yaml as yamlbyło wszystko, czego potrzebowałem w moich testach.

Konwersja

Jak wspomniano, nie ma konwersji! Jeśli nie możesz być pewien, że zajmujesz się tylko wartościami ASCII (i nie masz pewności przez większość czasu), lepiej użyj funkcji konwersji :

Kilka razy korzystałem z tego od Marka Amery , działa świetnie i jest bardzo łatwy w użyciu. Zamiast tego możesz również użyć podobnej funkcji object_hook, ponieważ może ona zwiększyć wydajność dużych plików. Zobacz na to nieco bardziej zaangażowaną odpowiedź Mireca Miskufa .

Brutus
źródło
8
Zachowaj ostrożność, jeśli zdecydujesz się użyć tej odpowiedzi. Działa to doskonale w przypadku Brutusa, ale tylko dlatego, że wie, że jego dane zawierają tylko znaki kodowane w ASCII. Jeśli nie masz takiej gwarancji, ta odpowiedź nie zadziała. Na przykład spróbuj wykonać yaml.load(json.dumps([u'a', u'£', u'É']))w powłoce Pythona i zauważ, że odzyskujesz ['a', u'\xa3', u'\xc9'](który zawiera unicodeciągi znaków). Jeśli nie możesz być pewien, że twoje dane zawierają tylko znaki z zestawu znaków ASCII, powinieneś zastosować inne podejście (polecam własną odpowiedź).
Mark Amery
1
YAML korzysta również z [u'a', u'b']ostrożności.
Carlos Calla,
1
To miłe, ale nie działa z niskimi liczbami .. spójrz tutaj: stackoverflow.com/questions/30458977/...
Oren
@Oren: To nie jest błąd w specyfikacji YAML, ale w parserze PyYAML. YAML parser z ruamel prac.
Brutus
Chcę mieć wynik jak [„a”, „b”], a nie jak ['a', 'b'] @Brutus
user60679
141

Nie ma wbudowanej opcji powodującej, że funkcje modułu json zwracają ciągi bajtów zamiast ciągów Unicode. Jednak ta krótka i prosta funkcja rekurencyjna przekształci dowolny zdekodowany obiekt JSON z użycia ciągów Unicode w ciągi bajtów zakodowanych w 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

Po prostu wywołaj to na wyjściu otrzymanym z połączenia json.loadlub json.loads.

Kilka uwag:

  • Aby wesprzeć Pyton 2,6 lub powyżej, zastępuje return {byteify(key): byteify(value) for key, value in input.iteritems()}się return dict([(byteify(key), byteify(value)) for key, value in input.iteritems()]), od słownika Ułatwienia nie były obsługiwane, aż Pythonie 2.7.
  • Ponieważ ta odpowiedź powtarza się w całym zdekodowanym obiekcie, ma ona kilka niepożądanych charakterystyk wydajnościowych, których można uniknąć dzięki bardzo ostrożnemu użyciu parametrów object_hooklub object_pairs_hook. Odpowiedź Mireca Miskufa jest jak dotąd jedyną, która potrafi to poprawnie wykonać, choć w konsekwencji jest znacznie bardziej skomplikowana niż moje podejście.
Mark Amery
źródło
1
Podoba mi się to - nie jest to ignorowanie - zdaje sobie sprawę, że kiedy ludzie mówią „struny” i „ascii”, naiwnie na ogół mają na myśli, że chcą bajtów, a nie teoretycznych znaków Unicode. (i nie ascii, ponieważ nadal chcą znaków funta na drugim końcu)
Danny Staple
Podoba mi się to, działa prawie tak samo, jak działa moja ładna drukarka, ponieważ wiem, że Json nie tworzy krotki, należy dodać wyjątek dla krotki.
y.petremann
Jest to okropnie nieefektywne, wymagające rekurencyjnego przechodzenia przez węzły, które mogą nie być konieczne. Moduł json zapewnia haczyki, aby zrobić to znacznie wydajniej. Odpowiedź poniżej przy użyciu object_hookjest w rzeczywistości znacznie gorsza niż ta, ale przy użyciu object_pairs_hookmożna uzyskać dość wydajną metodę, która nie wymaga rekurencji ani ponownej wizyty węzłów, które nie zawierają ciągów.
Travis Jensen
1
@TravisJensen Interesujące. object_pairs_hookMetoda jest chyba bardzo nieznacznie trudniejszy do zrozumienia niż ten jeden (trzeba zrozumieć, jak działa i dlaczego listy parametrów i dicts wymagają innego traktowania), a korzyści wydajność nie sprawa dla większości ludzi ... ale bym się spodziewać istnieje, szczególnie dla każdego, kto ma do czynienia z niezwykle głęboko zagnieżdżonym obiektem JSON.
Mark Amery
plus1 To jest najbardziej zwięzła odpowiedź; poza tym PyYAML jest trudny do zainstalowania. Jedyne, co byłoby lepsze, to jakoś mikrostrumieniowa konwersja, aby nie korzystała z pamięci 4x.
personal_cloud
74

Możesz użyć object_hookparametru, json.loadsaby przekazać konwerter. Po fakcie nie trzeba wykonywać konwersji. jsonModuł będzie zawsze przechodzą object_hookdicts tylko, a będzie to rekurencyjnie przechodzić w zagnieżdżonych dicts, więc nie musisz rekursja do zagnieżdżonych dicts siebie. Nie sądzę, bym konwertował ciągi znaków Unicode na liczby takie jak programy Wellsa. Jeśli jest to ciąg znaków Unicode, został zacytowany jako ciąg znaków w pliku JSON, więc powinien to być ciąg znaków (lub plik jest zły).

Ponadto starałbym się unikać robienia czegoś takiego jak str(val)na unicodeobiekcie. Powinieneś używać value.encode(encoding)z prawidłowym kodowaniem, w zależności od tego, czego oczekuje twoja biblioteka zewnętrzna.

Na przykład:

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)
Mike Brennan
źródło
3
Jest to w porządku, jeśli obiekt w sjest JSON Object(nieuporządkowany zbiór klucz: wartość paruje się ze znakiem „:” oddzielającym klucz od wartości, oddzielony przecinkami i ujęty w nawiasy klamrowe), ale nie, jeśli, powiedzmy, JSON Array. Więc jeśli otrzymamy JSON Arrayjak ["a", "b"], wynik nadal będzie [u'a', u'b']. Żaden z innych obecnie dostępnych dostosowywania parametrów typu zaczepu json.loads()nie może wykonać zadania.
martineau
2
Ponieważ, jak wspomniałeś, jsonmoduł będzie rekurencyjnie przekazywał w zagnieżdżone dicts, nie ma potrzeby sprawdzania ich w dwóch funkcjach - więc dwie elifklauzule, które je sprawdzają, powinny zostać usunięte.
martineau,
1
Zauważ, że rozpoczynanie nazw funkcji znakiem podkreślenia ma specjalne znaczenie dla instrukcji importu. Jeśli umieścisz te funkcje w pliku o nazwie Utility.py, a w innym pliku tak from Utility import *, funkcje nie będą widoczne z powodu tego podkreślenia.
M Katz
1
To naprawdę zły pomysł. object_hookjest wywoływany dla każdego przeanalizowanego obiektu json, więc jeśli powrócisz do tego, co zostało ci dane, ponownie „utwierdzasz” rzeczy, które już „utwierdziłeś”. Wydajność wzrośnie geometrycznie wraz z rozmiarem obiektu. Podałem tutaj odpowiedź , która wykorzystuje object_pairs_hooki nie cierpi z powodu tego problemu.
Travis Jensen
38

Jest tak, ponieważ json nie ma różnicy między obiektami łańcuchowymi a obiektami Unicode. Wszystkie są łańcuchami w javascript.

Myślę, że JSON ma rację zwracając obiekty Unicode . W rzeczywistości nie zaakceptowałbym niczego mniejszego, ponieważ ciągi javascript są w rzeczywistości unicodeobiektami (tzn. Ciągi JSON (javascript) mogą przechowywać dowolny rodzaj znaku Unicode), więc sensowne jest tworzenie unicodeobiektów podczas tłumaczenia ciągów z JSON. Zwykłe ciągi po prostu nie pasowałyby, ponieważ biblioteka musiałaby odgadnąć kodowanie, jakie chcesz.

Lepiej jest używać unicodeobiektów łańcuchowych wszędzie. Dlatego najlepszą opcją jest aktualizacja bibliotek, aby mogły one obsługiwać obiekty Unicode.

Ale jeśli naprawdę chcesz bajtowania, po prostu zakoduj wyniki do wybranego kodowania:

>>> nl = json.loads(js)
>>> nl
[u'a', u'b']
>>> nl = [s.encode('utf-8') for s in nl]
>>> nl
['a', 'b']
nosklo
źródło
Dzięki nosklo, właśnie to zrobiłem pierwszy. Ale, jak powiedziałem, rzeczywiste dane, których użyłem, są dość zagnieżdżone i tak dalej, więc wprowadziło to trochę narzutu. Wciąż szukam automatycznego rozwiązania ... Istnieje co najmniej jeden raport o błędzie, w którym ludzie narzekają na simplejson zwracający obiekty łańcuchowe zamiast Unicode.
Brutus
1
@Brutus: Myślę, że json ma rację zwracając obiekty Unicode. W rzeczywistości nie zaakceptowałbym niczego mniejszego, ponieważ ciągi javascript są w rzeczywistości obiektami Unicode. Chodzi mi o to, że ciągi json (javascript) mogą przechowywać dowolny rodzaj znaku Unicode, więc sensowne jest tworzenie obiektów Unicode podczas tłumaczenia z json. Zamiast tego powinieneś naprawdę naprawić swoje biblioteki.
nosklo
16

Istnieje łatwe obejście problemu.

TL; DR - użyj ast.literal_eval()zamiast json.loads(). Zarówno asti jsonsą w bibliotece standardowej.

Chociaż nie jest to „idealna” odpowiedź, jest ona dość daleko, jeśli twoim planem jest całkowite zignorowanie Unicode. W Pythonie 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))

daje:

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

Robi się bardziej włochaty, gdy niektóre obiekty są tak naprawdę łańcuchami Unicode. Pełna odpowiedź szybko staje się owłosiona.

Charles Merriam
źródło
11
Lepiej mieć pewność, że json nie zawiera żadnych null, trueani falsewartości, ponieważ nie są one ważne w Pythonie i spowoduje literal_eval()niepowodzenie.
ʇsәɹoɈ
3
@ ʇsәɹoɈ Lepiej też mieć nadzieję, że twój JSON nie zawiera znaku ucieczki solidus ( \/) wewnątrz łańcucha ani sekwencji ucieczki Unicode (jak "\u0061", co jest innym sposobem pisania "a"). Dosłowna składnia Pythona jest niezgodna z JSON na kilka sposobów i nie ufałbym tej odpowiedzi dla żadnego skryptu, którego nie zamierzałem wyrzucić.
Mark Amery
Ludzie mają rację, że jeśli łańcuch znaków jest naprawdę Unicode, to odpowiedź nie powiedzie się, ale gdyby tak było, nie bylibyśmy w stanie rzutować na łańcuch. +1 za odpowiedź, która działa tylko wtedy, gdy działa i zgłasza wyjątek w inny sposób
Stefan Sullivan
jeśli to możliwe, nie używaj jsondo zrzucania danych, po prostu użyj, printjeśli uruchomisz Pythona. Potem ast.literal_evaldziała
Jean-François Fabre
11

Odpowiedź Mike'a Brennana jest bliska, ale nie ma powodu, aby przemierzać całą strukturę. Jeśli używasz parametru object_hook_pairs(Python 2.7+):

object_pairs_hookjest funkcją opcjonalną, która zostanie wywołana z wynikiem dowolnego literału obiektu dekodowanego za pomocą uporządkowanej listy par. Zwrócona wartość object_pairs_hookzostanie użyta zamiast dict. Ta funkcja może być wykorzystana do implementacji niestandardowych dekoderów, które opierają się na kolejności dekodowania par klucza i wartości (na przykład collections.OrderedDictzapamięta kolejność wstawiania). Jeśli object_hookjest również zdefiniowane, object_pairs_hookpierwszeństwo ma.

Za jego pomocą otrzymujesz każdy obiekt JSON, dzięki czemu możesz dekodować bez potrzeby rekurencji:

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'}}

Zauważ, że nigdy nie muszę wywoływać haka rekurencyjnie, ponieważ każdy obiekt zostanie przekazany do haka, gdy go użyjesz object_pairs_hook. Musisz dbać o listy, ale jak widać, obiekt na liście zostanie poprawnie przekonwertowany i nie musisz się powtarzać, aby tak się stało.

EDYCJA: Współpracownik zauważył, że Python2.6 nie ma object_hook_pairs. Nadal możesz tego używać w Pythonie 2.6, dokonując bardzo małej zmiany. W haku powyżej zmień:

for key, value in pairs:

do

for key, value in pairs.iteritems():

Następnie użyj object_hookzamiast 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'}}

Wykorzystanie object_pairs_hookwyników powoduje utworzenie instancji jednego mniejszego słownika dla każdego obiektu w obiekcie JSON, co może być przydatne, jeśli analizujesz ogromny dokument.

Travis Jensen
źródło
1
Jest to schludne i wydaje się bardzo bliskie zasłużenia na zielony znacznik wyboru (który Brutus, co godne podziwu, już przekazał swobodnie, gdy pojawiły się lepsze odpowiedzi). Ale ... dlaczego właściwie nie obsługiwać list poprawnie w tym deunicodify_hook, co wyświetlasz w tej odpowiedzi? W tej chwili masz implementację deunicodify_hook, która nie dokonuje iteracji po listach i deunicodify napisów i list w nich zawartych, a zatem wyniki, które wyświetlasz, nie pasują do wyników, które faktycznie wygeneruje hak. Napraw to, a ta odpowiedź będzie lepsza od mojej.
Mark Amery
Frywolne: Sugerowałbym również wykazanie funkcji za pomocą zwykłego interpretera CPython zamiast tego, którego tu używasz (który moim zdaniem to IronPython)? Interpretator CPython jest bardziej znany większości użytkowników Pythona i, moim zdaniem, ładniejszy.
Mark Amery
To nie działa dla mnie, ale jestem pewien, że to dziwactwo z tego, co robię ... Przechowuję jedną listę z większego dokumentu json do pliku. Bez względu na to, czy ładuję go z tym hakiem object_pairs_hace, czy bez niego, każdy element pojawia się w trybie Unicode. Cerować.
rsaw
1
@rsaw Dobra uwaga! Ponieważ object_pairs_hookwywoływane są tylko obiekty , jeśli tekst JSON zawiera listę ciągów na najwyższym poziomie, to rozwiązanie zakończy się niepowodzeniem. Nie ma sposobu, aby to naprawić bez wywołania jakiejś funkcji dla rzeczy zwróconej json.load; żaden z json.loadhaczyków nie gwarantuje, że poradzisz sobie z każdym sznurkiem. Myślę, że jest to na tyle duża wada, że ​​wciąż zalecam moje rozwiązanie zamiast używania haków.
Mark Amery
-1, ponieważ właśnie zdałem sobie sprawę, że Mirec Miskuf już opublikował odpowiedź polegającą na przechwytywaniu obiektów, która nie ma wad związanych z podejściem Mike'a Brennana (wielokrotnie bajty te same słowniki) ani tego (nie bajtuje list zagnieżdżonych ani list najwyższego poziomu lub ciągi). Nie jestem pewien, dlaczego jego odpowiedź zniknęła prawie bez uwagi, podczas gdy ta - która jest gorsza - szybko zdobyła głosy.
Mark Amery
9

Obawiam się, że nie ma możliwości automatycznego osiągnięcia tego celu w bibliotece simplejson.

Skaner i dekoder w simplejson są zaprojektowane do generowania tekstu Unicode. Aby to zrobić, biblioteka używa funkcji o nazwie c_scanstring(jeśli jest dostępna, dla szybkości) lub py_scanstringjeśli wersja C nie jest dostępna. scanstringFunkcja nazywa się kilka razy przez prawie każdej rutyny że simplejson ma do dekodowania strukturę, która może zawierać tekst. Będziesz musiał albo scanstringwpisać małpą wartość w simplejson.decoder, albo podklasę JSONDecoderi podać prawie całą własną implementację wszystkiego, co może zawierać tekst.

Powodem, dla którego simplejson wypisuje kod Unicode, jest jednak to, że specyfikacja json wyraźnie wspomina, że ​​„Łańcuch jest zbiorem zerowych lub więcej znaków Unicode” ... zakłada się, że obsługa Unicode jest częścią samego formatu. Implementacja Simplejsona scanstringposuwa się tak daleko, że skanuje i interpretuje zmiany znaczenia Unicode (nawet sprawdzanie błędów w przypadku zniekształconych reprezentacji wielobajtowych zestawów znaków), więc jedynym sposobem, w jaki można niezawodnie zwrócić ci wartość, jest unicode.

Jeśli masz starzejącą się bibliotekę, która tego potrzebuje str, polecam ci żmudne przeszukanie zagnieżdżonej struktury danych po parsowaniu (co, jak mówię, jest tym, co wyraźnie powiedziałeś, że chcesz uniknąć ... przepraszam), lub może zapakuj swoje biblioteki w coś w rodzaju fasada, w której można masować parametry wejściowe na bardziej szczegółowym poziomie. Drugie podejście może być łatwiejsze do zarządzania niż pierwsze, jeśli struktury danych są rzeczywiście głęboko zagnieżdżone.

Jarret Hardie
źródło
4

Jak słusznie zauważa Mark (Amery): Używanie deserializatora PyYamla na zrzutie jsona działa tylko, jeśli masz tylko ASCII. Przynajmniej po wyjęciu z pudełka.

Dwa szybkie komentarze na temat podejścia PyYaml:

  1. NIGDY nie używaj yaml.load do danych z pola. Jest to funkcja (!) Programu yaml do wykonywania dowolnego kodu ukrytego w strukturze.

  2. Państwo może zrobić to działa również za brak ASCII przez to:

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

Ale pod względem wydajności nie ma porównania z odpowiedzią Marka Amery:

Rzucając głęboko zagnieżdżone przykładowe dykty na dwie metody, otrzymuję to (z dt [j] = delta czasowa json.loads (json.dumps (m))):

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

Tak więc deserializacja, w tym pełne chodzenie po drzewie i kodowanie, w zakresie wielkości implementacji opartej na C. Uważam to za niezwykle szybkie i również bardziej wytrzymałe niż obciążenie yaml w głęboko zagnieżdżonych strukturach. I mniej podatne na błędy bezpieczeństwa, patrząc na yaml.load.

=> Chociaż doceniłbym wskaźnik do konwertera opartego tylko na C, funkcja bajtowania powinna być odpowiedzią domyślną.

Jest to szczególnie ważne, jeśli twoja struktura json pochodzi z pola zawierającego dane wprowadzone przez użytkownika. Bo wtedy prawdopodobnie trzeba chodzić w każdym razie nad strukturą - niezależnego od żądanej wewnętrznych struktur danych (unicode „kanapkę” lub ciągów bajtów tylko).

Czemu?

Normalizacja Unicode . Dla nieświadomych: weź środek przeciwbólowy i przeczytaj to .

Tak więc za pomocą rekurencji bajtowej zabijasz dwa ptaki jednym kamieniem:

  1. pobierz swoje bajty z zagnieżdżonych zrzutów Jsona
  2. znormalizować wartości wejściowe użytkownika, aby znaleźć rzeczy w pamięci.

W moich testach okazało się, że zastąpienie input.encode ('utf-8') unicodedata.normalize ('NFC', wejście) .encode ('utf-8') było nawet szybsze niż bez NFC - ale to w dużym stopniu zależy od przykładowych danych.

Czerwona pigułka
źródło
3

Gotcha jest tym simplejsoni jsonsą to dwa różne moduły, przynajmniej w sposób, w jaki radzą sobie z Unicode. Masz jsonw py 2.6+, a to daje ci wartości Unicode, podczas gdy simplejsonzwraca obiekty łańcuchowe. Po prostu spróbuj easy_install-ing simplejson w swoim środowisku i sprawdź, czy to działa. To mi zrobiło.

ducu
źródło
2

Po prostu użyj marynaty zamiast jsona do zrzutu i załadowania, tak:

    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"))

Dane wyjściowe są generowane (ciągi i liczby całkowite są obsługiwane poprawnie):

    {u'field2': 2, u'field1': u'value1'}
    {'field2': 2, 'field1': 'value1'}
Stefan Gruenwald
źródło
1
+1 za rozwiązanie, które nie wymaga dodatkowych pakietów (takich jak yaml ). Ale czasami - tak jak w moim oryginalnym przypadku - muszę mieć dane w JSON, więc piklowanie nie zawsze jest najlepszą opcją. Poza tym masz safe_loadw YAML, nie wiem, czy istnieje coś podobnego do marynaty .
Brutus
1

Więc napotkałem ten sam problem. Zgadnij, jaki był pierwszy wynik Google.

Ponieważ muszę przekazać wszystkie dane do PyGTK, ciągi Unicode też nie są dla mnie bardzo przydatne. Mam więc inną metodę konwersji rekurencyjnej. W rzeczywistości jest również potrzebny do bezpiecznej konwersji JSON - json.dump () zwolniłby kaucję za wszelkie nie-literały, takie jak obiekty Python. Nie konwertuje jednak indeksów 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
Mario
źródło
Jedynym problemem, który może się tu pojawić, jest potrzeba konwersji kluczy ze słownika w Unicode. Chociaż ta implementacja przekonwertuje wartości, zachowuje klucze Unicode. Jeśli utworzysz „newobj”, użyj newobj [str (i)] = ... i po zakończeniu przypisz obj = newobj, klucze również zostaną przekonwertowane.
Neal Stublen
Może to być ładniejsze ze zrozumieniem lub lepiej przez konwersję kluczy. Jest to również unidiomatic; zarówno mutuje obiekty w miejscu (w przypadku słowników), jak i zwraca nową wartość, co jest niezgodne z wbudowanymi metodami kolekcji Pythona, które albo mutują bieżący obiekt, albo zwracają nowy, ale nie oba.
Mark Amery
1

Miałem dykta JSON jako ciąg. Klucze i wartości były obiektami Unicode, jak w poniższym przykładzie:

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

Mógłbym użyć byteifyfunkcji sugerowanej powyżej, przekształcając ciąg znaków w dictobiekt za pomocą ast.literal_eval(myStringDict).

narko
źródło
Podany przykład nie jest przykładem JSON. {u'key':u'value'}to nie JSON.
Mark Amery
2
Doskonale wiem, że to nie JSON. W ten sposób został przeanalizowany z zewnętrznego źródła w moim skrypcie python. Gdyby to był JSON bezpośrednio jak w poniższym przykładzie, nie potrzebowałbym funkcji bajtowania oznaczonej jako rozwiązanie: {„firstName”: „John”, „lastName”: „Doe”}. Byłoby wspaniale, gdybyś przed głosowaniem przeczytał odpowiedzi. Dzięki.
narko
1

Obsługa Python2 i 3 przy użyciu hooka (od 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)

Zwroty:

 {'three': '', 'key': 'value', 'one': 'two'}
abarik
źródło
0

Jest późno na grę, ale zbudowałem ten rekurencyjny rzucający. Działa na moje potrzeby i myślę, że jest względnie kompletny. To może ci pomóc.

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

Po prostu przekaż mu obiekt JSON w taki sposób:

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

Mam go jako prywatnego członka klasy, ale możesz zmienić przeznaczenie metody według własnego uznania.

Studnie
źródło
Wystąpił problem polegający na tym, że próbuję przeanalizować JSON i przekazać wynikowe mapowanie do funkcji jako ** kwargs. Wygląda na to, że nazwy parametrów funkcji nie mogą być Unicode, więc funkcja _parseJSON jest świetna. Jeśli istnieje prostszy sposób, ktoś może dać mi znać.
Neal Stublen
1
Ten kod ma problem - wykonujesz wywołanie rekurencyjne w elemencie Listy, które zawiedzie, jeśli elementy listy same nie są słownikami.
I82 Wiele
Poza błędem opisanym przez @ I82 Wiele jest również źle nazwanych (tak naprawdę nie analizuje JSON; json.loadsnajpierw potrzebne jest wywołanie), arbitralnie próbuje konwertować ciągi znaków na int bez żadnego wyjaśnionego powodu i nie kopiuje i- wklej gotowy.
Mark Amery
0

Przepisałem _parse_json () Wellsa, aby obsługiwać przypadki, w których sam obiekt json jest tablicą (mój przypadek użycia).

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
darnmarshall
źródło
0

tutaj jest koder rekurencyjny napisany w C: https://github.com/axiros/nested_encode

Narzut wydajności dla „średnich” struktur około 10% w porównaniu do 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%

za pomocą tej struktury testowej:

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)
Czerwona pigułka
źródło
0

W Pythonie 3.6 czasami napotykam ten problem. Na przykład podczas pobierania odpowiedzi z interfejsu API REST i ładowania tekstu odpowiedzi do JSON nadal otrzymuję ciągi Unicode. Znaleziono proste rozwiązanie za pomocą json.dumps ().

response_message = json.loads(json.dumps(response.text))
print(response_message)
Yuelin
źródło
-1

Wpadłem również na ten problem i mając do czynienia z JSON, wpadłem na małą pętlę, która konwertuje klucze Unicode na ciągi. ( simplejsonw GAE nie zwraca kluczy ciągów).

obj jest obiekt zdekodowany z 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()

kwargsprzekazuję konstruktorowi aplikacji GAE (która nie lubi unicodekluczy **kwargs)

Nie tak solidne jak rozwiązanie Wellsa, ale znacznie mniejsze.

Boatcoder
źródło
-1

Mam dostosowany kod z odpowiedzią z Markiem Amery , w szczególności w celu pozbycia isinstancedla profesjonalistów z kaczki wzorcowego.

Kodowanie odbywa się ręcznie i ensure_asciijest wyłączone. Python docs json.dumpmówi o tym

Jeśli parametr sure_ascii ma wartość True (domyślnie), wszystkie znaki inne niż ASCII na wyjściu są poprzedzane znakami \ uXXXX

Uwaga: w doctest użyłem języka węgierskiego. Niektóre znaczące kodowania znaków związane z Węgrami to: cp852stosowane kodowanie IBM / OEM, np. w DOS (czasami nazywany ascii , błędnie myślę, że zależy to od ustawienia strony kodowej ), cp1250używany np. w systemie Windows (czasem określanym jako ansi , w zależności od ustawień regionalnych), a iso-8859-2czasem używany na serwerach HTTP. Tekst testowy Tüskéshátú kígyóbűvölőjest przypisany Koltai László (natywny formularz imienia) i pochodzi z wikipedii .

# 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)

Chciałbym również podkreślić jak odpowiedź z Jarret Hardie który References Spec JSON , cytując:

Łańcuch jest zbiorem zerowego lub więcej znaków Unicode

W moim przypadku użycia miałem pliki z Jsonem. Są to utf-8pliki zakodowane. ensure_asciipowoduje powstanie poprawnie ukrytych, ale niezbyt czytelnych plików json, dlatego dostosowałem odpowiedź Marka Ameryka do moich potrzeb.

Doctest nie jest szczególnie przemyślany, ale dzielę się kodem w nadziei, że komuś się przyda.

n611x007
źródło
Nie jestem pewien, czy widzę korzyści płynące z pisania kaczką tutaj? Wiemy, że kolekcje, które zostały zwrócone, json.loadsbędą listami lub dyktami, a nie jakimś typem zdefiniowanym przez użytkownika lub biblioteką, który implementuje swoje metody i metody magiczne, więc dlaczego po prostu nie isinstancesprawdzić? Czy nie jest to łatwiejsze do zrozumienia niż sprawdzanie istnienia iteritemslub iterakceptacji obiektu jako argumentu?
Mark Amery
@ MarkAmery chodzi o zrzuty, a nie ładunki. jeśli tworzysz dane do zrzucenia - w przeciwieństwie do ich ładowania - nie możesz być pewien, co to jest. Chodziło o to, aby pochodziło z dowolnego miejsca w kodzie.
n611x007
-2

Sprawdź odpowiedź na podobne pytanie, które to stwierdza

Prefiks u oznacza po prostu, że masz ciąg Unicode. Gdy naprawdę użyjesz ciągu, nie pojawi się on w twoich danych. Nie daj się wyrzucić wydrukowi.

Na przykład spróbuj tego:

print mail_accounts[0]["i"]

Nie zobaczysz cię.

kunal
źródło
Nieprawda, jeśli np. Chcesz sformatować coś zawierającego ciąg Unicode, w Py2. na przykład'{}'.format({u'x' : u'y'}) nadal obejmuje u.
Ponkadoodle