Permitir que o objeto JSON aceite bytes ou permita que as cadeias de saída sejam abertas

177

Com o Python 3, estou solicitando um documento json a partir de uma URL.

response = urllib.request.urlopen(request)

O responseobjeto é um objeto parecido com um arquivo com reade readlinemétodos. Normalmente, um objeto JSON pode ser criado com um arquivo aberto no modo de texto.

obj = json.load(fp)

O que eu gostaria de fazer é:

obj = json.load(response)

No entanto, isso não funciona, pois o urlopen retorna um objeto de arquivo no modo binário.

Uma solução alternativa é, obviamente:

str_response = response.read().decode('utf-8')
obj = json.loads(str_response)

mas isso parece ruim ...

Existe uma maneira melhor de transformar um objeto de arquivo de bytes em um objeto de arquivo de string? Ou estou faltando algum parâmetro para uma codificação urlopenou json.loadpara ela?

Peter Smit
fonte
2
Eu acho que você tem um erro de digitação lá, "readall" deve ser "lido"?
Bob Yoplait
@BobYoplait Concordo.
CaptainNemo 23/09

Respostas:

79

HTTP envia bytes. Se o recurso em questão for texto, a codificação de caracteres é normalmente especificada, pelo cabeçalho HTTP do tipo de conteúdo ou por outro mecanismo (um RFC, HTML meta http-equiv, ...).

urllib devemos saber como codificar os bytes para uma string, mas é muito ingênuo - é uma biblioteca terrivelmente fraca e não-Pythonic.

Dive Into Python 3 fornece uma visão geral sobre a situação.

Sua "solução alternativa" está correta - embora pareça errado, é a maneira correta de fazê-lo.

Humphrey Bogart
fonte
6
Essa pode ser a maneira "correta" de fazer isso, mas se houvesse uma coisa que eu poderia desfazer no Python 3, seria essa porcaria de bytes / strings. Você pensaria que as funções internas da biblioteca pelo menos saberiam como lidar com outras funções internas da biblioteca. Parte do motivo pelo qual usamos python é a sintaxe intuitiva simples. Essa mudança quebra isso em todo o lugar.
ThatAintWorking
4
Confira a biblioteca "pedidos" - ele lida com esse tipo de coisa automaticamente.
precisa saber é o seguinte
2
Este não é o caso das funções internas da biblioteca que precisam "saber como" lidar com outras funções. JSON é definido como uma representação de objetos UTF-8, portanto, não é possível decodificar magicamente bytes dos quais não conhece a codificação. Eu concordo que urlopendeve ser capaz de decodificar os bytes em si, pois conhece a codificação. De qualquer forma, publiquei a solução da biblioteca padrão do Python como resposta - você pode fazer decodificação de fluxo de bytes usando o codecsmódulo.
jbg
1
@ThatAintWorking: eu discordo. Embora seja uma dor no pescoço explicitamente ter que gerenciar a diferença entre bytes e seqüências de caracteres, é uma dor muito maior fazer com que o idioma faça alguma conversão implícita para você. Conversões implícitas de bytes <-> string são uma fonte de muitos bugs, e o Python3 é muito útil para apontar as armadilhas. Mas concordo que a biblioteca tenha espaço para melhorias nessa área.
EvertW
@ Evite a falha, na minha opinião, forçando as strings a serem unicode em primeiro lugar.
ThatAintWorking
99

Maravilhosa biblioteca padrão do Python para o resgate…

import codecs

reader = codecs.getreader("utf-8")
obj = json.load(reader(response))

Funciona com py2 e py3.

Documentos: Python 2 , Python3

jbg
fonte
11
Eu recebi esse erro ao tentar esta resposta, python 3.4.3não sei por que? O erro foiTypeError: the JSON object must be str, not 'StreamReader'
Aaron Lelevier 5/08/2015
9
@AronYsidoro Você possivelmente usou em json.loads()vez de json.load()?
sleepycal
6
Para pontos de bonificação, usar a codificação especificada na resposta, em vez de assumir utf-8: response.headers.get_content_charset(). Retorna Nonese não houver codificação e não existir no python2.
Phil Frost
5
@PhilFrost Isso é bom. Na prática, vale a pena ter cuidado com isso; JSON é sempre UTF-8, UTF-16 ou UTF-32 por definição (e é provavelmente muito provável que seja UTF-8); portanto, se outra codificação for retornada pelo servidor da Web, é possível que haja uma configuração incorreta do software do servidor da Web em vez de JSON genuinamente não padrão.
jbg
6
quando eu usei no python 3.5, o erro foi "AttributeError: 'bytes' objeto não tem nenhum atributo 'read'"
Harper Koo
66

Cheguei à opinião de que a pergunta é a melhor resposta :)

import json
from urllib.request import urlopen

response = urlopen("site.com/api/foo/bar").read().decode('utf8')
obj = json.loads(response)
SergO
fonte
18

Para qualquer outra pessoa que tente resolver isso usando a requestsbiblioteca:

import json
import requests

r = requests.get('http://localhost/index.json')
r.raise_for_status()
# works for Python2 and Python3
json.loads(r.content.decode('utf-8'))
Luke Yeager
fonte
12
Essa funcionalidade está embutida em requests: você pode simplesmente fazer issor.json()
jbg
1
O esclarecer, se você usar o método @ jbg, você não precisa fazer json.loads. Tudo o que você precisa fazer é r.json()e já tem seu objeto JSON carregado em um dict.
precisa saber é o seguinte
*** UnicodeEncodeError: 'ascii' codec can't encode characters in position 264-265: ordinal not in range(128)
andilabs
13

Este funciona para mim, usei a biblioteca 'request' com o json()check-out do documento em pedidos para humanos

import requests

url = 'here goes your url'

obj = requests.get(url).json() 
Sarthak Gupta
fonte
Esta é a melhor maneira. Realmente legível, e qualquer pessoa que esteja fazendo algo assim deve ter solicitações.
Baldrickk
6

Eu tive problemas semelhantes usando o Python 3.4.3 e 3.5.2 e o Django 1.11.3. No entanto, quando atualizei para o Python 3.6.1, os problemas desapareceram.

Você pode ler mais sobre isso aqui: https://docs.python.org/3/whatsnew/3.6.html#json

Se você não está vinculado a uma versão específica do Python, considere atualizar para a 3.6 ou posterior.

PaulMest
fonte
3

Se você estiver enfrentando esse problema ao usar a microframework do balão, poderá fazer o seguinte:

data = json.loads(response.get_data(as_text=True))

Na documentação : "Se as_text estiver definido como True, o valor retornado será uma string unicode decodificada"

cs_stackX
fonte
Cheguei a esta página porque estava tendo problemas com os testes de unidade do Flask - obrigado por postar a chamada de linha única.
precisa saber é o seguinte
1

Sua solução alternativa realmente me salvou. Eu estava tendo muitos problemas ao processar a solicitação usando a estrutura Falcon. Isso funcionou para mim. req sendo o formulário de requisição curl pr httpie

json.loads(req.stream.read().decode('utf-8'))
thielyrics
fonte
1

Isso transmitirá os dados de bytes para o json.

import io

obj = json.load(io.TextIOWrapper(response))

io.TextIOWrapper é preferível ao leitor de módulo do codec. https://www.python.org/dev/peps/pep-0400/

Collin Anderson
fonte
`*** AttributeError: o objeto 'Response' não tem atributo 'legível' ''
andilabs
*** AttributeError: o objeto 'bytes' não tem nenhum atributo 'legível'
andilabs
Você está usando urllib ou solicitações? Isto é para urllib. Se você possui um objeto bytes, basta usar json.loads(bytes_obj.decode()).
18718 Collin Anderson
0

Acabei de encontrar este método simples para criar conteúdo HttpResponse como um json

import json

request = RequestFactory() # ignore this, this just like your request object

response = MyView.as_view()(request) # got response as HttpResponse object

response.render() # call this so we could call response.content after

json_response = json.loads(response.content.decode('utf-8'))

print(json_response) # {"your_json_key": "your json value"}

Espero que ajude você

Aditya Kresna Permana
fonte
0

No Python 3.6, você pode usar json.loads()para desserializar um bytesobjeto diretamente (a codificação deve ser UTF-8, UTF-16 ou UTF-32). Portanto, usando apenas módulos da biblioteca padrão, você pode:

import json
from urllib import request

response = request.urlopen(url).read()
data = json.loads(response)
Eugene Yarmash
fonte
-2

Eu usei abaixo programa para usar de json.loads()

import urllib.request
import json
endpoint = 'https://maps.googleapis.com/maps/api/directions/json?'
api_key = 'AIzaSyABbKiwfzv9vLBR_kCuhO7w13Kseu68lr0'
origin = input('where are you ?').replace(' ','+')
destination = input('where do u want to go').replace(' ','+')
nav_request = 'origin={}&destination={}&key={}'.format(origin,destination,api_key)
request = endpoint + nav_request
response = urllib.request.urlopen(request).read().decode('utf-8')
directions = json.loads(response)
print(directions)
Jayesh
fonte