Bagaimana cara mendapatkan objek string daripada Unicode dari JSON?

276

Saya menggunakan Python 2 untuk mem-parsing JSON dari file teks yang disandikan ASCII .

Saat memuat file-file ini dengan salah satu jsonatau simplejson, semua nilai string saya dilemparkan ke objek Unicode, bukan objek string. Masalahnya adalah, saya harus menggunakan data dengan beberapa perpustakaan yang hanya menerima objek string. Saya tidak dapat mengubah perpustakaan atau memperbaruinya.

Apakah mungkin untuk mendapatkan objek string daripada objek Unicode?

Contoh

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

Memperbarui

Pertanyaan ini sudah lama ditanyakan , ketika saya terjebak dengan Python 2 . Salah satu solusi mudah dan bersih untuk hari ini adalah dengan menggunakan versi Python terbaru - yaitu Python 3 dan seterusnya.

Brutus
sumber
1
Tidak ada masalah di bawah Python3, jenis item di new_list adalahstr
GoingMyWay
1
Python 3k bukan 'versi terbaru dari Python', itu hanya cabang alternatif.
user2589273
11
Sungguh aneh melihat komentar seperti itu pada bulan Desember 2017 - Python 2 sudah usang dan tidak ada pemeliharaan yang akan terjadi setelah 1 Januari 2020, yang kurang dari 2 tahun: pythonclock.org
Zaar Hai
1
@ZaarHai BANYAK orang terjebak di Python 2 di luar kehendak mereka. Ada banyak aplikasi yang menanamkan versi Python mereka sendiri untuk otomatisasi dan skrip sehingga orang harus menggunakannya sampai pembaruan vendor (Saya melihat Anda Maya, Houdini, Nuke ..)
Geordie
1
@ Geordie, saya pasti tahu dan mengerti itu. Komentar saya adalah tentang terminologi - Python bukan "cabang alternatif", melainkan kurangnya alternatif (pun intended) bagi mereka yang terjebak dengannya.
Zaar Hai

Jawaban:

101

Solusi dengan 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

Contoh penggunaan:

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

Bagaimana cara kerjanya dan mengapa saya menggunakannya?

Fungsi Mark Amery lebih pendek dan lebih jelas daripada yang ini, jadi apa gunanya? Mengapa Anda ingin menggunakannya?

Murni untuk kinerja . Jawaban Mark menerjemahkan teks JSON sepenuhnya terlebih dahulu dengan string unicode, kemudian berulang melalui seluruh nilai yang diterjemahkan untuk mengkonversi semua string ke string byte. Ini memiliki beberapa efek yang tidak diinginkan:

  • Salinan seluruh struktur yang didekodekan akan dibuat dalam memori
  • Jika objek JSON Anda benar-benar mendalam bersarang (500 tingkat atau lebih) maka Anda akan memukul kedalaman rekursi maksimum Python

Jawaban ini mengurangi kedua masalah kinerja tersebut dengan menggunakan object_hookparameter json.loaddan json.loads. Dari dokumen :

object_hookadalah fungsi opsional yang akan dipanggil dengan hasil dari setiap objek yang diterjemahkan secara literal (a dict). Nilai pengembalian object_hook akan digunakan sebagai ganti dict. Fitur ini dapat digunakan untuk mengimplementasikan decoder kustom

Karena kamus bersarang banyak level dalam kamus lain yang dilewati object_hook saat diterjemahkan , kita dapat mengubah byte atau daftar apa pun di dalamnya pada saat itu dan menghindari perlunya rekursi mendalam nantinya.

Jawaban Markus tidak cocok untuk digunakan sebagaimana object_hookmestinya, karena berulang menjadi kamus bersarang. Kami mencegah rekursi dalam jawaban ini dengan ignore_dictsparameter to _byteify, yang diteruskan ke sana setiap saat kecuali saat object_hookmelewatinya yang baru dictuntuk byteify. The ignore_dictsbendera mengatakan _byteifyuntuk mengabaikan dicts karena mereka sudah byteified.

Akhirnya, implementasi json_load_byteifieddan json_loads_byteifiedpanggilan kami _byteify(dengan ignore_dicts=True) pada hasil yang dikembalikan dari json.loadatau json.loadsuntuk menangani kasus di mana teks JSON yang diterjemahkan tidak memiliki dictdi tingkat atas.

Mirec Miskuf
sumber
1
+1 untuk pendekatan di sini; Saya tidak benar-benar memahami ketika saya pertama kali membacanya, tetapi akhirnya mengerti ketika membaca ulang dengan mengingat jawaban Travis Jensen. Saya telah melakukan pengeditan yang cukup agresif dengan harapan memperjelas cara kerjanya dan apa keuntungannya atas jawaban saya. Gagasan inti dari kode tetap tidak tersentuh, tetapi saya telah memodifikasi hampir semua hal lainnya. Jangan ragu untuk memutar kembali edit saya jika Anda keberatan - ini jawaban Anda!
Mark Amery
Tidak masalah Mark, terima kasih banyak. Saya suka hasil edit Anda, ini jauh lebih jelas daripada aslinya. Mungkin, suatu hari, saya akan belajar memberikan jawaban yang lebih ringkas.
Mirec Miskuf
2
Ini solusi hebat; efisien dan elegan. Namun, jika Anda terjebak di ranah Python <2,7, seperti saya, Anda akan harus mengganti baris: return { byteify(key, ignore_dicts=True): _byteify(value, ignore_dicts=True) for key, value in data.iteritems() }dengan return dict((_byteify(key, ignore_dicts=True), _byteify(value, ignore_dicts=True)) for key, value in data.iteritems())untuk itu untuk bekerja.
Richard Dunn
Saya pikir Anda salah tentang masalah kedalaman rekursi. Dengan Anda, saya bisa naik ke 990: json_loads_byteified('[' * 990 + ']' * 990). Dengan 991 crash. Mark masih bekerja dengan 991: byteify(json.loads('[' * 991 + ']' * 991)). Itu crash di 992. Jadi setidaknya dalam tes ini, Mark bisa lebih dalam, bertentangan dengan apa yang Anda katakan.
Stefan Pochmann
@MarkAmery Apa pendapat Anda tentang komentar saya di atas? (Saya baru saja melihat dalam riwayat sunting bahwa sebenarnya Anda yang menambahkan klaim itu).
Stefan Pochmann
180

Meskipun ada beberapa jawaban yang baik di sini, saya akhirnya menggunakan PyYAML untuk mengurai file JSON saya, karena memberikan kunci dan nilai sebagai strstring tipe bukan unicodetipe. Karena JSON adalah bagian dari YAML berfungsi dengan baik:

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

Catatan

Beberapa hal yang perlu diperhatikan:

  • Saya mendapatkan objek string karena semua entri saya dikodekan ASCII . Jika saya akan menggunakan entri yang disandikan unicode, saya akan mendapatkannya kembali sebagai objek unicode - tidak ada konversi!

  • Anda harus (mungkin selalu) menggunakan safe_loadfungsi PyYAML ; jika Anda menggunakannya untuk memuat file JSON, Anda tidak memerlukan "kekuatan tambahan" dari loadfungsi tersebut.

  • Jika Anda menginginkan parser YAML yang memiliki lebih banyak dukungan untuk versi 1.2 dari spesifikasi (dan dengan benar mem-parsing angka yang sangat rendah ) coba Ruamel YAML : pip install ruamel.yamldan hanya import ruamel.yaml as yamlitu yang saya butuhkan dalam pengujian saya.

Konversi

Seperti yang dinyatakan, tidak ada konversi! Jika Anda tidak yakin untuk hanya berurusan dengan nilai-nilai ASCII (dan Anda tidak bisa memastikan sebagian besar waktu), lebih baik gunakan fungsi konversi :

Saya menggunakan yang dari Mark Amery beberapa kali sekarang, ini berfungsi dengan baik dan sangat mudah digunakan. Anda juga dapat menggunakan fungsi yang sama sebagai object_hookgantinya, karena dapat meningkatkan kinerja Anda pada file besar. Lihat jawaban yang sedikit lebih terlibat dari Mirec Miskuf untuk itu.

Brutus
sumber
8
Berhati-hatilah jika Anda memutuskan untuk menggunakan jawaban ini. Ini berfungsi dengan baik untuk kasus Brutus, tetapi hanya karena dia tahu bahwa datanya hanya berisi karakter yang dapat dikodekan ASCII. Jika Anda tidak memiliki jaminan itu, jawaban ini tidak akan berfungsi. Misalnya, coba jalankan yaml.load(json.dumps([u'a', u'£', u'É']))di shell Python dan amati bahwa Anda kembali ['a', u'\xa3', u'\xc9'](yang berisi unicodestring). Jika Anda tidak dapat memastikan bahwa data Anda hanya berisi karakter dari rangkaian karakter ASCII, Anda harus menggunakan pendekatan yang berbeda (saya sarankan jawaban saya sendiri).
Mark Amery
1
YAML juga menggunakan [u'a', u'b']hati-hati.
Carlos Calla
1
Ini bagus, tetapi tidak bekerja dengan angka rendah .. lihat di sini: stackoverflow.com/questions/30458977/…
Oren
@ Oren: Ini bukan kesalahan dalam spesifikasi YAML tetapi dalam parser PyYAML. The YAML parser dari ruamel karya.
Brutus
Saya ingin memiliki ouput seperti ["a", "b"] tidak seperti ['a', 'b'] @Brutus
user60679
141

Tidak ada opsi bawaan untuk membuat fungsi modul json mengembalikan string byte alih-alih string unicode. Namun, fungsi rekursif singkat dan sederhana ini akan mengubah objek JSON yang didekode dari menggunakan string unicode menjadi string byte UTF-8-encoded:

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

Sebut saja ini pada output yang Anda dapatkan dari json.loadatau json.loadspanggilan.

Beberapa catatan:

  • Untuk mendukung Python 2.6 atau sebelumnya, ganti return {byteify(key): byteify(value) for key, value in input.iteritems()}dengan return dict([(byteify(key), byteify(value)) for key, value in input.iteritems()]), karena pemahaman kamus tidak didukung hingga Python 2.7.
  • Karena jawaban ini berulang melalui seluruh objek yang diterjemahkan, itu memiliki beberapa karakteristik kinerja yang tidak diinginkan yang dapat dihindari dengan sangat hati-hati menggunakan parameter object_hookatau object_pairs_hook. Sejauh ini jawaban Mirec Miskuf adalah satu-satunya yang berhasil melakukan ini dengan benar, meskipun sebagai konsekuensinya, secara signifikan lebih rumit daripada pendekatan saya.
Mark Amery
sumber
1
Saya suka ini - ini bukan sebuah mengabaikan - itu mengakui bahwa ketika orang mengatakan "string" dan "ascii" mereka kebanyakan secara naif berarti mereka menginginkan byte, bukan karakter unicode teoretis. (dan bukan ascii karena mereka masih menginginkan tanda-tanda pound di ujung lain)
Danny Staple
Saya suka ini, kerjanya hampir sama dengan printer cantik saya bekerja, karena saya tahu bahwa json tidak membuat tuple, Anda harus menambahkan pengecualian untuk tuple juga.
y.petremann
Ini sangat tidak efisien, mengharuskan Anda untuk melintasi node secara rekursif yang mungkin tidak perlu. Modul json memberi Anda kait untuk melakukan ini jauh lebih efisien. Jawaban di bawah menggunakan object_hooksebenarnya jauh lebih buruk daripada yang ini, tetapi, menggunakan object_pairs_hook, Anda dapat menemukan metode yang cukup efisien yang tidak memerlukan rekursi atau meninjau kembali node yang tidak mengandung string.
Travis Jensen
1
@TravisJensen Menarik. The object_pairs_hookMetode ini mungkin sangat sedikit lebih sulit untuk memahami dari satu ini (Anda perlu memahami cara kerja parameter dan mengapa daftar dan dicts membutuhkan penanganan yang berbeda), dan manfaat kinerja akan tidak peduli untuk kebanyakan orang ... tapi aku harapkan itu ada, terutama bagi siapa pun yang berurusan dengan objek JSON bersarang sangat luar biasa.
Mark Amery
plus1 Ini adalah jawaban yang paling ringkas; selain itu PyYAML sulit dipasang. Satu-satunya hal yang lebih baik adalah dengan entah bagaimana melakukan streaming mikro konversi sehingga tidak menggunakan memori 4X.
personal_cloud
74

Anda dapat menggunakan object_hookparameter untuk json.loadslewat dalam konverter. Anda tidak perlu melakukan konversi setelah faktanya. The jsonmodul akan selalu melewati object_hookdicts saja, dan secara rekursif akan lulus dalam dicts bersarang, sehingga Anda tidak perlu recurse ke dicts bersarang sendiri. Saya tidak berpikir saya akan mengubah string unicode ke angka seperti yang ditunjukkan Wells. Jika itu adalah string unicode, itu dikutip sebagai string dalam file JSON, jadi itu seharusnya string (atau file tersebut buruk).

Juga, saya akan mencoba untuk menghindari melakukan sesuatu seperti str(val)pada suatu unicodeobjek. Anda harus menggunakan value.encode(encoding)dengan penyandian yang valid, tergantung pada apa yang diharapkan oleh lib eksternal Anda.

Jadi, misalnya:

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
sumber
3
Ini bagus jika objek di dalam sadalah JSON Object(kumpulan kunci unordered: nilai berpasangan dengan karakter ':' yang memisahkan kunci dan nilainya, dipisahkan dengan koma dan tertutup dalam kurung kurawal), tetapi tidak jika itu, katakanlah, a JSON Array. Jadi jika diberikan Arrayseperti JSON ["a", "b"], hasilnya akan tetap [u'a', u'b']. Tak satu pun dari parameter hook-type kustomisasi lain yang tersedia saat ini untuk json.loads()dapat melakukan pekerjaan dengan baik.
martineau
2
Karena, seperti yang Anda sebutkan, jsonmodul akan secara berulang melewati nested dicts, tidak perlu memeriksa mereka di dua fungsi - jadi dua elifklausa yang memeriksa mereka harus dihapus.
martineau
1
Perhatikan bahwa nama fungsi awal dengan garis bawah memiliki arti khusus untuk pernyataan impor. Jika Anda meletakkan fungsi-fungsi ini dalam file yang disebut Utility.py dan di file lain lakukan from Utility import *, fungsi tidak akan terlihat karena garis bawah itu.
M Katz
1
Ini ide yang sangat buruk. object_hookdipanggil untuk setiap objek json yang diuraikan, jadi jika Anda kembali ke apa yang diberikan kepada Anda, Anda mengulangi "byteifikasi" hal-hal yang telah Anda "byteified". Kinerja akan tumbuh secara geometris dengan ukuran objek. Saya telah menyertakan jawaban di sini yang menggunakan object_pairs_hookdan tidak menderita masalah itu.
Travis Jensen
38

Itu karena json tidak memiliki perbedaan antara objek string dan objek unicode. Mereka semua adalah string dalam javascript.

Saya pikir JSON benar untuk mengembalikan objek unicode . Sebenarnya, saya tidak akan menerima apa pun yang kurang, karena string javascript sebenarnya adalah unicodeobjek (yaitu string JSON (javascript) dapat menyimpan segala jenis karakter unicode) sehingga masuk akal untuk membuat unicodeobjek saat menerjemahkan string dari JSON. String biasa tidak cocok karena perpustakaan harus menebak pengodean yang Anda inginkan.

Lebih baik menggunakan unicodeobjek string di mana-mana. Jadi pilihan terbaik Anda adalah memperbarui perpustakaan Anda sehingga mereka dapat menangani objek unicode.

Tetapi jika Anda benar-benar ingin bytestrings, cukup enkode hasilnya ke enkode pilihan Anda:

>>> nl = json.loads(js)
>>> nl
[u'a', u'b']
>>> nl = [s.encode('utf-8') for s in nl]
>>> nl
['a', 'b']
nosklo
sumber
Terima kasih nosklo, itulah yang telah saya lakukan pertama kali. Tapi seperti yang saya katakan, data asli yang saya gunakan cukup bersarang dan sebagainya, jadi ini memperkenalkan beberapa overhead. Saya masih mencari solusi otomatis ... Setidaknya ada satu laporan bug di luar sana di mana orang mengeluh tentang simplejson mengembalikan objek string bukan unicode.
Brutus
1
@Brutus: Saya pikir json benar untuk mengembalikan objek unicode. Sebenarnya, saya tidak akan menerima apa pun yang kurang, karena string javascript sebenarnya adalah objek unicode. Maksud saya adalah string json (javascript) dapat menyimpan segala jenis karakter unicode, jadi masuk akal untuk membuat objek unicode saat menerjemahkan dari json. Anda benar-benar harus memperbaiki perpustakaan Anda sebagai gantinya.
nosklo
16

Ada solusi yang mudah.

TL; DR - Gunakan ast.literal_eval()sebagai ganti json.loads(). Keduanya astdan jsonberada di perpustakaan standar.

Meskipun bukan jawaban 'sempurna', ada satu yang cukup jauh jika rencana Anda mengabaikan Unicode sama sekali. Dalam 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))

memberi:

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

Ini menjadi lebih berbulu ketika beberapa objek benar-benar string Unicode. Jawaban lengkap menjadi cepat berbulu.

Charles Merriam
sumber
11
Lebih baik pastikan json Anda tidak berisi null, trueatau falsenilai-nilai, karena mereka tidak berlaku di python dan akan menyebabkan literal_eval()gagal.
ʇsәɹoɈ
3
@ әɹsәɹoɈ Lebih baik berharap JSON Anda tidak mengandung solidus yang lolos ( \/) di dalam string, atau urutan escape unicode (seperti "\u0061", yang merupakan cara penulisan lain "a"). Sintaks literal Python tidak kompatibel dengan JSON dalam beberapa hal, dan saya tidak akan mempercayai jawaban ini untuk skrip apa pun yang tidak akan saya buang.
Mark Amery
Orang-orang benar bahwa jika string benar-benar unicode maka jawaban ini gagal, tetapi jika itu masalahnya kita tidak akan dapat dilemparkan ke string. Memberi +1 untuk jawaban yang hanya berfungsi saat bekerja dan melempar pengecualian
Stefan Sullivan
jika memungkinkan jangan gunakan jsonuntuk membuang data, gunakan saja printjika menjalankan python. Kemudian ast.literal_evalbekerja
Jean-François Fabre
11

Jawaban Mike Brennan dekat, tetapi tidak ada alasan untuk menelusuri ulang seluruh struktur. Jika Anda menggunakan object_hook_pairsparameter (Python 2.7+):

object_pairs_hookadalah fungsi opsional yang akan dipanggil dengan hasil objek apa pun yang diterjemahkan dengan daftar pasangan terurut. Nilai pengembalian object_pairs_hookakan digunakan sebagai ganti dict. Fitur ini dapat digunakan untuk mengimplementasikan decoder kustom yang bergantung pada urutan pasangan kunci dan nilai yang diterjemahkan (misalnya, collections.OrderedDictakan mengingat urutan penyisipan). Jika object_hookjuga ditentukan, object_pairs_hookprioritas akan diambil.

Dengan itu, Anda mendapatkan setiap objek JSON yang diserahkan kepada Anda, sehingga Anda dapat melakukan decoding tanpa perlu rekursi:

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

Perhatikan bahwa saya tidak pernah harus memanggil kait secara berulang karena setiap objek akan diserahkan ke kait ketika Anda menggunakan object_pairs_hook. Anda memang harus memperhatikan daftar, tetapi seperti yang Anda lihat, objek dalam daftar akan dikonversi dengan benar, dan Anda tidak perlu berulang untuk mewujudkannya.

EDIT: Seorang rekan kerja menunjukkan bahwa Python2.6 tidak punya object_hook_pairs . Anda masih bisa menggunakan ini akan Python2.6 dengan membuat perubahan yang sangat kecil. Pada kait di atas, ubah:

for key, value in pairs:

untuk

for key, value in pairs.iteritems():

Kemudian gunakan object_hooksebagai ganti 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'}}

Menggunakan object_pairs_hookhasil dalam satu kamus kurang sedang dipakai untuk setiap objek di objek JSON, yang, jika Anda parsing dokumen besar, mungkin bernilai sementara.

Travis Jensen
sumber
1
Ini rapi dan tampaknya sangat dekat dengan tanda centang hijau (yang Brutus telah, mengagumkannya, sudah lulus secara bebas karena jawaban yang lebih baik telah masuk). Tapi ... mengapa tidak benar-benar menangani daftar dengan benar seperti deunicodify_hookyang Anda tunjukkan dalam jawaban ini? Saat ini, Anda memiliki implementasi deunicodify_hookyang tidak mengulangi daftar dan membatalkan tanda string dan daftar di dalamnya, dan dengan demikian output yang Anda tampilkan tidak cocok dengan output yang benar-benar dihasilkan oleh hook Anda. Perbaiki itu, dan jawaban ini akan lebih unggul dari saya.
Mark Amery
Frivolous: Saya juga menyarankan menunjukkan fungsi dengan juru bahasa CPython biasa daripada yang Anda gunakan di sini (yang saya pikir adalah IronPython)? Penerjemah CPython lebih akrab bagi sebagian besar pengguna Python dan, menurut pendapat saya, lebih cantik.
Mark Amery
Ini tidak berfungsi untuk saya, tetapi saya yakin ini adalah kekhasan dari apa yang saya lakukan ... Saya menyimpan satu daftar dari dokumen json yang lebih besar ke sebuah file. Apakah saya memuatnya dengan atau tanpa object_pairs_hook ini, setiap item muncul unicode. Menisik.
rsaw
1
@ Wah, Poin bagus! Karena object_pairs_hookhanya dipanggil untuk objek , jika teks JSON Anda memiliki daftar string di tingkat atas, solusi ini akan gagal. Tidak ada cara untuk memperbaikinya tanpa memanggil beberapa fungsi dari benda yang dikembalikan json.load; tidak ada json.loadkait yang dapat menjamin Anda dapat menangani setiap string. Saya pikir ini adalah kesalahan yang cukup besar bagi saya untuk terus merekomendasikan solusi saya menggunakan kait.
Mark Amery
-1 karena saya baru menyadari bahwa Mirec Miskuf sudah memposting jawaban objek-kait yang tidak memiliki kelemahan dari pendekatan Mike Brennan (meng-bytealkan kamus yang sama beberapa kali) atau yang satu ini (gagal men-byteify daftar bersarang atau daftar tingkat atas) atau string). Saya tidak yakin mengapa jawabannya sudah hampir habis tanpa perhatian sementara yang ini - yang lebih rendah - dengan cepat mendapatkan suara.
Mark Amery
9

Saya khawatir tidak ada cara untuk mencapai ini secara otomatis dalam perpustakaan simplejson.

Pemindai dan dekoder di simplejson dirancang untuk menghasilkan teks unicode. Untuk melakukan ini, perpustakaan menggunakan fungsi yang disebut c_scanstring(jika tersedia, untuk kecepatan), atau py_scanstringjika versi C tidak tersedia. The scanstringfungsi disebut beberapa kali oleh hampir setiap rutinitas yang simplejson memiliki untuk decoding struktur yang mungkin berisi teks. Anda harus melakukan monkeypatch scanstringnilai dalam simplejson.decoder, atau subclass JSONDecoderdan memberikan cukup banyak seluruh implementasi Anda sendiri dari apa pun yang mungkin berisi teks.

Alasan bahwa simplejson mengeluarkan unicode, adalah, karena spesifikasi json menyebutkan bahwa "string adalah kumpulan dari nol atau lebih karakter Unicode" ... dukungan untuk unicode dianggap sebagai bagian dari format itu sendiri. Implementasi Simplejson scanstringsejauh ini untuk memindai dan menafsirkan unicode escapes (bahkan pengecekan kesalahan untuk representasi charset multi-byte yang salah), sehingga satu-satunya cara andal dapat mengembalikan nilai kepada Anda adalah sebagai unicode.

Jika Anda memiliki perpustakaan lama yang membutuhkan str, saya sarankan Anda dengan susah payah mencari struktur data bersarang setelah penguraian (yang saya akui adalah apa yang secara eksplisit Anda katakan ingin Anda hindari ... maaf), atau mungkin bungkus perpustakaan Anda dalam semacam fasad tempat Anda dapat memijat parameter input pada tingkat yang lebih terperinci. Pendekatan kedua mungkin lebih mudah dikelola daripada yang pertama jika struktur data Anda benar-benar bersarang.

Jarret Hardie
sumber
4

Sebagai Mark (Amery) dengan benar mencatat: Menggunakan deserializer PyYaml pada json dump hanya berfungsi jika Anda memiliki ASCII saja. Setidaknya di luar kotak.

Dua komentar cepat tentang pendekatan PyYaml:

  1. JANGAN PERNAH menggunakan yaml.load data dari lapangan. Ini adalah fitur (!) Dari yaml untuk mengeksekusi kode arbitrer yang tersembunyi dalam struktur.

  2. Anda dapat membuatnya berfungsi juga untuk non ASCII melalui ini:

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

Namun kinerja tidak ada bedanya dengan jawaban Mark Amery:

Melemparkan beberapa dict sampel yang sangat bersarang ke dua metode, saya mendapatkan ini (dengan dt [j] = waktu delta json.loads (json.dumps (m))):

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

Jadi deserialisasi termasuk sepenuhnya berjalan pohon dan pengkodean, baik dalam urutan besarnya implementasi berbasis json C. Saya menemukan ini sangat cepat dan juga lebih kuat daripada beban yaml di struktur yang sangat bersarang. Dan rawan kesalahan keamanan, melihat yaml.load.

=> Sementara saya akan menghargai pointer ke konverter hanya berbasis C fungsi byteify harus menjadi jawaban default.

Ini berlaku terutama jika struktur json Anda berasal dari bidang, yang berisi input pengguna. Karena itu, Anda mungkin harus tetap berjalan di atas struktur Anda - independen pada struktur data internal yang Anda inginkan ('unicode sandwich' atau string byte saja).

Mengapa?

Normalisasi Unicode . Untuk yang tidak sadar: Ambil obat penghilang rasa sakit dan baca ini .

Jadi menggunakan rekursi byteify Anda membunuh dua burung dengan satu batu:

  1. dapatkan bytestrings Anda dari nested json dumps
  2. dapatkan nilai input pengguna dinormalisasi, sehingga Anda menemukan barang-barang di penyimpanan Anda.

Dalam tes saya ternyata mengganti input.encode ('utf-8') dengan unicodedata.normalisasi ('NFC', input) .encode ('utf-8') bahkan lebih cepat daripada tanpa NFC - tetapi itu sangat tergantung pada data sampel saya kira.

Pil Merah
sumber
3

Gotcha adalah itu simplejsondan jsondua modul yang berbeda, setidaknya dalam cara mereka menangani unicode. Anda memiliki jsondi py 2.6+, dan ini memberi Anda nilai unicode, sedangkan simplejsonmengembalikan objek string. Coba saja easy_install-ing simplejson di lingkungan Anda dan lihat apakah itu berhasil. Itu untuk saya.

ducu
sumber
2

Cukup gunakan acar daripada json untuk dump dan memuat, seperti:

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

Output yang dihasilkannya adalah (string dan integer ditangani dengan benar):

    {u'field2': 2, u'field1': u'value1'}
    {'field2': 2, 'field1': 'value1'}
Stefan Gruenwald
sumber
1
+1 untuk solusi yang tidak memerlukan paket tambahan (seperti yaml ). Tapi kadang-kadang - seperti dalam kasus asli saya - saya perlu memiliki data di JSON, jadi acar tidak selalu merupakan pilihan terbaik. Selain itu, Anda memiliki safe_loaddi YAML, saya tidak tahu apakah ada yang serupa untuk acar .
Brutus
1

Jadi, saya mengalami masalah yang sama. Coba tebak apa hasil Google pertama.

Karena saya harus meneruskan semua data ke PyGTK, string unicode juga tidak berguna bagi saya. Jadi saya punya metode konversi rekursif lain. Ini sebenarnya juga diperlukan untuk konversi JSON typesafe - json.dump () akan menjamin pada non-literal, seperti objek Python. Tidak mengonversi indeks 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
sumber
Satu-satunya masalah yang mungkin muncul di sini adalah jika Anda memerlukan kunci dalam kamus yang dikonversi dari unicode. Meskipun implementasi ini akan mengonversi nilai, ia mempertahankan kunci unicode. Jika Anda membuat 'newobj', gunakan newobj [str (i)] = ..., dan tetapkan obj = newobj ketika Anda selesai, kuncinya juga akan dikonversi.
Neal Stublen
Ini bisa lebih cantik dengan pemahaman atau lebih baik dengan mengonversi kunci. Itu juga tidak otomatis; keduanya bermutasi objek di tempat (dalam kasus kamus) dan mengembalikan nilai baru, yang tidak konsisten dengan metode pengumpulan built-in Python yang baik bermutasi objek saat ini atau mengembalikan yang baru, tetapi tidak keduanya.
Mark Amery
1

Saya memiliki dict JSON sebagai string. Kunci dan nilai adalah objek unicode seperti pada contoh berikut:

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

Saya bisa menggunakan byteifyfungsi yang disarankan di atas dengan mengubah string ke dictobjek menggunakan ast.literal_eval(myStringDict).

narko
sumber
Contoh yang Anda berikan bukan contoh JSON. {u'key':u'value'}bukan JSON.
Mark Amery
2
Saya sangat tahu itu bukan JSON. Begitulah cara diuraikan dari sumber eksternal dalam skrip python saya. Jika JSON langsung seperti dalam contoh berikut, saya tidak akan memerlukan fungsi byteify yang ditandai sebagai solusinya: {"firstName": "John", "lastName": "Doe"}. Akan sangat bagus jika sebelum memilih Anda membaca jawabannya. Terima kasih.
narko
1

Mendukung Python2 & 3 menggunakan kait (dari 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)

Pengembalian:

 {'three': '', 'key': 'value', 'one': 'two'}
abarik
sumber
0

Ini sudah terlambat dari permainan, tetapi saya membangun kastor rekursif ini. Ini berfungsi untuk kebutuhan saya dan saya pikir itu relatif lengkap. Ini dapat membantu Anda.

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

Cukup berikan objek JSON seperti ini:

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

Saya memilikinya sebagai anggota pribadi kelas, tetapi Anda dapat menggunakan kembali metode yang Anda inginkan.

Sumur
sumber
Saya mengalami masalah di mana saya mencoba mengurai JSON dan meneruskan pemetaan yang dihasilkan ke fungsi sebagai ** kwargs. Sepertinya nama parameter fungsi tidak bisa unicode, jadi fungsi _parseJSON Anda hebat. Jika ada cara yang lebih mudah, seseorang dapat memberi tahu saya.
Neal Stublen
1
Kode ini memiliki masalah - Anda membuat panggilan rekursif di bagian Daftar, yang akan gagal jika elemen daftar itu sendiri bukan kamus.
I82Much
Selain bug yang dijelaskan oleh @ I82Much, ini juga bernama buruk (sebenarnya tidak mem-parsing JSON; json.loadspanggilan diperlukan terlebih dahulu), secara sewenang-wenang mencoba mengubah string menjadi int tanpa alasan yang jelas, dan tidak menyalin-dan- tempel siap.
Mark Amery
0

Saya menulis ulang Wells _parse_json () untuk menangani kasus-kasus di mana objek json itu sendiri adalah array (kasus penggunaan saya).

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
sumber
0

di sini adalah encoder rekursif yang ditulis dalam C: https://github.com/axiros/nested_encode

Overhead kinerja untuk struktur "rata-rata" sekitar 10% dibandingkan dengan 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%

menggunakan struktur tes ini:

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)
Pil Merah
sumber
0

Dengan Python 3.6, terkadang saya masih mengalami masalah ini. Misalnya, ketika mendapatkan respons dari REST API dan memuat teks respons ke JSON, saya masih mendapatkan string unicode. Menemukan solusi sederhana menggunakan json.dumps ().

response_message = json.loads(json.dumps(response.text))
print(response_message)
Yuelin
sumber
-1

Saya mengalami masalah ini juga, dan karena harus berurusan dengan JSON, saya membuat loop kecil yang mengubah kunci unicode menjadi string. ( simplejsonpada GAE tidak mengembalikan kunci string.)

obj adalah objek yang diterjemahkan dari 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()

kwargsadalah apa yang saya sampaikan ke konstruktor aplikasi GAE (yang tidak suka unicodekunci **kwargs)

Tidak sekuat solusi dari Wells, tetapi jauh lebih kecil.

pembuat perahu
sumber
-1

Saya sudah diadaptasi kode dari jawaban dari Mark Amery , terutama untuk menyingkirkanisinstance untuk pro bebek-mengetik.

Pengkodean dilakukan secara manual dan ensure_asciidinonaktifkan. Python docs for json.dumpmengatakan itu

Jika sure_ascii Benar (default), semua karakter non-ASCII dalam output dikeluarkan dengan urutan \ uXXXX

Penafian: dalam dokumen saya menggunakan bahasa Hongaria. Beberapa pengkodean karakter terkait Hongaria yang terkenal adalah: pengkodean cp852IBM / OEM yang digunakan misalnya. di DOS (kadang-kadang disebut ascii , salah saya pikir, itu tergantung pada pengaturan codepage ), cp1250misalnya digunakan. di Windows (kadang-kadang disebut sebagai ansi , tergantung pada pengaturan lokal), dan iso-8859-2, kadang-kadang digunakan pada server http. Teks tes Tüskéshátú kígyóbűvölődikaitkan dengan Koltai László (bentuk nama pribadi asli) dan dari 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)

Saya juga ingin menyoroti jawaban dari Jarret Hardie yang referensi yang JSON spesifikasi , mengutip:

String adalah kumpulan nol atau lebih karakter Unicode

Dalam kasus penggunaan saya, saya punya file dengan json. Mereka adalah utf-8file yang disandikan. ensure_asciihasil dalam file json yang lolos dengan benar tetapi tidak terlalu mudah dibaca, itulah sebabnya saya mengadaptasi jawaban Mark Amery agar sesuai dengan kebutuhan saya.

Doctest tidak terlalu bijaksana tetapi saya membagikan kode dengan harapan akan bermanfaat bagi seseorang.

n611x007
sumber
Saya tidak yakin saya melihat manfaat menggunakan mengetik bebek di sini? Kita tahu bahwa koleksi yang dikembalikan dari json.loadsakan berupa daftar atau dikte, bukan tipe yang ditentukan pengguna atau yang ditentukan perpustakaan yang mengimplementasikan metode dan metode sihir mereka, jadi mengapa tidak hanya melakukan isinstancepemeriksaan? Bukankah itu lebih mudah dipahami daripada memeriksa keberadaan iteritemsatau apakah iterakan menerima objek sebagai argumen?
Mark Amery
@MarkAmery ini tentang kesedihan, bukan beban. jika Anda membuat data untuk dibuang - sebagai lawan memuatnya - Anda tidak dapat memastikan apa itu. idenya adalah membiarkannya datang dari mana saja dalam kode.
n611x007
-2

Lihat jawaban ini untuk pertanyaan serupa seperti ini yang menyatakan itu

Awalan u- artinya Anda memiliki string Unicode. Ketika Anda benar-benar menggunakan string, itu tidak akan muncul di data Anda. Jangan terlempar oleh hasil cetak.

Misalnya, coba ini:

print mail_accounts[0]["i"]

Anda tidak akan melihat Anda.

kunal
sumber
Tidak benar jika misalnya Anda ingin memformat sesuatu yang berisi string unicode, di Py2. misalnya '{}'.format({u'x' : u'y'})masih termasuk u.
Ponkadoodle