JSON'dan Unicode yerine dize nesneleri nasıl alınır?


276

ASCII kodlu metin dosyalarından JSON ayrıştırmak için Python 2 kullanıyorum .

Bu dosyaları jsonveya ile yüklerken simplejson, tüm dize değerlerim dize nesneleri yerine Unicode nesnelerine dönüştürülür. Sorun, sadece dize nesneleri kabul bazı kütüphaneler ile veri kullanmak zorunda. Ben kütüphaneler değiştiremezsiniz ne de güncelleyin.

Unicode olanlar yerine string nesneleri almak mümkün müdür?

Misal

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

Güncelleme

Bu soru uzun zaman önce Python 2 ile sıkışıp kaldığımda soruldu . Bugün için kolay ve temiz bir çözüm, Python'un yeni bir sürümünü kullanmaktır - yani Python 3 ve ileri.


1
Python3 altında herhangi bir sorun yoktur, new_list'deki öğelerin türüstr
GoingMyWay

1
Python 3k 'Python'un son sürümü değil, sadece alternatif bir dal.
user2589273

11
Aralık 2017'de böyle bir yorum görmek garip - Python 2 kullanımdan kaldırıldı ve 1 Ocak 2020'den sonra bakım yapılmayacak, bu da 2 yıldan az: pythonclock.org
Hai

1
@ZaarHai Çok sayıda insan Python 2'de kendi isteklerine karşı sıkışmış durumda. Otomasyon ve komut dosyası oluşturma için kendi Python sürümlerini gömen birçok uygulama var, bu yüzden insanlar satıcı güncellemelerine kadar kullanmak zorundalar (Sana bakıyorum Maya, Houdini, Nuke ..)
Geordie

1
@Geordie Bunu kesinlikle biliyorum ve anlıyorum. Benim yorumum terminoloji hakkındaydı - Python bir "alternatif dal" değil, onunla sıkışmış olanlar için talihsiz bir alternatif (pun amaçlı) eksikliği.
Zaar Hai

Yanıtlar:


101

İle bir çözüm 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

Örnek kullanım:

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

Bu nasıl çalışır ve neden kullanayım?

Mark Amery'nin işlevi bunlardan daha kısa ve açıktır, peki bunların anlamı nedir? Neden kullanmak istersiniz?

Tamamen performans için . Mark'ın yanıtı, önce unicode dizelerle JSON metninin kodunu tamamen çözer, ardından tüm dizeleri bayt dizelerine dönüştürmek için kodu çözülen değerin tamamı boyunca geri çekilir. Bunun birkaç istenmeyen etkisi vardır:

  • Çözülmüş tüm yapının bir kopyası bellekte oluşturulur
  • JSON nesneniz gerçekten derinden iç içe yerleştirilmişse (500 düzey veya daha fazla), Python'un maksimum yineleme derinliğini vurursunuz

Bu cevap kullanarak bu performans sorunları her iki azaltan object_hookbir parametreyi json.loadve json.loads. Gönderen docs :

object_hookkodu çözülmüş herhangi bir nesnenin sonucu ile çağrılacak isteğe bağlı bir işlevdir (a dict). Object_hook'un dönüş değeri. Yerine kullanılacaktır dict. Bu özellik, özel kod çözücüleri uygulamak için kullanılabilir

Kodları object_hook çözüldükçe diğer sözlüklerin derinliklerine birçok düzeyde yuvalanmış sözlükler geçtiği için , bu noktada içindeki tüm dizeleri veya listeleri bayt edebilir ve daha sonra derin özyineleme ihtiyacından kaçınabiliriz.

Mark'ın cevabı olduğu gibi kullanmak için uygun değildir object_hook, çünkü iç içe sözlüklere geri döner. Biz bu cevap o özyinelemeye önlemek ignore_dictsiçin parametre _byteifyher zaman kendisine geçirilen alır, ancak ne zaman object_hookyeni bir geçer dictbyteify için. ignore_dictsBayrak söyler _byteifygörmezden dictzaten byteified zamandan beri s.

Son olarak, çözülen JSON metninin en üst düzeyde olmadığı bir durumu ele almak veya işlemek için döndürülen sonuca ilişkin uygulama json_load_byteifiedve json_loads_byteifiedçağrı _byteify(ile ignore_dicts=True) .json.loadjson.loadsdict


1
Buradaki yaklaşım için +1; İlk okuduğumda gerçekten anlamadım, ama sonunda Travis Jensen'in cevabı ışığında yeniden okurken anladım. Nasıl çalıştığını ve cevabım üzerindeki avantajlarının ne olduğunu açıklamak umuduyla oldukça agresif bir düzenleme yaptım. Kodun ana fikri dokunulmadan kalır, ancak hemen hemen her şeyi değiştirdim. Buna itiraz ederseniz düzenlememi geri almaktan çekinmeyin - bu sizin cevabınız!
Mark Amery

Sorun değil Mark, çok teşekkürler. Düzenlemenizi beğendim, orijinalimden çok daha açıklayıcı. Belki bir gün daha kısa cevaplar vermeyi öğrenirim.
Mirec Miskuf

2
Bu harika bir çözüm; verimli ve zarif. : Eğer Python <2.7 dünyasında takılıp benim gibi Ancak, satır değiştirmeniz gerekir return { byteify(key, ignore_dicts=True): _byteify(value, ignore_dicts=True) for key, value in data.iteritems() }ile return dict((_byteify(key, ignore_dicts=True), _byteify(value, ignore_dicts=True)) for key, value in data.iteritems())bunun için işe.
Richard Dunn

Bence özyineleme derinliği konusunda yanlışsın. Sizinki ile, ben 990 kadar gidebilir: json_loads_byteified('[' * 990 + ']' * 990). 991 ile çöküyor. Mark hala 991: ile çalışıyor byteify(json.loads('[' * 991 + ']' * 991)). 992'de çöküyor. Yani en azından bu testte, Mark'ın söylediklerinizin aksine daha derine inebilir.
Stefan Pochmann

@MarkAmery Yukarıdaki yorumum hakkında ne düşünüyorsun? (Düzenleme geçmişinde bu iddiayı ekleyen kişinin siz olduğunu gördüm).
Stefan Pochmann

180

Burada bazı iyi cevaplar olsa da, JSY dosyalarımı ayrıştırmak için PyYAML kullanarak sonuçlandım , çünkü anahtarları ve değerleri yazım yerine yazımstr dizeleri olarak veriyor unicode. JSON, YAML'nin bir alt kümesi olduğundan güzel çalışır:

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

notlar

Dikkat edilmesi gereken bazı şeyler:

  • Ben olsun dize nesneleri tüm girişler çünkü ASCII kodlanmış . Unicode kodlu girişleri kullanırsanız, onları unicode nesneleri olarak geri alırım - dönüşüm yok!

  • PyYAML safe_loadişlevini (muhtemelen her zaman) kullanmalısınız ; JSON dosyalarını yüklemek için kullanırsanız, loadyine de işlevin "ek gücüne" ihtiyacınız yoktur .

  • Spesifikasyonun 1.2 sürümü için daha fazla desteğe sahip bir YAML ayrıştırıcısı istiyorsanız (ve çok düşük sayıları doğru bir şekilde ayrıştırır ) Ruamel YAML'yi deneyin : pip install ruamel.yamlve import ruamel.yaml as yamltestlerimde ihtiyacım olan tek şey buydu .

Dönüştürmek

Belirtildiği gibi, dönüşüm yok! Yalnızca ASCII değerleri ile ilgileneceğinizden emin değilseniz (ve çoğu zaman emin olamıyorsanız), bir dönüşüm işlevi kullanın :

Ben bir tane kullanılan Mark Amery o inşaat büyük ve kullanımı çok kolay olan, birkaç kez şimdi. object_hookBüyük dosyalarda performans artışı sağlayabileceğinden, bunun yerine benzer bir işlevi de kullanabilirsiniz . Bunun için Mirec Miskuf'un biraz daha ilgili cevabına bakın .


8
Bu yanıtı kullanmaya karar verirseniz biraz dikkatli olun. Brutus'un davası için mükemmel çalışıyor, ancak sadece verilerinin sadece ASCII ile kodlanabilir karakterler içerdiğini bildiği için. Bu garantiniz yoksa, bu cevap işe yaramaz. Örneğin yaml.load(json.dumps([u'a', u'£', u'É'])), Python kabuğunda yürütmeyi deneyin ve geri döndüğünüzü ['a', u'\xa3', u'\xc9']( unicodedizeleri içerir ) gözlemleyin . Verilerinizin yalnızca ASCII karakter kümesindeki karakterleri içerdiğinden emin değilseniz, bunun yerine farklı bir yaklaşım kullanmalısınız (kendi cevabımı öneririm).
Mark Amery

1
YAML aynı zamanda [u'a', u'b']dikkatli olmayı da kullanır .
Carlos Calla

1
Bu güzel, ama düşük sayılarla çalışmıyor .. buraya bakın: stackoverflow.com/questions/30458977/…
Ören

@Oren: Bu YAML spesifikasyonunda değil, PyYAML ayrıştırıcısında bir hata . Ruamel gelen YAML ayrıştırıcı eserler.
Brutus

Çıkışı ['a', “b”] gibi yapmak istemiyorum ['a', 'b'] gibi değil
user60679

141

Json modülü işlevlerinin unicode dizeleri yerine bayt dizeleri döndürmesini sağlamak için yerleşik bir seçenek yoktur. Ancak, bu kısa ve basit özyinelemeli işlev, kodu çözülmüş tüm JSON nesnelerini unicode dizeleri kullanmaktan UTF-8 kodlu bayt dizelerine dönüştürür:

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

Bunu, bir json.loadveya json.loadsçağrıdan aldığınız çıkışta arayın.

Birkaç not:

  • Python 2.6 destekleyen veya önceki değiştirmek için return {byteify(key): byteify(value) for key, value in input.iteritems()}birlikte return dict([(byteify(key), byteify(value)) for key, value in input.iteritems()])Sözlük comprehensions Python 2.7 kadar desteklenmediği beri.
  • Bu cevap, kodu çözülmüş nesnenin tamamında geri çekildiğinden, object_hook veya object_pairs_hookparametrelerinin . Mirec Miskuf'un cevabı şu ana kadar bunu doğru bir şekilde çekmeyi başaran tek şey, sonuç olarak benim yaklaşımımdan çok daha karmaşık.

1
Bunu beğendim - görmezden gelmek değil - insanlar "dizeler" ve "ascii" dediğinde, çoğunlukla naif olarak teorik unicode karakterleri değil, bayt istedikleri anlamına geliyorlar. (ve hala diğer ucunda pound işaretleri istedikleri gibi ascii değil)
Danny Staple

Bunu beğendim, güzel yazıcımla neredeyse aynı şekilde çalışıyor, çünkü json'un tuple yapmadığını biliyorum, tuple için de istisna eklemelisiniz.
y.petremann

Bu, korkunç bir şekilde verimsizdir, gerekmeyebileceğiniz düğümleri tekrar tekrar geçmenizi gerektirir. Json modülü, bunu çok daha verimli bir şekilde yapmanızı sağlar. Bununla birlikte, aşağıdaki cevap object_hookaslında bundan daha kötüdür, ancak kullanarak object_pairs_hook, dize içermeyen düğümlerin özyinelemesini veya yeniden gözden geçirilmesini gerektirmeyen makul derecede etkili bir yöntem ortaya çıkarabilirsiniz .
Travis Jensen

1
@TravisJensen İlginç. object_pairs_hookYöntem çok az sert (eğer listeleri ve dicts farklı biçimde kullanılması gerekir nasıl parametre çalıştığını ve anlamak gerekir) bu bir daha anlamak için belki de, ve performans yararı olsun çoğu insan için ... ama beklediğiniz olmaz özellikle alışılmadık derecede iç içe geçmiş bir JSON nesnesiyle uğraşan herkes için var olmak.
Mark Amery

artı1 Bu en kısa cevap; yanı sıra PyYAML yüklemek için bir acıdır. Daha iyi olan tek şey, dönüşümü bir şekilde mikro akış yapmaktır, böylece 4X bellek kullanmaz.
personal_cloud

74

Bir dönüştürücüyü iletmek için object_hookparametresini kullanabilirsiniz json.loads. Gerçeği sonra dönüşümü yapmak zorunda değilsiniz. jsonModül daima geçecekobject_hook yalnızca dicts ve iç içe geçmiş dicts Kendini Recurse zorunda kalmamak için yinelemeli, iç içe dicts geçecek. Wells gösterileri gibi unicode dizeleri dönüştürmek olacağını sanmıyorum. Unicode bir dize ise, JSON dosyasında bir dize olarak alıntılanmıştır, bu nedenle bir dize olması gerekir (veya dosya bozuk).

Ayrıca, str(val)bir unicodenesne gibi bir şey yapmaktan kaçınmaya çalışacağım . Kullanmalısınvalue.encode(encoding)Harici lib'inizin ne beklediğine bağlı olarak geçerli bir kodlamayla .

Yani mesela:

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
Nesne sbir JSON ise Object(anahtar: değer çiftleri, anahtarı ve virgülle ayrılmış ve kıvırcık parantez içine alınmış değeri ayıran ':' karakteriyle eşleşirse), ancak JSON Array. Yani JSON Arraygibi verilirse ["a", "b"], sonuç yine de olur [u'a', u'b']. Şu anda mevcut olan diğer kanca tipi parametrelerinin özelleştirilmesinin hiçbiri json.loads()de işi yapamaz.
martineau

2
Bahsettiğiniz gibi, jsonmodül tekrarlı olarak iç içe geçecekleri dictiçin, iki işlevde bunları kontrol etmek gereksizdir - bu yüzden elifonları kontrol eden iki cümle kaldırılmalıdır.
martineau

1
Alt çizgi ile işlev adlarını başlatmanın içe aktarma ifadeleri için özel bir anlamı olduğunu unutmayın. Yapmanız Utility.py adlı bir dosyada ve başka dosyada bu işlevleri koyarsanız from Utility import *, işlevler olacak değil çünkü bu çizgi içinde görülebilir.
M Katz

1
Bu gerçekten kötü bir fikir. object_hookayrıştırılan her json nesnesi için çağrılır, bu yüzden size verilen şeyi geri alırsanız, zaten "byteified" olan şeyleri yeniden "byte" edersiniz. Performans, nesnenin büyüklüğü ile geometrik olarak büyüyecektir. Bir cevap ekledik burada o kullanımları object_pairs_hookve bu problemden muzdarip değildir.
Travis Jensen

38

Bunun nedeni, json'un dize nesneleri ve unicode nesneleri arasında hiçbir farkı olmamasıdır. Hepsi javascript dizeleri.

Ben JSON unicode nesneleri döndürme hakkı olduğunu düşünüyorum . Aslında, javascript dizeleri aslında unicodenesneler (yani JSON (javascript) dizeleri unicode karakter herhangi bir tür saklayabilirsiniz) olduğundan daha az bir şey kabul etmem, bu yüzden unicodeJSON dizeleri çevirirken nesneleri oluşturmak için mantıklı . Düz dizeler uygun değildir çünkü kütüphane istediğiniz kodlamayı tahmin etmek zorunda kalır.

unicodeDize nesnelerini her yerde kullanmak daha iyidir . Bu yüzden en iyi seçeneğiniz kütüphanelerinizi unicode nesnelerle ilgilenebilmeleri için güncellemektir.

Ancak gerçekten bytestrings istiyorsanız, sonuçları seçtiğiniz kodlamaya kodlamanız yeterlidir:

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

Teşekkürler nosklo, ilk olarak bunu yaptım. Ama dediğim gibi, kullandığım gerçek veriler oldukça iç içe ve hepsi, bu yüzden bu biraz genel gider getirdi. Hala otomatik bir çözüm arıyorum ... Orada insanlar unicode yerine simplejson dönen dize nesneleri hakkında şikayet en az bir hata raporu var.
Brutus

1
@Brutus: Bence json unicode nesneleri iade etme hakkına sahip. Aslında, javascript dizeleri aslında unicode nesneler olduğu için daha az bir şey kabul etmem. Demek istediğim, json (javascript) dizeleri her türlü unicode karakteri saklayabilir, bu yüzden json'dan çeviri yaparken unicode nesneleri oluşturmak mantıklıdır. Bunun yerine kütüphanelerinizi gerçekten düzeltmelisiniz.
Nosklo

16

Kolay bir çözüm var.

TL; DR - ast.literal_eval()Bunun yerine kullanın json.loads(). Hem astvejson standart kütüphanede.

'Mükemmel' bir cevap olmasa da, planınız Unicode'u tamamen görmezden gelmek oldukça zorlaşıyor. Python 2.7 sürümünde

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

verir:

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

Bazı nesneler gerçekten Unicode dizeleri olduğunda bu daha kıllı olur. Tam cevap çabucak kıllı olur.


11
Json'unuzun herhangi bir null, trueveya falsedeğeri içermediğinden emin olun , çünkü bunlar python'da geçerli değildir ve literal_eval()başarısızlığa neden olur.
ɈsәɹoɈ

3
@ ʇsәɹoɈ JSON'unuzun \/bir dize içinde kaçan bir solidus ( ) veya bir unicode kaçış dizisi ( "\u0061"başka bir yazma yolu gibi) içermemesini umuyoruz "a". Python'un gerçek sözdizimi çeşitli şekillerde JSON ile uyumsuzdur ve atmayacağım herhangi bir komut dosyası için bu cevaba güvenmem.
Mark Amery

İnsanlar, eğer dize gerçekten unicode ise, bu cevabın başarısız olduğunu, ancak bu durumda olsaydı, yine de bir dizeye yayınlayamayacağımızı haklıyız. Sadece çalışıp başka bir istisna attığında çalışan bir yanıt için +1
Stefan Sullivan

mümkünse jsonverileri dökmek için kullanmayın, sadece printpython çalıştırıyorsanız kullanın . Sonra ast.literal_evalçalışır
Jean-François Fabre

11

Mike Brennan'ın yanıtı yakın, ancak tüm yapıyı yeniden dolaşmak için bir neden yok. Eğer kullanırsanız object_hook_pairs(Python 2.7+) parametresini:

object_pairs_hook, sıralı bir çift listesi ile kodu çözülen herhangi bir nesnenin sonucuyla çağrılacak isteğe bağlı bir işlevdir. Dönüş değeri. object_pairs_hookYerine kullanılacaktır dict. Bu özellik, anahtar ve değer çiftlerinin kodunun çözülme sırasına dayanan özel kod çözücüleri uygulamak için kullanılabilir (örneğin, collections.OrderedDictekleme sırasını hatırlar). Eğer object_hookayrıca tanımlanır, object_pairs_hooköncelik kazanır.

Bununla birlikte, her JSON nesnesini size teslim edersiniz, böylece kod çözmeyi özyineleme gerektirmeden yapabilirsiniz:

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

Kancayı yinelemeli olarak çağırmam gerektiğine dikkat edin, çünkü her nesne kullandığınızda kancaya teslim edilecektir object_pairs_hook. Listeleri önemsemeniz gerekir, ancak gördüğünüz gibi, bir listedeki bir nesne düzgün bir şekilde dönüştürülür ve bunun gerçekleşmesi için tekrarlaması gerekmez.

EDIT: Bir iş arkadaşı Python2.6 sahip olmadığına dikkat çekti object_hook_pairs. Yine de çok küçük bir değişiklik yaparak Python2.6'yı kullanabilirsiniz. Yukarıdaki kancada değiştirin:

for key, value in pairs:

için

for key, value in pairs.iteritems():

Sonra kullanmak object_hookyerine 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'}}

object_pairs_hookJSON nesnesindeki her nesne için somutlaştırılmakta olan daha az bir sözlükte sonuçları kullanmak , eğer büyük bir belgeyi ayrıştırıyorsanız, buna değer olabilir.


1
Bu temiz ve yeşil onay işaretini hak etmeye çok yakın görünüyor (Brutus, daha iyi cevaplar geldikçe, zaten liberal olarak geçti). Ama ... neden deunicodify_hookbu cevapta sergilediğinizde listeleri düzgün işlemiyorsunuz? Şu an için bir uygulama var deunicodify_hookolduğu listeler üzerinde yinelerler değil ve bunların içindeki dizeleri ve listeleri deunicodify ve böylece çıkış sen gelmez ediyoruz sergilenmesi değil çıktıyı uyanları kanca aslında üretecek. Bunu düzeltin ve bu cevap benimkinden daha üstün olacak.
Mark Amery

Anlamsız: Ayrıca burada kullandığınızdan ziyade sıradan CPython yorumlayıcısıyla işlevi göstermenizi öneririm (ki bunun IronPython olduğunu düşünüyorum)? CPython yorumlayıcısı çoğu Python kullanıcısına daha tanıdık geliyor ve bence daha güzel.
Mark Amery

Bu benim için çalışmıyor ama eminim ki yaptığım şey biraz tuhaf ... Daha büyük bir json doc'den bir dosyaya bir liste saklıyorum. Bu object_pairs_hook ile veya bu nesne olmadan yüklesem de, her öğe unicode ortaya çıkar. Lanet.
28'de testere

1
@ rsaw İyi bir nokta! Yana object_pairs_hooksadece çağrılır nesneler JSON metin en üst düzeyde dizeleri listesini varsa, bu çözüm başarısız olur. Geri döndürülen şey üzerinde bazı işlev çağırmadan bunu düzeltmenin bir yolu yoktur json.load; json.loadkancaların hiçbiri her ip ile başa çıkabileceğinizi garanti edemez. Sanırım bu, kancaları kullanarak çözümümü tavsiye etmeye devam etmem için yeterince büyük bir kusur.
Mark Amery

-1 çünkü Mirec Miskuf'un Mike Brennan'ın yaklaşımının dezavantajlarına sahip olmayan (aynı sözlükleri birden çok kez bayt ediyor) veya bunun (iç içe listeler veya üst düzey listeler baytlanamadığı) bir nesne kanca yanıtı yayınladığını fark ettim. veya dizeler). Cevabının neden neredeyse hiç dikkat çekmeden durduğundan emin değilim; bu daha düşük olan cevap hızla oy aldı.
Mark Amery

9

Korkarım bunu simplejson kütüphanesinde otomatik olarak elde etmenin bir yolu yok.

Simplejson'daki tarayıcı ve dekoder, unicode metin üretmek için tasarlanmıştır. Bunu yapmak için kütüphane c_scanstring(eğer mevcutsa, hız için mevcutsa) veya py_scanstringC versiyonu mevcut değilse bir fonksiyon kullanır . scanstringFonksiyon simplejson metni içerebilecek bir yapıya kodunu çözmek için sahip olduğu neredeyse her rutin tarafından defalarca denir. Ya monkeypatch olurdu scanstringsimplejson.decoder veya alt sınıfta değerini JSONDecoderve metin içeren her hangi bir şeyin hemen hemen Kendi Tüm uygulanmasını sağlarlar.

Simplejson çıkışları unicode Ancak, yani nedeni json Spec özellikle bahseder "Bir dize sıfır veya daha fazla Unicode karakter topluluğudur" ... unicode desteği biçiminde kendisinin bir parçası olarak kabul edilir. Simplejson'un scanstringuygulaması, unicode kaçışlarını (hatalı biçimlendirilmiş çok baytlı charset gösterimleri için hata denetimi bile) taramak ve yorumlamak kadar ileri gider, bu nedenle değeri güvenilir bir şekilde size geri verebilmenin tek yolu unicode gibidir.

İhtiyacınız olan yaşlı bir kütüphaneniz varsa str, ayrıştırdıktan sonra iç içe veri yapısını zahmetli bir şekilde aramanızı öneririm (ki açıkça kaçınmak istediğinizi söylediğiniz şeydir ... üzgünüm) ya da belki de kitaplıklarınızı bir çeşit giriş parametrelerine daha ayrıntılı bir seviyede masaj yapabileceğiniz cephe. Veri yapılarınız gerçekten derinden yuvalanmışsa, ikinci yaklaşım birinciden daha yönetilebilir olabilir.


4

Mark'ın (Amery) doğru bir şekilde not ettiği gibi: Pysonam'ın serpiştiricisini bir json dökümünde kullanmak yalnızca ASCII'niz varsa çalışır. En azından kutunun dışında.

PyYaml yaklaşımı hakkında iki hızlı yorum:

  1. ASLA alandan gelen veriler üzerinde yaml.load kullanmayın. Onun yaml özelliğinin (!) Yapısı içinde gizli rasgele kod yürütmek.

  2. Sen olabilir bu aracılığı olmayan ASCII için de çalışmasını sağlayın:

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

Ama performans akıllıca Mark Amery'nin cevabı ile karşılaştırılamaz:

Bazı derinlemesine iç içe örnek dikte iki yöntem üzerine atma, bunu elde (dt [j] = json.loads (json.dumps (m)) zaman deltası ile):

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

Bu nedenle, ağacın tamamen yürüyüşünü ve kodlamayı içeren jerializasyon, json'un C tabanlı uygulama büyüklüğünde. Bunu oldukça hızlı buluyorum ve aynı zamanda derin yuvalanmış yapılarda yaml yükünden daha sağlam. Ve yaml.load'a bakarak daha az güvenlik hatası.

Ben sadece C tabanlı bir dönüştürücü için bir işaretçi takdir ederken byteify fonksiyonu varsayılan cevap olmalıdır.

Bu, özellikle json yapınız kullanıcı girdisini içeren alandan geliyorsa geçerlidir. Çünkü o zaman muhtemelen yürümek gerekiyor zaten istediğiniz iç veri yapıları bağımsız (sadece 'unicode sandviç' ya bayt dizeleri) - senin yapısı üzerinde.

Neden?

Unicode normalizasyonu . Habersiz: Bir ağrı kesici alın ve okumaya bu .

Yani byteify özyineleme kullanarak bir taş ile iki kuş öldürmek:

  1. iç içe geçmiş json dökümlerinden bytestringlerinizi alın
  2. kullanıcı giriş değerlerini normalleştirin, böylece depolama alanınızdaki öğeleri bulabilirsiniz.

Testlerimde input.encode ('utf-8') yerine unicodedata.normalize ('NFC', input) .encode ('utf-8') yerine NFC olmadan daha hızlı olduğu ortaya çıktı - ancak Bu büyük ölçüde örnek verilere bağlı sanırım.


3

Gotcha budur simplejsonve jsonen azından unicode ile baş ettikleri şekilde iki farklı modüldür. jsonPy 2.6 ve sonraki sürümlerinde sahipsiniz ve bu size unicode değerleri verirken simplejsondize nesnelerini döndürür. Ortamınızda easy_install-ing simplejson'u deneyin ve çalışıp çalışmadığına bakın. Benim için oldu.


2

Boşaltma ve yükleme için json yerine turşu kullanın, şöyle:

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

Ürettiği çıktı (dizeler ve tamsayılar doğru işleniyor):

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

1
Ek paket gerektirmeyen ( yaml gibi ) bir çözüm için +1 . Ama bazen - orijinal durumumda olduğu gibi - verileri JSON'da almam gerekiyor, bu yüzden turşu her zaman en iyi seçenek değil. Ayrıca, safe_loadYAML'de var, turşu için benzer bir şey olup olmadığını bilmiyorum .
Brutus

1

Ben de aynı problemle karşılaştım. İlk Google sonucunun ne olduğunu tahmin edin.

Tüm verileri PyGTK'ya geçirmem gerektiğinden, unicode dizeleri de benim için çok yararlı değil. Başka bir özyinelemeli dönüşüm yöntemim var. Aslında, tipesafe JSON dönüşümü için de gereklidir - json.dump (), Python nesneleri gibi değişmez olmayanlar için kefaletle karşılanır. Dict dizinleri olsa dönüştürmez.

# 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

Buraya gelebilecek tek sorun, unicode'dan dönüştürülmüş bir sözlükteki anahtarlara ihtiyacınız varsa. Bu uygulama değerleri dönüştürecek olsa da, unicode anahtarları korur. Bir 'newobj' oluşturursanız, newobj [str (i)] = ... kullanın ve işiniz bittiğinde obj = newobj atayın, anahtarlar da dönüştürülür.
Neal Stublen

Bu, kavrayışlarla daha güzel veya anahtarları dönüştürerek daha iyi olabilir. Ayrıca unidiomatik; her ikisi de nesneleri yerinde değiştirir (sözlükler söz konusu olduğunda) ve Python'un geçerli nesneyi değiştiren veya yenisini döndüren yerleşik toplama yöntemleriyle tutarsız olan yeni değeri döndürür, ancak her ikisini birden döndürmez.
Mark Amery

1

Dize olarak bir JSON diktesi vardı. Anahtarlar ve değerler, aşağıdaki örnekteki gibi unicode nesnelerdi:

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

byteifyDize kullanarak bir dictnesneye dize dönüştürerek yukarıda önerilen işlevi kullanabilirsiniz ast.literal_eval(myStringDict).


Verdiğiniz örnek JSON örneği değildir. {u'key':u'value'}JSON değil.
Mark Amery

2
JSON olmadığını çok iyi biliyorum. Python betiğimdeki harici bir kaynaktan bu şekilde ayrıştırıldı. Aşağıdaki örnekte olduğu gibi JSON olsaydı, çözüm olarak işaretlenen byteify işlevine ihtiyacım olmazdı: {"firstName": "John", "lastName": "Doe"}. Oy vermeden önce cevapları okumanız harika olur. Teşekkürler.
narko

1

Kanca kullanarak Python2 ve 3'ü destekleyin ( 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)

İadeler:

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

0

Bu oyuna geç, ama ben bu tekrarlayan tekeri inşa ettim. Benim ihtiyaçlarım için çalışıyor ve bence nispeten eksiksiz. Size yardımcı olabilir.

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

Sadece bu şekilde bir JSON nesnesi iletin:

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

Bir sınıfın özel bir üyesi olarak var, ama uygun gördüğünüz yöntemi yeniden kullanabilirsiniz.


Ben JSON ayrıştırmak ve elde edilen eşleme ** kwargs olarak bir işleve geçmek çalışıyorum bir sorunla karşılaştım. İşlev parametre adları unicode olamaz, bu nedenle _parseJSON işleviniz harika. Daha kolay bir yol varsa, biri bana haber verebilir.
Neal Stublen

1
Bu kodun bir sorunu vardır - Liste parçasında, listenin öğeleri kendileri sözlük değilse başarısız olacak yinelemeli bir çağrı yaparsınız.
I82Much

@ I82Much tarafından açıklanan hatanın yanı sıra, bu da kötü bir şekilde adlandırılmıştır (aslında JSON'u ayrıştırmaz; önce bir json.loadsçağrıya ihtiyaç vardır), keyfi olarak dizeleri int'ye açıklanmış bir sebep olmadan dönüştürmeye çalışır ve kopyala ve macun hazır.
Mark Amery

0

Ben json nesnesinin kendisi bir dizi (benim kullanım durum) olduğu durumlarda işlemek için Wells'in _parse_json () yeniden yazdı.

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

İşte C ile yazılmış bir özyinelemeli kodlayıcı: https://github.com/axiros/nested_encode

"Ortalama" yapılar için performans yükü, json.load'lara kıyasla% 10 civarındadır.

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%

bu test yapısını kullanarak:

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

Python 3.6 ile, bazen hala bu sorunla karşılaşıyorum. Örneğin, bir REST API'sinden yanıt alırken ve yanıt metnini JSON'a yüklerken, hala unicode dizelerini alıyorum. Json.dumps () kullanarak basit bir çözüm bulundu.

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

-1

Ben de bu sorunla karşılaştı ve JSON ile uğraşmak zorunda, ben unicode anahtarları dizeleri dönüştüren küçük bir döngü ile geldi. (simplejson GAE'de dize anahtarları döndürmez.)

obj nesne JSON'dan çözülür:

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

kwargsGAE uygulamasının yapıcısına ilettiğim şey ( unicodeanahtarları sevmiyor **kwargs)

Wells'in çözümü kadar sağlam değil, çok daha küçük.


-1

Ben kodu adapte ettik cevap ait Mark Amery özellikle kurtulmak için,isinstance ördek yazı yazma profesyoneller için.

Kodlama manuel olarak yapılır ve ensure_asciidevre dışı bırakılır. İçin piton dokümanlar json.dumpsöylüyor

Sure_ascii True (varsayılan) ise, çıktıdaki ASCII olmayan tüm karakterler \ uXXXX dizileriyle kaçar

Feragatname: doctest'te Macar dilini kullandım. Macarca ile ilgili bazı önemli karakter kodlamaları şunlardır: cp852kullanılan IBM / OEM kodlaması örn. DOS (bazen ascii olarak da adlandırılır , yanlış düşünüyorum, kod sayfası ayarına bağlıdır ), cp1250örneğin kullanılır. Windows'ta (bazen yerel ayarlara bağlı olarak ansi olarak anılır ) ve iso-8859-2bazen http sunucularında kullanılır. Test metni Koltai László'ya (yerel kişisel ad formu) Tüskéshátú kígyóbűvölőatfedilmiştir ve wikipedia'dan alınmıştır .

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

Ben vurgulamak için böyle de olur cevabı ait Jarret Hardie hangi referanslar JSON Spec , alıntı:

Dize, sıfır veya daha fazla Unicode karakterden oluşan bir koleksiyondur

Benim kullanım durumunda json ile dosya vardı. Bunlar utf-8kodlanmış dosyalardır.ensure_asciisonuç düzgün kaçtı ama çok okunabilir json dosyaları, bu yüzden Mark Amery cevap benim ihtiyaçlarına uyacak şekilde adapte ettik.

Doctest özellikle düşünceli değil ama kodu birisi için yararlı olacağı ümidiyle paylaşıyorum.


Burada ördek yazmanın faydalarını gördüğümden emin değil misiniz? Geri döndürülen koleksiyonların json.loads, yöntemlerini ve sihirli yöntemlerini uygulayan bazı kullanıcı tanımlı veya kütüphane tanımlı bir tür değil, liste veya dikte olacağını biliyoruz, neden sadece bir isinstancekontrol yapmıyorsunuz ? Bunu anlamak , nesnenin varlığını kontrol etmek iteritemsya iterda bir argüman olarak kabul edip etmeyeceğinden daha kolay değil mi?
Mark Amery

@MarkAmery Bu yüklerle değil dökümlerle ilgili. Eğer varsa oluşturmak dökümü veri - aksine yükleme it - Emin ne olduğunu olamaz. fikir, kodun herhangi bir yerinden gelmesine izin vermekti.
n611x007

-2

Check out buŞuna benzer bir soruya verilen cevaba

U- öneki, sadece bir Unicode dizginiz olduğu anlamına gelir. Dizeyi gerçekten kullandığınızda, verilerinizde görünmez. Yazdırılan çıktı tarafından fırlatılmamalıdır.

Örneğin, şunu deneyin:

print mail_accounts[0]["i"]

Bir u görmeyeceksin.


Örneğin, Unicode dizesi içeren bir şeyi Py2'de biçimlendirmek istiyorsanız doğru değil. örneğin '{}'.format({u'x' : u'y'})hala u içerir.
Ponkadoodle
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.