J'ai un fichier txt avec des structures json. le problème est que le fichier ne contient pas seulement des structures json mais aussi du texte brut comme une erreur de journal:
import json
with open("data.txt", "r", encoding="utf-8", errors='ignore') as f:
json_data = json.load(f)
J'ai lu le fichier .txt mais en essayant de patcher les structures jason j'ai une erreur: IN:
2019-01-18 21:00:05.4521|INFO|Technical|Batch Started|
2019-01-18 21:00:08.8740|INFO|Technical|Got Entities List from 20160101 00:00 :
{
"name": "1111",
"results": [{
"filename": "xxxx",
"numberID": "7412"
}, {
"filename": "xgjhh",
"numberID": "E52"
}]
}
2019-01-18 21:00:05.4521|INFO|Technical|Batch Started|
2019-01-18 21:00:08.8740|INFO|Technical|Got Entities List from 20160101 00:00 :
{
"name": "jfkjgjkf",
"results": [{
"filename": "hhhhh",
"numberID": "478962"
}, {
"filename": "jkhgfc",
"number": "12544"
}]
}
OUT: json.decoder.JSONDecodeError: Données supplémentaires: ligne 1 colonne 5 (car 4)
Je voudrais parce json et enregistrer en tant que fichier csv.
5 Réponses :
Vous pouvez compter les accolades dans votre fichier pour trouver le début et la fin de vos jsons, et les stocker dans la liste, ici found_jsons.
{
"results": [
{
"numberID": "7412",
"filename": "xxxx"
},
{
"numberID": "E52",
"filename": "xgjhh"
}
],
"name": "1111"
}
{
"results": [
{
"numberID": "478962",
"filename": "hhhhh"
},
{
"number": "12544",
"filename": "jkhgfc"
}
],
"name": "jfkjgjkf"
}
Sortie
import json
open_chars = 0
saved_content = []
found_jsons = []
for i in content.splitlines():
open_chars += i.count('{')
if open_chars:
saved_content.append(i)
open_chars -= i.count('}')
if open_chars == 0 and saved_content:
found_jsons.append(json.loads('\n'.join(saved_content)))
saved_content = []
for i in found_jsons:
print(json.dumps(i, indent=4))
Notez que cela ne fonctionnera que si les lignes non JSON ne contiennent pas de parenthèses.
Cela sera également interrompu si le JSON contient une chaîne avec un crochet bouclé sans correspondance. Quel est possible dans le champ filename .
Vous pouvez faire l'une des choses suivantes:
Sur la ligne de commande, supprimez toutes les lignes où, par exemple, "| INFO | Technical |" apparaît (en supposant que cela apparaisse dans chaque ligne de texte brut):
sed -i '' -e '/ \ | INFO \ | Technique / d' votre nom de fichier (si sur Mac),
sed -i '/ \ | INFO \ | Technique / d' votre nom de fichier (si sous Linux).
Déplacez ces lignes brutes dans leurs propres champs JSON
J'imagine que l'utilisation de sed comme étape de prétraitement avant de lire le json en python sera beaucoup plus performant que de tout faire en python.
En supprimant tout le contenu non-JSON connu, le reste du fichier ne serait pas analysable comme un objet JSON valide car il deviendrait en fait plusieurs objets JSON. Notez également que le concept ne fonctionnerait que si le contenu non JSON suit un modèle connu et cohérent.
Vous devez toujours lire plusieurs objets json à partir d'un seul fichier, qui est une sorte de douleur .
Une solution plus générale pour analyser un fichier avec des objets JSON mélangés à un autre contenu sans aucune hypothèse sur le contenu non JSON serait de diviser le contenu du fichier en fragments par les accolades, en commençant par le premier fragment qui est une ouverture crochet bouclé, puis joignez le reste des fragments un par un jusqu'à ce que la chaîne jointe soit analysable en JSON:
{'name': '1111', 'results': [{'filename': 'xxxx', 'numberID': '7412'}, {'filename': 'xgjhh', 'numberID': 'E52'}]}
{'name': 'jfkjgjkf', 'results': [{'filename': 'hhhhh', 'numberID': '478962'}, {'filename': 'jkhgfc', 'number': '12544'}]}
Ceci génère:
import re
fragments = iter(re.split('([{}])', f.read()))
while True:
try:
while True:
candidate = next(fragments)
if candidate == '{':
break
while True:
candidate += next(fragments)
try:
print(json.loads(candidate))
break
except json.decoder.JSONDecodeError:
pass
except StopIteration:
break
p>
Utilisez les "structures de texte" comme délimiteur entre les objets JSON.
Parcourez les lignes du fichier, en les enregistrant dans une mémoire tampon jusqu'à ce que vous rencontriez une ligne qui est une ligne de texte, à quel point analysez les lignes que vous avez enregistrées en tant qu'objet JSON.
import re
import json
def is_text(line):
# returns True if line starts with a date and time in "YYYY-MM-DD HH:MM:SS" format
line = line.lstrip('|') # you said some lines start with a leading |, remove it
return re.match("^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})", line)
json_objects = []
with open("data.txt") as f:
json_lines = []
for line in f:
if not is_text(line):
json_lines.append(line)
else:
# if there's multiple text lines in a row json_lines will be empty
if json_lines:
json_objects.append(json.loads("".join(json_lines)))
json_lines = []
# we still need to parse the remaining object in json_lines
# if the file doesn't end in a text line
if json_lines:
json_objects.append(json.loads("".join(json_lines)))
print(json_objects)
Répéter la logique dans les deux dernières lignes est un peu moche, mais vous devez gérer le cas où la dernière ligne de votre fichier n'est pas une ligne de texte, donc lorsque vous avez terminé avec le pour la boucle , vous devez analyser le dernier objet assis dans json_lines s'il y en a un.
Je suppose qu'il n'y a jamais plus d'un objet JSON entre les lignes de texte et aussi mon expression regex pour une date sera interrompue dans 8 000 ans.
Cette solution supprimera les structures non JSON et les encapsulera dans une structure JSON contenant. Cela devrait faire le travail pour vous. Je publie ceci tel quel par souci de commodité, puis je modifierai ma réponse pour une explication plus claire. J'éditerai ce premier bit quand j'aurai fait cela:
{"entries":[{"name": "1111","results": [{"filename": "xxxx","numberID": "7412"}, {"filename": "xgjhh","numberID": "E52"}]}, {"name": "jfkjgjkf","results": [{"filename": "hhhhh","numberID": "478962"}, {"filename": "jkhgfc","number": "12544"}]}]}
Sortie:
import json
with open("data.txt", "r", encoding="utf-8", errors='ignore') as f:
cleaned = ''.join([item.strip() if item.strip() is not '' else '-split_here-' for item in f.readlines() if '|INFO|' not in item]).split('-split_here-')
json_data = json.loads(json.dumps(('{"entries":[' + ''.join([entry + ', ' for entry in cleaned])[:-2] + ']}')))
Que se passe-t-il ici? P >
Dans la ligne nettoyé = ... , nous utilisons une compréhension de liste qui crée une liste des lignes dans le fichier ( f.readlines () ) qui ne contient pas la chaîne | INFO | et ajoute la chaîne -split_here- à la liste chaque fois qu'il y a une ligne vide (où .strip () donne '').
Ensuite, nous convertissons cette liste de lignes ( '' .join () ) en une string.
Enfin, nous convertissons cette chaîne ( .split (' -split_here- ') dans une liste de listes, séparant les structures JSON en leurs propres listes s, marquées par des lignes vides dans data.txt .
Dans la ligne json_data = ... , nous ajoutons un «,» à chacune des structures JSON en utilisant une compréhension de liste. p >
Ensuite, nous convertissons cette liste cod e> en une seule chaîne , en supprimant le dernier ',' ( .join () [: - 2] . [: -2] tranches des deux derniers caractères de la chaîne.).
Nous enveloppons ensuite la chaîne avec '{"entries": [' code> et ']}' pour faire de l'ensemble une structure JSON valide, et le transmettre à json.dumps et json.loads à nettoyer tout encodage et charger vos données dans un objet python.
Chaque ligne non-json commence-t-elle par une date et une heure? Vous pouvez utiliser une expression régulière pour trouver toutes les lignes commençant par
"{number} - {number} - {number}"et passer toutes les lignes entre celles-ci àjson.loads ()La première ligne est datetime et entre chaque structure json, elle commence par le caractère | puis une date / heure, comme ceci: | 2019-01-18 21: 00: 11.7022 | INFO | Technique | Profil d'entité obtenu pour 372245 en 0,43s | 2019-01-18 21: 00: 11.8897 | INFO | Technique | Vous avez le profil suivant pour l'entité 372514: {et un autre début de structure json ...