JsonWriterSax - une bibliothèque pour créer JSON

Il y a quelque temps, j'ai écrit une application c ++ / Qt qui envoyait de grandes quantités de données au format JSON sur le réseau. Le QJsonDocument standard a été utilisé . Lors de la mise en œuvre, j'ai rencontré de faibles performances, ainsi qu'une conception de classe peu pratique, qui ne permettait pas une détection normale des erreurs pendant le fonctionnement. Le résultat a été la bibliothèque JsonWriterSax , qui vous permet d'écrire des documents JSON en style SAX à grande vitesse, que je publie sur github.com sous la licence MIT. Peu importe - je demande un chat.


Un peu de théorie


JSON (JavaScript Object Notation) est un format de données de texte structuré développé par Douglas Crockford et un sous-ensemble du langage ECMAScript (JavaScript, JScript, etc. ont été créés sur cette base). JSON remplace XML, élargissant les capacités d'imbrication et ajoutant des types de données. Il est actuellement activement utilisé sur Internet.


Mais il y a des défauts dans JSON. À mon avis, parmi les types standard, il n'y a clairement pas assez de type DateTime - vous devez transférer la valeur sous la forme d'un nombre ou d'une chaîne, et lors de l'analyse, il est nécessaire de prendre une décision en fonction du contexte. Mais il convient de noter que dans ECMAScript, le type Date a été créé il y a longtemps, n'a pas été pensé, et dans le monde js, les bibliothèques tierces sont utilisées pour travailler avec les dates.


Il existe 2 approches principales pour analyser et créer des documents structurés - SAX et DOM. Ils sont apparus pour XML, mais peuvent être utilisés comme modèles pour créer des gestionnaires d'autres formats.


SAX (API simple pour XML)


Il est utilisé pour le traitement séquentiel des données et vous permet de traiter de gros documents dans un flux. Lors de la lecture, il renvoie à l'application des informations sur l'élément trouvé ou l'erreur, mais la conservation des informations et le contrôle d'imbrication incombent à l'application elle-même. Lors de l'enregistrement, les étapes du style sont généralement indiquées: démarrer un élément, démarrer un sous-élément, écrire un nombre, écrire une ligne, fermer un sous-élément, fermer un élément. Les inconvénients incluent le fait que le programmeur doit écrire le code de manière plus approfondie, pour mieux comprendre la structure du document et le manque ou l'extrême limitation de l'édition d'un document existant.


DOM (modèle d'objet de document)


Avec cette méthode, une arborescence de documents est construite en mémoire qui peut être sérialisée, désérialisée et modifiée. Le principal inconvénient est la consommation élevée de mémoire et l'augmentation du temps de traitement. Sous le capot, un gestionnaire SAX est généralement utilisé.


Problèmes avec QJsonDocument


Le QJsonDocument standard utilise l'approche DOM. Lors de la création d'un document, la vitesse est faible - vous pouvez voir les repères à la fin de l'article. Mais le plus gros problème pour moi était la conception de retour d'erreur mal conçue.


auto max = std::numeric_limits<int>::max(); QJsonArray ja; for(auto i = 0; i < max; ++i) { ja.append(i); if(ja.size() - 1 != i) { break; } } 

Dans cet exemple, s'il n'y a pas assez de mémoire, le message sera écrit dans le flux d'erreur.


QJson: Document too large to store in data structure
et les données ne seront plus ajoutées. Dans le cas d'un tableau, vous pouvez vérifier la condition


 ja.size() - 1 != i 

Mais que faire quand on travaille avec un objet? Vérifier constamment qu'une nouvelle clé a été ajoutée? Analyser le journal à la recherche d'une erreur?


La bibliothèque


La bibliothèque JsonWriterSax vous permet d'écrire un document JSON dans un QTextStream en style SAX et est disponible sur github sous la licence MIT. Le contrôle de la mémoire appartient à l'application. La bibliothèque contrôle l'intégrité de JSON - si l'élément n'est pas ajouté correctement, la fonction d'écriture retournera une erreur. Pour le contrôle, une grammaire KS est utilisée. Des tests ont été écrits, mais peut-être que certains cas n'ont pas été surveillés. Si quelqu'un corrige le mauvais fonctionnement du contrôle et rapporte pour corriger l'erreur - je serai très reconnaissant.


Je pense que la meilleure description d'une bibliothèque pour un programmeur est un exemple de code =)


Des exemples


Création de tableaux


 QByteArray ba; QTextStream stream(&ba); stream.setCodec("utf-8"); JsonWriterSax writer(stream); writer.writeStartArray(); for(auto i = 0; i < 10; ++i) { writer.write(i); } writer.writeEndArray(); if(writer.end()) { stream.flush(); } else { qWarning() << "Error json"; } 

En conséquence, nous obtenons


 [0,1,2,3,4,5,6,7,8,9] 

Création d'objets


 QByteArray ba; QTextStream stream(&ba); stream.setCodec("utf-8"); JsonWriterSax writer(stream); writer.writeStartObject(); for(auto i = 0; i < 5; ++i) { writer.write(QString::number(i), i); } for(auto i = 5; i < 10; ++i) { writer.write(QString::number(i), QString::number(i)); } writer.writeKey("arr"); writer.writeStartArray(); writer.writeEndArray(); writer.writeKey("o"); writer.writeStartObject(); writer.writeEndObject(); writer.writeKey("n"); writer.writeNull(); writer.write(QString::number(11), QVariant(11)); writer.write("dt", QVariant(QDateTime::fromMSecsSinceEpoch(10))); writer.writeEndObject(); if(writer.end()) { stream.flush(); } else { qWarning() << "Error json"; } 

En conséquence, nous obtenons


 {"0":0,"1":1,"2":2,"3":3,"4":4,"5":"5","6":"6","7":"7","8":"8","9":"9","arr":[],"o":{},"n":null,"11":11,"dt":"1970-01-01T03:00:00.010"} 

Création d'un document avec imbrication et différents types


 QByteArray ba; QTextStream stream(&ba); stream.setCodec("utf-8"); JsonWriterSax writer(stream); writer.writeStartArray(); for(auto i = 0; i < 1000; ++i) { writer.writeStartObject(); writer.writeKey("key"); writer.writeStartObject(); for(auto j = 0; j < 1000; ++j) { writer.write(QString::number(j), j); } writer.writeEndObject(); writer.writeEndObject(); } writer.writeEndArray(); if(writer.end()) { stream.flush(); } else { qWarning() << "Error json"; } 

Repères


Utilisé par QBENCHMARK lors de la génération de la version. La fonctionnalité est implémentée dans la classe JsonWriterSaxTest .


élémentaire OS 5.0 Juno, noyau 4.15.0-38-générique, cpu Intel® Core (TM) 2 Quad CPU 9550 @ 2,83 GHz, RAM 4G, Qt 5.11.2 GCC 5.3.1


Tableau de nombres longs


  • QJsonDocument: 42 ms par itération (total: 85, itérations: 2)
  • JsonWriterSax: 23 ms par itération (total: 93, itérations: 4)

Grand objet à un niveau


  • QJsonDocument: 1 170 ms par itération (total: 1 170, itérations: 1)
  • JsonWriterSax: 53 ms par itération (total: 53, itérations: 1)

Grand document complexe


  • QJsonDocument: 1 369 ms par itération (total: 1 369, itérations: 1)
  • JsonWriterSax: 463 ms par itération (total: 463, itérations: 1)

élémentaire OS 5.0 Juno, noyau 4.15.0-38-générique, processeur Intel® Core (TM) i7-7500U à 2,70 GHz, 8 Go de RAM, Qt 5.11.2 GCC 5.3.1


Tableau de nombres longs


  • QJsonDocument: 29,5 ms par itération (total: 118, itérations: 4)
  • JsonWriterSax: 13 ms par itération (total: 52, itérations: 4)

Grand objet à un niveau


  • QJsonDocument: 485 ms par itération (total: 485, itérations: 1)
  • JsonWriterSax: 31 ms par itération (total: 62, itérations: 2)

Grand document complexe


  • QJsonDocument: 734 ms par itération (total: 734, itérations: 1)
  • JsonWriterSax: 271 ms par itération (total: 271, itérations: 1)

MS Windows 7 SP1, CPU Intel® Core (TM) i7-4770 CPU @ 3.40GHz, 8G RAM, Qt 5.11.0 GCC 5.3.0


Tableau de nombres longs


  • QJsonDocument: 669 ms par itération (total: 669, itérations: 1)
  • JsonWriterSax: 20 ms par itération (total: 81, itérations: 4)

Grand objet à un niveau


  • QJsonDocument: 1 568 ms par itération (total: 1 568, itérations: 1)
  • JsonWriterSax: 44 ms par itération (total: 88, itérations: 2)

Grand document complexe


  • QJsonDocument: 1 167 ms par itération (total: 1 167, itérations: 1)
  • JsonWriterSax: 375 ms par itération (total: 375, itérations: 1)

MS Windows 7 SP1, CPU Intel® Core (TM) i3-3220 CPU @ 3.30GHz, 8G RAM, Qt 5.11.0 GCC 5.3.0


Tableau de nombres longs


  • QJsonDocument: 772 ms par itération (total: 772, itérations: 1)
  • JsonWriterSax: 26 ms par itération (total: 52, itérations: 2)

Grand objet à un niveau


  • QJsonDocument: 2,029 ms par itération (total: 2,029, itérations: 1)
  • JsonWriterSax: 59 ms par itération (total: 59, itérations: 1)

Grand document complexe


  • QJsonDocument: 1 530 ms par itération (total: 1 530, itérations: 1)
  • JsonWriterSax: 495 ms par itération (total: 495, itérations: 1)

Perspectives


Dans les versions futures, je prévois d'ajouter la possibilité de décrire le format des données utilisateur via les fonctions lambda à l'aide de QVariant, d'ajouter la possibilité d'utiliser des séparateurs pour formater le document (joli document) et, si la communauté est intéressée, je peux ajouter un analyseur SAX.


Par ailleurs, pour trouver une erreur de débordement, ma bibliothèque m'a aidé, ce qui permet à qInfo (), qDebug (), qWarning () de définir le format et la sortie dans le style du module de journalisation Python. Je prévois également de publier cette bibliothèque en open source - si quelqu'un est intéressé - écrivez dans les commentaires.

Source: https://habr.com/ru/post/fr427899/


All Articles