Hace algún tiempo escribí una aplicación c ++ / Qt que enviaba grandes cantidades de datos en formato JSON a través de la red. Se usó el QJsonDocument estándar. Durante la implementación, encontré un bajo rendimiento, así como un diseño de clase inconveniente, que no permitió la detección normal de errores durante la operación. El resultado fue la biblioteca JsonWriterSax , que le permite escribir documentos JSON en estilo SAX con alta velocidad, que publico en github.com bajo la licencia MIT. A quién le importa: le pido un gato.
Poco de teoría
JSON (JavaScript Object Notation) es un formato de datos de texto estructurado desarrollado por Douglas Crockford y se crea un subconjunto del lenguaje ECMAScript (JavaScript, JScript, etc.). JSON reemplaza XML, ampliando las capacidades de anidamiento y agregando tipos de datos. Actualmente se usa activamente en Internet.
Pero hay fallas en JSON. En mi opinión, entre los tipos estándar, claramente no hay suficiente tipo DateTime: debe transferir el valor en forma de un número o una cadena, y al analizarlo es necesario tomar una decisión según el contexto. Pero vale la pena señalar que en ECMAScript, el tipo Fecha se creó hace mucho tiempo, no se pensó, y en el mundo js, las bibliotecas de terceros se utilizan para trabajar con fechas.
Hay 2 enfoques principales para analizar y crear documentos estructurados: SAX y DOM. Aparecieron para XML, pero se pueden usar como patrones para crear controladores de otros formatos.
SAX (API simple para XML)
Se utiliza para el procesamiento secuencial de datos y le permite procesar documentos grandes en una secuencia. Al leer, vuelve a la información de la aplicación sobre el elemento encontrado o error, pero la preservación de la información y el control de anidamiento recae en la aplicación misma. Al grabar, los pasos en el estilo generalmente se indican: iniciar un elemento, iniciar un subelemento, escribir un número, escribir una línea, cerrar un subelemento, cerrar un elemento. Las desventajas incluyen el hecho de que se requiere que el programador escriba el código más a fondo, para comprender mejor la estructura del documento y la falta o limitación extrema de la edición de un documento existente.
DOM (Modelo de objeto de documento)
Con este método, se construye un árbol de documentos en la memoria que se puede serializar, deserializar y modificar. La principal desventaja es el alto consumo de memoria y el mayor tiempo de procesamiento. Debajo del capó, generalmente se usa un controlador SAX.
QJsonProblemas de documentos
El QJsonDocument estándar utiliza el enfoque DOM. Al crear un documento, la velocidad es baja: puede ver los puntos de referencia al final del artículo. Pero el mayor problema para mí fue el diseño de retorno de error mal concebido.
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; } }
En este ejemplo, si no hay suficiente memoria, el mensaje se escribirá en la secuencia de error.
QJson: Document too large to store in data structure
y los datos ya no se agregarán. En el caso de una matriz, puede verificar la condición
ja.size() - 1 != i
Pero, ¿qué hacer cuando se trabaja con un objeto? ¿Comprueba constantemente que se ha agregado una nueva clave? Analizar el inicio de sesión en busca de un error?
La biblioteca
La biblioteca JsonWriterSax le permite escribir un documento JSON en un QTextStream en estilo SAX y está disponible en github bajo la licencia MIT. El control de la memoria recae en la aplicación. La biblioteca controla la integridad de JSON: si el elemento se agrega incorrectamente, la función de escritura devolverá un error. Para el control, se utiliza una gramática KS. Las pruebas fueron escritas, pero quizás algún caso se dejó desatendido. Si alguien corrige el funcionamiento incorrecto de la verificación e informa para corregir el error, le estaré muy agradecido.
Creo que la mejor descripción de una biblioteca para un programador es el ejemplo de código =)
Ejemplos
Creación de matriz
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"; }
Como resultado, obtenemos
[0,1,2,3,4,5,6,7,8,9]
Creación de objetos
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"; }
Como resultado, obtenemos
{"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"}
Crear un documento con anidamiento y diferentes tipos
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"; }
Puntos de referencia
Usado por QBENCHMARK durante la compilación de lanzamiento. La funcionalidad se implementa en la clase JsonWriterSaxTest .
Elemental OS 5.0 Juno, kernel 4.15.0-38-generic, CPU Intel® Core (TM) 2 Quad CPU 9550 @ 2.83GHz, 4G RAM, Qt 5.11.2 GCC 5.3.1
Matriz de números largos
- QJsonDocument: 42 ms por iteración (total: 85, iteraciones: 2)
- JsonWriterSax: 23 ms por iteración (total: 93, iteraciones: 4)
Gran objeto de un nivel
- QJsonDocument: 1,170 ms por iteración (total: 1,170, iteraciones: 1)
- JsonWriterSax: 53 ms por iteración (total: 53, iteraciones: 1)
Gran documento complejo
- QJsonDocument: 1,369 ms por iteración (total: 1,369, iteraciones: 1)
- JsonWriterSax: 463 ms por iteración (total: 463, iteraciones: 1)
Elemental OS 5.0 Juno, kernel 4.15.0-38-generic, CPU Intel® Core (TM) i7-7500U CPU @ 2.70GHz, 8G RAM, Qt 5.11.2 GCC 5.3.1
Matriz de números largos
- QJsonDocument: 29.5 ms por iteración (total: 118, iteraciones: 4)
- JsonWriterSax: 13 ms por iteración (total: 52, iteraciones: 4)
Gran objeto de un nivel
- QJsonDocument: 485 ms por iteración (total: 485, iteraciones: 1)
- JsonWriterSax: 31 ms por iteración (total: 62, iteraciones: 2)
Gran documento complejo
- QJsonDocument: 734 ms por iteración (total: 734, iteraciones: 1)
- JsonWriterSax: 271 ms por iteración (total: 271, iteraciones: 1)
MS Windows 7 SP1, CPU Intel® Core (TM) i7-4770 CPU @ 3.40GHz, 8G RAM, Qt 5.11.0 GCC 5.3.0
Matriz de números largos
- QJsonDocument: 669 ms por iteración (total: 669, iteraciones: 1)
- JsonWriterSax: 20 ms por iteración (total: 81, iteraciones: 4)
Gran objeto de un nivel
- QJsonDocument: 1,568 ms por iteración (total: 1,568, iteraciones: 1)
- JsonWriterSax: 44 ms por iteración (total: 88, iteraciones: 2)
Gran documento complejo
- QJsonDocument: 1,167 ms por iteración (total: 1,167, iteraciones: 1)
- JsonWriterSax: 375 ms por iteración (total: 375, iteraciones: 1)
MS Windows 7 SP1, CPU Intel® Core (TM) i3-3220 CPU @ 3.30GHz, 8G RAM, Qt 5.11.0 GCC 5.3.0
Matriz de números largos
- QJsonDocument: 772 ms por iteración (total: 772, iteraciones: 1)
- JsonWriterSax: 26 ms por iteración (total: 52, iteraciones: 2)
Gran objeto de un nivel
- QJsonDocument: 2.029 ms por iteración (total: 2.029, iteraciones: 1)
- JsonWriterSax: 59 ms por iteración (total: 59, iteraciones: 1)
Gran documento complejo
- QJsonDocument: 1,530 ms por iteración (total: 1,530, iteraciones: 1)
- JsonWriterSax: 495 ms por iteración (total: 495, iteraciones: 1)
Perspectivas
En futuras versiones, planeo agregar la capacidad de describir el formato de los datos del usuario a través de funciones lambda usando QVariant, agregar la capacidad de usar separadores para formatear el documento (documento bonito) y, si la comunidad está interesada, puedo agregar un analizador SAX.
Por cierto, mi biblioteca me ayudó a encontrar el error de desbordamiento, que permite qInfo (), qDebug (), qWarning () para configurar el formato y la salida en el estilo del módulo de registro de Python. También planeo publicar esta biblioteca en código abierto, si alguien está interesado, escriba los comentarios.