Serialisierungskonverter

Wie man zwischen verschiedenen Serialisierungsformaten konvertiert.

Dieses Beispiel konvertiert zwischen JSON, CBOR, XML, QDataStream und einigen einfachen Textformaten. Es kann das verwendete Format automatisch erkennen oder es kann ihm mitgeteilt werden, welches Format zu verwenden ist. Nicht alle Formate unterstützen sowohl die Eingabe als auch die Ausgabe, und sie haben unterschiedliche Sätze von Inhaltsdatentypen, die sie unterstützen. QDataStream und XML sind die umfangreichsten, gefolgt von CBOR, dann JSON und schließlich die einfachen Textformate. Bei der Konvertierung über die weniger leistungsfähigen Formate kann die Struktur der Daten verloren gehen.

Die Konverterklasse

Die Klasse Converter ist die abstrakte Oberklasse für alle Konverter von und nach allen Formaten. Sie alle konvertieren von oder nach der Klasse QVariant, die zur internen Darstellung aller Datenstrukturen verwendet wird.

class Converter
{
    static QList<const Converter *> &converters();
protected:
    Converter();
    static bool isNull(const Converter *converter); // in nullconverter.cpp

public:
    static const QList<const Converter *> &allConverters();

    enum class Direction { In = 1, Out = 2, InOut = In | Out };
    Q_DECLARE_FLAGS(Directions, Direction)

    enum Option { SupportsArbitraryMapKeys = 0x01 };
    Q_DECLARE_FLAGS(Options, Option)

    virtual ~Converter() = 0;

    virtual QString name() const = 0;
    virtual Directions directions() const = 0;
    virtual Options outputOptions() const;
    virtual const char *optionsHelp() const;
    virtual bool probeFile(QIODevice *f) const;
    virtual QVariant loadFile(QIODevice *f, const Converter *&outputConverter) const;
    virtual void saveFile(QIODevice *f, const QVariant &contents,
                          const QStringList &options) const = 0;
};

Q_DECLARE_OPERATORS_FOR_FLAGS(Converter::Directions)
Q_DECLARE_OPERATORS_FOR_FLAGS(Converter::Options)

Der Konstruktor und der Destruktor von Converter verwalten eine Liste der verfügbaren Konverter, die vom Hauptprogramm verwendet wird, damit es weiß, welche Konverter verfügbar sind. Jeder Konvertertyp definiert eine statische Instanz, die sicherstellt, dass er konstruiert ist und somit dem Hauptprogramm über diese Liste zur Verfügung steht. Die Methode allConverters() ermöglicht dem Code von main() den Zugriff auf diese Liste.

Converter::Converter()
{
    converters().append(this);
}

Converter::~Converter()
{
    converters().removeAll(this);
}

QList<const Converter *> &Converter::converters()
{
    Q_CONSTINIT static QList<const Converter *> store;
    return store;
}

const QList<const Converter *> &Converter::allConverters()
{
    return converters();
}

Die Funktion name() gibt den Namen des Konverters zurück. Mit der Funktion directions() wird bestimmt, ob ein Konverter für die Eingabe, die Ausgabe oder beides verwendet werden kann. Damit kann das Hauptprogramm in seinem Hilfetext für die Befehlszeilenoptionen zur Auswahl von Eingabe- und Ausgabeformaten angeben, welche Konverter verfügbar sind.

    QStringList inputFormats;
    QStringList outputFormats;
    for (const Converter *conv : Converter::allConverters()) {
        auto direction = conv->directions();
        QString name = conv->name();
        if (direction.testFlag(Converter::Direction::In))
            inputFormats << name;
        if (direction.testFlag(Converter::Direction::Out))
            outputFormats << name;
    }

Die Funktion optionsHelp() meldet die verschiedenen Befehlszeilenoptionen, die von den verfügbaren Formaten unterstützt werden, wenn sie mit der Befehlszeilenoption --format-options <format> abgefragt werden.

       for(const Converter *conv: Converter::allConverters()) { if (conv->name() == format) { const char *help =  conv->optionsHelp(); if (help) {                    qInfo("The following options are available for format '%s':\n\n%s",
                          qPrintable(format), help);
                } sonst {                    qInfo("Format '%s' supports no options.", qPrintable(format));
                } return EXIT_SUCCESS; } }

Die Funktion outputOptions() meldet die Ausgabemöglichkeiten eines Konverters. Zurzeit ist die einzige optionale Funktion die Unterstützung für beliebige Schlüssel in Zuordnungen von Schlüsseln zu Werten. Die Funktion loadFile() eines Eingabekonverters kann diese Informationen verwenden, um die Form, in der er die gelesenen Daten darstellt, so anzupassen, dass sie vom Ausgabekonverter so originalgetreu dargestellt werden, wie es seine Fähigkeiten erlauben.

Die Funktion probeFile() wird verwendet, um festzustellen, ob eine Datei dem Format des Konverters entspricht. Das Hauptprogramm verwendet diese Funktion, um zu ermitteln, welches Format beim Lesen oder Schreiben einer Datei zu verwenden ist, und zwar auf der Grundlage ihres Namens und möglicherweise ihres Inhalts, wenn der Benutzer das zu verwendende Format nicht in der Befehlszeile angegeben hat.

Die Funktion loadFile() deserialisiert Daten. Der Aufrufer teilt loadFile() mit, welchen Serializer er zu verwenden beabsichtigt, so dass loadFile() seine outputOptions() abfragen kann, um die Form zu bestimmen, in der die geladenen Daten dargestellt werden sollen. Wenn sich der Aufrufer nicht für einen Ausgabekonverter entschieden hat, liefert loadFile() einen Standard-Ausgabekonverter, der für die zurückgegebenen Daten geeignet ist.

Die Funktion saveFile() serialisiert die Daten. Ihm werden, wie bei loadHelp() beschrieben, Optionen von der Kommandozeile übergeben, mit denen die Details der Datendarstellung beim Speichern in eine Datei eingestellt werden können.

Sowohl loadFile() als auch saveFile() können mit einem beliebigen QIODevice verwendet werden. Das bedeutet, dass ein Konverter auch mit einem Netzwerk-Socket oder einer anderen Datenquelle verwendet werden kann, um von dort zu lesen oder zu schreiben. Im vorliegenden Programm übergibt das Hauptprogramm immer ein QFile, das entweder auf eine Datei auf der Festplatte oder auf einen der Standard-Streams des Prozesses zugreift.

Die verfügbaren Konverter

Es werden mehrere Konverter unterstützt, die veranschaulichen, wie das Konverterprogramm an andere Formate angepasst werden kann, falls dies erforderlich ist. Einzelheiten finden Sie im Quellcode der einzelnen Programme. Die CBOR-Konverter dienen als relativ vollständiges Beispiel für die Funktionsweise von Konvertern, auf die wir weiter unten noch näher eingehen werden. Diese Tabelle gibt einen Überblick über die verfügbaren Konverter:

KlasseModusFormat
CborConverterEingang/AusgangCBOR
CborDiagnosticDumperAusCBOR-Diagnose
DatenStreamConverterEingang/AusgangQDataStream
DebugTextDumperAusVerlustfrei, nicht standardisiert, menschenlesbar
JsonKonverterEingang/AusgangJSON
NullConverterAusKeine Ausgabe
TextKonverterEingang/AusgangStrukturierter Klartext
XmlKonverterEin-/AusgabeXML

Diejenigen, die Eingaben unterstützen, verwenden sich selbst als Fallback-Konverter von loadFile(), mit Ausnahme der CBOR- und QDataStream -Konverter, die ihre jeweiligen Nur-Ausgabe-Dumper-Begleitklassen verwenden. Der Null-Konverter kann als Ausgabe-Konverter verwendet werden, wenn das Programm im Interesse einer Validierung oder Überprüfung, die ein Eingabe-Konverter durchführen kann, ausgeführt wird.

Die Klassen CborConverter und CborDiagnosticDumper

Die Klasse CborConverter unterstützt die Serialisierung in und aus dem CBOR-Format. Sie unterstützt verschiedene Optionen, um die Ausgabe von Fließkommazahlen zu konfigurieren, sowie eine Option signature, mit der festgelegt werden kann, ob die Ausgabe mit einem CBOR-Tag beginnen soll, das als Dateikopf dient und die Datei als CBOR-Daten enthaltend kennzeichnet.

Es gibt auch eine CborDiagnosticDumper-Klasse für die Ausgabe in CBOR-Diagnose-Notation. Sie unterstützt das Laden von Daten nicht. Die Form der Ausgabe kann mit zwei Optionen konfiguriert werden. Die eine legt fest, ob das (ausführlichere) erweiterte CBOR-Diagnoseformat verwendet werden soll. Die andere steuert, ob jeder CBOR-Wert in einer eigenen Zeile erscheint.

Die einfache Diagnosenotation ist ähnlich wie JSON, aber nicht genau, denn sie unterstützt die verlustfreie Anzeige des Inhalts eines CBOR-Streams, während eine Konvertierung in JSON verlustbehaftet sein kann. Die Funktion loadFile() von CborConverter verwendet CborDiagnosticDumper als Fallback-Ausgabekonverter, wenn der Aufrufer das Ausgabeformat nicht selbst bestimmt hat.

Die Hilfsfunktionen convertCborValue(), convertCborMap() und convertCborArray() werden verwendet, um eine QCborValue in eine QVariant zu konvertieren, damit CborConverter::loadFile() davon profitieren kann.

static QVariant convertCborValue(const QCborValue &value);

static QVariant convertCborMap(const QCborMap &map)
{
    VariantOrderedMap result;
    result.reserve(map.size());
    for (auto pair : map)
        result.append({ convertCborValue(pair.first), convertCborValue(pair.second) });
    return QVariant::fromValue(result);
}

static QVariant convertCborArray(const QCborArray &array)
{
    QVariantList result;
    result.reserve(array.size());
    for (auto value : array)
        result.append(convertCborValue(value));
    return result;
}

static QVariant convertCborValue(const QCborValue &value)
{
    if (value.isArray())
        return convertCborArray(value.toArray());
    if (value.isMap())
        return convertCborMap(value.toMap());
    return value.toVariant();
}

Die Funktion convertFromVariant() wird verwendet, um eine QVariant in eine QCborValue zu konvertieren, damit sie von der saveFile() der beiden Klassen ausgegeben werden kann.

enum TrimFloatingPoint { Double, Float, Float16 };
static QCborValue convertFromVariant(const QVariant &v, TrimFloatingPoint fpTrimming)
{
    if (v.userType() == QMetaType::QVariantList) {
        const QVariantList list = v.toList();
        QCborArray array;
        for (const QVariant &v : list)
            array.append(convertFromVariant(v, fpTrimming));

        return array;
    }

    if (v.userType() == qMetaTypeId<VariantOrderedMap>()) {
        const auto m = qvariant_cast<VariantOrderedMap>(v);
        QCborMap map;
        for (const auto &pair : m)
            map.insert(convertFromVariant(pair.first, fpTrimming),
                       convertFromVariant(pair.second, fpTrimming));
        return map;
    }

    if (v.userType() == QMetaType::Double && fpTrimming != Double) {
        float f = float(v.toDouble());
        if (fpTrimming == Float16)
            return float(qfloat16(f));
        return f;
    }

    return QCborValue::fromVariant(v);
}

Das Programm convert

Die Funktion main() richtet eine QApplication und eine QCommandLineParser ein, um die vom Benutzer angegebenen Optionen auszuwerten und Hilfe zu leisten, wenn der Benutzer danach fragt. Sie verwendet die Werte, die sie für die verschiedenen QCommandLineOption Instanzen erhält, die die Auswahl des Benutzers beschreiben, sowie die Positionsargumente für die Dateinamen, um die Konverter vorzubereiten, die sie verwenden wird.

Es verwendet dann seinen Eingabekonverter, um Daten zu laden (und möglicherweise seine Wahl des Ausgabekonverters aufzulösen, wenn es noch keinen ausgewählt hat), und seinen Ausgabekonverter, um diese Daten zu serialisieren, wobei es alle Ausgabeoptionen berücksichtigt, die der Benutzer in der Befehlszeile angegeben hat.

    QStringList files = parser.positionalArguments();
    QFile input(files.value(0));
    QFile output(files.value(1));
    const Converter *inconv = prepareConverter(parser.value(inputFormatOption),
                                               Converter::Direction::In, &input);
    const Converter *outconv = prepareConverter(parser.value(outputFormatOption),
                                                Converter::Direction::Out, &output);

    // Now finally perform the conversion:
    QVariant data = inconv->loadFile(&input, outconv);
    Q_ASSERT_X(outconv, "Serialization Converter",
               "Internal error: converter format did not provide default");
    outconv->saveFile(&output, data, parser.values(optionOption));
    return EXIT_SUCCESS;

Beispielprojekt @ code.qt.io

Siehe auch Parsen und Anzeigen von CBOR-Daten, Speichern und Laden eines Spiels und CBOR-Unterstützung in Qt.

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.