Como obter objetos string em vez de Unicode a partir do JSON?


276

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

Ao carregar esses arquivos com jsonou simplejson, todos os meus valores de sequência são convertidos em objetos Unicode em vez de objetos de sequência. 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 , quando eu estava preso no Python 2 . Uma solução fácil e limpa para hoje é usar uma versão recente do Python - ie Python 3 e para a frente.


1
Não há nenhum problema no Python3, o tipo de item em new_list é #str
GoingMyWay

1
O Python 3k não é uma 'versão recente do Python', é apenas um ramo alternativo.
user2589273

11
É estranho ver tal comentário em dezembro de 2017 - Python 2 é obsoleto e sem manutenção vai acontecer depois de 1 de janeiro de 2020, que é menos de 2 anos: pythonclock.org
Zaar Hai

1
@ZaarHai Muitas pessoas estão presas no Python 2 contra sua vontade. Existem muitas aplicações que incorporam sua própria versão Python para automação e scripting para que as pessoas tem que usá-lo até que as atualizações do fornecedor (estou olhando para você Maya, Houdini, Nuke ..)
Geordie

1
@ Geordie Eu certamente sei e entendo isso. Meu comentário foi sobre terminologia - o Python não é um "ramo alternativo", mas uma infeliz falta de alternativa (trocadilhos) para aqueles que estão presos a ele.
Zaar Hai

Respostas:


101

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 o usaria?

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

Puramente para desempenho . A resposta de Mark decodifica o texto JSON totalmente primeiro com cadeias unicode e depois repete todo o valor decodificado para converter todas as cadeias em cadeias 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 profundamente aninhado (500 níveis ou mais), você atingirá a profundidade máxima de recursão do Python

Esta resposta atenua esses dois problemas de desempenho usando o object_hookparâmetro de json.loade json.loads. Dos documentos :

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

Como os dicionários aninhados em muitos níveis em outros dicionários são passados ​​à object_hook medida que são decodificados , podemos byteify qualquer sequência ou lista dentro deles naquele momento e evitar a necessidade de recursão profunda posteriormente.

A resposta de Mark não é adequada para uso como object_hookestá, porque se repete em dicionários aninhados. Evitamos essa recursão nesta resposta com o ignore_dictsparâmetro para _byteify, que é passado a ele o tempo todo, exceto quando object_hookpassa um novo dictpara byteify. A ignore_dictsbandeira diz _byteifypara ignorar dicts, pois eles já foram byteified.

Por fim, nossas implementações json_load_byteifiede json_loads_byteifiedcall _byteify(with ignore_dicts=True) no resultado retornado de json.loadou json.loadspara lidar com o caso em que o texto JSON que está sendo decodificado não possui um dictno nível superior.


1
+1 para a abordagem aqui; Eu realmente não entendi quando li pela primeira vez, mas finalmente entendi ao relê-lo à luz da resposta de Travis Jensen. Fiz uma edição bastante agressiva na esperança de esclarecer como funciona e quais são suas vantagens sobre a minha resposta. A idéia central do código permanece intocada, mas modifiquei praticamente todo o resto. Sinta-se à vontade para reverter minha edição se você se opuser a isso - é a sua resposta!
Mark Amery

Não tem problema Mark, muito obrigado. Gosto da sua edição, é muito mais explicativa do que a minha original. Talvez um dia eu aprenda a dar respostas mais concisas.
Mirec Miskuf

2
Esta é uma ótima solução; eficiente e elegante. No entanto, se você estiver preso no reino do Python <2,7, como eu, precisará substituir a linha: return { byteify(key, ignore_dicts=True): _byteify(value, ignore_dicts=True) for key, value in data.iteritems() }por return dict((_byteify(key, ignore_dicts=True), _byteify(value, ignore_dicts=True)) for key, value in data.iteritems())para que funcione.
Richard Dunn

Acho que você está errado sobre a questão da profundidade da recursão. Com o seu, I pode ir até 990: json_loads_byteified('[' * 990 + ']' * 990). Com o 991, ele trava. Mark ainda trabalha com 991: byteify(json.loads('[' * 991 + ']' * 991)). Ele falha em 992. Portanto, pelo menos neste teste, o de Mark pode ir mais fundo, ao contrário do que você disse.
Stefan Pochmann

@MarkAmery O que você acha do meu comentário acima? (Acabei de ver no histórico de edição que foi você quem adicionou essa reivindicação).
Stefan Pochmann

180

Embora existam boas respostas aqui, acabei usando o PyYAML para analisar meus arquivos JSON, pois ele fornece as chaves e os valores como strcadeias de caracteres em vez de unicodetipo. 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 de string porque todas as minhas entradas são codificadas em ASCII . Se eu usasse entradas codificadas em unicode, as recuperaria como objetos unicode - não há conversão!

  • Você deve (provavelmente sempre) usar a safe_loadfunção do PyYAML ; se você o usar para carregar arquivos JSON, não precisará da "energia adicional" da loadfunção.

  • Se você deseja um analisador YAML que tenha mais suporte para a versão 1.2 da especificação (e analise corretamente números muito baixos ), experimente o Ruamel YAML : pip install ruamel.yamle import ruamel.yaml as yamlera tudo o que eu precisava nos meus testes.

Conversão

Como afirmado, não há conversão! Se você não pode ter certeza de lidar apenas com valores ASCII (e não pode ter certeza na maioria das vezes), use melhor uma função de conversão :

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


8
Tome um pouco de cuidado se você decidir usar esta resposta. Funciona perfeitamente no caso de Brutus, mas apenas porque ele sabe que seus dados contêm apenas caracteres codificáveis ​​em ASCII. Se você não tiver essa garantia, esta resposta não funcionará. Por exemplo, tente executar yaml.load(json.dumps([u'a', u'£', u'É']))no shell Python e observe que você volta ['a', u'\xa3', u'\xc9'](que contém unicodeseqüências de caracteres). Se você não tiver certeza de que seus dados contêm apenas caracteres do conjunto de caracteres ASCII, use uma abordagem diferente (recomendo minha própria resposta).
Mark Amery

1
YAML também faz uso de [u'a', u'b']cuidado.
Carlos Calla

1
Isso é bom, mas ele não funciona com números baixos .. olhada aqui: stackoverflow.com/questions/30458977/...
Oren

@ Oren: Este não é um erro na especificação YAML, mas no analisador PyYAML. O analisador YAML de ruamel funciona.
Brutus

Eu quero ter saída como ["a", "b"] não como ['a', 'b'] @Brutus
#

141

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

Basta chamar isso na saída obtida de uma chamada json.loadou json.loads.

Algumas notas:

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

1
Gosto disso - não é uma ignorância - é reconhecer que, quando as pessoas dizem "strings" e "ascii", elas na maioria das vezes ingenuamente queriam dizer que queriam bytes, não caracteres unicode teóricos. (e não ASCII como eles ainda querem sinais de libra na outra extremidade)
Danny Staple

Eu gosto disso, funciona quase da mesma maneira que minha bonita impressora, pois sei que json não faz tupla, você deve adicionar a exceção para tupla também.
y.petremann

Isso é terrivelmente ineficiente, exigindo que você atravesse recursivamente os nós que talvez não sejam necessários. O módulo json fornece ganchos para fazer isso com muito mais eficiência. A resposta abaixo de usar object_hooké realmente muito pior do que esta, porém, mas, usando object_pairs_hook, você pode criar um método razoavelmente eficiente que não exija recursão ou revisão de nós que não contêm cadeias.
Travis Jensen

1
@TravisJensen Interesting. O object_pairs_hookmétodo é talvez muito ligeiramente mais difícil de entender do que este (você precisa entender como funciona o parâmetro e por listas e dicts requerem um tratamento diferente), e o benefício de desempenho não importa para a maioria das pessoas ... mas eu esperaria ele existe, especialmente para quem lida com um objeto JSON aninhado profundamente incomum.
Mark Amery

plus1 Esta é a resposta mais concisa; além do PyYAML, é difícil instalar. A única coisa melhor seria, de alguma forma, transmitir a conversão de maneira micro, para que ela não use memória 4X.
personal_cloud

74

Você pode usar o object_hookparâmetro para json.loadspassar em um conversor. Você não precisa fazer a conversão após o fato. O jsonmódulo sempre passará object_hookapenas os ditados e passará recursivamente em ditados aninhados, para que você não precise recursar em ditados aninhados. Acho que não converteria cadeias unicode em números como os de Wells. Se for uma sequência unicode, foi citada como uma sequência no arquivo JSON, portanto, deve ser uma sequência (ou o arquivo está incorreto).

Além disso, eu tentaria evitar fazer algo como str(val)em um unicodeobjeto. Você deve usar value.encode(encoding)uma codificação válida, dependendo do que sua lib externa espera.

Então, por exemplo:

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)

3
Isso é bom se o objeto in sfor um JSON Object(uma coleção não ordenada de chave: valor emparelha-se com o caractere ':' que separa a chave e o valor, separados por vírgula e entre chaves), mas não se for, digamos, um JSON Array. Então, se for dada uma JSON Arraycomo ["a", "b"], o resultado ainda será [u'a', u'b']. Nenhum dos outros parâmetros de tipo de gancho de personalização disponíveis atualmente também json.loads()pode fazer o trabalho.
27612 martineau

2
Como, como você mencionou, o jsonmódulo passará recursivamente dicts aninhados , não é necessário procurá-los nas duas funções - portanto, as duas elifcláusulas que os procuram devem ser removidas.
27612 martineau

1
Observe que o início dos nomes das funções com sublinhado tem um significado especial para as instruções de importação. Se você colocar essas funções em um arquivo chamado Utility.py e em outro arquivo from Utility import *, as funções não serão vistas por causa desse sublinhado.
M Katz

1
Essa é uma péssima idéia. object_hooké chamado para cada objeto json analisado; portanto, se você recorrer ao que é dado, estará re-byteificando as coisas que já "byteified". O desempenho aumentará geometricamente com o tamanho do objeto. Incluí aqui uma resposta que usa object_pairs_hooke não sofre desse problema.
Travis Jensen

38

Isso ocorre porque o json não tem diferença entre objetos de cadeia e objetos unicode. Eles são todos strings em javascript.

Eu acho que o JSON está certo em retornar objetos unicode . Na verdade, eu não aceitaria nada menos, já que as strings javascript são de fato unicodeobjetos (ou seja, strings JSON (javascript) podem armazenar qualquer tipo de caractere unicode), por isso faz sentido criar unicodeobjetos ao traduzir strings do JSON. As strings simples simplesmente não se encaixam, pois a biblioteca precisa adivinhar a codificação desejada.

É melhor usar unicodeobjetos de string em qualquer lugar. Portanto, sua melhor opção é atualizar suas bibliotecas para que elas possam lidar com objetos unicode.

Mas se você realmente deseja bytestrings, apenas codifique os resultados para a codificação de sua escolha:

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

Obrigado nosklo, foi o que fiz primeiro. Mas, como eu disse, os dados reais que eu usei são bastante aninhados e tudo, então isso introduziu um pouco de sobrecarga. Ainda estou procurando uma solução automática ... Há pelo menos um relatório de bug por aí onde as pessoas reclamam sobre o simplejson retornando objetos de string em vez de unicode.
Brutus

1
@Brutus: Acho que o json está certo ao retornar objetos unicode. Na verdade, eu não aceitaria nada menos, já que as strings javascript são de fato objetos unicode. O que quero dizer é que as strings json (javascript) podem armazenar qualquer tipo de caractere unicode, por isso faz sentido criar objetos unicode ao traduzir a partir de json. Você realmente deve consertar suas bibliotecas.
Nosklo 5/06/09

16

Existe uma solução fácil.

TL; DR - use em ast.literal_eval()vez de json.loads(). Ambos aste jsonestão na biblioteca padrão.

Embora não seja uma resposta 'perfeita', é muito importante 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 complicado quando alguns objetos são realmente cadeias Unicode. A resposta completa fica peluda rapidamente.


11
Melhor ter certeza que seu json não contém null, trueou falsevalores, porque eles não são válidos em python e fará com que literal_eval()a falhar.
ʇsәɹoɈ

3
@ ʇsәɹoɈ Também espero que o seu JSON não contenha um solidus ( \/) dentro de uma string ou uma sequência de escape unicode (como "\u0061", que é outra maneira de escrever "a"). A sintaxe literal do Python é incompatível com o JSON de várias maneiras, e eu não confiaria nesta resposta para nenhum script que não fosse jogar fora.
Mark Amery

As pessoas têm razão em dizer que, se a string realmente é unicode, essa resposta falha, mas se esse fosse o caso, não poderíamos converter uma string de qualquer maneira. +1 para uma resposta que só funciona quando ele funciona e gera uma exceção de outra forma
Stefan Sullivan

se possível, não use jsonpara despejar os dados, apenas use printse estiver executando o python. Então ast.literal_evalfunciona
Jean-François Fabre

11

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

object_pairs_hooké uma função opcional que será chamada com o resultado de qualquer objeto literal decodificado com uma lista ordenada de pares. O valor de retorno de object_pairs_hookserá usado em vez de 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.OrderedDictlembrará a ordem de inserção). Se object_hooktambém estiver definido, ele object_pairs_hookterá prioridade.

Com ele, você recebe cada objeto JSON para poder decodificar 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'}}

Observe que eu nunca preciso chamar o gancho recursivamente, pois todos os objetos serão entregues ao gancho quando você usar o object_pairs_hook. Você precisa se preocupar com as listas, mas, como pode ver, um objeto dentro de uma lista será convertido corretamente e não precisará recursar para que isso aconteça.

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

for key, value in pairs:

para

for key, value in pairs.iteritems():

Em seguida, use em object_hookvez 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'}}

O uso de object_pairs_hookresultados em um dicionário a menos é instanciado para cada objeto no objeto JSON, que, se você estivesse analisando um grande documento, pode valer a pena.


1
Isso é legal e parece muito próximo de merecer a marca de verificação verde (que Brutus, admiravelmente, já passou liberalmente à medida que surgiram melhores respostas). Mas ... por que não realmente lidar com listas corretamente no deunicodify_hookque você exibe nesta resposta? No momento, você tem uma implementação deunicodify_hookque não itera sobre listas e desunicodifica as seqüências e listas dentro delas; portanto, a saída que você está exibindo não corresponde à saída que seu gancho realmente produzirá. Corrija isso, e essa resposta será superior à minha.
Mark Amery

Frívolo: Eu também sugeriria demonstrar a função com o intérprete CPython comum, e não com o que você está usando aqui (que eu acho que é o IronPython)? O intérprete CPython é mais familiar para a maioria dos usuários de Python e, na minha opinião, é mais bonito.
Mark Amery

Isso não funciona para mim, mas tenho certeza de que é uma peculiaridade do que estou fazendo ... Estou armazenando uma lista de um documento json maior em um arquivo. Quer eu o carregue com ou sem este object_pairs_hook, cada item será unicode. Droga.
rsaw

1
@rsaw Bom ponto! Como o object_pairs_hookúnico é chamado para objetos , se o seu texto JSON tiver uma lista de cadeias no nível superior, esta solução falhará. Não há como corrigir isso sem chamar alguma função na coisa retornada json.load; nenhum dos json.loadganchos pode garantir que você será capaz de lidar com todas as cordas. Eu acho que essa é uma falha grande o suficiente para eu continuar recomendando minha solução usando os ganchos.
Mark Amery

-1 porque acabei de perceber que Mirec Miskuf já postou uma resposta de gancho de objeto que não tem as desvantagens da abordagem de Mike Brennan (re-byteify os mesmos dicionários várias vezes) nem deste (falha em byteify listas aninhadas ou listas de nível superior ou strings). Não sei ao certo por que a resposta dele definha sem quase nenhuma atenção, enquanto essa - que é inferior - rapidamente ganhou votos.
Mark Amery

9

Receio que não haja maneira de conseguir isso automaticamente na biblioteca simplejson.

O scanner e o decodificador no simplejson foram 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_scanstringse a versão C não estiver disponível. A scanstringfunção é chamada várias vezes por quase todas as rotinas que o simplejson possui para decodificar uma estrutura que pode conter texto. Você precisaria digitar o scanstringvalor em simplejson.decoder ou subclasse JSONDecodere fornecer praticamente toda a sua própria implementação de qualquer coisa que possa conter texto.

A razão pela qual simplejson gera unicode, no entanto, é que a especificação json menciona especificamente que "Uma string é uma coleção de zero ou mais caracteres Unicode" ... o suporte ao unicode é assumido como parte do próprio formato. A scanstringimplementação do Simplejson chega ao ponto de varrer e interpretar escapes unicode (mesmo verificação de erros para representações de conjuntos de caracteres de bytes múltiplos malformados), portanto, a única maneira pela qual ele pode retornar o valor com segurança é como unicode.

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


4

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

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

  1. NUNCA use yaml.load nos dados do campo. É um recurso (!) Do yaml para executar código arbitrário oculto na estrutura.

  2. Você pode fazê-lo funcionar também para não ASCII através deste:

    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 o desempenho não tem comparação com a resposta de Mark Amery:

Jogando algumas amostras profundamente aninhadas nos dois métodos, recebo isso (com dt [j] = delta time de json.loads (json.dumps (m))):

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

Portanto, desserialização, incluindo caminhar completamente na árvore e codificar, bem dentro da ordem de magnitude da implementação baseada em C do json. Acho isso notavelmente rápido e também mais robusto que a carga yaml em estruturas profundamente aninhadas. E menos propenso a erros de segurança, olhando para yaml.load.

=> Embora eu aprecie um ponteiro para um conversor baseado apenas em C, a função byteify deve ser a resposta padrão.

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

Por quê?

Normalização Unicode . Para quem não sabe: Pegue um analgésico e leia isso .

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

  1. obtenha suas derivações a partir de dumps json aninhados
  2. normalize os valores de entrada do usuário para encontrar as coisas em seu armazenamento.

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


3

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


2

Basta usar pickle em vez de json para despejo e carregamento, da seguinte maneira:

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

A saída que produz é (seqüências de caracteres e números inteiros são manipulados corretamente):

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

1
+1 para uma solução que não requer pacotes adicionais (como yaml ). Mas às vezes - como no meu caso original - eu preciso ter os dados em JSON, portanto, pickle nem sempre é a melhor opção. Além disso, você tem safe_loadno YAML, não sei se existe algo semelhante para picles .
Brutus

1

Então, eu encontrei o mesmo problema. Adivinhe qual foi o primeiro resultado do Google.

Como eu preciso passar todos os dados para o PyGTK, as strings unicode também não são muito úteis para mim. Então, eu tenho outro método de conversão recursiva. Na verdade, também é necessário para a conversão de JSON tipesafe - json.dump () seria liberado em quaisquer literais, como objetos Python. Porém, não converte í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

O único problema que pode surgir aqui é se você precisar das chaves em um dicionário convertido do unicode. Embora essa implementação converta os valores, ela mantém as chaves unicode. Se você criar um 'newobj', use newobj [str (i)] = ... e atribua obj = newobj quando terminar, as chaves também serão convertidas.
Neal Stublen

Isso pode ser mais bonito com compreensões ou melhor com a conversão de chaves. Também é unidiomatic; ele muda os objetos no lugar (no caso de dicionários) e retorna o novo valor, que é inconsistente com os métodos de coleção internos do Python que modificam o objeto atual ou retornam um novo, mas não ambos.
Mark Amery

1

Eu tinha um ditado JSON como uma string. As chaves e os valores eram objetos unicode, como no exemplo a seguir:

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

Eu poderia usar a byteifyfunção sugerida acima convertendo a string em um dictobjeto usando ast.literal_eval(myStringDict).


O exemplo que você deu não é um exemplo de JSON. {u'key':u'value'}não é JSON.
Mark Amery

2
Eu sei perfeitamente que não é JSON. Foi assim que foi analisado de uma fonte externa no meu script python. Se fosse JSON diretamente como no exemplo a seguir, eu não precisaria da função byteify marcada como a solução: {"firstName": "John", "lastName": "Doe"}. Seria ótimo se antes de votar você ler as respostas. Obrigado.
Narko

1

Suporte Python2 e 3 usando gancho (em 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)

Devoluções:

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

0

É tarde para o jogo, mas eu construí esse lançador recursivo. Funciona para as minhas necessidades e acho que é relativamente completo. Isso pode ajudá-lo.

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

Apenas passe um objeto JSON assim:

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

Eu o tenho como um membro privado de uma classe, mas você pode redefinir o método como achar melhor.


Eu encontrei um problema em que estou tentando analisar o JSON e passar o mapeamento resultante para uma função como ** kwargs. Parece que os nomes dos parâmetros das funções não podem ser unicode, portanto, sua função _parseJSON é excelente. Se houver uma maneira mais fácil, alguém pode me avisar.
Neal Stublen

1
Esse código tem um problema - você faz uma chamada recursiva na parte da lista, que falhará se os elementos da lista não forem dicionários.
I82Much

Além do bug descrito por @ I82Much, esse nome também é mal nomeado (na verdade, ele não analisa o JSON; json.loadsé necessária uma chamada primeiro), arbitrariamente tenta converter seqüências de caracteres em ints sem motivo explicado e não é copiar e copiar colar pronto.
Mark Amery

0

Reescrevi _parse_json () de Wells para lidar com casos em que o próprio objeto json é uma matriz (meu 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

0

aqui está um codificador recursivo escrito em C: https://github.com/axiros/nested_encode

Sobrecarga de desempenho para estruturas "médias" em torno de 10% em comparação com 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%

usando esta estrutura de teste:

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)

0

Com o Python 3.6, às vezes ainda encontro esse problema. Por exemplo, ao obter resposta de uma API REST e carregar o texto de resposta no JSON, ainda recebo as seqüências unicode. Encontrei uma solução simples usando json.dumps ().

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

-1

Também encontrei esse problema e, tendo que lidar com o JSON, criei um pequeno loop que converte as chaves unicode em strings. (simplejson no GAE não retorna chaves de sequência.)

obj é o objeto decodificado do 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é o que passo ao construtor do aplicativo GAE (que não gosta de unicodedigitar **kwargs)

Não é tão robusto quanto a solução da Wells, mas muito menor.


-1

Eu adaptei o código da resposta de Mark Amery , particularmente para me livrar deisinstance profissionais da digitação de patos.

A codificação é feita manualmente e ensure_asciiestá desativada. A documentação do python para json.dumpdiz que

Se sure_ascii for True (o padrão), todos os caracteres não ASCII na saída serão escapados com sequências \ uXXXX

Isenção de responsabilidade: no doctest usei a língua húngara. Algumas codificações de caracteres relacionadas ao húngaro são: cp852a codificação IBM / OEM usada, por exemplo. no DOS (às vezes referido como ascii , incorretamente eu acho, depende da configuração da página de código ), cp1250usado por exemplo. no Windows (às vezes chamado de ansi , dependente das configurações de localidade) e iso-8859-2, às vezes, usado em servidores http. O texto do teste Tüskéshátú kígyóbűvölőé atribuído a Koltai László (formulário de nome pessoal nativo) e é da 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)

Eu também gostaria de destacar a resposta de Jarret Hardie que faz referência à especificação JSON , citando:

Uma sequência é uma coleção de zero ou mais caracteres Unicode

No meu caso de uso, eu tinha arquivos com json. Eles são utf-8arquivos codificados. ensure_asciiresulta em arquivos json corretamente escapados, mas não muito legíveis, por isso adaptei a resposta de Mark Amery para atender às minhas necessidades.

O doctest não é particularmente atencioso, mas eu compartilho o código na esperança de que seja útil para alguém.


Não tenho certeza de ver os benefícios do uso da digitação de pato aqui? Sabemos que as coleções retornadas json.loadsserão listas ou dictos, não algum tipo definido pelo usuário ou definido pela biblioteca que implementa seus métodos e métodos mágicos; então, por que não fazer apenas uma isinstanceverificação? Não é mais fácil entender do que verificar a existência iteritemsou iteraceitar o objeto como argumento?
Mark Amery

@ MarkAmery é sobre lixões, não cargas. se você criar dados para despejar - em vez de carregá- los - não poderá ter certeza do que são. a idéia era deixá-lo vir de qualquer lugar no código.
N611x007

-2

Confira esta resposta para uma pergunta semelhante como esta, que afirma que

O prefixo u significa apenas que você tem uma string Unicode. Quando você realmente usa a string, ela não aparece nos seus dados. Não seja jogado pela saída impressa.

Por exemplo, tente o seguinte:

print mail_accounts[0]["i"]

Você não verá um u.


Não é verdade se, por exemplo, você deseja formatar algo que contenha uma string unicode, em Py2. por exemplo, '{}'.format({u'x' : u'y'})ainda inclui os u's.
Ponkadoodle 15/04
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.