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

276

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

Al cargar estos archivos con jsono simplejson, todos mis valores de cadena se convierten 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 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 atascado 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 .

Brutus
fuente
1
No hay ningún problema en Python3, el tipo de elementos en new_list esstr
GoingMyWay
1
Python 3k no es una 'versión reciente de Python', es solo una rama alternativa.
user2589273
11
Es extraño ver ese comentario en diciembre de 2017: Python 2 está en desuso y no se realizará ningún mantenimiento después del 1 de enero de 2020, que es menos de 2 años: pythonclock.org
Zaar Hai
1
@ZaarHai MUCHAS personas están atrapadas en Python 2 contra su voluntad. Hay muchas aplicaciones que incorporan su propia versión de Python para la automatización y las secuencias de comandos, por lo que la gente tiene que usarla hasta que el proveedor se actualice (te estoy mirando Maya, Houdini, Nuke ...)
Geordie
1
@ Geederie seguramente lo sé y lo entiendo. Mi comentario fue sobre la terminología: Python no es una "rama alternativa", sino una desafortunada falta de alternativas (juego de palabras) para aquellos que están atrapados en ella.
Zaar Hai

Respuestas:

101

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, entonces, ¿qué sentido tienen? ¿Por qué querrías usarlos?

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

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

Esta respuesta mitiga ambos problemas de rendimiento mediante el uso del object_hookparámetro de json.loady json.loads. De los documentos :

object_hookes una función opcional que se llamará con el resultado de cualquier objeto literal decodificado (a dict). Se utilizará el valor de retorno de object_hook en lugar de dict. Esta característica se puede usar para implementar decodificadores personalizados

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

La respuesta de Mark no es adecuada para su uso tal object_hookcomo está, porque se repite en diccionarios anidados. Prevenimos esa recursividad en esta respuesta con el ignore_dictsparámetro to _byteify, que se le pasa en todo momento, excepto cuando se le object_hookpasa un nuevo dictpara byteificar. La ignore_dictsbandera le dice _byteifyque ignore dictlos mensajes de correo electrónico ya que han sido modificados.

Finalmente, nuestras implementaciones de json_load_byteifiedand json_loads_byteifiedcall _byteify(with ignore_dicts=True) en el resultado devuelto por json.loado json.loadspara manejar el caso en el que el texto JSON que se decodifica no tiene un dictnivel superior.

Mirec Miskuf
fuente
1
+1 para el enfoque aquí; Realmente no lo entendí cuando lo leí por primera vez, pero finalmente entendí al releerlo a la luz de la respuesta de Travis Jensen. Hice una edición bastante agresiva con la esperanza de aclarar cómo funciona y cuáles son sus ventajas sobre mi respuesta. La idea central del código permanece intacta, pero he modificado casi todo lo demás. No dude en revertir mi edición si se opone a esto: ¡es su respuesta!
Mark Amery el
No hay problema Mark, muchas gracias. Me gusta tu edición, es mucho más explicativa que mi original. Tal vez, algún día, aprenderé a dar respuestas más concisas.
Mirec Miskuf
2
Esta es una gran solución; Eficiente y elegante. Sin embargo, si está atrapado en el reino de Python <2.7, como estoy, necesitará reemplazar la línea: return { byteify(key, ignore_dicts=True): _byteify(value, ignore_dicts=True) for key, value in data.iteritems() }con return dict((_byteify(key, ignore_dicts=True), _byteify(value, ignore_dicts=True)) for key, value in data.iteritems())para que funcione.
Richard Dunn el
Creo que te equivocas sobre el problema de la profundidad de recursión. Con los suyos, que puede ir hasta 990: json_loads_byteified('[' * 990 + ']' * 990). Con 991 se bloquea. Marcos todavía trabaja con 991: byteify(json.loads('[' * 991 + ']' * 991)). Se bloquea a 992. Entonces, al menos en esta prueba, Mark puede ir más profundo, al contrario de lo que dijiste.
Stefan Pochmann
@ MarkAmery ¿Qué opinas sobre mi comentario anterior? (Acabo de ver en el historial de edición que en realidad fuiste tú quien agregó ese reclamo).
Stefan Pochmann
180

Si bien hay algunas buenas respuestas aquí, terminé usando PyYAML para analizar mis archivos JSON, ya que proporciona las claves y los valores como strcadenas de unicodetipo en lugar de tipo. Como JSON es un subconjunto de YAML, funciona 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 Unicode, las recuperaría como objetos Unicode , ¡no hay conversión!

  • Debería (probablemente siempre) usar la safe_loadfunción de PyYAML ; Si lo usa para cargar archivos JSON, no necesita la "potencia adicional" de la loadfunción de todos modos.

  • Si desea un analizador YAML que tenga más soporte para la versión 1.2 de la especificación (y analiza correctamente números muy bajos ) intente Ruamel YAML : pip install ruamel.yamly import ruamel.yaml as yamlfue 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 valores ASCII (y no puede estar seguro la mayor parte del tiempo), mejor use una función de conversión :

Utilicé 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 aumentar el rendimiento en archivos grandes. Vea la respuesta un poco más complicada de Mirec Miskuf para eso.

Brutus
fuente
8
Tenga un poco de cuidado si decide usar esta respuesta. Funciona perfectamente para el caso de Brutus, pero solo porque sabe que sus datos solo contienen caracteres codificables ASCII. Si no tiene esa garantía, esta respuesta no funcionará. Por ejemplo, intente ejecutar yaml.load(json.dumps([u'a', u'£', u'É']))en el shell de Python y observe que regresa ['a', u'\xa3', u'\xc9'](que contiene unicodecadenas). Si no puede estar seguro de que sus datos solo contienen caracteres del conjunto de caracteres ASCII, debe usar un enfoque diferente (recomiendo mi propia respuesta).
Mark Amery
1
YAML también hace uso de [u'a', u'b']tener cuidado.
Carlos Calla
1
Esto es bueno, pero no funciona con números bajos ... mira aquí: stackoverflow.com/questions/30458977/…
Oren
@Oren: Esto no es un error en la especificación YAML sino en el analizador PyYAML. El analizador YAML de ruamel funciona.
Brutus
Quiero tener una salida como ["a", "b"] no como ['a', 'b'] @Brutus
usuario60679
141

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

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 de diccionario no fueron compatibles hasta Python 2.7.
  • Dado que esta respuesta se repite a través de todo el objeto decodificado, tiene un par de características de rendimiento indeseables que se pueden evitar con un uso muy cuidadoso de los parámetros object_hooko object_pairs_hook. La respuesta de Mirec Miskuf es hasta ahora la única que logra llevar esto a cabo correctamente, aunque como consecuencia, es significativamente más complicado que mi enfoque.
Mark Amery
fuente
1
Me gusta esto, no es ignorarlo, es reconocer que cuando las personas dicen "cadenas" y "ascii", en su mayoría ingenuamente querían decir bytes, no caracteres unicode teóricos. (y no ascii ya que todavía quieren signos de libra en el otro extremo)
Danny Staple
Me gusta esto, funciona casi de la misma manera que funciona mi impresora bonita, ya que sé que json no hace tuplas, también debe agregar la excepción para tuplas.
y.petremann
Esto es terriblemente ineficiente, lo que requiere que atraviese recursivamente nodos que quizás no necesite. El módulo json le ofrece ganchos para hacer esto de manera mucho más eficiente. Sin object_hookembargo, la respuesta a continuación usando es en realidad mucho peor que esta, pero, usando object_pairs_hook, puede llegar a un método razonablemente eficiente que no requiera recurrencia o revisión de nodos que no contengan cadenas.
Travis Jensen
1
@TravisJensen Interesante. El object_pairs_hookmétodo es quizás un poco más difícil de entender que este (debe comprender cómo funciona el parámetro y por qué las listas y los dictados requieren un manejo diferente), y el beneficio de rendimiento no será importante para la mayoría de las personas ... pero esperaría existe, especialmente para cualquiera que esté lidiando con un objeto JSON inusualmente anidado.
Mark Amery
plus1 Esta es la respuesta más concisa; Además, PyYAML es difícil de instalar. Lo único mejor sería de alguna manera micro-transmitir la conversión para que no use memoria 4X.
personal_cloud
74

Puede usar el object_hookparámetro para json.loadspasar en un convertidor. No tiene que hacer la conversión después del hecho. El jsonmódulo siempre pasará object_hooksolo los dictados, y pasará recursivamente rectas anidadas, por lo que no tiene que recurrir a los dictados anidados usted mismo. No creo que convertiría cadenas Unicode en números como los programas 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 incorrecto).

Además, trataría de evitar hacer algo como str(val)en un unicodeobjeto. Debe usar value.encode(encoding)una codificación válida, según lo que espere su lib externa.

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)
Mike Brennan
fuente
3
Esto está bien si el objeto en ses un JSON Object(una colección desordenada de clave: pares de valores con el carácter ':' que separa la clave y el valor, separados por comas y encerrados entre llaves), pero no si es, por ejemplo, un JSON Array. Así que si se les da un JSON Arraycomo ["a", "b"], el resultado seguirá siendo [u'a', u'b']. Ninguno de los otros parámetros de tipo gancho de personalización disponibles actualmente json.loads()puede hacer el trabajo tampoco.
Martineau
2
Como, como mencionó, el jsonmódulo pasará recursivamente dicts anidados , no es necesario verificarlos en las dos funciones, por lo que las dos elifcláusulas que los verifican deben eliminarse.
Martineau
1
Tenga en cuenta que los nombres de funciones iniciales con un guión bajo tienen un significado especial para las declaraciones de importación. Si coloca estas funciones en un archivo llamado Utility.py y en otro archivo from Utility import *, las funciones no se verán debido a ese guión bajo.
M Katz
1
Esta es una muy mala idea. object_hookse llama por cada objeto json analizado, por lo que si recurres a lo que se te da, estás re "byteificando" cosas que ya has "byteificado". El rendimiento crecerá geométricamente con el tamaño del objeto. He incluido una respuesta aquí que usa object_pairs_hooky no sufre ese problema.
Travis Jensen
38

Eso es porque json no tiene diferencia entre los objetos de cadena y los objetos unicode. Todas son cadenas en javascript.

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

Es mejor usar unicodeobjetos de cadena en todas partes. Por lo tanto, su mejor opción es actualizar sus bibliotecas para que puedan manejar objetos Unicode.

Pero si realmente desea cadenas de bytes, simplemente codifique los resultados a la codificación 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']
nosklo
fuente
Gracias nosklo, eso es lo que hice primero. Pero como dije, los datos reales que utilicé están bastante anidados y todo eso, por lo que esto introdujo un poco de sobrecarga. Todavía estoy buscando una solución automática ... Hay al menos un informe de error por ahí donde la gente se queja de que simplejson devuelve objetos de cadena en lugar de unicode.
Brutus
1
@Brutus: creo que json tiene razón al devolver objetos unicode. De hecho, no aceptaría nada menos, ya que las cadenas de JavaScript son, de hecho, objetos unicode. Lo que quiero decir es que las cadenas json (javascript) pueden almacenar cualquier tipo de carácter unicode, por lo que tiene sentido crear objetos unicode al traducir de json. Realmente deberías arreglar tus bibliotecas en su lugar.
nosklo 05 de
16

Existe una solución fácil.

TL; DR: uso en ast.literal_eval()lugar de json.loads(). Ambos asty jsonestán en la biblioteca estándar.

Si bien no es una respuesta "perfecta", se obtiene bastante lejos si su plan es ignorar Unicode por completo. 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 cadenas Unicode. La respuesta completa se vuelve peluda rápidamente.

Charles Merriam
fuente
11
Mejor asegúrese de que su json no contenga ninguno null, trueo falsevalores, porque no son válidos en python y causarán literal_eval()un error.
ɈsәɹoɈ
3
@ ʇsәɹoɈ También es mejor que su JSON no contenga un solidus escapado ( \/) dentro de una cadena, o una secuencia de escape unicode (como "\u0061", que es otra forma de escritura "a"). La sintaxis literal de Python es incompatible con JSON de varias maneras, y no confiaría en esta respuesta para ningún script que no iba a tirar.
Mark Amery
La gente tiene razón en que si la cadena realmente es unicode, entonces esta respuesta falla, pero si ese fuera el caso, no podríamos convertirla en una cadena de todos modos. +1 para una respuesta que funciona solo cuando funciona y lanza una excepción de lo contrario
Stefan Sullivan
si es posible, no use jsonpara volcar los datos, solo use printsi ejecuta python. Luego ast.literal_evaltrabaja
Jean-François Fabre
11

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

object_pairs_hookes una función opcional que se llamará con el resultado de cualquier objeto literal decodificado con una lista ordenada de pares. Se object_pairs_hookutilizará el valor de retorno de en lugar de dict. Esta característica se puede utilizar para implementar decodificadores personalizados que se basan en el orden en que se decodifican los pares clave y valor (por ejemplo, collections.OrderedDictrecordará el orden de inserción). Si object_hooktambién se define, object_pairs_hooktiene prioridad.

Con él, recibe cada objeto JSON, por lo que puede hacer la decodificación sin necesidad de recurrencia:

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 será entregado al gancho cuando use el object_pairs_hook. Debes preocuparte por las listas, pero como puedes ver, un objeto dentro de una lista se convertirá correctamente y no tienes que recurrir para que suceda.

EDITAR: Un compañero de trabajo señaló que Python2.6 no tiene object_hook_pairs. Todavía puede usar este Python2.6 haciendo un cambio muy pequeño. En el gancho de arriba, cambie:

for key, value in pairs:

a

for key, value in pairs.iteritems():

Luego use en object_hooklugar 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_hookresultados en un diccionario menos que se instancia para cada objeto en el objeto JSON, que, si estaba analizando un documento enorme, podría valer la pena.

Travis Jensen
fuente
1
Esto es ordenado y parece muy cercano a merecer la marca de verificación verde (que Brutus, admirablemente, ya ha pasado generosamente a medida que se reciben mejores respuestas). Pero ... ¿por qué no manejar realmente las listas correctamente en el deunicodify_hookque exhibes en esta respuesta? Por el momento, tiene una implementación deunicodify_hookque no itera sobre las listas y desunicodifica las cadenas y las listas dentro de ellas, y por lo tanto, el resultado que está exhibiendo no coincide con el resultado que su gancho realmente producirá. Arregla eso, y esta respuesta será superior a la mía.
Mark Amery
Frívola: ¿También sugeriría demostrar la función con el intérprete CPython ordinario en lugar del que está usando aquí (que creo que es IronPython)? El intérprete de CPython es más familiar para la mayoría de los usuarios de Python y, en mi opinión, es más bonito.
Mark Amery
Esto no funciona para mí, pero estoy seguro de que es una peculiaridad de lo que estoy haciendo ... Estoy almacenando una lista de un json doc más grande en un archivo. Ya sea que lo cargue con o sin este object_pairs_hook, cada elemento aparece unicode. Maldito.
rsaw
1
@rsaw Buen punto! Como object_pairs_hooksolo se solicitan objetos , si su texto JSON tiene una lista de cadenas en el nivel superior, esta solución fallará. No hay forma de arreglar esto sin invocar alguna función en la cosa devuelta json.load; ninguno de los json.loadganchos puede garantizar que podrá lidiar con cada cadena. Creo que este es un defecto lo suficientemente grande como para seguir recomendando mi solución en lugar de usar los ganchos.
Mark Amery
-1 porque me acabo de dar cuenta de que Mirec Miskuf ya ha publicado una respuesta de enlace de objetos que no tiene las desventajas del enfoque de Mike Brennan (re-byteifica los mismos diccionarios varias veces) ni de esta (no puede byteificar listas anidadas o listas de nivel superior o cadenas). No estoy seguro de por qué su respuesta ha languidecido casi sin atención, mientras que esta, que es inferior, ha ganado rápidamente votos.
Mark Amery el
9

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_scanstringsi la versión C no está disponible. La scanstringfunción se llama varias veces por casi todas las rutinas que tiene simplejson para decodificar una estructura que puede contener texto. Tendría que monopatch el scanstringvalor en simplejson.decoder, o subclase JSONDecodery proporcionar prácticamente su propia 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" ... se supone que el soporte para unicode forma parte del formato en sí. La scanstringimplementación de Simplejson va tan lejos como para escanear e interpretar escapes unicode (incluso la comprobación de errores para representaciones de caracteres de múltiples bytes con formato incorrecto), 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 un archivo str, le recomiendo que busque laboriosamente en la estructura de datos anidados después del análisis (lo que reconozco es lo que dijo explícitamente que quería evitar ... lo siento), o tal vez incluya sus bibliotecas en algún tipo 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 realmente anidadas.

Jarret Hardie
fuente
4

Como Mark (Amery) señala correctamente: El uso del deserializador de PyYaml en un volcado json solo funciona si solo tiene ASCII. Al menos fuera de la caja.

Dos comentarios rápidos sobre el enfoque PyYaml:

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

  2. Usted puede hacer que funcione también para no ASCII a través de este:

    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 es comparable con la respuesta de Mark Amery:

Al arrojar algunos dictados de muestra profundamente anidados en los dos métodos, obtengo esto (con dt [j] = time 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]

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

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

Esto es especialmente cierto si su estructura json es del campo, que contiene la entrada del usuario. Porque entonces probablemente necesite caminar de todos modos sobre su estructura, independientemente de sus estructuras de datos internos deseadas ('sandwich unicode' o cadenas de bytes solamente).

¿Por qué?

Normalización Unicode . Para los que no saben: tome un analgésico y lea esto .

Entonces, usando la recurrencia byteify, matas dos pájaros de un tiro:

  1. Obtenga sus cadenas de bytes de volcados JSON anidados
  2. obtener valores de entrada del usuario normalizados, para que pueda encontrar 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 sin NFC, pero eso depende en gran medida de los datos de la muestra, supongo.

Píldora roja
fuente
3

El Gotcha es que simplejsony jsonson dos módulos diferentes, al menos en la forma que se ocupan de Unicode. Tienes jsonen py 2.6+, y esto te da valores unicode, mientras que simplejsondevuelve objetos de cadena. Simplemente intente easy_install-ing simplejson en su entorno y vea si eso funciona. Lo hizo por mi.

ducu
fuente
2

Simplemente 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'}
Stefan Gruenwald
fuente
1
+1 para una solución que no requiere paquetes adicionales (como yaml ). Pero a veces, como en mi caso original, necesito tener los datos en JSON, por lo que pickle no siempre es la mejor opción. Además, tienes safe_loaden YAML, no sé si existe algo similar para el pepinillo .
Brutus
1

Entonces, me he encontrado con el mismo problema. Adivina cuál fue el primer resultado de Google.

Como necesito pasar todos los datos a PyGTK, las cadenas Unicode tampoco me son muy útiles. Entonces tengo otro método de conversión recursivo. En realidad, también es necesario para la conversión JSON de typesafe: json.dump () rescataría cualquier no literal, como los objetos de Python. Sin embargo, no convierte índices 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
mario
fuente
El único problema que puede surgir aquí es si necesita las claves en un diccionario convertido de Unicode. Aunque esta implementación convertirá los valores, mantiene las claves unicode. Si crea un 'newobj', use newobj [str (i)] = ... y asigne obj = newobj cuando haya terminado, las claves también se convertirán.
Neal Stublen
Esto podría ser más bonito con las comprensiones o mejor mediante la conversión de claves. También es unidiomático; ambos muta objetos en su lugar (en el caso de los diccionarios) y devuelve el nuevo valor, que es inconsistente con los métodos de colección incorporados de Python que mutan el objeto actual o devuelven uno nuevo, pero no ambos.
Mark Amery
1

Tenía un JSON dict como una cadena. Las claves y los valores eran objetos unicode como en el siguiente ejemplo:

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

Podría usar la byteifyfunción sugerida anteriormente al convertir la cadena a un dictobjeto usando ast.literal_eval(myStringDict).

narko
fuente
El ejemplo que ha dado no es un ejemplo de JSON. {u'key':u'value'}no es JSON
Mark Amery
2
Sé perfectamente que no es JSON. Así fue analizado desde una fuente externa en mi script de Python. Si fuera JSON directamente como en el siguiente ejemplo, no necesitaría la función byteify marcada como la solución: {"firstName": "John", "lastName": "Doe"}. Sería genial si antes de votar leyeras las respuestas. Gracias.
narko
1

Admite Python2 y 3 usando hook (desde 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'}
abarik
fuente
0

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áselo como un objeto JSON así:

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

Lo tengo como miembro privado de una clase, pero puede reutilizar el método como mejor le parezca.

Pozos
fuente
Me he encontrado con un problema en el que intento analizar JSON y pasar la asignación resultante a una función como ** kwargs. Parece que los nombres de los parámetros de la función no pueden ser unicode, por lo que su función _parseJSON es excelente. Si hay una manera más fácil, alguien puede avisarme.
Neal Stublen
1
Este código tiene un problema: realiza una llamada recursiva en la pieza de la lista, que fallará si los elementos de la lista no son diccionarios.
I82Much
Además del error descrito por @ I82Much, este también tiene un nombre incorrecto (en realidad no analiza el JSON; json.loadsprimero se necesita una llamada), intenta arbitrariamente convertir cadenas en ints sin razón explicada, y no es copiar y ... pegar listo
Mark Amery
0

Reescribí _parse_json () de Wells para manejar casos en los que el objeto json en sí mismo es una matriz (mi caso de uso).

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
Darnmarshall
fuente
0

Aquí hay un codificador recursivo escrito en C: https://github.com/axiros/nested_encode

Sobrecarga de rendimiento para estructuras "promedio" alrededor del 10% en comparación con 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%

utilizando esta estructura de prueba:

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)
Píldora roja
fuente
0

Con Python 3.6, a veces todavía me encuentro con este problema. Por ejemplo, al obtener una respuesta de una API REST y cargar el texto de respuesta en JSON, sigo obteniendo las cadenas Unicode. Encontré una solución simple usando json.dumps ().

response_message = json.loads(json.dumps(response.text))
print(response_message)
Yuelin
fuente
-1

También me encontré con este problema, y ​​al tener que lidiar con JSON, se me ocurrió un pequeño bucle que convierte las claves Unicode en cadenas. ( simplejsonen GAE no devuelve claves de cadena).

obj es el objeto decodificado de 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()

kwargses lo que paso al constructor de la aplicación GAE (que no le gustan las unicodeclaves **kwargs)

No es tan robusto como la solución de Wells, pero es mucho más pequeño.

codificador de barcos
fuente
-1

He adaptado el código de la respuesta de Mark Amery , particularmente para deshacerme deisinstance los pros del tipeo.

La codificación se realiza manualmente y ensure_asciiestá deshabilitada. La documentación de Python json.dumpdice que

Si sure_ascii es True (el valor predeterminado), todos los caracteres no ASCII en la salida se escapan con secuencias \ uXXXX

Descargo de responsabilidad: en el doctest utilicé el idioma húngaro. Algunas codificaciones de caracteres relacionadas con Hungría son: cp852la codificación IBM / OEM utilizada, por ejemplo. en DOS (a veces denominado ascii , incorrectamente creo que depende de la configuración de la página de códigos ), cp1250por ejemplo , se usa. en Windows (a veces denominado ansi , depende de la configuración regional) y iso-8859-2, a veces, se usa en servidores http. El texto de la prueba Tüskéshátú kígyóbűvölőse atribuye a Koltai László (formulario de nombre personal nativo) y es de 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)

También me gustaría destacar la respuesta de Jarret Hardie que hace referencia a la especificación JSON , citando:

Una cadena es una colección de cero o más caracteres Unicode

En mi caso de uso, tenía archivos con json. Son utf-8archivos codificados. ensure_asciida como resultado archivos json que escapan correctamente pero no son muy legibles, es por eso que he adaptado la respuesta de Mark Amery para satisfacer mis necesidades.

El documento no es particularmente atento, pero comparto el código con la esperanza de que sea útil para alguien.

n611x007
fuente
No estoy seguro de ver los beneficios de usar pato escribiendo aquí? Sabemos que las colecciones devueltas json.loadsserán listas o dictados, no algún tipo definido por el usuario o por la biblioteca que implemente sus métodos y métodos mágicos, entonces, ¿por qué no hacer una isinstanceverificación? ¿No es más fácil de entender que verificar la existencia iteritemso si iteraceptará el objeto como argumento?
Mark Amery
@ MarkAmery se trata de vertederos, no de cargas. Si crea datos para volcar, en lugar de cargarlos , no puede estar seguro de qué es. La idea era dejar que viniera de cualquier parte del código.
n611x007
-2

Mira esta respuesta a una pregunta similar como esta que dice que

El prefijo u solo significa que tiene una cadena Unicode. Cuando realmente use la cadena, no aparecerá en sus datos. No se deje llevar por la salida impresa.

Por ejemplo, intente esto:

print mail_accounts[0]["i"]

No verás una u.

kunal
fuente
No es cierto si, por ejemplo, desea formatear algo que contiene una cadena Unicode, en Py2. por ejemplo, '{}'.format({u'x' : u'y'})todavía incluye los u.
Ponkadoodle