¿Cómo obtener objetos de cadena en lugar de Unicode desde JSON?

Estoy usando Python 2 para analizar JSON desde archivos de texto codificados ASCII .

Al cargar estos archivos con json o simplejson , todos mis valores de cadena se simplejson en objetos Unicode en lugar de objetos de cadena. El problema es que tengo que usar los datos con algunas bibliotecas que solo aceptan objetos de cadena. No puedo cambiar las bibliotecas ni actualizarlas.

¿Es posible obtener objetos de cadena en lugar de objetos Unicode?

Ejemplo

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

Actualizar

Esta pregunta se hizo hace mucho tiempo , cuando estaba atrapado con Python 2 . Una solución fácil y limpia para hoy es usar una versión reciente de Python, es decir, Python 3 y versiones posteriores.

Una solución con 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 

Ejemplo de uso:

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

¿Cómo funciona esto y por qué lo usaría?

La función de Mark Amery es más corta y clara que estas, ¿cuál es el punto de ellas? ¿Por qué querrías usarlos?

Puramente para el rendimiento . La respuesta de Mark descodifica el texto JSON completamente primero con cadenas Unicode, luego recurre a través de todo el valor descodificado para convertir todas las cadenas en cadenas de bytes. Esto tiene un par de efectos indeseables:

  • Una copia de toda la estructura decodificada se crea en la memoria
  • Si su objeto JSON está realmente nested (500 niveles o más), entonces alcanzará la profundidad máxima de recursión de Python

Esta respuesta mitiga ambos problemas de rendimiento utilizando el parámetro json.load de json.load y json.loads . De los documentos :

object_hook es una función opcional que se llamará con el resultado de cualquier objeto decodificado literalmente (un dict ). El valor de retorno de object_hook se utilizará en lugar del dict . Esta característica se puede utilizar para implementar decodificadores personalizados

Ya que los diccionarios nesteds en muchos niveles profundos en otros diccionarios se pasan a object_hook medida que se decodifican , podemos byteificar cualquier cadena o lista dentro de ellos en ese momento y evitar la necesidad de una recursión profunda más adelante.

La respuesta de Mark no es adecuada para su uso como object_hook tal como está, porque se repite en diccionarios nesteds. ignore_dicts esa recursión en esta respuesta con el parámetro ignore_dicts a _byteify , que se le pasa en todo momento, excepto cuando object_hook pasa un nuevo dictado a byteify. La bandera ignore_dicts le dice a _byteify que ignore los dict s ya que ya fueron bytes.

Finalmente, nuestras implementaciones de json_load_byteified y json_loads_byteified llaman a _byteify (con ignore_dicts=True ) en el resultado devuelto por json.load o json.loads para manejar el caso en el que el texto JSON que se está descodificando no tiene un dict en el nivel superior.

Si bien hay algunas buenas respuestas aquí, terminé usando PyYAML para analizar mis archivos JSON, ya que proporciona las claves y los valores como cadenas de tipo str lugar de tipo unicode . Como JSON es un subconjunto de YAML, funciona muy bien:

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

Notas

Algunas cosas a tener en cuenta sin embargo:

  • Obtengo objetos de cadena porque todas mis entradas están codificadas en ASCII . Si usara entradas codificadas en Unicode, las recuperaría como objetos Unicode , ¡no hay conversión!

  • Usted debería (probablemente siempre) usar la función safe_load de safe_load ; si lo usa para cargar archivos JSON, de todos modos no necesita la “potencia adicional” de la función de load .

  • Si desea un analizador YAML que tenga más soporte para la versión 1.2 de la especificación (y analice correctamente los números muy bajos ) intente con Ruamel YAML : pip install ruamel.yaml e import ruamel.yaml as yaml era todo lo que necesitaba en mis pruebas.

Conversión

Como se dijo, no hay conversión! Si no puede estar seguro de tratar solo con los valores ASCII (y no puede estar seguro la mayor parte del tiempo), utilice mejor una función de conversión :

Usé el de Mark Amery un par de veces, funciona muy bien y es muy fácil de usar. También puede usar una función similar como object_hook , ya que podría obtener un aumento de rendimiento en archivos grandes. Vea la respuesta un poco más complicada de Mirec Miskuf para eso.

No hay una opción incorporada para hacer que las funciones del módulo json devuelvan cadenas de bytes en lugar de cadenas Unicode. Sin embargo, esta corta y simple función recursiva convertirá cualquier objeto JSON descodificado de usar cadenas Unicode a cadenas de bytes codificadas en 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 

Simplemente llame a esto en la salida que obtiene de una llamada json.load o json.loads .

Un par de notas:

  • Para admitir Python 2.6 o anterior, reemplace return {byteify(key): byteify(value) for key, value in input.iteritems()} con return dict([(byteify(key), byteify(value)) for key, value in input.iteritems()]) , ya que las comprensiones del diccionario no fueron compatibles hasta Python 2.7.
  • Dado que esta respuesta recurre a través de todo el objeto descodificado, tiene un par de características de rendimiento indeseables que se pueden evitar con un uso muy cuidadoso de los parámetros object_hook o object_pairs_hook . La respuesta de Mirec Miskuf es hasta ahora la única que logra llevar a cabo esto correctamente, aunque como consecuencia, es significativamente más complicada que mi enfoque.

Puede usar el parámetro object_hook para que json.loads pase un convertidor. No tienes que hacer la conversión después del hecho. El módulo json siempre pasará solo los object_hook object_hook, y pasará recursivamente en dicts nesteds, por lo que no tiene que recurrir a dicts nesteds usted mismo. No creo que convierta las cadenas Unicode en números como los de Wells. Si se trata de una cadena Unicode, se citó como una cadena en el archivo JSON, por lo que se supone que es una cadena (o el archivo es malo).

Además, trataría de evitar hacer algo como str(val) en un objeto unicode . Debería usar value.encode(encoding) con una encoding válida, dependiendo de lo que espera su lib externo.

Así por ejemplo:

 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) 

Eso es porque json no tiene ninguna diferencia entre los objetos de cadena y los objetos Unicode. Son todas las cadenas en javascript.

Creo que JSON tiene razón al devolver objetos Unicode . De hecho, no aceptaría nada menos, ya que las cadenas javascript son de hecho objetos unicode (es decir, las cadenas JSON (javascript) pueden almacenar cualquier tipo de carácter Unicode), por lo que tiene sentido crear objetos unicode al traducir cadenas desde JSON. Las cadenas simples no encajarían ya que la biblioteca tendría que adivinar la encoding que desea.

Es mejor usar objetos de cadena unicode todas partes. Así que su mejor opción es actualizar sus bibliotecas para que puedan tratar con objetos Unicode.

Pero si realmente quiere secuencias de bytes, simplemente codifique los resultados a la encoding de su elección:

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

Existe una solución fácil.

TL; DR: use ast.literal_eval() lugar de json.loads() . Tanto ast como json están en la biblioteca estándar.

Si bien no es una respuesta “perfecta”, llega bastante lejos si su plan es ignorar por completo a Unicode. En 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)) 

da:

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

Esto se vuelve más peludo cuando algunos objetos son realmente cuerdas Unicode. La respuesta completa se vuelve peluda rápidamente.

La respuesta de Mike Brennan es cercana, pero no hay razón para volver a atravesar toda la estructura. Si usa el object_hook_pairs (Python 2.7+):

object_pairs_hook es una función opcional que se llamará con el resultado de cualquier objeto decodificado literalmente con una lista ordenada de pares. El valor de retorno de object_pairs_hook se utilizará en lugar del dict . Esta función se puede usar para implementar decodificadores personalizados que dependen del orden en que se decodifican los pares de clave y valor (por ejemplo, collections.OrderedDict OrderedDict recordará el orden de inserción). Si también se define object_pairs_hook , el object_pairs_hook tiene prioridad.

Con él, obtienes cada objeto JSON entregado, por lo que puedes hacer la deencoding sin necesidad de recursión:

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

Tenga en cuenta que nunca tengo que llamar al gancho de forma recursiva, ya que cada objeto se entregará al gancho cuando use el object_pairs_hook . Usted tiene que preocuparse por las listas, pero como puede ver, un objeto dentro de una lista se convertirá correctamente, y no tiene que repetir para hacer que suceda.

EDITAR: un compañero de trabajo señaló que Python2.6 no tiene object_hook_pairs . Aún puedes usar este Python2.6 haciendo un cambio muy pequeño. En el gancho de arriba, cambia:

 for key, value in pairs: 

a

 for key, value in pairs.iteritems(): 

Luego use object_hook lugar de 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'}} 

El uso de object_pairs_hook da object_pairs_hook resultado que se object_pairs_hook una instancia de un diccionario menos para cada objeto en el objeto JSON, que, si estuviera analizando un gran documento, podría valer la pena.

Me temo que no hay forma de lograr esto automáticamente dentro de la biblioteca simplejson.

El escáner y el decodificador en simplejson están diseñados para producir texto Unicode. Para hacer esto, la biblioteca usa una función llamada c_scanstring (si está disponible, por velocidad), o py_scanstring si la versión C no está disponible. La función scanstring es llamada varias veces por casi todas las rutinas que tiene simplejson para descodificar una estructura que puede contener texto. Tendría que ajustar el valor de la cadena de exploración en simplejson.decoder o la subclase JSONDecoder y proporcionar prácticamente toda su implementación completa de cualquier cosa que pueda contener texto.

Sin embargo, la razón por la que simplejson genera unicode es que la especificación json menciona específicamente que “Una cadena es una colección de cero o más caracteres Unicode” … el soporte para Unicode se asume como parte del formato en sí. La implementación de la cadena de exploración de scanstring va tan lejos como para escanear e interpretar escapes de Unicode (incluso la comprobación de errores para las representaciones de conjuntos de caracteres de múltiples bytes mal formados), por lo que la única forma en que puede devolverle el valor de manera confiable es como unicode.

Si tiene una biblioteca antigua que necesita una str , le recomiendo que busque laboriosamente la estructura de datos anidada después de analizar (lo que reconozco es lo que dijo explícitamente que quería evitar … lo siento), o tal vez envuelva sus bibliotecas de alguna manera. de fachada donde puede masajear los parámetros de entrada a un nivel más granular. El segundo enfoque podría ser más manejable que el primero si sus estructuras de datos están profundamente anidadas.

Como Mark (Amery) observa correctamente: el uso del deserializador de PyYaml en un volcado json solo funciona si tiene ASCII solamente. Al menos fuera de la caja.

Dos comentarios rápidos sobre el enfoque PyYaml:

  1. NUNCA use yaml.load en los datos del campo. Es una característica (!) De yaml para ejecutar código arbitrario oculto dentro de la estructura.

  2. Puedes hacerlo funcionar también para no ASCII a través de esto:

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

Pero el rendimiento no tiene comparación con la respuesta de Mark Amery:

Al lanzar algunos ejemplos de ejemplos profundamente nesteds en los dos métodos, obtengo esto (con dt [j] = tiempo delta de json.loads (json.dumps (m))):

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

Por lo tanto, la deserialización incluye caminar por el árbol y la encoding, dentro del orden de magnitud de la implementación basada en C de json. Encuentro esto notablemente rápido y también más robusto que la carga de yaml en estructuras profundamente anidadas. Y menos propenso a errores de seguridad, mirando yaml.load.

=> Aunque apreciaría un puntero a un convertidor basado únicamente en C, la función de byteify debería ser la respuesta predeterminada.

Esto es especialmente cierto si su estructura json es del campo y contiene información del usuario. Porque es probable que tenga que caminar de todos modos sobre su estructura, independientemente de las estructuras de datos internas que desee (“sándwich Unicode” o solo cadenas de bytes).

¿Por qué?

Normalización Unicode. Para los que no lo saben: tomar un analgésico y leer esto .

Entonces usando la recursión de byteify matas dos pájaros de un tiro:

  1. obtener sus bytestrings de volcados JSON nesteds
  2. Obtenga valores de entrada de usuario normalizados, para que encuentre las cosas en su almacenamiento.

En mis pruebas, resultó que reemplazar el input.encode (‘utf-8’) con unicodedata.normalize (‘NFC’, input) .encode (‘utf-8’) fue incluso más rápido que w / o NFC, pero eso es muy dependiente de los datos de la muestra, supongo.

El problema es que simplejson y json son dos módulos diferentes, al menos en la forma en que tratan con Unicode. Tienes json en py 2.6+, y esto te da valores Unicode, mientras que simplejson devuelve objetos de cadena. Solo intente easy_install-ing simplejson en su entorno y vea si funciona. Lo hizo por mí.

Solo use pickle en lugar de json para volcar y cargar, así:

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

La salida que produce es (las cadenas y los enteros se manejan correctamente):

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

Por lo tanto, me he encontrado con el mismo problema. Adivina cual fue el primer resultado de Google.

Como necesito pasar todos los datos a PyGTK, las cadenas Unicode tampoco son muy útiles para mí. Así que tengo otro método de conversión recursiva. En realidad, también es necesario para la conversión JSON a prueba de tipos: json.dump () se liberaría de cualquier no-literales, como los objetos de Python. Sin embargo, no convierte los índices de 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 

Admite Python2 y 3 utilizando gancho (de 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) 

Devoluciones:

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

Esto es tarde para el juego, pero construí este lanzador recursivo. Funciona para mis necesidades y creo que es relativamente completo. Te puede ayudar.

 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 

Simplemente pásale un objeto JSON así:

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

I have it as a private member of a class, but you can repurpose the method as you see fit.

I rewrote Wells’s _parse_json() to handle cases where the json object itself is an array (my use case).

 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 

here is a recursive encoder written in C: https://github.com/axiros/nested_encode

Performance overhead for “average” structures around 10% compared to 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% 

using this 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) 

I had a JSON dict as a string. The keys and values were unicode objects like in the following example:

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

I could use the byteify function suggested above by converting the string to a dict object using ast.literal_eval(myStringDict) .

Check out this answer to a similar question like this which states that

The u- prefix just means that you have a Unicode string. When you really use the string, it won’t appear in your data. Don’t be thrown by the printed output.

For example, try this:

 print mail_accounts[0]["i"] 

You won’t see a u.

With Python 3.6, sometimes I still run into this problem. For example, when getting response from a REST API and loading the response text to JSON, I still get the unicode strings. Found a simple solution using json.dumps().

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

I ran into this problem too, and having to deal with JSON, I came up with a small loop that converts the unicode keys to strings. ( simplejson on GAE does not return string keys.)

obj is the object decoded from 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 is what I pass to the constructor of the GAE application (which does not like unicode keys in **kwargs )

Not as robust as the solution from Wells, but much smaller.

I’ve adapted the code from the answer of Mark Amery , particularly in order to get rid of isinstance for the pros of duck-typing.

The encoding is done manually and ensure_ascii is disabled. The python docs for json.dump says that

If ensure_ascii is True (the default), all non-ASCII characters in the output are escaped with \uXXXX sequences

Disclaimer: in the doctest I used the Hungarian language. Some notable Hungarian-related character encodings are: cp852 the IBM/OEM encoding used eg. in DOS (sometimes referred as ascii , incorrectly I think, it is dependent on the codepage setting), cp1250 used eg. in Windows (sometimes referred as ansi , dependent on the locale settings), and iso-8859-2 , sometimes used on http servers. The test text Tüskéshátú kígyóbűvölő is attributed to Koltai László (native personal name form) and is from 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 .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) 

I’d also like to highlight the answer of Jarret Hardie which references the JSON spec , quoting:

A string is a collection of zero or more Unicode characters

In my use-case I had files with json. They are utf-8 encoded files. ensure_ascii results in properly escaped but not very readable json files, that is why I’ve adapted Mark Amery’s answer to fit my needs.

The doctest is not particularly thoughtful but I share the code in the hope that it will useful for someone.