Travailler avec des données JSON en Python

Travailler avec des données JSON en Python

Depuis sa création, JSON est rapidement devenu la norme de facto pour l’échange d’informations. Il y a de fortes chances que vous soyez ici, car vous devez transporter des données d’ici à là. Vous collectez peut-être des informations via un API ou stockez vos données dans un base de données de documents. D’une manière ou d’une autre, vous êtes jusqu’au cou dans JSON, et vous devez utiliser Python pour sortir.

Heureusement, c’est une tâche assez courante et, comme pour la plupart des tâches courantes, Python rend la tâche presque dégoûtante. N’ayez pas peur, chers Pythoniens et Pythonistes. Ça va être un jeu d’enfant!

_ Donc, nous utilisons JSON pour stocker et échanger des données? Oui, vous l’avez! Ce n’est rien de plus qu’un format normalisé utilisé par la communauté pour transmettre des données. Gardez à l’esprit que JSON n’est pas le seul format disponible pour ce type de travail, mais XML et YAML sont probablement les seuls d’autres méritent d’être mentionnés dans le même souffle. _

*Téléchargement PDF gratuit:* https://realpython.com/bonus/python-cheat-sheet-short/[Python 3 Cheat Sheet]

Une (très) brève histoire de JSON

Sans surprise, * J ava S cript O bject N * otation a été inspiré par un sous-ensemble du langage de programmation JavaScript traitant de la syntaxe littérale des objets. Ils ont un nifty site Web qui explique le tout. Ne vous inquiétez pas cependant: JSON est depuis longtemps devenu indépendant du langage et existe sous la forme son propre standard, nous pouvons donc heureusement éviter JavaScript pour le bien de cette discussion.

En fin de compte, la communauté dans son ensemble a adopté JSON car il est facile pour les humains et les machines de créer et de comprendre.

Regardez, c’est JSON!

Sois prêt. Je suis sur le point de vous montrer du vrai JSON, tout comme vous le verriez dans la nature. Ce n’est pas grave: JSON est censé être lisible par tous ceux qui utilisent un langage de style C, et Python est un langage de style C… donc c’est vous!

{
    "firstName": "Jane",
    "lastName": "Doe",
    "hobbies": ["running", "sky diving", "singing"],
    "age": 35,
    "children": [
        {
            "firstName": "Alice",
            "age": 6
        },
        {
            "firstName": "Bob",
            "age": 8
        }
    ]
}

Comme vous pouvez le voir, JSON prend en charge les types primitifs, comme les chaînes et les nombres, ainsi que les listes et les objets imbriqués.

_ Attendez, ça ressemble à un dictionnaire Python! Je sais, non? C’est à peu près la notation objet universelle à ce stade, mais je ne pense pas que UON roule aussi bien de la langue. N’hésitez pas à discuter des alternatives dans les commentaires. _

Ouf! Vous avez survécu à votre première rencontre avec un JSON sauvage. Il ne vous reste plus qu’à apprendre à l’apprivoiser.

Python prend en charge JSON nativement!

Python est livré avec un package intégré appelé https://docs.python.org/3/library/json.html [+ json +] pour l’encodage et le décodage des données JSON.

Jetez simplement ce petit gars en haut de votre dossier:

import json

Un petit vocabulaire

Le processus d’encodage JSON est généralement appelé sérialisation . Ce terme fait référence à la transformation des données en une série d’octets (d’où série) à stocker ou à transmettre sur un réseau. Vous pouvez également entendre le terme marshaling , mais c’est a toute autre discussion. Naturellement, la désérialisation est le processus réciproque de décodage des données qui ont été stockées ou livrées dans la norme JSON.

_ Ouais! Cela semble assez technique. Certainement. Mais en réalité, tout ce dont nous parlons ici est _lecture et écriture. Pensez-y comme ceci: encoding est pour écriture de données sur disque, tandis que decoding est pour lecture de données en mémoire. __

Sérialisation de JSON

Que se passe-t-il après qu’un ordinateur a traité de nombreuses informations? Il doit prendre un vidage de données. En conséquence, la bibliothèque + json + expose la méthode + dump () + pour écrire des données dans des fichiers. Il existe également une méthode + dumps () + (prononcée comme "dump-s") pour écrire dans une chaîne Python.

Les objets Python simples sont traduits en JSON selon une conversion assez intuitive.

Python JSON

dict

object

list, tuple

array

str

string

int, long, float

number

True

true

False

false

None

null

Un exemple de sérialisation simple

Imaginez que vous travaillez avec un objet Python en mémoire qui ressemble à quelque chose comme ceci:

data = {
    "president": {
        "name": "Zaphod Beeblebrox",
        "species": "Betelgeusian"
    }
}

Il est essentiel de sauvegarder ces informations sur le disque, votre mission est donc de les écrire dans un fichier.

À l’aide du gestionnaire de contexte de Python, vous pouvez créer un fichier appelé + data_file.json + et l’ouvrir en mode écriture. (Les fichiers JSON se terminent commodément par une extension + .json +.)

with open("data_file.json", "w") as write_file:
    json.dump(data, write_file)

Notez que + dump () + prend deux arguments positionnels: (1) l’objet de données à sérialiser et (2) l’objet de type fichier dans lequel les octets seront écrits.

Ou, si vous étiez si enclin à continuer à utiliser ces données JSON sérialisées dans votre programme, vous pourriez les écrire dans un objet Python natif + str +.

json_string = json.dumps(data)

Notez que l’objet de type fichier est absent car vous n’écrivez pas réellement sur le disque. En dehors de cela, + dumps () + est exactement comme + dump () +.

Hourra! Vous avez donné naissance à un bébé JSON, et vous êtes prêt à le libérer dans la nature pour devenir grand et fort.

Quelques arguments de mots clés utiles

Rappelez-vous, JSON est censé être facilement lisible par les humains, mais la syntaxe lisible n’est pas suffisante si elle est entièrement écrasée. De plus, vous avez probablement un style de programmation différent de moi, et il pourrait être plus facile pour vous de lire le code lorsqu’il est formaté à votre convenance.

_ REMARQUE: Les méthodes + dump () + et + dumps () + utilisent les mêmes arguments de mot clé. _

La première option que la plupart des gens souhaitent modifier est l’espace blanc. Vous pouvez utiliser l’argument de mot clé + indent + 'pour spécifier la taille d’indentation pour les structures imbriquées. Découvrez la différence par vous-même en utilisant `+ data +, que nous avons défini ci-dessus, et en exécutant les commandes suivantes dans une console:

>>>

>>> json.dumps(data)
>>> json.dumps(data, indent=4)

Une autre option de formatage est l’argument de mot clé + separators +. Par défaut, il s’agit d’un 2-tuple des chaînes de séparation + (", ",": ") +, mais une alternative courante pour JSON compact est + (", ",": ") +. Jetez à nouveau un œil à l’exemple JSON pour voir où ces séparateurs entrent en jeu.

Il y en a d’autres, comme + sort_keys +, mais je n’ai aucune idée de ce que cela fait. Vous pouvez trouver une liste complète dans docs si vous êtes curieux.

Désérialisation JSON

Super, on dirait que vous vous êtes capturé du JSON sauvage! Il est maintenant temps de le mettre en forme. Dans la bibliothèque + json +, vous trouverez + load () + et + charges () + pour transformer les données encodées JSON en objets Python.

Tout comme la sérialisation, il existe une table de conversion simple pour la désérialisation, bien que vous puissiez probablement deviner à quoi elle ressemble déjà.

JSON Python

object

dict

array

list

string

str

number (int)

int

number (real)

float

true

True

false

False

null

None

Techniquement, cette conversion n’est pas l’inverse parfait de la table de sérialisation. Cela signifie essentiellement que si vous encodez un objet maintenant puis le décodez à nouveau plus tard, vous risquez de ne pas récupérer exactement le même objet. J’imagine que c’est un peu comme la téléportation: brisez mes molécules ici et remettez-les là-bas. Suis-je toujours la même personne?

En réalité, cela revient probablement plus à demander à un ami de traduire quelque chose en japonais et à un autre ami de le traduire à nouveau en anglais. Quoi qu’il en soit, l’exemple le plus simple serait d’encoder un + tuple + et de récupérer une + liste + après le décodage, comme ceci:

>>>

>>> blackjack_hand = (8, "Q")
>>> encoded_hand = json.dumps(blackjack_hand)
>>> decoded_hand = json.loads(encoded_hand)

>>> blackjack_hand == decoded_hand
False
>>> type(blackjack_hand)
<class 'tuple'>
>>> type(decoded_hand)
<class 'list'>
>>> blackjack_hand == tuple(decoded_hand)
True

Un exemple simple de désérialisation

Cette fois, imaginez que vous avez des données stockées sur le disque que vous souhaitez manipuler en mémoire. Vous utiliserez toujours le gestionnaire de contexte, mais cette fois, vous ouvrirez le fichier + data_file.json + existant en mode lecture.

with open("data_file.json", "r") as read_file:
    data = json.load(read_file)

Les choses sont assez simples ici, mais gardez à l’esprit que le résultat de cette méthode pourrait renvoyer l’un des types de données autorisés de la table de conversion. Cela n’est important que si vous chargez des données que vous n’avez jamais vues auparavant. Dans la plupart des cas, l’objet racine sera un + dict + ou un + list +.

Si vous avez extrait des données JSON d’un autre programme ou si vous avez obtenu une chaîne de données au format JSON en Python, vous pouvez facilement la désérialiser avec + charges () +, qui se charge naturellement à partir d’une chaîne:

json_string = """
{
    "researcher": {
        "name": "Ford Prefect",
        "species": "Betelgeusian",
        "relatives": [
            {
                "name": "Zaphod Beeblebrox",
                "species": "Betelgeusian"
            }
        ]
    }
}
"""
data = json.loads(json_string)

Voilà! Vous avez apprivoisé le JSON sauvage, et maintenant il est sous votre contrôle. Mais ce que vous faites avec ce pouvoir dépend de vous. Vous pouvez le nourrir, le nourrir et même lui enseigner des astuces. Ce n’est pas que je ne te fais pas confiance… mais garde-le en laisse, d’accord?

Un exemple du monde réel (en quelque sorte)

Pour votre exemple d’introduction, vous utiliserez JSONPlaceholder, une excellente source de fausses données JSON à des fins pratiques.

Créez d’abord un fichier de script appelé + scratch.py ​​+, ou tout ce que vous voulez. Je ne peux pas vraiment t’arrêter.

Vous devrez faire une demande d’API au service JSONPlaceholder, alors utilisez simplement le package + demandes + pour faire le gros du travail. Ajoutez ces importations en haut de votre fichier:

import json
import requests

Maintenant, vous allez travailler avec une liste de TODO comme… vous savez, c’est un rite de passage ou autre.

Allez-y et faites une demande à l’API JSONPlaceholder pour le point de terminaison +/todos +. Si vous n’êtes pas familier avec + requêtes +, il existe en fait une méthode pratique + json () + qui fera tout le travail pour vous, mais vous pouvez vous entraîner à utiliser la bibliothèque + json + pour désérialiser le `+ texte + `attribut de l’objet de réponse. Ça devrait ressembler a quelque chose comme ca:

response = requests.get("https://jsonplaceholder.typicode.com/todos")
todos = json.loads(response.text)

Vous ne croyez pas que cela fonctionne? Très bien, exécutez le fichier en mode interactif et testez-le par vous-même. Pendant que vous y êtes, vérifiez le type de "+ todos +". Si vous vous sentez aventureux, jetez un œil aux quelque 10 premiers éléments de la liste.

>>>

>>> todos == response.json()
True
>>> type(todos)
<class 'list'>
>>> todos[:10]
...

Vous voyez, je ne vous mentirais pas, mais je suis content que vous soyez sceptique.

_ Qu’est-ce que le mode interactif? Ah, je pensais que tu ne demanderais jamais! Vous savez comment vous faites toujours des allers-retours entre votre éditeur et le terminal? Eh bien, nous Pythoners sournois utilisons le drapeau interactif + -i + lorsque nous exécutons le script. C’est une excellente petite astuce pour tester le code car il exécute le script et ouvre ensuite une invite de commande interactive avec accès à toutes les données du script! _

Très bien, il est temps de passer à l’action. Vous pouvez voir la structure des données en visitant le endpoint dans un navigateur, mais voici un exemple TODO:

{
    "userId": 1,
    "id": 1,
    "title": "delectus aut autem",
    "completed": false
}

Il y a plusieurs utilisateurs, chacun avec un + userId + 'unique, et chaque tâche a une propriété booléenne + terminé + `. Pouvez-vous déterminer quels utilisateurs ont effectué le plus de tâches?

# Map of userId to number of complete TODOs for that user
todos_by_user = {}

# Increment complete TODOs count for each user.
for todo in todos:
    if todo["completed"]:
        try:
            # Increment the existing user's count.
            todos_by_user[todo["userId"]] += 1
        except KeyError:
            # This user has not been seen. Set their count to 1.
            todos_by_user[todo["userId"]] = 1

# Create a sorted list of (userId, num_complete) pairs.
top_users = sorted(todos_by_user.items(),
                   key=lambda x: x[1], reverse=True)

# Get the maximum number of complete TODOs.
max_complete = top_users[0][1]

# Create a list of all users who have completed
# the maximum number of TODOs.
users = []
for user, num_complete in top_users:
    if num_complete < max_complete:
        break
    users.append(str(user))

max_users = " and ".join(users)

Ouais, ouais, votre implémentation est meilleure, mais le fait est que vous pouvez maintenant manipuler les données JSON comme un objet Python normal!

Je ne sais pas pour vous, mais lorsque je relance le script de manière interactive, j’obtiens les résultats suivants:

>>>

>>> s = "s" if len(users) > 1 else ""
>>> print(f"user{s} {max_users} completed {max_complete} TODOs")
users 5 and 10 completed 12 TODOs

C’est cool et tout, mais vous êtes ici pour en savoir plus sur JSON. Pour votre tâche finale, vous allez créer un fichier JSON qui contient les TODO terminés pour chacun des utilisateurs ayant effectué le nombre maximum de TODO.

Il vous suffit de filtrer + todos + et d’écrire la liste résultante dans un fichier. Dans un souci d’originalité, vous pouvez appeler le fichier de sortie + filter_data_file.json +. Il y a plusieurs façons de procéder, mais en voici une:

# Define a function to filter out completed TODOs
# of users with max completed TODOS.
def keep(todo):
    is_complete = todo["completed"]
    has_max_count = str(todo["userId"]) in users
    return is_complete and has_max_count

# Write filtered TODOs to file.
with open("filtered_data_file.json", "w") as data_file:
    filtered_todos = list(filter(keep, todos))
    json.dump(filtered_todos, data_file, indent=2)

Parfait, vous vous êtes débarrassé de toutes les données dont vous n’avez pas besoin et avez enregistré les bonnes choses dans un tout nouveau fichier! Exécutez à nouveau le script et consultez + filter_data_file.json + pour vérifier que tout a fonctionné. Il sera dans le même répertoire que + scratch.py ​​+ lorsque vous l’exécuterez.

Maintenant que vous êtes arrivé jusqu’ici, je parie que vous vous sentez comme quelque chose d’assez chaud, non? Ne soyez pas arrogant: l’humilité est une vertu. Je suis cependant enclin à être d’accord avec vous. Jusqu’à présent, la navigation s’est bien déroulée, mais vous voudrez peut-être abattre les écoutilles pour cette dernière étape du voyage.

Encodage et décodage d’objets Python personnalisés

Que se passe-t-il lorsque nous essayons de sérialiser la classe + Elf + à partir de l’application Dungeons & Dragons sur laquelle vous travaillez?

class Elf:
    def __init__(self, level, ability_scores=None):
        self.level = level
        self.ability_scores = {
            "str": 11, "dex": 12, "con": 10,
            "int": 16, "wis": 14, "cha": 13
        } if ability_scores is None else ability_scores
        self.hp = 10 + self.ability_scores["con"]

Sans surprise, Python se plaint que + Elf + n’est pas sérialisable (ce que vous savez si vous avez déjà essayé de dire le contraire à un Elfe):

>>>

>>> elf = Elf(level=4)
>>> json.dumps(elf)
TypeError: Object of type 'Elf' is not JSON serializable

Bien que le module + json + puisse gérer la plupart des types Python intégrés, il ne comprend pas comment coder les types de données personnalisés par défaut. C’est comme essayer d’installer une cheville carrée dans un trou rond - vous avez besoin d’une scie circulaire et de la surveillance parentale.

Simplifier les structures de données

Maintenant, la question est de savoir comment gérer des structures de données plus complexes. Eh bien, vous pouvez essayer d’encoder et de décoder le JSON à la main, mais il existe une solution un peu plus intelligente qui vous fera économiser du travail. Au lieu de passer directement du type de données personnalisé à JSON, vous pouvez lancer une étape intermédiaire.

Tout ce que vous devez faire est de représenter vos données en fonction des types intégrés que + json + comprend déjà. Essentiellement, vous traduisez l’objet plus complexe en une représentation plus simple, que le module + json + traduit ensuite en JSON. C’est comme la propriété transitive en mathématiques: si A = B et B = C, alors A = C.

Pour comprendre cela, vous aurez besoin d’un objet complexe pour jouer. Vous pouvez utiliser n’importe quelle classe personnalisée que vous aimez, mais Python a un type intégré appelé + complexe + pour représenter les nombres complexes, et il n’est pas sérialisable par défaut. Donc, pour ces exemples, votre objet complexe va être un objet + complexe +. Vous êtes encore confus?

>>>

>>> z = 3 + 8j
>>> type(z)
<class 'complex'>
>>> json.dumps(z)
TypeError: Object of type 'complex' is not JSON serializable

_ D’où viennent les nombres complexes? Vous voyez, quand un nombre réel et un nombre imaginaire s’aiment beaucoup, ils s’additionnent pour produire un nombre qui est (à juste titre) appelé _complex