Taro3

View on GitHub

JSON形式でのオブジェクトのシリアライズ

TrackクラスとSoundEventクラスを共通のQt形式のQVariantに変換できるようになりました。これで、Track (とそのSoundEventオブジェクト) クラスをテキストまたはバイナリ形式のファイルに記述する必要があります。このサンプルプロジェクトでは、すべてのフォーマットを扱うことができます。保存したファイルのフォーマットを一行で切り替えることができるようになります。では、どこに特定のフォーマットのコードを置くのでしょうか?それが100万ドルの問題です。ここに主なアプローチがあります。

image

この命題では、特定のファイル・フォーマットのシリアライズ・コードは専用の子クラスの中にあります。さて、それはうまくいきますが、もし2つの新しいファイル・フォーマットを追加した場合、階層はどのようになるでしょうか?さらに、シリアル化する新しいオブジェクトを追加するたびに、異なるシリアル化ファイル形式を処理するための子クラスをすべて作成しなければなりません。この巨大な継承ツリーは、すぐに厄介なものになってしまいます。コードはメンテナンスできなくなります。そんなことはしたくないでしょう。そこで、ここでブリッジパターンが良い解決策になります。

image

ブリッジパターンでは、2つの継承階層でクラスをデカップリングします。

古典的なブリッジパターンでは、抽象化(Serializable)は実装者(Serializer)変数を含むべきであることに注意してください。呼び出し元は抽象化を扱うだけです。しかし、このプロジェクトの例では、MainWindow は Serializable の所有権と Serializer の所有権を持っています。これは、デザインパターンの力を利用しながら、機能クラスを分離しないようにするための個人的な選択です。

SerializableとSerializerのアーキテクチャは明確です。Serializableクラスはすでに実装されているので、Serializer.hという新しいC++ヘッダーファイルを作成することができます。

#include <QString>

#include "Serializable.h"

class Serializer
{
public:
    virtual ~Serializer() {}

    virtual void save(const Serializable& serializable,
        const QString& filepath,
        const QString& rootName = "") = 0;
    virtual void load(Serializable& serializable,
        const QString& filepath) = 0;
};

Serializer クラスはインターフェイスであり、純粋な仮想関数のみを持ち、データを持たない抽象クラスです。今回はsave()関数の話をしましょう。

load()関数もわかりやすいです。

インターフェイスSerializerの準備が整い、いくつかの実装を待っています!まずはJSONを使ってみましょう。C++クラスJsonSerializerを作成します。以下がJsonSerializer.hのヘッダです。

#include "Serializer.h"

class JsonSerializer : public Serializer
{
public:
    JsonSerializer();
    void save(const Serializable& serializable,
    const QString& filepath,
    const QString& rootName) override;
    void load(Serializable& serializable,
    const QString& filepath) override;
};

ここでは難しいことはありません。save() と load() の実装を提供しなければなりません。ここに save() の実装があります。

void JsonSerializer::save(const Serializable& serializable,
    const QString& filepath, const QString& /*rootName*/)
{
    QJsonDocument doc =
        QJsonDocument::fromVariant(serializable.toVariant());
    QFile file(filepath);
    file.open(QFile::WriteOnly);
    file.write(doc.toJson());
    file.close();
}

Qtフレームワークでは、QJsonDocumentクラスを使ってJSONファイルを読み書きする素敵な方法を提供しています。QVariant クラスから QJsonDocument クラスを作成することができます。QJsonDocument が受け入れる QVariant は QVariantMap、QVariantList、または QStringList でなければならないことに注意してください。TrackクラスとSoundEventのtoVariant()関数がQVariantMapを生成してくれるので心配ありません。あとは、保存先のファイルパスを指定してQFileファイルを作成すればOKです。これをQJsonDocument::toJson()関数でUTF-8エンコードされたテキスト表現に変換します。この結果をQFileファイルに書き込み、ファイルを閉じます。


Tips

QJsonDocument::toJson() 関数は、Indented または Compact JSON フォーマットを生成することができます。デフォルトでは、フォーマットは QJsonDocument::Indented です。


load()の実装も短い。

void JsonSerializer::load(Serializable& serializable,
    const QString& filepath)
{
    QFile file(filepath);
    file.open(QFile::ReadOnly);
    QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
    file.close();
    serializable.fromVariant(doc.toVariant());
}

ソースファイルパスでQFileを開きます。QFile::readAll()で全てのデータを読み込みます。そして、QJsonDocument::fromJson()関数でQJsonDocumentクラスを作成します。最後に、QVariantクラスに変換されたQJsonDocumentでSerializableを埋めることができます。QJsonDocument::toVariant()関数は、JSONドキュメントの性質に応じてQVariantListまたはQVariantMapを返すことができることに注意してください。

このJsonSerializerで保存されたTrackクラスの例です。

{
    "duration": 6205,
    "soundEvents": [
        {
        "soundId": 0,
        "timestamp": 2689
        },
        {
        "soundId": 2,
        "timestamp": 2690
        },
        {
        "soundId": 2,
        "timestamp": 3067
        }
    ]
}

ルート要素はJSONオブジェクトで、2つのキーを持つマップで表されます。


戻る