utf8 - python doc json



Como obter objetos de string em vez de Unicode de JSON? (14)

Estou usando o Python 2 para analisar JSON a partir de arquivos de texto codificados em ASCII .

Ao carregar esses arquivos com json ou simplejson , todos os meus valores de string são convertidos em objetos Unicode em vez de objetos de string. O problema é que eu tenho que usar os dados com algumas bibliotecas que aceitam apenas objetos de string. Não consigo alterar as bibliotecas nem atualizá-las.

É possível obter objetos de string em vez de Unicode?

Exemplo

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

Atualizar

Esta pergunta foi feita há muito tempo atrás , quando eu estava preso ao Python 2 . Uma solução fácil e limpa para hoje é usar uma versão recente do Python - por exemplo, Python 3 e encaminhar.

https://ffff65535.com


Uma solução com 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

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

Como isso funciona e por que eu usaria isso?

A função de Mark Amery é mais curta e mais clara do que essas, então qual é o objetivo delas? Por que você quer usá-los?

Puramente pelo desempenho . A resposta de Mark decodifica o texto JSON totalmente primeiro com strings unicode e recorre ao longo do valor decodificado inteiro para converter todas as strings em strings de bytes. Isso tem alguns efeitos indesejáveis:

  • Uma cópia de toda a estrutura decodificada é criada na memória
  • Se o seu objeto JSON estiver realmente aninhado (500 níveis ou mais), você atingirá a profundidade máxima de recursão do Python

Essa resposta atenua esses dois problemas de desempenho usando o parâmetro json.load de json.load e json.loads . Dos docs :

object_hook é uma função opcional que será chamada com o resultado de qualquer literal de objeto decodificado (um dict ). O valor de retorno de object_hook será usado em vez do dict . Esse recurso pode ser usado para implementar decodificadores personalizados

Uma vez que os dicionários aninharam muitos níveis em outros dicionários passados ​​para object_hook à medida que são decodificados , podemos byteificar quaisquer cadeias de caracteres ou listas dentro deles nesse ponto e evitar a necessidade de recursão profunda posteriormente.

A resposta de Mark não é adequada para uso como um object_hook como está, porque recorre a dicionários aninhados. Nós evitamos essa recursão nesta resposta com o parâmetro ignore_dicts para _byteify , que é passado a ela todo o tempo, exceto quando object_hook passa um novo dict para byteify. O sinalizador ignore_dicts diz ao _byteify para ignorar os dict s, uma vez que eles já foram bytes.

Finalmente, nossas implementações de json_load_byteified e json_loads_byteified chamam _byteify (com ignore_dicts=True ) no resultado retornado de json.load ou json.loads para manipular o caso em que o texto JSON sendo decodificado não possui um dict no nível superior.


A pegadinha é que simplejson e json são dois módulos diferentes, pelo menos da maneira que lidam com unicode. Você tem json em py 2.6+, e isso fornece valores unicode, enquanto simplejson retorna objetos string. Apenas tente easy_install-ing simplejson em seu ambiente e veja se isso funciona. Isso fez por mim.


Como Mark (Amery) corretamente observa: Usar o desserializador do PyYaml em um json dump funciona somente se você tiver apenas ASCII. Pelo menos fora da caixa.

Dois comentários rápidos sobre a abordagem PyYaml:

  1. NEVER use yaml.load nos dados do campo. É um recurso (!) De yaml para executar código arbitrário escondido dentro da estrutura.

  2. Você pode fazê-lo funcionar também para não ASCII via isso:

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

Mas desempenho sábio não tem comparação com a resposta de Mark Amery:

Lançando alguns dicts de samples profundamente aninhados nos dois métodos, eu entendo isso (com 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]

Assim, desserialização, incluindo andar completamente a árvore e codificação, bem dentro da ordem de grandeza da implementação baseada em C de json. Eu acho isso incrivelmente rápido e também mais robusto do que a carga yaml em estruturas profundamente aninhadas. E menos propenso a erros de segurança, olhando para yaml.load.

=> Embora eu apreciasse um ponteiro para um conversor baseado em C, a função de byteify deveria ser a resposta padrão.

Isso vale especialmente se sua estrutura json for do campo, contendo a entrada do usuário. Porque então você provavelmente precisará andar de qualquer maneira sobre sua estrutura - independente de suas estruturas de dados internas desejadas ('sanduíche unicode' ou apenas strings de byte).

Por quê?

Normalização Unicode. Para o inconsciente: Tome um analgésico e leia this .

Então, usando a recursão de byteify, você mata dois coelhos com uma cajadada:

  1. obter suas bytestrings de json dumps aninhados
  2. obtenha valores de entrada do usuário normalizados, para que você encontre as coisas em seu armazenamento.

Em meus testes, descobriu-se que substituir o input.encode ('utf-8') por um unicodedata.normalize ('NFC', entrada) .encode ('utf-8') era ainda mais rápido do que sem NFC - mas Isso é fortemente dependente dos dados da amostra, eu acho.


Embora haja algumas boas respostas aqui, acabei usando o PyYAML para analisar meus arquivos JSON, já que ele fornece as chaves e os valores como str strings em vez do tipo unicode . Como o JSON é um subconjunto do YAML, ele funciona bem:

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

Algumas coisas a serem observadas:

  • Eu recebo objetos string porque todas as minhas entradas são codificadas em ASCII . Se eu usasse entradas codificadas unicode, eu as recuperaria como objetos unicode - não há conversão!

  • Você deve (provavelmente sempre) usar a função safe_load do safe_load ; Se você usá-lo para carregar arquivos JSON, você não precisa do "poder adicional" da função de load qualquer maneira.

  • Se você quer um analisador YAML que tenha mais suporte para a versão 1.2 da especificação (e analisa números muito baixos ) tente Ruamel YAML : pip install ruamel.yaml e import ruamel.yaml as yaml era tudo que eu precisava em meus testes.

Conversão

Como afirmado, não há conversão! Se você não pode ter certeza de apenas lidar com valores ASCII (e não pode ter certeza na maior parte do tempo), é melhor usar uma função de conversão :

Eu usei o de Mark Amery algumas vezes agora, ele funciona muito bem e é muito fácil de usar. Você também pode usar uma função semelhante como um object_hook , já que pode ganhar um aumento de desempenho em arquivos grandes. Veja a resposta um pouco mais envolvida de Mirec Miskuf para isso.


Eu tenho medo que não haja maneira de conseguir isso automaticamente dentro da biblioteca simplejson.

O scanner e o decodificador em simplejson são projetados para produzir texto unicode. Para fazer isso, a biblioteca usa uma função chamada c_scanstring (se estiver disponível, para velocidade) ou py_scanstring se a versão C não estiver disponível. A função scanstring é chamada várias vezes por quase todas as rotinas que o simplejson tem para decodificar uma estrutura que possa conter texto. Você terá que capturar o valor scanstring em simplejson.decoder, ou subclasse JSONDecoder e fornecer praticamente toda a sua implementação de qualquer coisa que possa conter texto.

A razão pela qual o simplejson produz o unicode, no entanto, é que a especificação do json menciona especificamente que "Uma string é uma coleção de zero ou mais caracteres Unicode" ... o suporte para unicode é assumido como parte do próprio formato. A implementação de scanstring do scanstring vai tão longe quanto varrer e interpretar escapes unicode (mesmo a verificação de erros para representações charset mal-formadas de múltiplos bytes), então a única maneira de retornar o valor a você é tão unicode quanto confiável.

Se você tem uma biblioteca antiga que precisa de str , eu recomendo que você procure laborativamente a estrutura de dados aninhada após a análise (que reconheço é o que você disse explicitamente que queria evitar ... desculpe) ou talvez envolva suas bibliotecas de alguma forma da fachada, onde você pode massagear os parâmetros de entrada em um nível mais granular. A segunda abordagem pode ser mais gerenciável do que a primeira, se suas estruturas de dados estiverem realmente aninhadas.


Existe uma solução fácil.

TL; DR - Use ast.literal_eval() vez de json.loads() . Ambas ast e json estão na biblioteca padrão.

Apesar de não ser uma resposta "perfeita", é bem provável que seu plano seja ignorar completamente o Unicode. No 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))

dá:

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

Isso fica mais difícil quando alguns objetos são realmente seqüências de caracteres Unicode. A resposta completa fica peluda rapidamente.


Não há nenhuma opção interna para fazer com que as funções do módulo json retornem strings de bytes em vez de strings unicode. No entanto, essa função recursiva curta e simples converterá qualquer objeto JSON decodificado de usar strings unicode em strings de bytes codificados em 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

Apenas chame isso na saída que você obtém de uma chamada json.loads ou json.loads .

Um par de notas:

  • Para suportar o Python 2.6 ou anterior, substitua return {byteify(key): byteify(value) for key, value in input.iteritems()} com return dict([(byteify(key), byteify(value)) for key, value in input.iteritems()]) , uma vez que as compreensões do dicionário não eram suportadas até o Python 2.7.
  • Como essa resposta recorre ao longo de todo o objeto decodificado, ela apresenta algumas características de desempenho indesejáveis ​​que podem ser evitadas com o uso muito cuidadoso dos parâmetros object_pairs_hook ou object_pairs_hook . A resposta de Mirec Miskuf é até agora a única que consegue fazer isso corretamente, embora, como conseqüência, seja significativamente mais complicada do que a minha abordagem.

Suporte Python2 e 3 usando hook (de https://.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)

Returns:

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

A resposta de Mike Brennan está próxima, mas não há razão para voltar a percorrer toda a estrutura. Se você usar o object_hook_pairs (Python 2.7+):

object_pairs_hook é uma função opcional que será chamada com o resultado de qualquer literal de objeto decodificado com uma lista ordenada de pares. O valor de retorno de object_pairs_hook será usado em vez do dict . Esse recurso pode ser usado para implementar decodificadores personalizados que dependem da ordem em que os pares de chave e valor são decodificados (por exemplo, collections.OrderedDict lembrará da ordem de inserção). Se object_hook também estiver definido, o object_pairs_hook terá prioridade.

Com ele, você obtém cada objeto JSON entregue a você para poder fazer a decodificação sem necessidade de recursão:

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

Repare que eu nunca tenho que chamar o hook de maneira recursiva, já que todos os objetos serão entregues ao hook quando você usar o object_pairs_hook . Você precisa se preocupar com listas, mas como você pode ver, um objeto dentro de uma lista será convertido corretamente, e você não precisa recorrer para fazer isso acontecer.

EDIT: Um colega de trabalho apontou que o Python2.6 não tem object_hook_pairs . Você ainda pode usar este Python2.6 fazendo uma pequena alteração. No gancho acima, altere:

for key, value in pairs:

para

for key, value in pairs.iteritems():

Então use object_hook invés 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'}}

Usar object_pairs_hook resulta em um dicionário a menos sendo instanciado para cada objeto no objeto JSON, que, se você estivesse analisando um documento enorme, poderia valer a pena.


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.


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

This is late to the game, but I built this recursive caster. It works for my needs and I think it's relatively complete. It may help you.

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

Just pass it a JSON object like so:

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.


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)




python-2.x