7.4.1.  Serializer Pattern: QTextStream and QDataStream

[ fromfile: serializer.xml id: serializer ]

With QDataStream, it is already possible to serialize and deserialize all QVariant-supported types, including QList, QMap, QVector, and others. With QTextStream, if you want the extraction (>>) operator to work with the output of the insertion (<<) operator on your custom type, you must define proper field and record delimiters for the string types, and write and test the operators properly with sample data.

Because these streams can be created from any QIODevice, and there are many other Qt classes that use QIODevice to communicate, your operators can send objects over a network, or through pipes, or to a database.

Example 7.4 shows the friend declarations of input/output operator functions of MetaDataValue, a class used for storing song metadata. The operators are not member functions because the left operand is a QDataStream or QTextStream, classes that cannot modified. Furthermore, the operators should not be member functions of MetaDataValue because the idea of the serializer pattern is to separate the I/O code from the class itself. The METADATAEXPORT macro facilitates the reuse of this code on a Windows platform.[59]

Example 7.4. src/libs/metadata/metadatavalue.h

[ . . . . ]
class METADATAEXPORT MetaDataValue {
public:

    friend METADATAEXPORT QTextStream& operator<< (QTextStream& os, 
                                         const MetaDataValue& mdv);
    friend METADATAEXPORT QTextStream& operator>> (QTextStream& is,
                                         MetaDataValue& mdv);
    friend METADATAEXPORT QDataStream& operator<< (QDataStream& os,
                                         const MetaDataValue& mdv);
    friend METADATAEXPORT QDataStream& operator>> (QDataStream& is,
                                         MetaDataValue& mdv);
    friend METADATAEXPORT bool operator==(const MetaDataValue&,
                                          const MetaDataValue&);
[ . . . . ]
    virtual QString fileName() const ;
    virtual Preference preference() const ;
    virtual QString genre() const;
    virtual QString artist() const;
    virtual QString albumTitle() const;
    virtual QString trackTitle() const;
    virtual QString trackNumber() const;
    virtual const QImage &image() const;
    virtual QTime trackTime() const;
    virtual QString trackTimeString() const;
    virtual QString comment() const;
[ . . . . ]
protected:
    bool m_isNull;
    QUrl m_Url;
    QString m_TrackNumber;
    QString m_TrackTitle;
    QString m_Comment;
    Preference m_Preference;
    QString m_Genre;
    QString m_Artist;
    QTime m_TrackTime;
    QString m_AlbumTitle;
    QImage m_Image;
};
Q_DECLARE_METATYPE(MetaDataValue);  1
[ . . . . ]

1

Add to QVariant type system.

<include src="src/libs/metadata/metadatavalue.h" href="src/libs/metadata/metadatavalue.h" id="metadatavalue-operators.h" mode="cpp"/>


Each operator deals with one MetaDataValue object. operator<<() inserts its data into the output stream. operator>>() extracts its data from the input stream. Each operator returns a reference to the left operand to enable chaining.

The QTextStream operators in Example 7.5 need to be concerned with whitespace and delimiters because everything is streamed out in string representations.

Example 7.5. src/libs/metadata/metadatavalue.cpp

[ . . . . ]


QTextStream& operator<< (QTextStream& os, const MetaDataValue& mdv) {
    QStringList sl;
    sl << mdv.url().toString() << mdv.trackTitle() << mdv.artist() 
       << mdv.albumTitle() << mdv.trackNumber() 
       << mdv.trackTime().toString("m:ss") << mdv.genre() 
       << mdv.preference().toString() << mdv.comment();

    os << sl.join("\t") << "\n";           1
    return os;
}

QTextStream& operator>> (QTextStream& is, MetaDataValue& mdv) {
    QString line = is.readLine();
    QStringList fields = line.split("\t"); 2
    while (fields.size() < 9) {
        fields << "";
    }
    mdv.m_isNull = false;
    mdv.setUrl(QUrl::fromUserInput(fields[0]));
    mdv.setTrackTitle(fields[1]);
    mdv.setArtist(fields[2]);
    mdv.setAlbumTitle(fields[3]);
    mdv.setTrackNumber(fields[4]);
    QTime t = QTime::fromString(fields[5], "m:ss");
    mdv.setTrackTime(t);
    mdv.setGenre(fields[6]);
    Preference p(fields[7]);
    mdv.setPreference(p);
    mdv.setComment(fields[8]);
    return is;
}

1

Output to TSV (tab-separated-values)

2

Read as TSV

<include segid="textstream" mode="cpp" href="src/libs/metadata/metadatavalue.cpp" id="metadatavalue-operators-text.cpp" src="src/libs/metadata/metadatavalue.cpp"/>


The QDataStream operators, shown in Example 7.6, are much simpler to use because they relieve the programmer of the responsibility for separating data items from one another.

Example 7.6. src/libs/metadata/metadatavalue.cpp

[ . . . . ]

QDataStream& operator<< (QDataStream& os, const MetaDataValue& mdv) {
    os << mdv.m_Url << mdv.trackTitle() << mdv.artist() 
       << mdv.albumTitle() << mdv.trackNumber() << mdv.trackTime() 
       << mdv.genre() << mdv.preference() << mdv.comment() 
       << mdv.image();
    return os;
}

QDataStream& operator>> (QDataStream& is, MetaDataValue& mdv) {
    is >> mdv.m_Url >> mdv.m_TrackTitle >> mdv.m_Artist 
       >> mdv.m_AlbumTitle >> mdv.m_TrackNumber >> mdv.m_TrackTime 
       >> mdv.m_Genre >> mdv.m_Preference >> mdv.m_Comment 
       >> mdv.m_Image;
    mdv.m_isNull= false;
    return is;
}

<include segid="datastream" mode="cpp" href="src/libs/metadata/metadatavalue.cpp" id="metadatavalue-operators-data.cpp" src="src/libs/metadata/metadatavalue.cpp"/>


Example 7.7 shows how to use these operators with the different streams. The only disadvantage to using QDataStream is that the resulting file is binary (i.e., not human readable).

Example 7.7. src/serializer/testoperators/tst_testoperators.cpp

[ . . . . ]
void TestOperators::testCase1()
{
    QFile textFile("playlist1.tsv");
    QFile binaryFile("playlist1.bin");
    QTextStream textStream;
    QDataStream dataStream;

    if (textFile.open(QIODevice::ReadOnly)) {
        textStream.setDevice(&textFile);
    }
    if (binaryFile.open(QIODevice::WriteOnly)) {
        dataStream.setDevice(&binaryFile);
    }
    QList<MetaDataValue> values;
    while (!textStream.atEnd()) {
        MetaDataValue mdv;
        textStream >> mdv;             1
        values << mdv;                 2
        dataStream << mdv;             3
    }
    textFile.close();
    binaryFile.close();
    textFile.setFileName("playlist2.tsv");
    if (binaryFile.open(QIODevice::ReadOnly)) {
        dataStream.setDevice(&binaryFile);
        for (int i=0; i<values.size(); ++i) {
            MetaDataValue mdv;
            dataStream >> mdv;         4
            QCOMPARE(mdv, values[i]);  5
        }
    }
}
[ . . . . ]

1

Read as TSV.

2

Add to list.

3

Write to binaryFile.

4

Read binary data.

5

Is it same as what we read before?

<include src="src/serializer/testoperators/tst_testoperators.cpp" href="src/serializer/testoperators/tst_testoperators.cpp" id="testserializer.cpp" mode="cpp"/>




[59] We discuss such macros in Building DLLs .