序列化转换器

如何在不同序列化格式之间进行转换。

本示例可在 JSON、CBOR、XML、QDataStream 和一些简单文本格式之间进行转换。它可以自动检测正在使用的格式,也可以被告知使用哪种格式。并非所有格式都支持输入和输出,它们支持的内容数据类型也各不相同。QDataStream 和 XML 的内容最丰富,其次是 CBOR,然后是 JSON,最后是纯文本格式。通过功能较弱的格式进行转换容易丢失数据结构。

转换器类

转换器类是所有格式转换器的抽象超类。它们都从QVariant 类转换或转换到该类,该类用于在内部表示所有数据结构。

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)

转换器构造函数和析构函数管理主程序使用的可用转换器列表,以便主程序知道有哪些转换器可用。每种转换器类型都定义了一个静态实例,以确保其已构建完毕,并通过该列表提供给主程序。allConverters() 方法可让main() 的代码访问该列表。

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();
}

name() 函数返回转换器的名称。directions() 函数用于确定转换器可用于输入、输出还是两者。这样,主程序就能在命令行选项的帮助文本中报告有哪些转换器可用于选择输入和输出格式。

    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;
    }

当使用--format-options <format> 命令行选项查询时,optionsHelp() 函数用于报告可用格式所支持的各种命令行选项。

       for(constConverter*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);
               否则{                    qInfo("Format '%s' supports no options.", qPrintable(format));
                }returnEXIT_SUCCESS; } }

outputOptions() 函数报告转换器的输出功能。目前,唯一的可选功能是支持键值映射中的任意键。输入转换器的 loadFile() 可以使用这些信息来调整它所读取数据的显示形式,以便在输出转换器的能力允许范围内忠实地表示这些数据。

probeFile() 函数用于确定文件是否符合转换器的格式。当用户没有在命令行中指定文件格式时,主程序会根据文件名和可能的内容来确定读取或写入文件时使用的格式。

loadFile() 函数对数据进行反序列化。调用者会告诉 loadFile() 它打算使用哪种序列化器,这样 loadFile() 就能查询其 outputOptions() 以确定以何种形式表示加载的数据。如果调用者尚未确定输出转换器的选择,loadFile() 会向其提供适合其返回数据的默认输出转换器。

saveFile() 函数将数据序列化。如 loadHelp() 所述,它通过命令行传递选项,可以调整保存到文件时如何表示数据的细节。

loadFile() 和 saveFile() 均可用于任意QIODevice 。这意味着转换器也可以与网络套接字或其他数据源一起使用,以便读取或写入数据。在本程序中,主程序总是传递一个QFile ,访问磁盘上的文件或进程的一个标准流。

可用的转换器

本程序支持几种转换器,说明如果有需要,转换器程序如何适应其他格式。详情请查看每个转换器的源代码。CBOR 转换器是功能相对齐全的转换器工作方式示例,我们将在下文中详细介绍。本表总结了可用的转换器:

类别模式格式
CborConverter输入/输出CBOR
CborDiagnosticDumper输出CBOR 诊断
数据流转换器输入/输出QDataStream
调试文本跳线输出无损、非标准、人类可读
JsonConverter输入/输出JSON
NullConverter输出无输出
文本转换器输入/输出结构化纯文本
XmlConverter输入/输出XML

除了 CBOR 和QDataStream 转换器外,支持输入的转换器都使用自己作为 loadFile() 的后备转换器,而只支持输出的转换器则使用各自的输出倾卸器配套类。在运行程序时,空转换器可用作输出转换器,以便进行输入转换器可能执行的任何验证或校验。

CborConverter 和 CborDiagnosticDumper 类

CborConverter 类支持与 CBOR 格式之间的序列化。它支持各种选项来配置浮点数值的输出,还支持一个signature 选项来决定是否以 CBOR 标签作为文件头开始输出,以识别文件包含 CBOR 数据。

还有一个 CborDiagnosticDumper 类,用于以 CBOR 诊断符号输出。该类不支持加载数据。其输出形式可通过两个选项进行配置。一个是选择是否使用(更冗长的)扩展 CBOR 诊断格式。另一个选项则控制每个 CBOR 值是否显示在单独一行上。

纯诊断符号类似于 JSON,但并不完全相同,因为它支持无损显示 CBOR 流的内容,而转换为 JSON 时可能会有损。如果调用者尚未确定输出格式,CborConverter 的 loadFile() 将使用 CborDiagnosticDumper 作为后备输出转换器。

convertCborValue()、convertCborMap() 和 convertCborArray() 辅助函数用于将QCborValue 转换为QVariant ,以便 CborConverter::loadFile() 使用。

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();
}

convertFromVariant()函数用于将QVariant 转换为QCborValue ,以便由任何一个类的saveFile() 输出。

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);
}

转换程序

main() 函数设置了一个QApplication 和一个QCommandLineParser ,以便理解用户指定的选项,并在用户要求时提供帮助。它使用为描述用户选择的各种QCommandLineOption 实例获取的值,加上文件名的位置参数,来准备将使用的转换器。

然后,它将使用输入转换器加载数据(如果尚未选择输出转换器,则可能需要解决输出转换器的选择问题),并使用输出转换器将数据序列化,同时考虑用户在命令行上提供的任何输出选项。

    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;

示例项目 @ code.qt.io

另请参阅 解析和显示 CBOR 数据保存和加载游戏以及Qt 中的 CBOR 支持

© 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.