Gostaria de ler vários objetos JSON de um arquivo / fluxo em Python, um de cada vez. Infelizmente, json.load()
apenas .read()
s até o final do arquivo; não parece haver nenhuma maneira de usá-lo para ler um único objeto ou para iterar preguiçosamente sobre os objetos.
Há alguma maneira de fazer isso? Usar a biblioteca padrão seria o ideal, mas se houver uma biblioteca de terceiros, eu a usaria.
No momento, estou colocando cada objeto em uma linha separada e usando json.loads(f.readline())
, mas realmente prefiro não precisar fazer isso.
Exemplo de uso
example.py
import my_json as json
import sys
for o in json.iterload(sys.stdin):
print("Working on a", type(o))
in.txt
{"foo": ["bar", "baz"]} 1 2 [] 4 5 6
sessão de exemplo
$ python3.2 example.py < in.txt
Working on a dict
Working on a int
Working on a int
Working on a list
Working on a int
Working on a int
Working on a int
python
json
serialization
Jeremy
fonte
fonte
{"foo": ["bar", "baz"]}
no meu exemplo), ele deveyield
chegar e então continuar para o próximo (1
).'\n'
(uma única nova linha, não dois caracteres) em sua representação json porque'\n'
deve ser escapado dentro de uma string json e, portanto,'\n'
pode ser usado apenas para formatação, por exemplo, eu acredito quejson.dumps()
não t introduzir'\n'
por padrão. Esteja ciente de que novas linhas Unicode como U + 0085 podem não ter escape dentro de strings json.Respostas:
Esta é uma solução muito mais simples. O segredo é tentar, falhar e usar as informações da exceção para analisar corretamente. A única limitação é que o arquivo deve ser pesquisável.
Editar: acabei de notar que isso só funcionará para Python> = 3.5. Anteriormente, as falhas retornam um ValueError, e você deve analisar a posição da string, por exemplo
fonte
re
não vai funcionar - as barras invertidas precisam escapar. Considere uma string brutar'...'
.ujson
vez de,json
terá uma grande aceleraçãoJSON geralmente não é muito bom para esse tipo de uso incremental; não há uma maneira padrão de serializar vários objetos para que eles possam ser carregados facilmente um por vez, sem analisar todo o lote.
A solução de objeto por linha que você está usando também é vista em outro lugar. Scrapy chama isso de 'linhas JSON':
Você pode fazer isso um pouco mais Pythonically:
Acho que essa é a melhor maneira - não depende de nenhuma biblioteca de terceiros e é fácil de entender o que está acontecendo. Eu o usei em alguns de meus próprios códigos também.
fonte
Um pouco tarde talvez, mas eu tive exatamente esse problema (bem, mais ou menos). Minha solução padrão para esses problemas é geralmente fazer uma divisão de regex em algum objeto raiz conhecido, mas no meu caso era impossível. A única maneira viável de fazer isso genericamente é implementar um tokenizer adequado .
Depois de não encontrar uma solução genérica o suficiente e com desempenho razoável, acabei fazendo isso sozinho, escrevendo o
splitstream
módulo. É um pré-tokenizador que entende JSON e XML e divide um fluxo contínuo em vários blocos para análise (no entanto, deixa a análise real para você). Para obter algum tipo de desempenho com isso, ele é escrito como um módulo C.Exemplo:
fonte
Claro que você pode fazer isso. Você apenas tem que atender
raw_decode
diretamente. Essa implementação carrega todo o arquivo na memória e opera nessa string (da mesma forma quejson.load
faz); se você tiver arquivos grandes, pode modificá-lo para ler apenas o arquivo conforme necessário, sem muita dificuldade.Utilização: tal como solicitou, é um gerador.
fonte
Este é um problema bastante desagradável, na verdade, porque você tem que fazer o stream em linhas, mas a correspondência de padrões em várias linhas contra chaves, mas também a correspondência de padrões json. É uma espécie de preparação json seguida por uma análise json. Json é, em comparação com outros formatos, fácil de analisar, então nem sempre é necessário ir para uma biblioteca de análise, no entanto, como devemos resolver esses problemas conflitantes?
Geradores para o resgate!
A beleza dos geradores para um problema como esse é que você pode empilhá-los um em cima do outro, abstraindo gradualmente a dificuldade do problema enquanto mantém a preguiça. Também considerei usar o mecanismo para passar os valores de volta para um gerador (send ()), mas felizmente descobri que não precisava usá-lo.
Para resolver o primeiro dos problemas, você precisa de algum tipo de streamingfinditer, como uma versão de streaming de re.finditer. Minha tentativa de fazer isso abaixo puxa as linhas conforme necessário (descomente a instrução de depuração para ver) enquanto ainda retorna correspondências. Na verdade, eu o modifiquei ligeiramente para produzir linhas não correspondidas, bem como correspondências (marcadas como 0 ou 1 na primeira parte da tupla produzida).
Com isso, é possível combinar até chaves, levar em conta cada vez se as chaves estão balanceadas e retornar objetos simples ou compostos, conforme apropriado.
Isso retorna tuplas da seguinte maneira:
Basicamente, essa é a parte desagradável feita. Agora só temos que fazer o nível final de análise conforme acharmos adequado. Por exemplo, podemos usar a função iterload de Jeremy Roman (Obrigado!) Para fazer a análise de uma única linha:
Teste-o:
Recebo estes resultados (e se você ativar a linha de depuração, verá que ela puxa as linhas conforme necessário):
Isso não funcionará em todas as situações. Devido à implementação da
json
biblioteca, é impossível funcionar totalmente corretamente sem reimplementar o analisador você mesmo.fonte
"}"
e"]"
ocorrerem dentro de strings JSON? Acho que essa é uma limitação geral da análise com regex.Acredito que a melhor maneira de fazer isso seria usar uma máquina de estado. Abaixo está um código de amostra que desenvolvi convertendo um código NodeJS no link abaixo para Python
3 (palavra-chave não local usada disponível apenas em Python 3, o código não funcionará em Python 2)Edit-1: Código atualizado e compatível com Python 2
Edit-2: Atualizado e adicionado uma versão somente Python3 também
https://gist.github.com/creationix/5992451
Python 3 apenas versão
Versão compatível com Python 2
Testando
A saída do mesmo é
fonte
Eu gostaria de fornecer uma solução. O pensamento principal é "tentar" decodificar: se falhar, forneça mais alimentação, caso contrário, use as informações de deslocamento para preparar a próxima decodificação.
No entanto, o módulo json atual não pode tolerar que ESPAÇO no cabeçalho da string seja decodificado, então eu tenho que retirá-los.
============================= Eu testei vários arquivos txt e funciona bem. (in1.txt)
(in2.txt)
(in.txt, sua inicial)
(saída para o caso de teste de Benedict)
fonte
Aqui está o meu:
fonte
Usei a solução elegante de @wuilang. A abordagem simples - ler um byte, tentar decodificar, ler um byte, tentar decodificar, ... - funcionou, mas infelizmente era muito lenta.
No meu caso, eu estava tentando ler objetos JSON "bem impressos" do mesmo tipo de objeto de um arquivo. Isso me permitiu otimizar a abordagem; Pude ler o arquivo linha por linha, decodificando apenas quando encontrei uma linha que continha exatamente "}":
Se você estiver trabalhando com um JSON compacto por linha que escapa de novas linhas em literais de string, poderá simplificar ainda mais essa abordagem com segurança:
Obviamente, essas abordagens simples funcionam apenas para tipos muito específicos de JSON. No entanto, se essas suposições forem válidas, essas soluções funcionarão correta e rapidamente.
fonte
Se você usar uma instância json.JSONDecoder, poderá usar a
raw_decode
função de membro. Ele retorna uma tupla de representação python do valor JSON e um índice de onde a análise parou. Isso torna mais fácil dividir (ou buscar em um objeto de fluxo) os valores JSON restantes. Não estou muito feliz com o loop while extra para pular o espaço em branco entre os diferentes valores JSON na entrada, mas na minha opinião ele dá conta do recado.A próxima versão é muito mais curta e come a parte da string que já foi analisada. Parece que, por algum motivo, uma segunda chamada json.JSONDecoder.raw_decode () parece falhar quando o primeiro caractere na string é um espaço em branco, essa também é a razão pela qual pulo o espaço em branco no whileloop acima ...
Na documentação sobre a classe json.JSONDecoder, o método raw_decode https://docs.python.org/3/library/json.html#encoders-and-decoders contém o seguinte:
E esses dados estranhos podem facilmente ser outro valor JSON. Em outras palavras, o método pode ser escrito com esse propósito em mente.
Com o input.txt usando a função superior, obtenho a saída de exemplo apresentada na questão original.
fonte
Você pode usar https://pypi.org/project/json-stream-parser/ exatamente para essa finalidade.
resultado
fonte