序列化转换器

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

此示例在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)

转换器构造函数和析构函数管理由主程序使用的可用转换器列表,以便它知道有什么转换器可用。每种转换器类型定义了一个静态实例,确保它被构造并且 thus 通过此列表对主程序可用。《allConverters()`方法为主程序的代码提供了访问该列表的权限。

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

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

        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);
                } else {
                    qInfo("Format '%s' supports no options.", qPrintable(format));
                }
                return EXIT_SUCCESS;
            }
        }

outputOptions()函数报告转换器的输出能力。目前唯一的可选功能是对从键到值的映射中任意键的支持。输入转换器的loadFile()可以使用此信息调整其呈现读取数据的格式,以便输出转换器以最忠实的方式表示数据。

probeFile()函数用于确定文件是否与转换器的格式匹配。主程序使用此信息来根据其名称和内容(如果有的话)确定在用户没有在命令行上指定使用的格式时使用哪种格式。

loadFile()函数反序列化数据。调用者告诉loadFile()它打算使用哪个序列器,以便loadFile()可以查询其outputOptions()以确定表示加载数据的格式。如果调用者还没有确定输出转换器选择,loadFile()会提供适合返回数据的默认输出转换器。

saveFile()函数序列化数据。它接收来自命令行的选项,如loadHelp()所述,这些选项可调整在保存到文件时表示数据的详细程度。

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

可用的转换器

支持多个转换器,展示了如果需要,转换器程序如何适应其他格式。请参阅每个源代码以获取其详细信息。CBOR 转换器作为相对完整功能的示例,我们将更详细地探讨它。此表总结了可用的转换器

模式格式
CborConverter输入/输出CBOR
CborDiagnosticDumper输出CBOR 诊断
DataStreamConverter输入/输出QDataStream
DebugTextDumper输出无损,非标准,可读性高
JsonConverter输入/输出JSON
NullConverter输出没有输出
TextConverter输入/输出结构化纯文本
XmlConverter输入/输出XML

支持输入的转换器将自身作为 loadFile() 的后备转换器,除 CBOR 和 QDataStream 转换器外,这些转换器使用它们各自的只输出 dumper 伴随类。null 转换器可以用作输出转换器,当运行程序进行任何验证或验证输入转换器可能执行的操作时。

CborConverter 和 CborDiagnosticDumper 类

CborConverter 类支持将数据序列化和反序列化到 CBOR 格式。它支持各种选项来配置浮点数的输出,以及一个 signature 选项来决定是否以一个作为文件开头标识文件的 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() 函数用于将 variants 转换为 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);
}

convert 程序

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 支持

© 2024 Qt 公司有限公司。本文档中的文档贡献是各自所有者的版权。本文档提供的文档是根据自由软件基金会发布的 GNU 自由文档许可第 1.3 版 许可的。Qt 和相关徽标是芬兰和/或世界其他地区 Qt 公司的商标。所有其他商标均为其各自所有者的财产。