Как получить строковые объекты вместо Unicode от JSON?

Я использую 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 и forward.

Ответ 1

Решение с 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 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'}

Как это работает и почему я буду использовать его?

Функция Mark Amery короче и яснее, чем эти, и что это за точка? Почему вы хотите использовать их?

Чисто для производительности. Отметка ответа полностью декодирует текст JSON с помощью строк unicode, а затем рекурсирует через все декодированное значение, чтобы преобразовать все строки в байтовые строки. Это имеет несколько нежелательных эффектов:

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

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

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

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

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

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

Ответ 2

В то время как есть несколько хороших ответов, я закончил использование 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 - конверсии не было!

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

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

Конверсия

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

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

Ответ 3

Нет встроенной опции, чтобы функции json-модуля возвращали байтовые строки вместо строк unicode. Однако эта короткая и простая рекурсивная функция преобразует любой декодированный объект JSON с использованием строк юникода в строки байтов с кодировкой 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. Ответ Mirec Miskuf до сих пор является единственным, кому удается правильно это сделать, хотя, как следствие, это значительно сложнее, чем мой подход.

Ответ 4

Вы можете использовать параметр object_hook для json.loads для передачи в конвертер. После этого вам не нужно делать конверсию. Модуль json всегда будет передавать только теги object_hook, и он будет рекурсивно проходить в вложенных диктофонах, поэтому вам не придется самостоятельно переписывать вложенные dicts, Я не думаю, что я бы преобразовал строки unicode в числа, подобные шоу Wells. Если это строка в Юникоде, она была указана как строка в файле 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)

Ответ 5

Это потому, что json не имеет разницы между строковыми объектами и объектами unicode. Это все строки в javascript.

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

Ответ 6

Существует легкая работа.

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. Полный ответ становится быстро волосистым.

Ответ 7

Я боюсь, что нет возможности добиться этого автоматически в библиотеке simplejson.

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

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

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

Ответ 8

Ответ Майка Бреннана близок, но нет причин перешагивать всю структуру. Если вы используете параметр 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, который, если бы вы разбирали огромный документ, может стоить времени.

Ответ 9

Как правильно отмечает Mark (Amery): Использование дескриптора PyYaml в json-дампе работает только в том случае, если у вас только ASCII. По крайней мере, из коробки.

Два быстрых комментария к подходу PyYaml:

  • НИКОГДА использовать yaml.load для данных из поля. Его функция (!) Yaml выполняет произвольный код, скрытый внутри структуры.

  • Вы можете заставить его работать и для не 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)
    

Но производительность по сравнению с Mark Amery не соответствует:

Бросьте некоторые глубоко вложенные образцы dicts на два метода, я получаю это (с 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]

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

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

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

Почему?

Юникод нормализация. Для незнания: возьмите болеутоляющее средство и прочитайте это.

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

  • получить ваши байты из вложенных дампов json
  • получить значения пользовательских значений, нормализованные, чтобы вы нашли материал в своем хранилище.

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

Ответ 10

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

Ответ 11

Итак, я столкнулся с той же проблемой. Угадайте, каков был первый результат 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

Ответ 12

Просто используйте pickle вместо 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'}

Ответ 13

Поддержка Python2 & 3 с помощью hook (от fooobar.com/questions/29132/...)

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 anything else, return it in its original form
    return data

w = r.json(object_hook=_byteify)
print(w)

Возврат:

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

Ответ 14

Это уже поздно, но я создал этот рекурсивный заклинатель. Он работает для моих нужд, и я думаю, что он относительно завершен. Это может помочь вам.

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)

У меня это как частный член класса, но вы можете перепрофилировать метод по своему усмотрению.

Ответ 15

Я переписал Wells _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

Ответ 16

У меня был JSON dict в виде строки. Ключи и значения были объектами юникода, как в следующем примере:

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

Я мог бы использовать функцию byteify, предложенную выше, путем преобразования строки в объект dict с помощью ast.literal_eval(myStringDict).

Ответ 17

здесь рекурсивный кодировщик, написанный на 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)

Ответ 18

Отметьте this ответ на аналогичный вопрос, например:

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

Например, попробуйте следующее:

print mail_accounts[0]["i"]

Вы не увидите u.

Ответ 19

С Python 3.6 иногда я сталкиваюсь с этой проблемой. Например, когда вы получаете ответ от REST API и загружаете текст ответа в JSON, я все равно получаю строки unicode. Нашел простое решение с помощью json.dumps().

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

Ответ 20

Я столкнулся с этой проблемой и, имея дело с 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, но намного меньше.

Ответ 21

Я адаптировал код из ответа Mark Amery, в частности, чтобы избавиться из isinstance для плюсов печати. ​​

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

Если security_ascii имеет значение True (по умолчанию), все символы, отличные от ASCII, выводятся с последовательностями \uXXXX

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

# coding: utf-8
"""
This file should be encoded correctly with utf-8.
"""
import json

def encode_items(input, encoding='utf-8'):
    u"""original from: https://stackoverflow.com/a/13101776/611007
    adapted by SO/u/611007 (20150623)
    >>> 
    >>> ## run this with `python -m doctest <this file>.py` from command line
    >>> 
    >>> txt = u"Tüskéshátú kígyóbűvölő"
    >>> txt2 = u"T\\u00fcsk\\u00e9sh\\u00e1t\\u00fa k\\u00edgy\\u00f3b\\u0171v\\u00f6l\\u0151"
    >>> txt3 = u"uúuutifu"
    >>> txt4 = b'u\\xfauutifu'
    >>> # txt4 shouldn't be 'u\\xc3\\xbauutifu', string content needs double backslash for doctest:
    >>> assert u'\\u0102' not in b'u\\xfauutifu'.decode('cp1250')
    >>> txt4u = txt4.decode('cp1250')
    >>> assert txt4u == u'u\\xfauutifu', repr(txt4u)
    >>> txt5 = b"u\\xc3\\xbauutifu"
    >>> txt5u = txt5.decode('utf-8')
    >>> txt6 = u"u\\u251c\\u2551uutifu"
    >>> there_and_back_again = lambda t: encode_items(t, encoding='utf-8').decode('utf-8')
    >>> assert txt == there_and_back_again(txt)
    >>> assert txt == there_and_back_again(txt2)
    >>> assert txt3 == there_and_back_again(txt3)
    >>> assert txt3.encode('cp852') == there_and_back_again(txt4u).encode('cp852')
    >>> assert txt3 == txt4u,(txt3,txt4u)
    >>> assert txt3 == there_and_back_again(txt5)
    >>> assert txt3 == there_and_back_again(txt5u)
    >>> assert txt3 == there_and_back_again(txt4u)
    >>> assert txt3.encode('cp1250') == encode_items(txt4, encoding='utf-8')
    >>> assert txt3.encode('utf-8') == encode_items(txt5, encoding='utf-8')
    >>> assert txt2.encode('utf-8') == encode_items(txt, encoding='utf-8')
    >>> assert {'a':txt2.encode('utf-8')} == encode_items({'a':txt}, encoding='utf-8')
    >>> assert [txt2.encode('utf-8')] == encode_items([txt], encoding='utf-8')
    >>> assert [[txt2.encode('utf-8')]] == encode_items([[txt]], encoding='utf-8')
    >>> assert [{'a':txt2.encode('utf-8')}] == encode_items([{'a':txt}], encoding='utf-8')
    >>> assert {'b':{'a':txt2.encode('utf-8')}} == encode_items({'b':{'a':txt}}, encoding='utf-8')
    """
    try:
        input.iteritems
        return {encode_items(k): encode_items(v) for (k,v) in input.iteritems()}
    except AttributeError:
        if isinstance(input, unicode):
            return input.encode(encoding)
        elif isinstance(input, str):
            return input
        try:
            iter(input)
            return [encode_items(e) for e in input]
        except TypeError:
            return input

def alt_dumps(obj, **kwargs):
    """
    >>> alt_dumps({'a': u"T\\u00fcsk\\u00e9sh\\u00e1t\\u00fa k\\u00edgy\\u00f3b\\u0171v\\u00f6l\\u0151"})
    '{"a": "T\\xc3\\xbcsk\\xc3\\xa9sh\\xc3\\xa1t\\xc3\\xba k\\xc3\\xadgy\\xc3\\xb3b\\xc5\\xb1v\\xc3\\xb6l\\xc5\\x91"}'
    """
    if 'ensure_ascii' in kwargs:
        del kwargs['ensure_ascii']
    return json.dumps(encode_items(obj), ensure_ascii=False, **kwargs)

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

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

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

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