Écrivez facilement des conversions de classes de données JSON et Python avec quicktype et dacite

Contexte

Je pense qu'il existe de nombreuses situations dans lesquelles JSON est analysé lors de la liaison d'API avec des services externes en Python. Perth est ennuyeux et vous ne voulez pas passer trop de temps dans des situations comme Hackerson où la vitesse est particulièrement nécessaire. Cette fois, je vais vous montrer comment convertir facilement JSON en classe de données Python.

Le code introduit dans cet article est disponible sur GitHub. https://github.com/gaiax/quicktype-dacite-demo

Bibliothèque à utiliser

Comme le titre l'indique, nous utilisons principalement les bibliothèques appelées quicktype et dacite.

quicktype Une bibliothèque qui déduit le type d'exemples de données tels que JSON et les génère dans la langue correspondante. https://github.com/quicktype/quicktype

Bien que publié sous forme de package npm, il peut également être facilement utilisé comme interface utilisateur Web. Même s'il s'agit d'une interface utilisateur Web, le processus est terminé sur le client sans passer par le serveur. [^ 1] Il est disponible sur https://app.quicktype.io/.

dacite Une bibliothèque qui convertit les dictées Python en classe de données. https://github.com/konradhalas/dacite

Normalement, cela devrait être le cas lors de la conversion de dict en dataclass.

from dataclasses import dataclass


@dataclass
class Data:
    hoge: int
    fuga: str


d = {"hoge": 10, "fuga": "Python"}

Data(**d)
# Data(hoge=10, fuga='Python')

Cela fonctionne bien, mais cela ne fonctionne pas bien avec les dictionnaires imbriqués.

from dataclasses import dataclass


@dataclass
class NestedData:
    foo: int
    bar: str


@dataclass
class Data:
    hoge: int
    fuga: str
    nested_data: NestedData


d = {
    "hoge": 10,
    "fuga": "Python",
    "nested_data": {
        "foo": 20,
        "bar": "Ruby"
    }
}

Data(**d)
# Data(hoge=10, fuga='Python', nested_data={'foo': 20, 'bar': 'Ruby'})
# nested_data n'est pas la classe NestedData, elle stocke juste le dictionnaire

dacite permet de telles conversions de dictionnaire imbriquées normalement non prises en charge.

from dataclasses import dataclass
from dacite import from_dict


@dataclass
class NestedData:
    foo: int
    bar: str


@dataclass
class Data:
    hoge: int
    fuga: str
    nested_data: NestedData


d = {
    "hoge": 10,
    "fuga": "Python",
    "nested_data": {
        "foo": 20,
        "bar": "Ruby"
    }
}

from_dict(data_class=Data, data=d)
# Data(hoge=10, fuga='Python', nested_data=NestedData(foo=20, bar='Ruby'))
# nested_data contient une instance de la classe NestedData!

Cela rend très facile la conversion de dictionnaires et de classes de données. De plus, au moment de la rédaction de l'article (2020/06/10), il existe les fonctions suivantes.

Au fait, la conversion de dataclass en dictionnaire est possible en passant une instance de dataclass à dataclasses.asdict dans la bibliothèque standard Python.

Convertir en fait

Comme certains d'entre vous ont déjà fourni une image d'implémentation à partir de l'explication jusqu'à présent, essayons en fait de convertir JSON et la classe de données à partir de la définition de la classe de données. Ici, à titre d'exemple, convertissons la valeur de retour de l'API d'événements de connpass en dataclass.

Préparation des données JSON

Nous sommes un groupe appelé «Gaiax Technical Meetups» de connpass, qui organise des rencontres d'introduction et d'échange pour les ingénieurs. ~~ Publicité soudaine ~~ Cette fois, l'API se concentrera sur ce groupe et préparera les données.

Pour faciliter la lecture de l'article, nous limiterons le nombre d'événements à deux.

GET https://connpass.com/api/v1/event/?series_id=3109&count=2

connpass.json


{
  "results_start": 1,
  "results_returned": 2,
  "results_available": 35,
  "events": [
    {
      "event_id": 175102,
      "title": "[Dialogue en ligne] Présentation de l'utilisation du script Google Apps#6",
      "catch": "Dialogue en ligne avec les dernières informations et cas d'utilisation de Google Apps Script",
      "description": "<h1>Campagne de réalisation du montant cible de financement du livre GAS!</h1>\n<p>M. Takahashi de Plan Notes, l'auteur de "Présentation complète de Google Apps Script" qui a réussi à financer "Je souhaite diffuser la dernière" Présentation complète de Google Apps Script "dans le monde dès que possible!" Sera sur scène.<br>\n<a href=\"https://camp-fire.jp/projects/view/249472\" rel=\"nofollow\">https://camp-fire.jp/projects/view/249472</a></p>\n<p>Pour fêter l'atteinte du montant cible, nous présenterons la première édition de "Google Apps Script Complete Introduction" à 5 personnes par tirage au sort des participants à l'événement dans le cadre d'une campagne limitée à cet événement! !! !!</p>\n<h1>détail</h1>\n<p>La 6ème édition de "Google Apps Script Utilization Meetup", qui est devenu un élément populaire et standard dans Technical Meetup, se tiendra tout en changeant le format!</p>\n<p>G Suite de Google (Google Apps) commence à être utilisé par de nombreuses entreprises. Découvrez les dernières informations et exemples d'application de Google Apps Script, un langage de script qui étend G Suite!</p>\n<p>Les entreprises informatiques migrent d'un logiciel de bureau installé directement sur leur PC vers G Suite, un logiciel de bureau de type application Web. Plusieurs personnes peuvent modifier en même temps, les fichiers sont automatiquement enregistrés dans le cloud et de nombreuses fonctions prennent en charge le style de travail requis aujourd'hui.</p>\n<p>Google Apps Script étend les fonctionnalités de G Suite. Comme il est basé sur le langage JavaScript, qui est familier à de nombreuses personnes, son histoire est courte, mais ses cas d'utilisation augmentent rapidement.</p>\n<p>※référence</p>\n<p>Veuillez consulter l'URL suivante pour les événements passés!</p>\n<p><a href=\"http://gaiax.hatenablog.com/archive\" rel=\"nofollow\">http://gaiax.hatenablog.com/archive</a></p>\n<h1>calendrier</h1>\n<ul>\n<li>19:15 Début de livraison</li>\n<li>19:30 début de conversation</li>\n<li>20:30 questions et réponses</li>\n<li>20:50 Questionnaire</li>\n<li>21:00 fermer</li>\n</ul>\n<h1>Entretien programmé</h1>\n<h3>M. Nobunari Takahashi, directeur représentant de Plan Notes Co., Ltd.</h3>\n<p>Diplômé de la division de recherche en informatique électronique de l'Université des télécommunications, il a travaillé comme saxophoniste jusqu'à l'âge de 30 ans et a connu une expérience de producteur et de marketing dans l'industrie du contenu mobile et l'industrie du livre électronique. J'ai ressenti un problème avec le style de travail, la productivité, l'utilisation informatique, etc. des hommes d'affaires au Japon et j'ai démarré une entreprise indépendante en 2015. Exceller,VBA,GSuite,GAS,Travail sur le développement d'outils système, le conseil, la formation, la rédaction, etc. en utilisant le cloud.\n Formateur du service d'apprentissage en ligne "Linked In Learning",Présidé la communauté "Skill Up Study Group for Non-Programmers".\n Un blog géré par moi-même<a href=\"https://tonari-it.com/\" rel=\"nofollow\">"Toujours un travail informatique à côté"</a>Bénéficie d'une popularité mensuelle de 960 000 PV.</p>\n<h3>Gaiax Co., Ltd. Division des solutions de médias sociaux Shogo Matsushita</h3>\n<p>Né en 1995 Né dans la préfecture de Kanagawa. RPG Tsukuru et Click au collège&Rencontrez Create et franchissez la porte de la programmation grâce à la programmation de jeux. Après cela, inscrivez-vous à Yokohama Medical Information College. Engagé dans le développement de services Web en tant que stagiaire à divers endroits tout en fréquentant l'école. A rejoint Gaiax en 2018. Actuellement, en plus du développement et de la production vidéo, il écrit des productions vidéo techniques doujinshi et doujin au sein de la division Social Media Marketing.</p>\n<p>Récemment publié un doujinshi sur AppMaker</p>\n<ul>\n<li>Booth <a href=\"https://godan.booth.pm/items/2035726\" rel=\"nofollow\">https://godan.booth.pm/items/2035726</a></li>\n<li>bookwalker  <a href=\"https://bookwalker.jp/de809d7a5b-1b32-4973-a3d6-61019cf222f2/\" rel=\"nofollow\">https://bookwalker.jp/de809d7a5b-1b32-4973-a3d6-61019cf222f2/</a></li>\n</ul>\n<h1>Remarques</h1>\n<p>Le but de cet événement est de partager le savoir-faire des ingénieurs, et nous n'acceptons pas de participation dans le seul but de manger et de boire. Par conséquent, nous vous demandons d'échanger vos cartes de visite à la réception pour confirmer votre identité. Merci de votre collaboration.</p>\n<p>En ce qui concerne le recrutement LT, nous n'acceptons pas les discussions qui incluent des LT sans rapport avec le thème et une publicité excessive qui ne correspond pas au thème de cet événement (hors G Suite de Google).</p>\n<p>À la conférence pour tous les participants qui seront sur scène ou présents<a href=\"http://ja.confcodeofconduct.com/\" rel=\"nofollow\">Code de conduite(Conference Code of Conduct) </a>Veuillez respecter. Veuillez noter que si la direction de l'événement détermine qu'il s'agit d'un acte de harcèlement grave, nous prendrons toute mesure, y compris l'expulsion de l'événement.</p>",
      "event_url": "https://gaiax.connpass.com/event/175102/",
      "started_at": "2020-05-29T19:30:00+09:00",
      "ended_at": "2020-05-29T22:00:00+09:00",
      "limit": null,
      "hash_tag": "Activité GAS",
      "event_type": "participation",
      "accepted": 45,
      "waiting": 0,
      "updated_at": "2020-05-08T14:09:19+09:00",
      "owner_id": 28874,
      "owner_nickname": "xtetsuji",
      "owner_display_name": "OGATA Tetsuji",
      "place": "en ligne",
      "address": "en ligne",
      "lat": null,
      "lon": null,
      "series": {
        "id": 3109,
        "title": "Gaiax Technical Meetups",
        "url": "https://gaiax.connpass.com/"
      }
    },
    {
      "event_id": 173835,
      "title": "[Pour les débutants] Introduction à Flutter Online Hands-on",
      "catch": "Événement pratique pour ceux qui veulent toucher Flutter",
      "description": "<h1>À propos de la tenue en ligne</h1>\n<p>Cette fois, il se tiendra en ligne. Le jour de l'événement, nous vous enverrons par e-mail l'URL de participation en ligne.</p>\n<h1>détail</h1>\n<p>iPhone/Flutter qui vous permet de développer des applications Andriod dans un seul environnement. Bien que le nombre de cas ait augmenté petit à petit, je pense qu'il y a beaucoup de gens qui ne l'ont pas encore abordé.</p>\n<p>Par conséquent, les ingénieurs qui développent et publient des applications natives à l'aide de Flutter expliqueront comment créer des applications simples pour les débutants, en se concentrant sur le codage en direct!</p>\n<p>※référence</p>\n<p>Veuillez consulter l'URL suivante pour les événements passés!</p>\n<p><a href=\"http://gaiax.hatenablog.com/archive\" rel=\"nofollow\">http://gaiax.hatenablog.com/archive</a></p>\n<h1>Rejoignez-nous si vous aimez ça</h1>\n<ul>\n<li>Je suis intéressé à connaître Flutter (j'en ai entendu parler)</li>\n<li>iPhone/Intéressé par le développement Android</li>\n<li>Swift /J'ai de l'expérience en développement avec kotlin et je veux connaître Flutter</li>\n<li>Avoir une certaine expérience du développement tel que le service WEB</li>\n</ul>\n<h1>calendrier</h1>\n<ul>\n<li>19:15 réception</li>\n<li>19:30 mains sur le départ</li>\n<li>21:30 Questionnaire</li>\n</ul>\n<h1>Ce que nous voulons que les participants préparent</h1>\n<ul>\n<li>Ceux qui veulent bouger leurs mains ensemble<ul>\n<li>Veuillez préparer l'environnement en vous référant à l'article Qiita suivant, etc.</li>\n<li><a href=\"https://qiita.com/tomy0610/items/896dc8ec9ba95c33194f\" rel=\"nofollow\">https://qiita.com/tomy0610/items/896dc8ec9ba95c33194f</a></li>\n</ul>\n</li>\n</ul>\n<h1>Expérience requise / ensemble de compétences</h1>\n<p>Il est destiné aux personnes ayant une certaine expérience du développement.\n Il n'y a aucune explication sur la création d'un environnement de développement, d'un langage de programmation ou d'un mécanisme de base.</p>\n<h1>Remarques</h1>\n<p>Le but de cet événement est de partager le savoir-faire des ingénieurs, et nous n'acceptons pas les exposés qui incluent des LT qui ne sont pas liés au thème ou une publicité excessive qui ne correspond pas au thème de cet événement.</p>\n<p>Veuillez noter que nous l'utiliserons comme un matériel tel que des photos et des vidéos de la scène de tenue afin de publier le rapport de l'événement sur les blogs et divers médias.</p>\n<p>Pour tous les participants qui seront sur scène ou assisteront à la conférence, à la conférence<a href=\"https://ja.confcodeofconduct.com/\" rel=\"nofollow\">Code de conduite(Conference Code of Conduct)</a>Veuillez suivre le. Veuillez noter que si la direction de l'événement détermine qu'il s'agit d'un acte de harcèlement grave, nous prendrons toute mesure, y compris l'expulsion de l'événement.</p>",
      "event_url": "https://gaiax.connpass.com/event/173835/",
      "started_at": "2020-05-07T19:30:00+09:00",
      "ended_at": "2020-05-07T22:00:00+09:00",
      "limit": null,
      "hash_tag": "Flutter mains sur",
      "event_type": "participation",
      "accepted": 106,
      "waiting": 0,
      "updated_at": "2020-05-07T21:44:47+09:00",
      "owner_id": 11134,
      "owner_nickname": "norinux",
      "owner_display_name": "norinux",
      "place": "en ligne",
      "address": "en ligne",
      "lat": null,
      "lon": null,
      "series": {
        "id": 3109,
        "title": "Gaiax Technical Meetups",
        "url": "https://gaiax.connpass.com/"
      }
    }
  ]
}

Définir une classe de données avec quicktype

Cette fois, je veux l'essayer facilement, je vais donc définir une classe de données dans l'interface utilisateur Web. Accédez à https://app.quicktype.io/ et collez les données JSON que vous avez préparées précédemment dans le champ de saisie sur la gauche.

quicktype_sample.png

Si vous sélectionnez Python et cochez Classes only, le résultat de la conversion devrait s'afficher automatiquement sur le côté droit. Vérifiez également Transformer les noms de propriétés en Pythonic pour une conversion ultérieure avec dacite.

Au fait, si vous désactivez Classes only, cela définira également la méthode to_dict () et ainsi de suite. Cependant, il est nécessaire de réécrire chaque méthode chaque fois qu'un changement de colonne se produit. En laissant dacite gérer ces conversions, vous pouvez alléger la charge de codage.

De plus, quicktype est bien fait et ajoutera «Facultatif» aux propriétés qui peuvent ou non être incluses dans le tableau. Essayez de supprimer la colonne ʻevent_id` de l'un des deux événements du JSON sur quicktype.

Par défaut, le nom de classe de la classe de données générée est "Welcome", changez-le en conséquence. Dans cet article, nous l'appellerons «Connpass».

Enfin, copiez la classe de données convertie dans son intégralité et enregistrez-la en tant que fichier. Ici, enregistrez-le dans connpass.py en fonction du nom de classe de la classe de données.

Convertir en classe de données avec dacite

Maintenant que nous avons défini la classe de données, utilisons dacite pour convertir de JSON en classe de données.

En supposant que le JSON d'origine est enregistré dans un fichier sous le nom connpass.json, le code sera le suivant.

main.py


import json
from dataclasses import dataclass
from datetime import datetime

from dacite import Config, from_dict

from connpass import Connpass


def run():
    with open("connpass.json", "r") as f:
        data = json.load(f)
    connpass = from_dict(Connpass, data, Config({datetime: datetime.fromisoformat}))
    print(connpass.events)


if __name__ == "__main__":
    run()

La conversion réelle est effectuée sur cette ligne.

connpass = from_dict(Connpass, data, Config({datetime: datetime.fromisoformat}))

Config effectue une conversion de type en utilisant la fonction cast de dacite. Je passe datetime.fromisoformat via une propriété dont le type est datetime.

Si vous n'écrivez pas ceci, vous passerez str à la propriété de datetime et dacite.exceptions.WrongTypeError sera renvoyé.

connpass = from_dict(Connpass, data)
# dacite.exceptions.WrongTypeError: wrong value type for field "events.started_at" - should be "datetime" instead of value "2020-05-29T19:30:00+09:00" of type "str"

Convertir avec __post_init__ ()

Au fait, cette fois, le cast était suffisant, mais si le processus de conversion est différent selon la propriété même s'il s'agit du même type, vous pouvez l'accepter avec Union et le convertir manuellement avec __post_init__ ().

post_init_example.py


# {"text": "hello world", "created_at": "Thu Jun 04 11:27:06 +0900 2020",  "timestamp": 1591237626}
from typing import Union
from datetime import datetime

@dataclass
class Sample:
    text: str
    created_at: Union[str, datetime]
    timestamp: Union[int, datetime]

    def __post_init__(self):
        self.__convert_created_at()
        self.__convert_timestamp()
  
    def __convert_created_at(self):
        if type(self.created_at) is str:
            self.created_at =datetime.strptime(self.created_at, "%a %b %d %H:%M:%S %z %Y")

    def __convert_timestamp(self):
        if type(self.timestamp) is int:
            self.timestamp = datetime.fromtimestamp(self.timestamp)

Refactoriser un peu

Appeler connpass = from_dict (Connpass, data, Config ({datetime: datetime.fromisoformat})) à chaque fois est pénible, nous déplaçons donc le processus de conversion vers la classe Connpass.

connpass.py


from dataclasses import asdict, dataclass
from datetime import datetime
from typing import Any, Dict, List

from dacite import Config, from_dict

#réduction

@dataclass
class Connpass:
    results_start: int
    results_returned: int
    results_available: int
    events: List[Event]

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "Connpass":
        return from_dict(cls, data, config=Config({datetime: datetime.fromisoformat}))

    def to_dict(self) -> Dict[str, Any]:
        return asdict(self)

Ensuite, vous pouvez l'appeler comme ça. C'est plus simple à utiliser.

connpass = Connpass.from_dict(data)
exported_data = connpass.to_dict()

Résumé

Jusqu'à présent, j'ai présenté comment implémenter facilement la conversion JSON et de classe de données à l'aide de dacite et quicktype.

Le référentiel publié sur GitHub comprend également un exemple de mise en place d'un serveur simple et de vérification de son fonctionnement. https://github.com/gaiax/quicktype-dacite-demo

La complémentation fonctionnera également, donc l'expérience de développement s'est considérablement améliorée par rapport à l'utilisation d'un dictionnaire simple. Je suis heureux.

Recommended Posts

Écrivez facilement des conversions de classes de données JSON et Python avec quicktype et dacite
[Python3] Lecture et écriture avec isoformat datetime avec json
Encodage et décodage JSON avec python
Lire et écrire des fichiers JSON avec Python
Lire et écrire des fichiers JSON avec Python
Téléchargez facilement des mp3 / mp4 avec python et youtube-dl!
Lire JSON avec Python et générer un CSV
[Python] Utiliser JSON avec Python
Faites facilement un bip avec python
Analyser et visualiser JSON (application Web ⑤ avec Python + Flask)
Lire et écrire des fichiers avec Slackbot ~ Développement de bot avec Python ~
Lire le fichier json avec Python, le formater et générer le json
Programmation avec Python et Tkinter
Essayez d'utiliser l'API Twitter rapidement et facilement avec Python
Chiffrement et déchiffrement avec Python
Facilement sans serveur avec Python en utilisant Calice
Python et matériel - Utilisation de RS232C avec Python -
POST json avec le script Python 3
Écrivez facilement if-elif avec lambda
Écrivons python avec cinema4d.
Écrire en csv avec Python
python avec pyenv et venv
Formater json avec Vim (avec python)
Exemple d'analyse HTTP GET et JSON avec Pepper Python
Créez facilement une infrastructure réseau et EC2 avec AWS CDK Python
Fonctionne avec Python et R
Lire des données json avec python
[Python3] Enregistrez la matrice de moyenne et de covariance dans json avec les pandas
J'ai essayé de détecter facilement les points de repère du visage avec python et dlib
[python3] Implémentez facilement la fonction de sortie du journal de débogage avec journalisation et cliquez sur
Communiquez avec FX-5204PS avec Python et PyUSB
Briller la vie avec Python et OpenCV
Robot fonctionnant avec Arduino et python
Installez Python 2.7.9 et Python 3.4.x avec pip.
Réseau neuronal avec OpenCV 3 et Python 3
Modulation et démodulation AM avec python
Scraping avec Node, Ruby et Python
Ecrire un schéma JSON avec Python DSL
[Python] Ecrire dans un fichier csv avec Python
Grattage avec Python, Selenium et Chromedriver
Implémentez facilement des sous-commandes avec python click
Gérez facilement les listes avec python + sqlite3
Grattage avec Python et belle soupe
Introduction à Hadoop et MapReduce avec Python
[GUI en Python] PyQt5-Glisser-déposer-
Lire et écrire NetCDF avec Python
J'ai joué avec PyQt5 et Python3
Journalisation Python et vidage vers json
Lire et écrire du CSV avec Python
Intégration multiple avec Python et Sympy
Publiez facilement sur Twitter avec Python 3
Coexistence de Python2 et 3 avec CircleCI (1.0)
Ecrire un script batch avec Python3.5 ~
Jeu Sugoroku et jeu d'addition avec Python
Modulation et démodulation FM avec Python
Envoyer et recevoir des données d'image au format JSON en Python sur le réseau
Communiquez entre Elixir et Python avec gRPC