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

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

при загрузке этих файлов с помощью либо json или simplejson, все мои строковые значения приводятся к объектам Unicode вместо строковых объектов. Проблема в том, что я должен использовать данные с некоторыми библиотеками, которые принимают только строковые объекты. Я!--14-->невозможно изменить библиотеки и не обновлять их.

можно ли получить строковые объекты вместо 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 и вперед.

21 ответов


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

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

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


в то время как есть некоторые хорошие ответы здесь, я закончил с помощью 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']

Примечания

некоторые вещи, чтобы отметить хотя:

  • Я string объекты потому что все мои записи кодировка 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 вместо этого, поскольку это может повысить производительность больших файлов. См. немного более вовлеченный ответ от Miskuf Mirec для этого.


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

можно использовать на json.loads пройти в конвертере. Вам не нужно делать преобразование после факта. The json модуль всегда будет передавать object_hook только дикты, и он будет рекурсивно проходить во вложенных диктах, поэтому вам не нужно рекурсировать во вложенные дикты самостоятельно. Я не думаю, что я бы преобразовал строки unicode в числа, такие как шоу Уэллса. Если это строка unicode, она была указана как строка в файле JSON, поэтому она должна быть строкой (или файл плохой).

кроме того, я бы постарался избежать чего-то вроде str(val) на


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

Я думаю JSON имеет право возвращать объекты unicode. На самом деле, я бы не принял ничего меньшего, так как javascript strings на самом деле unicode объекты (т. е. строки JSON (javascript) могут хранить любой символа Юникода), поэтому имеет смысл создать unicode объекты при переводе строк из JSON. Простой строки просто не подходят, так как библиотека должна угадать кодировку, которую вы хотите.

лучше использовать unicode string объекты везде. Поэтому лучше всего обновить библиотеки, чтобы они могли работать с объектами unicode.

но если вы действительно хотите bytestrings, просто Закодируйте результаты в кодировку по вашему выбору:

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

существует простой обходной путь.

TL; DR-Use ast.literal_eval() вместо json.loads(). Оба!--4--> и 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. Полный ответ быстро становится волосатым.


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

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

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

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


ответ Майка Бреннана близко, но нет причин повторно пересекать всю структуру. Если вы используете object_hook_pairs (Python 2.7+) параметр:

object_pairs_hook - необязательная функция, которая будет вызываться в результате декодирования любого объектного литерала с упорядоченным списком пар. Возвращаемое значение object_pairs_hook будет использоваться вместо dict. Эту функцию можно использовать для реализации custom декодеры, которые полагаются на порядок декодирования пар ключей и значений (например,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:

to

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


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

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

  1. никогда используйте yaml.загрузка данных из поля. Его особенность(!) yaml для выполнения произвольного кода, скрытого в структуре.

  2. вы можете сделайте его работать также для non 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] = Time delta of json.нагрузок(в формате JSON.dumps (m))):

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

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

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

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

почему?

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

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

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

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


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


Итак, я столкнулся с той же проблемой. Угадайте, каким был первый результат Google.

поскольку мне нужно передать все данные PyGTK, строки unicode мне тоже не очень полезны. Итак, у меня есть другой метод рекурсивного преобразования. На самом деле это также необходимо для преобразования typesafe 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

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

поддержка 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'}

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

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)

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


я переписал 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

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

накладные расходы на производительность для" средних " структур около 10% по сравнению с json.нагрузки.

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%

С помощью этого teststructure осуществляет:

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)

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

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

Я мог бы использовать


проверить этой ответ на подобный вопрос, как это, который гласит, что

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

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

print mail_accounts[0]["i"]

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


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

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

Я тоже столкнулся с этой проблемой, и, имея дело с JSON, я придумал небольшой цикл, который преобразует ключи unicode в строки. (simplejson on 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)

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


я адаптировал код из ответ of Марк Амеры, в частности, для того, чтобы избавиться от isinstance для профессионалов duck-typing.

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

если ensure_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 Ласло (родная форма личного имени) и от Википедия.

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

я также хотел бы выделить ответ of Джаррет Харди, который ссылается на JSON spec, цитирую:

строка-это набор из нуля или более символов Unicode

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

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