本页内容

序列化转换器

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

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

终端输出显示了包含关卡、NPC和玩家信息的游戏存档数据的JSON转换结果

Converter 类

Converter 类是所有格式转换器的抽象超类。它们均将数据转换自或转换为 `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)

Converter类的构造函数和析构函数管理着主程序所使用的可用转换器列表,以便主程序知晓有哪些转换器可用。每种转换器类型都定义了一个静态实例,以确保其被正确构造,从而可通过该列表供主程序使用。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;
    }

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

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

outputOptions() 函数用于报告转换器的输出能力。目前唯一的可选功能是支持在键到值的映射中使用任意键。输入转换器的 loadFile() 方法可以利用这些信息,调整其读取数据的呈现形式,以便输出转换器能在其能力范围内尽可能忠实地呈现这些数据。

probeFile() 函数用于判断文件是否符合转换器的格式要求。当用户未在命令行中指定格式时,主程序会根据文件名及可能的内容,通过该函数确定读取或写入文件时应采用的格式。

loadFile() 函数负责对数据进行反序列化。 调用方需告知 loadFile() 计划使用的序列化器,以便 loadFile() 能通过查询该序列化器的 outputOptions() 来确定加载数据的呈现形式。如果调用方尚未确定输出转换器的选择,loadFile() 会根据其返回的数据提供一个合适的默认输出转换器。

saveFile() 函数负责将数据序列化。它会接收来自命令行的选项(如 loadHelp() 所述),这些选项可用于调整将数据保存到文件时的具体表示方式。

loadFile() 和 saveFile() 均可与任意QIODevice 配合使用。这意味着,Converter 还可以与网络套接字或其他数据源配合使用,用于读取或写入数据。在本程序中,主程序始终传递一个QFile ,用于访问磁盘上的文件或进程的标准流之一。

可用的转换器

本程序支持多种转换器,这说明了在需要时,如何将转换器程序适配到其他格式。 具体细节请参阅各转换器的源代码。CBOR 转换器作为功能相对齐全的示例,展示了转换器的工作方式,我们将在下文中进行更详细的探讨。下表总结了可用的转换器:

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

支持输入的转换器会将自身用作`loadFile()`的备用转换器,但CBOR和QDataStream 转换器除外,它们使用各自仅支持输出的转储伴生类。在运行程序时,可将null转换器用作输出转换器,以便进行输入转换器可能执行的任何验证或核查。

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

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

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