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?


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.


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


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)

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

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

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.


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"


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

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/


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


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)

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