Як отримати рядкові об'єкти замість Unicode від JSON?


276

Я використовую Python 2 для розбору JSON з кодованих текстових файлів ASCII .

Коли ви завантажуєте ці файли з будь-яким jsonабо simplejson, всі мої рядкові значення передаються об'єктам Unicode замість рядкових об'єктів. Проблема полягає в тому, що я повинен використовувати дані з деякими бібліотеками, які приймають лише рядкові об'єкти. Я не можу змінювати бібліотеки і не оновлювати їх.

Чи можна отримати рядкові об'єкти замість Unicode?

Приклад

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

Оновлення

Це питання було задано дуже давно , коли я застряг у Python 2 . Одне з простих і чистих рішень на сьогодні - використовувати останню версію Python - тобто Python 3 і вперед.


1
Під Python3 немає жодної проблеми, тип елементів у new_liststr
GoingMyWay

1
Python 3k не є "останньою версією Python", це лише альтернативна галузь.
користувач2589273

11
Дивно бачити такий коментар у грудні 2017 року - Python 2 застарілий, і технічне обслуговування не відбудеться після 1 січня 2020 року, що становить менше 2 років: pythonclock.org
Zaar Hai

1
@ZaarHai Багато людей застрягли в Python 2 проти своєї волі. Є багато додатків, які вбудовують власну версію Python для автоматизації та створення сценаріїв, тому людям доводиться користуватися нею до оновлення постачальника (дивлюся на вас Майя, Гудіні, Нуке ..)
Джорді

1
@Geordie Я, безумовно, це знаю і розумію. Мій коментар стосувався термінології - Python - це не "альтернативна галузь", а скоріше прикрою відсутністю альтернативи (каламбур) для тих, хто її застряг.
Заар Хай

Відповіді:


101

Розв’язання с 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

Приклад використання:

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

Як це працює і навіщо я його використовую?

Функція Марка Амері коротша і чіткіша за ці, тож у чому сенс їх? Навіщо ви хочете їх використовувати?

Чисто для виконання . Відповідь Марка дешифрує текст JSON спочатку за допомогою рядків Unicode, потім повторюється через все декодоване значення, щоб перетворити всі рядки в рядки байтів. Це має кілька небажаних ефектів:

  • Копія всієї декодованої структури створюється в пам'яті
  • Якщо ваш об'єкт JSON дійсно глибоко вкладений (500 рівнів або більше), ви будете вражати максимальну глибину рекурсії Python

Ця відповідь пом'якшує обидві ці проблеми продуктивності, використовуючи object_hookпараметр json.loadі json.loads. З документів :

object_hook- необов'язкова функція, яка буде викликана в результаті будь-якого об'єкта, буквально декодованого (а dict). Зворотне значення object_hook буде використане замість dict. Ця функція може бути використана для реалізації нестандартних декодерів

Оскільки словники вкладені на багато рівнів глибоко в інших словниках, переходять у object_hook міру їх розшифровки , ми можемо в цей момент відстежувати будь-які рядки чи списки всередині них і уникати необхідності глибокої рекурсії пізніше.

Відповідь Марка не підходить для використання як такої object_hook, що є, оскільки вона повторюється в вкладені словники. Ми запобігаємо рекурсії в цій відповіді з ignore_dictsпараметром до _byteify, який передається йому в будь-який час, за винятком випадків, коли object_hookвін передає новий dictдля byteify. ignore_dictsПрапор говорить _byteifyігнорувати dictS , так як вони вже byteified.

Нарешті, наші реалізації json_load_byteifiedта json_loads_byteifiedcall _byteify(with ignore_dicts=True) за результатом повертаються з json.loadабо json.loadsдля обробки випадку, коли текст JSON, який декодується, не має dictверхнього рівня.


1
+1 за підхід тут; Я не дуже зрозумів це, коли вперше прочитав його, але нарешті зрозумів, коли перечитував це у світлі відповіді Тревіса Дженсена. Я зробив досить агресивну редакцію, сподіваючись пояснити, як це працює, і які його переваги перед моєю відповіддю. Основна ідея коду залишається недоторканою, але я майже все змінив. Якщо ви заперечуєте проти цього, не соромтесь скасувати мою редакцію - це ваша відповідь!
Марк Амері

Без проблем Марк, велике спасибі Мені подобається ваша редакція, вона набагато більш пояснювальна, ніж моя оригінал. Можливо, одного дня я навчусь давати більш стислі відповіді.
Mirec Miskuf

2
Це чудове рішення; ефективний та елегантний. Однак якщо ви застрягли в царині Python <2.7, як і я, вам потрібно буде замінити рядок: return { byteify(key, ignore_dicts=True): _byteify(value, ignore_dicts=True) for key, value in data.iteritems() }на, return dict((_byteify(key, ignore_dicts=True), _byteify(value, ignore_dicts=True)) for key, value in data.iteritems())щоб він працював.
Річард Данн

Я думаю, ви помиляєтесь щодо проблеми глибини рекурсії. З вашої, я можу піти до 990: json_loads_byteified('[' * 990 + ']' * 990). З 991 він виходить з ладу. Марк все ще працює з 991:byteify(json.loads('[' * 991 + ']' * 991)) . Він зазнає краху на рівні 992. Тож принаймні в цьому тесті Марк може піти глибше, всупереч сказаному.
Стефан Похман

@MarkAmery Що ви думаєте про мій вище коментар? (Я щойно в історії редагування бачив, що саме ви додали цю заяву саме ви).
Стефан Похман

180

Хоча тут є кілька хороших відповідей, я в кінцевому підсумку використовую PyYAML для розбору моїх файлів JSON, оскільки він дає ключі та значення як strрядки типу замість unicodeтипу. Оскільки JSON - це підмножина YAML, вона прекрасно працює:

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

Примітки

Однак слід зазначити деякі речі:

  • Я отримую рядкові об'єкти, тому що всі мої записи закодовані ASCII . Якби я використав записи, кодовані unicode, я б повернув їх як об'єкти unicode - перетворення немає!

  • Ви повинні (мабуть, завжди) використовувати функцію PyYAML safe_load; якщо ви використовуєте його для завантаження файлів JSON, у loadбудь-якому разі вам не потрібна "додаткова потужність" функції.

  • Якщо ви хочете, щоб YAML-аналізатор мав більшу підтримку версії 1.2 специфікації (і правильно аналізує дуже низькі числа ), спробуйте Ruamel YAML : pip install ruamel.yamlі import ruamel.yaml as yamlвсе, що мені було потрібно в моїх тестах.

Перетворення

Як зазначено, конверсії немає! Якщо ви не можете бути впевнені, що маєте справу лише зі значеннями ASCII (і ви не можете бути впевнені більшу частину часу), краще скористайтеся функцією перетворення :

Я використовував той від Марка Амері пару разів, він чудово працює і дуже простий у використанні. Ви також можете використовувати аналогічну функцію object_hookзамість цього, оскільки це може призвести до підвищення продуктивності великих файлів. Побачте трохи більше залучених відповідь Мірека Міскуфа .


8
Будьте трохи обережні, якщо ви вирішили використовувати цю відповідь. Це ідеально підходить для випадку Брута, але лише тому, що він знає, що його дані містять лише символи, кодирувані ASCII. Якщо у вас немає гарантії, ця відповідь не буде працювати. Наприклад, спробуйте виконати yaml.load(json.dumps([u'a', u'£', u'É']))в оболонці Python і спостерігайте, що ви повернетесь ['a', u'\xa3', u'\xc9'](який містить unicodeрядки). Якщо ви не можете бути впевнені, що ваші дані містять лише символи з набору символів ASCII, замість цього слід скористатися іншим підходом (рекомендую власну відповідь).
Марк Амері

1
YAML також використовує [u'a', u'b']обережність.
Карлос Калла

1
Це приємно, але це не працює з низькою кількістю .. дивіться тут: stackoverflow.com/questions/30458977/…
Орен

@Oren: Це не помилка в специфікації YAML, а в аналізаторі PyYAML. YAML парсер з ruamel робіт.
Брут

Я хочу мати вихід, як ["a", "b"] не як ['a', 'b'] @Brutus
user60679

141

Не існує вбудованої опції, щоб змусити функції модуля json повертати рядки байтів замість рядків Unicode. Однак ця коротка і проста рекурсивна функція перетворить будь-який декодований об'єкт JSON з використання рядків Unicode в рядки байтів, кодовані 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

Просто зателефонуйте до цього на виході, який ви отримаєте від json.load або, або json.loadsзателефонуйте.

Пара приміток:

  • Для підтримки Python 2.6 або новішої версії замініть return {byteify(key): byteify(value) for key, value in input.iteritems()} на return dict([(byteify(key), byteify(value)) for key, value in input.iteritems()]), оскільки розуміння словника не підтримувалося до Python 2.7.
  • Оскільки ця відповідь повторюється через весь декодований об'єкт, він має пару небажаних характеристик продуктивності, яких можна уникнути при дуже обережному використанні параметрів object_hookабо object_pairs_hook. Відповідь Мірека Міскуфа поки що єдина, якій вдається виправити це правильно, хоча, як наслідок, це значно складніше, ніж мій підхід.

1
Мені подобається це - це не ігнорування - це визнання того, що коли люди говорять "рядки" та "асчі", вони здебільшого наївно означали, що хочуть байтів, а не теоретичних символів унікоду. (а не ascii, оскільки вони все ще хочуть знаків фунту на іншому кінці)
Danny Staple

Мені це подобається, він працює майже так само, як працює мій гарний принтер, оскільки я знаю, що json не робить кортеж, вам слід додати виняток і для кортежу.
y.petremann

Це жахливо неефективно, що вимагає від вас рекурсивно перетинати вузли, які вам, можливо, не знадобляться. Модуль json дає вам гачки зробити це набагато ефективніше. Відповідь, що використовується нижче, object_hookє насправді набагато гіршою, ніж ця, але, використовуючи object_pairs_hook, ви можете придумати досить ефективний метод, який не вимагає рекурсії чи перегляду вузлів, що не містять рядків.
Тревіс Йенсен

1
@TravisJensen Цікаво. Цей object_pairs_hookметод, можливо, є дещо складнішим для розуміння, ніж цей (вам потрібно зрозуміти, як працює параметр і чому списки та дикти вимагають різної обробки), а користь від продуктивності не матиме значення для більшості людей ... але я б очікував він існує, особливо для тих, хто має справу з незвичайно глибоко вкладеним об'єктом JSON.
Марк Амерді

плюс1 Це найкоротша відповідь; до того ж PyYAML - це біль встановити. Єдине, що краще було б якось мікрострумувати перетворення, щоб воно не використовувало 4X пам'ять.
personal_cloud

74

Ви можете використовувати object_hookпараметр для json.loadsпередачі в перетворювачі. Вам не потрібно робити перетворення після факту. jsonМодуль завжди буде проходити object_hookdicts тільки, і він буде рекурсивно пройти в вкладеної dicts, так що вам не доведеться рекурсія в вкладену dicts себе. Я не думаю, що я перетворив би рядки Unicode в числа, як показано Wells. Якщо це рядок unicode, він був котирується як рядок у файлі JSON, тому він повинен бути рядком (або файл поганий).

Також я б намагався уникати чогось подібного str(val)на unicodeпредметі. Вам слід скористатися value.encode(encoding)дійсним кодуванням, залежно від того, що очікує ваша зовнішня ліб.

Так, наприклад:

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
Це добре, якщо об'єктом sє JSON Object(не упорядкована колекція ключів: пара пар значень із символом ':', що розділяє ключ та значення, розділені комами та укладені у фігурні дужки), але не якщо це, скажімо, a JSON Array. Тож якщо дано Arrayподібний JSON ["a", "b"], результат все одно буде [u'a', u'b']. Жоден з інших доступних на даний момент налаштувань параметрів типу гачка json.loads()також не може виконати роботу.
мартіно

2
Оскільки, як ви вже згадували, jsonмодуль рекурсивно передаватиме вкладені dicts, не потрібно перевіряти їх у двох функціях - тому два elifпункти, які перевіряють їх, слід видалити.
мартіно

1
Зауважте, що імена функції запуску із підкресленням мають особливе значення для операторів імпорту. Якщо ви помістите ці функції у файл під назвою Utility.py та в іншому файлі do from Utility import *, функції не будуть видно із-за цього підкреслення.
М Кац

1
Це дійсно погана ідея. object_hookвикликується для кожного розібраного об'єкта json, тому якщо ви повторно вказуєте на те, що вам дано, ви повторно "бітетифікуєте" речі, які ви вже "зафіксували". Продуктивність буде зростати геометрично з розміром об'єкта. Я включив відповідь тут , який використовує object_pairs_hookі не страждає від цієї проблеми.
Тревіс Йенсен

38

Це тому, що json не має різниці між рядковими об'єктами та об'єктами unicode. Всі вони є рядками в JavaScript.

Я думаю, що JSON правильно повертати об'єкти unicode . Насправді я б не прийняв нічого менше, оскільки рядки javascript насправді є unicodeоб'єктами (тобто рядки JSON (javascript) можуть зберігати будь-який символ символу unicode), тому має сенс створювати unicodeоб'єкти при перекладі рядків з JSON. Звичайні рядки просто не підходять, оскільки бібліотеці доведеться вгадувати кодування, яке ви хочете.

Краще використовувати unicodeрядкові об’єкти скрізь. Тож ваш найкращий варіант - оновити свої бібліотеки, щоб вони могли працювати з об’єктами unicode.

Але якщо ви дійсно хочете виконувати швидкі джерела, просто зашифруйте результати на вибране вами кодування:

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

Дякую nosklo, саме це я зробив першим. Але, як я вже сказав, реальні дані, які я використав, досить вкладені, і все, тому це вводило химерність деякими накладними. Я все ще шукаю автоматичне рішення ... Там хоча б один звіт про помилку, де люди скаржаться на те, що simplejson повертає рядкові об'єкти замість unicode.
Брут

1
@Brutus: Я думаю, що json правильно повертати об’єкти unicode. Насправді я б не прийняв нічого менше, оскільки рядки JavaScript насправді є об’єктами unicode. Що я маю на увазі, це те, що рядки json (javascript) можуть зберігати будь-які символи unicode, тому має сенс створювати об’єкти unicode при перекладі з json. Справді слід замінити свої бібліотеки.
nosklo

16

Існує легка робота.

TL; DR - використовувати ast.literal_eval()замість json.loads(). Обидва astі jsonзнаходяться в стандартній бібліотеці.

Хоча це не "ідеальна" відповідь, вона отримує досить далеко, якщо ваш план взагалі ігнорувати Unicode. У 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))

дає:

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

Це стає більш волохатим, коли деякі об'єкти справді є струнами Unicode. Повна відповідь швидко стає волохатою.


11
Краще переконайтеся , що ваш JSON не містить null, trueабо falseзначення, тому що вони не діють в пітона й приведуть literal_eval()до збою.
ʇsәɹoɈ

3
@ ʇsәɹoɈ Також сподіваємось, що ваш JSON не містить втеченого солідуса ( \/) всередині рядка або послідовності уникнення юнікоду (наприклад "\u0061", це ще один спосіб написання "a"). Буквальний синтаксис Python декілька способів несумісний з JSON, і я б не довіряв цій відповіді жодному сценарію, який я не збирався викидати.
Марк Амері

Люди праві, що якщо рядок дійсно є unicode, то ця відповідь не вдається, але якби це було так, ми б не змогли передати це рядку все одно. +1 для відповіді, яка працює лише тоді, коли вона працює, і викидає виняток інакше
Стефан Салліван

якщо можливо, не використовуйте jsonдля скидання даних, просто використовуйте, printякщо працює python. Потім ast.literal_evalпрацює
Жан-Франсуа Фабре

11

Відповідь Майка Бреннана близька, але немає підстав повторно переходити всю структуру. Якщо ви використовуєте параметр object_hook_pairs(Python 2.7+):

object_pairs_hook- необов'язкова функція, яка буде викликана в результаті будь-якого об'єкта, буквально декодованого з упорядкованим списком пар. Повертається значення object_pairs_hookбуде використано замість dict. Ця функція може бути використана для реалізації користувальницьких декодерів, які спираються на порядок декодування пар ключів і значень (наприклад, collections.OrderedDictзапам'ятають порядок вставки). Якщо object_hookтакож визначено, object_pairs_hookпріоритет приймає.

З його допомогою ви отримуєте кожен переданий вам об’єкт JSON, тож ви можете робити розшифровку без рекурсії:

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

Зауважте, що мені ніколи не потрібно дзвонити на гачок рекурсивно, оскільки кожен об’єкт буде переданий на гачок, коли ви користуєтесь цим object_pairs_hook. Вам потрібно дбати про списки, але, як бачите, об’єкт у списку буде належним чином перетворений, і вам не доведеться повторюватися, щоб це відбулося.

EDIT: Співробітник зазначив, що Python2.6 не має object_hook_pairs. Ви все ще можете скористатися цією Python2.6, зробивши дуже невеликі зміни. У гачку вгорі змініть:

for key, value in pairs:

до

for key, value in pairs.iteritems():

Потім використовуйте object_hookзамість 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_hookрезультатів в одному словнику, який інстанціюється для кожного об'єкта в об'єкті JSON, що, якщо ви розбираєте величезний документ, може бути вартим певного часу.


1
Це акуратно і, здається, дуже близьке до того, щоб заслужити зелену галочку (яку Брутус, чудово, вже пройшов навколо, як краще відповіді). Але ... чому б насправді не правильно обробляти списки у тому, deunicodify_hookщо ви демонструєте у цій відповіді? На даний момент у вас є реалізація deunicodify_hook, яка не повторює списки і не денікодифікує рядки та списки всередині них, і таким чином вихід, який ви демонструєте, не відповідає результату, який ваш гак насправді отримає. Виправте це, і ця відповідь буде вищою за мою.
Марк Амері

Фривольний: Я б також запропонував продемонструвати цю функцію звичайним інтерпретатором CPython, а не тим, який ви тут використовуєте (що, на мою думку, є IronPython)? Інтерпретатор CPython більш відомий більшості користувачів Python і, на мою думку, гарніший.
Марк Амері

Це не працює для мене, але я впевнений, що це якась химерність того, що я роблю ... Я зберігаю один список із більшого документа json у файл. Якщо я завантажую його або без цього object_pairs_hook, кожен елемент з'являється унікодом. Дарн.
rsaw

1
@rsaw Добрий момент! Оскільки object_pairs_hookвикликується лише об'єкт , якщо у вашому тексті JSON є список рядків на верхньому рівні, це рішення не вдасться. Неможливо це виправити без виклику якоїсь функції на речі, поверненій з json.load; жоден з json.loadгачків не може гарантувати, що ви зможете обробити кожну струну. Я думаю, що це досить великий недолік для мене, щоб постійно рекомендувати моє рішення над використанням гачків.
Марк Амері

-1 тому, що я щойно зрозумів, що Мірек Міскуф вже розмістив відповідь на об'єкт, що не має жодних недоліків підходу Майка Бреннана (повторно батифікує одні й ті самі словники кілька разів), аніж цього (не дає змоги відстежувати вкладені списки чи списки найвищого рівня) або рядки). Я не впевнений, чому його відповідь зникає майже без уваги, в той час як ця, яка є неповноцінною, швидко набрала голосів.
Марк Амері

9

Боюся, немає можливості досягти цього автоматично в бібліотеці simplejson.

Сканер і декодер у simplejson призначені для отримання тексту з однокодовим кодом. Для цього бібліотека використовує функцію, яку називають c_scanstring(якщо вона доступна, для швидкості) або py_scanstringякщо версія C недоступна. scanstringФункція викликається кілька разів майже в кожній програмі , яка має simplejson для декодування структури , яка може містити текст. Вам доведеться або промальовувати scanstringзначення в simplejson.decoder, або підклас, JSONDecoderі в значній мірі забезпечити всю свою власну реалізацію всього, що може містити текст.

Однак причиною того, що simplejson видає unicode, є те, що специфікація json конкретно згадує, що "Рядок - це набір нульових або більше символів Unicode" ... Підтримка Unicode передбачається як частина самого формату. Simplejson'sscanstring заходить так далеко, щоб сканувати та інтерпретувати уникнення унікоду (навіть перевірку помилок на наявність неправильних представлень багатобайтових ), тому єдиним способом він може надійно повернути вам значення як unicode.

Якщо у вас є бібліотека у віці, якій потрібна бібліотека str, я рекомендую вам або ретельно шукати вкладені структури даних після розбору (який, я визнаю, ви прямо сказали, що хочете уникнути ... вибачте), або, можливо, оберніть свої бібліотеки в якусь фасад, де ви можете масажувати вхідні параметри на більш деталізованому рівні. Другий підхід може бути більш керованим, ніж перший, якщо ваші структури даних дійсно глибоко вкладені.


4

Як правильно зазначає Марк (Amery): Використання десеріалізатора PyYaml на дамсі json працює лише в тому випадку, якщо у вас є тільки ASCII. Принаймні з коробки.

Два швидких коментарі щодо підходу PyYaml:

  1. НІКОЛИ використовуйте yaml.load для даних з поля. Його особливість (!) Yaml виконує довільний код, прихований у структурі.

  2. Ви можете змусити його працювати також для не ASCII за допомогою цього:

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

Але продуктивність його не порівнянна з відповіддю Марка Амері:

Вкинувши деякий глибоко вкладений зразок диктату на два методи, я отримую це (з dt [j] = дельта часу json.loads (json.dumps (m))):

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

Тож десеріалізація, включаючи повноцінне ходіння по дереву і кодування, цілком в порядку величини реалізації на базі C json. Я вважаю це надзвичайно швидким і його також більш міцним, ніж навантаження ямлів у глибоко вкладених структурах. І менш схильні до помилок безпеки, дивлячись на yaml.load.

=> Хоча я би вдячний вказівнику на конвертер, заснований лише на C, функція byteify має бути відповідь за замовчуванням.

Це особливо актуально, якщо ваша структура json походить із поля, що містить введення користувача. Тому що тоді ви , ймовірно , потрібно йти в будь-якому випадку над структурою - незалежної від ваших бажаних внутрішніх структур даних ( «Юникода сандвіч» або байтових рядків тільки).

Чому?

Нормалізація Unicode . Для невідомого: візьміть знеболююче і прочитайте це .

Таким чином, використовуючи byteify рекурсію, ви вбиваєте двох птахів одним каменем:

  1. дістаньте свої найвідоміші джерела з вкладених відвалів json
  2. нормалізувати введені користувачем значення, щоб ви знайшли речі у вашому сховищі.

У моїх тестах виявилося, що заміна input.encode ('utf-8') на unicodedata.normalize ('NFC', введення) .encode ('utf-8') була навіть швидшою, ніж без NFC - але це сильно залежить від вибіркових даних, я думаю.


3

Гоча, що simplejsonі jsonдва різних модуля, по крайней мере , в порядку , вони мають справу з Юнікод. У вас є jsonpy 2.6+, і це дає значення unicode, тоді як simplejsonповертає рядкові об'єкти. Просто спробуйте easy_install-ing simplejson у вашому оточенні та побачите, чи це працює. Це зробило для мене.


2

Просто використовуйте соління замість json для скидання та завантаження:

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

Вихід, який він створює, (рядки та цілі числа обробляються правильно):

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

1
+1 для рішення, яке не потребує додаткових пакетів (наприклад, yaml ). Але іноді - як у моєму первісному випадку - мені потрібно мати дані в JSON, тому соління - це не завжди найкращий варіант. Крім того, у вас є safe_loadYAML, я не знаю, чи існує щось подібне для соління .
Брут

1

Отже, я зіткнувся з тією ж проблемою. Здогадайтесь, який був перший результат Google.

Оскільки мені потрібно передати всі дані в PyGTK, рядки Unicode також не дуже корисні для мене. Тож у мене є інший метод рекурсивного перетворення. Насправді він також потрібен для перетворення типів JSON - json.dump () може застосовуватися до будь-яких нелітералів, як-от об'єкти Python. Хоча не перетворює індекси 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

Тут може виникнути єдина проблема, якщо вам потрібні ключі в словнику, перетвореному з unicode. Хоча ця реалізація перетворить значення, вона підтримує ключі unicode. Якщо ви створюєте 'newobj', використовуйте newobj [str (i)] = ... і призначайте obj = newobj, коли закінчите, ключі також будуть перетворені.
Ніл Стублен

Це може бути гарнішим з розумінням або краще, перетворивши ключі. Це також однозначно; він обидві мутує об'єкти на місці (у випадку словників) і повертає нове значення, яке не відповідає вбудованим методам колекції Python, які або мутують поточний об'єкт, або повертають новий, але не обидва.
Марк Амері

1

У мене був диктант JSON як струна. Ключі та значення були об'єктами unicode, як у наступному прикладі:

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

Я міг би використовувати byteifyфункцію, запропоновану вище, перетворюючи рядок в dictоб'єкт за допомогою ast.literal_eval(myStringDict).


Наведений вами приклад - це не приклад JSON. {u'key':u'value'}не JSON.
Марк Амері

2
Я прекрасно знаю, що це не JSON. Ось як це було розібрано із зовнішнього джерела в моєму сценарії python. Якби це було JSON безпосередньо, як у наступному прикладі, мені не знадобиться функція byteify, позначена як рішення: {"firstName": "John", "lastName": "Doe"}. Було б просто чудово, якби перед голосуванням ви прочитали відповіді. Дякую.
narko

1

Підтримка Python2 & 3 за допомогою гачка (від 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)

Повернення:

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

0

Це вже пізно до гри, але я створив цей рекурсивний ролик. Це працює для моїх потреб, і я думаю, що це відносно повно. Це може вам допомогти.

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

Просто передайте йому об'єкт JSON так:

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

У мене це є приватним членом класу, але ви можете змінити метод, як вважаєте за потрібне.


Я зіткнувся з проблемою, коли я намагаюся проаналізувати JSON і передати отримане відображення у функцію ** kwargs. Схоже, імена параметрів функцій не можуть бути однокодовими, тому функція _parseJSON чудова. Якщо є простіший спосіб, хтось може повідомити мене.
Ніл Стублен

1
У цьому коді є проблема - ви здійснюєте рекурсивний дзвінок у розділі «Список», який вийде з ладу, якщо самі елементи списку не є словниками.
I82Муч

Окрім помилки, описаної @ I82Much, вона також називається погано (вона насправді не розбирає JSON; json.loadsвиклик потрібен спочатку), довільно намагається перетворити рядки в ints без пояснених причин і не є копіюванням і готова паста.
Марк Амері

0

Я переписав _parse_json () Веллса для обробки випадків, коли сам об'єкт json є масивом (мій випадок використання).

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

ось рекурсивний кодер, написаний на C: https://github.com/axiros/nested_encode

Витрати на ефективність для "середніх" структур близько 10% порівняно з 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%

використовуючи цю тестову структуру:

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 іноді я все ще стикаюся з цією проблемою. Наприклад, отримуючи відповідь від REST API та завантажуючи текст відповіді на JSON, я все одно отримую рядки Unicode. Знайшов просте рішення за допомогою json.dumps ().

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

-1

Я також зіткнувся з цією проблемою, і, маючи справу з JSON, я придумав невеликий цикл, який перетворює ключі unicode в рядки. (simplejson на GAE не повертає рядкові клавіші.)

obj є об'єктом, декодованим з JSON:

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

kwargsце те, що я передаю конструктору програми GAE (який не любить unicodeключі в **kwargs)

Не настільки надійний, як рішення від Wells, але набагато менший.


-1

Я пристосував код з відповіді на Марк Емері , в зокрема , для того , щоб позбутися відisinstance для профі качка-типування.

Кодування проводиться вручну і ensure_asciiвимикається. Документи python для цього json.dumpговорять

Якщо true_ascii є True (за замовчуванням), всі символи, що не є ASCII у висновку, видаляються з \ uXXXX послідовностями

Відмова: у докт-тесті я використав угорську мову. Деякі помітні кодовані символи, пов'язані з угорською мовою, це: кодування cp852IBM / OEM, що використовується, наприклад. в DOS (іноді його називають ascii , неправильно я думаю, це залежить від налаштування кодової сторінки ), що cp1250використовується, наприклад. в Windows (іноді їх називають ansi , залежно від налаштувань мови), а iso-8859-2іноді використовується на серверах http. Текст тесту Tüskéshátú kígyóbűvölőприписується Колтаю Ласло (рідна форма особистого імені) та походить з Вікіпедії .

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

Я також хотів би підкреслити відповідь на Jarret Гарді , який посилається на JSON специфікації , процитувати:

Рядок - це набір нульових або більше символів Unicode

У моєму випадку я мав файли з json. Вони utf-8кодуються файлами.ensure_asciiприводить до належних, але не дуже читаних файлів json, тому я адаптував відповідь Марка Амері відповідно до моїх потреб.

Доктест не особливо продуманий, але я ділюсь кодом у надії, що він комусь стане у нагоді.


Я не впевнений, що я бачу переваги використання набору качок тут? Ми знаємо, що колекції, повернуті з json.loadsяких, будуть списками або диктами, а не певним або визначеним бібліотекою типом, який реалізує свої методи і магічні методи, так чому б не просто зробити isinstanceперевірку? Хіба це не простіше зрозуміти, ніж перевірити наявність iteritemsчи iterприйме об’єкт як аргумент?
Марк Амері

@MarkAmery мова йде про звалищах, а не про вантажі. якщо ви створюєте дані для скидання - на відміну від завантаження - ви не можете бути впевнені, що це таке. ідея полягала в тому, щоб він походив з будь-якого місця в коді.
n611x007

-2

Ознайомтеся з цією відповіддю на подібне питання, як це, в якому зазначено це

Префікс u просто означає, що у вас є рядок Unicode. Якщо ви дійсно використовуєте рядок, він не відображатиметься у ваших даних. Не викидайте друкований вихід.

Наприклад, спробуйте це:

print mail_accounts[0]["i"]

Ви не побачите і


Не вірно, якщо, наприклад, ви хочете відформатувати щось, що містить рядок Unicode, в Py2. наприклад, '{}'.format({u'x' : u'y'})все ще включає в себе U.
Ponkadoodle
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.