Как получить строковые объекты вместо 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», это просто альтернативная ветка.
user2589273

11
Странно видеть такой комментарий в декабре 2017 года - Python 2 устарел, и после 1 января 2020 года, то есть менее 2 лет, никакого обслуживания не будет: pythonclock.org
Zaar Hai

1
@ZaarHai Множество людей застряли в Python 2 против их воли. Есть много приложений, которые встраивают свою собственную версию Python для автоматизации и создания сценариев, поэтому люди должны использовать ее до обновления поставщика (я смотрю на вас Maya, Houdini, Nuke ..)
Джорди

1
@ Джорди, я, конечно, знаю и понимаю это. Мой комментарий был о терминологии - 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 сначала со строками в юникоде, а затем повторяется по всему декодированному значению для преобразования всех строк в строки байтов. Это имеет пару нежелательных эффектов:

  • Копия всей декодированной структуры создается в памяти
  • Если ваш объект JSON действительно глубоко вложен (500 уровней или более), то вы достигнете максимальной глубины рекурсии Python

Этот ответ устраняет обе эти проблемы с производительностью, используя object_hookпараметр json.loadи json.loads. Из документов :

object_hookявляется необязательной функцией, которая будет вызываться с результатом декодирования любого литерала объекта (a dict). Возвращаемое значение object_hook будет использоваться вместо dict. Эта функция может быть использована для реализации пользовательских декодеров

Поскольку словари, вложенные во многие уровни в других словарях, передаются по object_hook мере их декодирования , мы можем байтировать любые строки или списки внутри них на этом этапе и избежать необходимости глубокой рекурсии позже.

Ответ Марка не подходит для использования в том виде, object_hookв каком он есть, поскольку он повторяется во вложенных словарях. Мы предотвращаем эту рекурсию в этом ответе с ignore_dictsпараметром to _byteify, который передается ему всегда, кроме случаев, когда object_hookон передается новым dictдля byteify. ignore_dictsФлаг говорит _byteifyигнорировать dictS , так как они уже byteified.

Наконец, наши реализации json_load_byteifiedи json_loads_byteifiedвызов _byteifyignore_dicts=True) для результата, возвращаемого json.loadили json.loadsобрабатывающего случай, когда декодируемый текст JSON не имеет a dictна верхнем уровне.


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

Нет проблем, Марк, большое спасибо. Мне нравится ваше редактирование, оно гораздо более объяснительно, чем мой оригинал. Возможно, однажды я научусь давать более краткие ответы.
Мирек Мискуф

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 - конвертации нет!

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

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

преобразование

Как указано, конверсии нет! Если вы не можете быть уверены, что имеете дело только со значениями ASCII (и не можете быть уверены в этом большую часть времени), лучше использовать функцию преобразования :

Я использовал пару раз от Mark Amery , он отлично работает и очень прост в использовании. object_hookВместо этого вы можете использовать аналогичную функцию , так как она может повысить производительность больших файлов. Посмотрите чуть более сложный ответ от Mirec Miskuf для этого.


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 возвращали строки байтов вместо строк Юникода. Однако эта короткая и простая рекурсивная функция преобразует любой декодированный объект 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», они в основном наивно подразумевают, что им нужны байты, а не теоретические символы юникода. (а не ascii, поскольку они все еще хотят знаки фунта на другом конце)
Дэнни Стейпл

Мне нравится это, он работает почти так же, как мой симпатичный принтер, так как я знаю, что json не создает кортеж, вы должны также добавить исключение для кортежа.
y.petremann

Это ужасно неэффективно, требуя от вас рекурсивного обхода узлов, которые вам могут не понадобиться. Модуль json дает вам возможность сделать это намного эффективнее. object_hookХотя приведенный ниже ответ на самом деле гораздо хуже, чем этот, но, используя object_pairs_hook, вы можете придумать достаточно эффективный метод, который не требует рекурсии или повторного посещения узлов, которые не содержат строк.
Трэвис Дженсен

1
@TravisJensen Интересно. Этот object_pairs_hookметод, возможно, немного сложнее для понимания, чем этот (вам нужно понять, как работает параметр и почему списки и разметки требуют другой обработки), и выигрыш в производительности не будет иметь значения для большинства людей ... но я ожидаю он существует, особенно для тех, кто имеет дело с необычно глубоко вложенным объектом JSON.
Марк Амери

plus1 Это самый краткий ответ; кроме того, PyYAML - это боль в установке. Единственное, что может быть лучше, это каким-то образом преобразовать микропоток, чтобы он не использовал 4X память.
personal_cloud

74

Вы можете использовать object_hookпараметр для json.loadsпередачи в конвертере. Вам не нужно делать преобразование после факта. jsonМодуль всегда будет проходить object_hookdicts только, и он будет рекурсивно пройти в вложенной dicts, так что вам не придется рекурсия в вложенную dicts себя. Я не думаю, что я бы конвертировал строки Юникода в числа, как показывает Уэллс. Если это строка в кодировке 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(неупорядоченная коллекция пар ключ: значение с символом ':', разделяющим ключ и значение, разделенные запятыми и заключенными в фигурные скобки), но не если это, скажем, JSON Array. Так что если дано JSON Arrayкак ["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 нет различий между строковыми и юникодными объектами. Они все строки в JavaScript.

Я думаю, что JSON подходит для возврата объектов в кодировке Unicode . На самом деле, я бы не стал соглашаться на меньшее, поскольку строки javascript на самом деле являются unicodeобъектами (т.е. строки JSON (javascript) могут хранить любые символы Юникода), поэтому имеет смысл создавать 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 возвращает строковые объекты вместо юникода.
Брут

1
@ Brutus: я думаю, что json прав, чтобы возвращать объекты Unicode. На самом деле, я бы не стал соглашаться на меньшее, поскольку строки javascript фактически являются объектами Unicode. Я имею в виду, что строки json (javascript) могут хранить любые символы Юникода, поэтому имеет смысл создавать объекты Юникода при переводе из json. Вы должны действительно исправить свои библиотеки вместо этого.
nosklo

16

Существует легкий обходной путь.

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

Хотя это и не идеальный ответ, он довольно далеко уходит, если вы планируете полностью игнорировать Юникод. В 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 не содержит экранированный solidus ( \/) внутри строки или escape-последовательность Unicode (например "\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. Вы должны заботиться о списках, но, как видите, объект в списке будет правильно преобразован, и вам не нужно повторяться, чтобы это произошло.

РЕДАКТИРОВАТЬ: сотрудник отметил, что 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, каждый элемент отображается в юникоде. Штопать.
Рассмотрение

1
@rsaw Хорошая мысль! Поскольку object_pairs_hookдля объектов вызывается единственное , если ваш текст JSON имеет список строк на верхнем уровне, это решение не будет выполнено. Нет способа исправить это, не вызвав какую-либо функцию для возвращаемой вещи json.load; ни один из json.loadкрючков не может гарантировать, что вы сможете справиться с каждой строкой. Я думаю, что это достаточно большой недостаток для меня, чтобы продолжать рекомендовать свое решение, используя крючки.
Марк Амери

-1, потому что я только что понял, что Mirec Miskuf уже опубликовал ответ об объектном хуке, который не имеет ни недостатков ни в подходе Майка Бреннана (многократно повторяет байтизацию одних и тех же словарей), ни в этом (не удается байтировать вложенные списки или списки верхнего уровня или строки). Я не уверен, почему его ответ томился почти без внимания, а этот, который уступает, быстро набрал голоса.
Марк Эмери

9

Боюсь, что нет способа достичь этого автоматически в библиотеке simplejson.

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

Причина, по которой simplejson выводит Unicode, заключается в том, что спецификация json специально упоминает, что «Строка - это набор из нуля или более символов Unicode» ... поддержка Unicode предполагается как часть самого формата. Реализация Simplejson scanstringзаходит так далеко, что сканирует и интерпретирует экранированные символы Юникода (даже проверку ошибок для искаженных многобайтовых представлений наборов символов), поэтому единственный способ, которым он может надежно вернуть значение, - это использовать Unicode.

Если у вас есть устаревшая библиотека, в которой требуется библиотека str, я рекомендую вам либо тщательно проанализировать вложенную структуру данных после анализа (я признаю, что вы явно сказали, что вы хотели бы избежать ... извините), либо, возможно, обернуть свои библиотеки каким-то образом. фасад, где вы можете массировать входные параметры на более детальном уровне. Второй подход может быть более управляемым, чем первый, если ваши структуры данных действительно глубоко вложены.


4

Как правильно замечает Марк (Амери): Использование десериализатора 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]

Таким образом, десериализация, включая полное обход дерева и кодирование, вполне соответствует порядку реализации на основе языка j в Си. Я нахожу это удивительно быстрым и более надежным, чем нагрузка yaml в глубоко вложенных структурах. И менее подвержены ошибкам безопасности, глядя на yaml.load.

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

Это особенно верно, если ваша структура json происходит из поля, содержащего пользовательский ввод. Потому что тогда вам, вероятно, нужно все равно пройтись по вашей структуре - независимо от ваших желаемых внутренних структур данных («сэндвич Юникод» или только байтовые строки).

Зачем?

Unicode нормализация . Для незнающих: возьмите обезболивающее и прочитайте это .

Таким образом, используя рекурсию byteify, вы убиваете двух зайцев одним выстрелом:

  1. получить ваши байты из вложенных JSON-дампов
  2. нормализовать вводимые пользователем значения, чтобы вы могли найти вещи в своем хранилище.

В моих тестах оказалось, что замена input.encode ('utf-8') на unicodedata.normalize ('NFC', input) .encode ('utf-8') была даже быстрее, чем без NFC - но это сильно зависит от выборки данных, я думаю.


3

Гоча, что simplejsonи jsonдва различных модуля, по крайней мере , в порядке , они имеют дело с Юникод. Вы имеете jsonв Py 2.6+, и это дает вам значения Unicode, тогда как simplejsonвозвращает строковые объекты. Просто попробуйте easy_install-ing simplejson в вашей среде и посмотрите, работает ли это. Это для меня.


2

Просто используйте pickle вместо json для dump и load, вот так:

    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_loadв YAML, я не знаю, существует ли что-то подобное для маринада .
Брут

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. Если вы создаете 'newobj', используете newobj [str (i)] = ... и назначаете obj = newobj, когда вы закончите, ключи также будут конвертированы.
Нил Стублен

Это может быть лучше с пониманием или лучше с помощью преобразования ключей. Это также однотипно; он как мутирует объекты на месте (в случае словарей), так и возвращает новое значение, что несовместимо со встроенными методами сбора данных Python, которые либо мутируют текущий объект, либо возвращают новый, но не оба.
Марк Амери

1

У меня был JSON dict в виде строки. Ключи и значения были объектами 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"}. Было бы здорово, если бы перед голосованием вы прочитали ответы. Спасибо.
Нарко

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. Похоже, что имена параметров функций не могут быть в Unicode, поэтому ваша функция _parseJSON великолепна. Если есть более простой способ, кто-нибудь может дать мне знать.
Нил Стублен

1
У этого кода есть проблема - вы делаете рекурсивный вызов в элементе List, который потерпит неудачу, если элементы списка сами не являются словарями.
10

Помимо ошибки, описанной @ I82Much, она также имеет неправильное название (фактически она не анализирует JSON; сначала json.loadsнеобходим вызов), произвольно пытается преобразовать строки в целые без объяснения причины и не копирует и не паста готова.
Марк Амери

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)

Не такой надежный, как решение от Уэллса, но гораздо меньше.


-1

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

Кодировка выполняется вручную и ensure_asciiотключена. Документы Python для json.dumpговорит, что

Если sure_ascii равно True (по умолчанию), все не-ASCII-символы в выводе экранируются последовательностями \ uXXXX

Отказ от ответственности: в doctest я использовал венгерский язык. Некоторые известные венгерские кодировки символов: cp852используемая кодировка IBM / OEM, например. в DOS (иногда его называют ascii , я думаю, что это неправильно, это зависит от настроек кодовой страницы ), cp1250например , используется. в Windows (иногда называемый ansi , в зависимости от настроек локали), а iso-8859-2иногда используется на http-серверах. Тестовый текст Tüskéshátú kígyóbűvölőотносится к Koltai László (родная личная форма имени) и из Википедии .

# 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 спецификации , процитировать:

Строка представляет собой набор из нуля или более символов Юникода

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

Документ не особо продуман, но я делюсь кодом в надежде, что он кому-нибудь пригодится.


Я не уверен, что вижу преимущества использования утки здесь? Мы знаем, что возвращаемые коллекции json.loadsбудут списками или диктатами, а не каким-то определенным пользователем или определенным библиотекой типом, который реализует их методы и магические методы, так почему бы просто не выполнить isinstanceпроверку? Разве это не легче понять, чем проверять существование объекта iteritemsили iterпринимать объект в качестве аргумента?
Марк Амери

@MarkAmery это дампы, а не нагрузки. если вы создаете данные для дампа, а не загружаете их, вы не можете быть уверены, что это такое. Идея состояла в том, чтобы позволить этому прийти откуда угодно в коде.
n611x007

-2

Проверьте этот ответ на подобный вопрос, который утверждает, что

Префикс u означает, что у вас есть строка Unicode. Когда вы действительно используете строку, она не появится в ваших данных. Не поддавайтесь распечатке.

Например, попробуйте это:

print mail_accounts[0]["i"]

Вы не увидите вас.


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