Je voudrais lire plusieurs objets JSON à partir d'un fichier / flux en Python, un à la fois. Malheureusement, json.load()
juste .read()
jusqu'à la fin du fichier; il ne semble pas y avoir de moyen de l'utiliser pour lire un seul objet ou pour parcourir paresseusement les objets.
Y a-t-il un moyen de faire ça? Utiliser la bibliothèque standard serait idéal, mais s'il y avait une bibliothèque tierce, je l'utiliserais à la place.
Pour le moment, je mets chaque objet sur une ligne distincte et json.loads(f.readline())
j'utilise, mais je préférerais vraiment ne pas avoir besoin de le faire.
Exemple d'utilisation
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
exemple de session
$ 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
Jérémie
la source
la source
{"foo": ["bar", "baz"]}
dans mon exemple), il devrait leyield
faire, puis passer au suivant (1
).'\n'
(un seul saut de ligne, pas deux caractères) dans sa représentation json car il'\n'
doit être échappé à l' intérieur d'une chaîne json et'\n'
peut donc être utilisé pour le formatage uniquement, par exemple, je crois que nonjson.dumps()
' t introduire'\n'
par défaut. Attention, les retours à la ligne Unicode tels que U + 0085 peuvent ne pas être échappés dans les chaînes json.Réponses:
Voici une solution beaucoup plus simple. Le secret est d'essayer, d'échouer et d'utiliser les informations de l'exception pour analyser correctement. La seule limitation est que le fichier doit être recherché.
Edit: vient de remarquer que cela ne fonctionnera que pour Python> = 3.5. Pour plus tôt, les échecs renvoient une ValueError, et vous devez analyser la position de la chaîne, par exemple
la source
re
ne fonctionnera pas - les contre-obliques doivent s'échapper. Considérez une chaîne bruter'...'
.ujson
place dejson
vous, vous obtiendrez une accélération énormeJSON n'est généralement pas très bon pour ce type d'utilisation incrémentielle; il n'y a pas de moyen standard de sérialiser plusieurs objets afin qu'ils puissent facilement être chargés un à la fois, sans analyser le lot entier.
La solution objet par ligne que vous utilisez est également vue ailleurs. Scrapy l'appelle `` lignes JSON '':
Vous pouvez le faire un peu plus en python:
Je pense que c'est le meilleur moyen - il ne repose sur aucune bibliothèque tierce et il est facile de comprendre ce qui se passe. Je l'ai également utilisé dans mon propre code.
la source
Un peu tard peut-être, mais j'ai eu ce problème exact (enfin, plus ou moins). Ma solution standard pour ces problèmes est généralement de simplement faire une division de regex sur un objet racine bien connu, mais dans mon cas, c'était impossible. Le seul moyen possible de le faire de manière générique est d'implémenter un tokenizer approprié .
Après n'avoir pas trouvé de solution suffisamment générique et raisonnablement performante, j'ai fini par le faire moi-même, en écrivant le
splitstream
module. C'est un pré-tokenizer qui comprend JSON et XML et divise un flux continu en plusieurs morceaux pour l'analyse (cela vous laisse cependant l'analyse réelle). Pour en tirer une sorte de performance, il est écrit comme un module C.Exemple:
la source
Bien sûr, vous pouvez le faire. Vous devez juste prendre
raw_decode
directement. Cette implémentation charge le fichier entier en mémoire et opère sur cette chaîne (tout comme lejson.load
fait); si vous avez des fichiers volumineux, vous pouvez le modifier pour ne lire que le fichier si nécessaire sans trop de difficulté.Utilisation: comme vous l'avez demandé, c'est un générateur.
la source
C'est un problème assez désagréable en fait parce que vous devez diffuser en lignes, mais que le motif correspond sur plusieurs lignes contre des accolades, mais aussi que le motif correspond à json. C'est une sorte de json-preparse suivi d'une analyse json. Json est, par rapport à d'autres formats, facile à analyser, il n'est donc pas toujours nécessaire d'opter pour une bibliothèque d'analyse, mais comment devrions-nous résoudre ces problèmes conflictuels?
Générateurs à la rescousse!
La beauté des générateurs pour un problème comme celui-ci est que vous pouvez les empiler les uns sur les autres en évitant progressivement la difficulté du problème tout en maintenant la paresse. J'ai également envisagé d'utiliser le mécanisme pour renvoyer les valeurs dans un générateur (send ()) mais heureusement j'ai trouvé que je n'avais pas besoin de l'utiliser.
Pour résoudre le premier des problèmes, vous avez besoin d'une sorte de streamfinditer, en tant que version streaming de re.finditer. Ma tentative ci-dessous tire les lignes au besoin (décommentez l'instruction de débogage pour voir) tout en renvoyant des correspondances. En fait, je l'ai ensuite légèrement modifié pour donner des lignes non correspondantes ainsi que des correspondances (marquées 0 ou 1 dans la première partie du tuple produit).
Avec cela, il est alors possible de faire correspondre jusqu'à des accolades, de prendre en compte à chaque fois si les accolades sont équilibrées, puis de renvoyer des objets simples ou composés selon le cas.
Cela renvoie les tuples comme suit:
En gros, c'est la partie la plus désagréable. Il ne nous reste plus qu'à faire le dernier niveau d'analyse comme bon nous semble. Par exemple, nous pouvons utiliser la fonction iterload de Jeremy Roman (Merci!) Pour analyser une seule ligne:
Essaye-le:
J'obtiens ces résultats (et si vous activez cette ligne de débogage, vous verrez qu'elle tire les lignes au besoin):
Cela ne fonctionnera pas pour toutes les situations. En raison de l'implémentation de la
json
bibliothèque, il est impossible de travailler entièrement correctement sans réimplémenter l'analyseur vous-même.la source
"}"
et"]"
apparaissent à l'intérieur de chaînes JSON? Je pense que c'est une limitation générale de l'analyse avec regex.Je pense qu'une meilleure façon de procéder serait d'utiliser une machine à états. Vous trouverez ci-dessous un exemple de code que j'ai élaboré en convertissant un code NodeJS sur le lien ci-dessous en Python
3 (mot clé non local utilisé uniquement disponible en Python 3, le code ne fonctionnera pas sur Python 2)Edit-1: code mis à jour et rendu compatible avec Python 2
Edit-2: Mise à jour et ajout d'une version Python3 uniquement
https://gist.github.com/creationix/5992451
Version Python 3 uniquement
Version compatible Python 2
Le tester
La sortie de la même chose est
la source
J'aimerais apporter une solution. L'idée clé est "d'essayer" de décoder: si cela échoue, donnez-lui plus de nourriture, sinon utilisez les informations de décalage pour préparer le prochain décodage.
Cependant, le module json actuel ne peut pas tolérer le décodage de SPACE en tête de chaîne, je dois donc les supprimer.
========================= J'ai testé plusieurs fichiers txt, et cela fonctionne très bien. (in1.txt)
(in2.txt)
(in.txt, votre initiale)
(sortie pour le testcase de Benedict)
la source
Voici la mienne:
la source
J'ai utilisé la solution élégante de @ wuilang. L'approche simple - lire un octet, essayer de décoder, lire un octet, essayer de décoder, ... - a fonctionné, mais malheureusement c'était très lent.
Dans mon cas, j'essayais de lire des objets JSON "joliment imprimés" du même type d'objet à partir d'un fichier. Cela m'a permis d'optimiser l'approche; Je pouvais lire le fichier ligne par ligne, décodant uniquement lorsque j'ai trouvé une ligne qui contenait exactement "}":
Si vous travaillez avec un JSON compact une par ligne qui échappe les nouvelles lignes dans les chaînes littérales, vous pouvez simplifier encore plus cette approche en toute sécurité:
De toute évidence, ces approches simples ne fonctionnent que pour des types très spécifiques de JSON. Cependant, si ces hypothèses sont valables, ces solutions fonctionnent correctement et rapidement.
la source
Si vous utilisez une instance json.JSONDecoder, vous pouvez utiliser la
raw_decode
fonction membre. Il renvoie un tuple de représentation python de la valeur JSON et un index de l'endroit où l'analyse s'est arrêtée. Cela facilite le découpage (ou la recherche dans un objet de flux) des valeurs JSON restantes. Je ne suis pas très content de la boucle while supplémentaire pour sauter l'espace blanc entre les différentes valeurs JSON dans l'entrée, mais cela fait le travail à mon avis.La prochaine version est beaucoup plus courte et mange la partie de la chaîne qui est déjà analysée. Il semble que pour une raison quelconque, un deuxième appel json.JSONDecoder.raw_decode () semble échouer lorsque le premier caractère de la chaîne est un espace, c'est aussi la raison pour laquelle je saute l'espace blanc dans le whileloop ci-dessus ...
Dans la documentation sur la classe json.JSONDecoder, la méthode raw_decode https://docs.python.org/3/library/json.html#encoders-and-decoders contient les éléments suivants:
Et ces données superflues peuvent facilement être une autre valeur JSON. En d'autres termes, la méthode pourrait être écrite avec cet objectif à l'esprit.
Avec input.txt en utilisant la fonction supérieure, j'obtiens l'exemple de sortie tel que présenté dans la question d'origine.
la source
Vous pouvez utiliser https://pypi.org/project/json-stream-parser/ exactement dans ce but.
production
la source